feat: V/R 유지보수 시스템 전환 및 대시보드 테이블 형태 완성
Some checks failed
Flutter Test & Quality Check / Test on macos-latest (push) Has been cancelled
Flutter Test & Quality Check / Test on ubuntu-latest (push) Has been cancelled
Flutter Test & Quality Check / Build APK (push) Has been cancelled

- 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:
JiWoong Sul
2025-09-05 14:33:20 +09:00
parent 2c20999025
commit 519e1883a3
46 changed files with 7804 additions and 1034 deletions

View File

@@ -0,0 +1,202 @@
import 'package:get_it/get_it.dart';
import 'package:superport/data/models/stock_status_dto.dart';
import 'package:superport/data/repositories/equipment_history_repository.dart';
/// 장비-창고 매핑 캐시 서비스
///
/// Stock Status API를 활용하여 장비별 현재 창고 정보를 캐싱하고
/// 빠른 조회를 제공하는 싱글톤 서비스입니다.
///
/// 주요 기능:
/// - 앱 시작 시 전체 장비-창고 매핑 로드 및 캐싱
/// - 출고 처리 후 자동 캐시 갱신
/// - 장비별 현재 창고 정보 빠른 조회
/// - Fallback 전략으로 안정성 보장
class EquipmentWarehouseCacheService {
static final EquipmentWarehouseCacheService _instance =
EquipmentWarehouseCacheService._internal();
factory EquipmentWarehouseCacheService() => _instance;
EquipmentWarehouseCacheService._internal();
// 의존성 주입
late final EquipmentHistoryRepository _repository = GetIt.instance<EquipmentHistoryRepository>();
// 캐시 저장소
final Map<int, StockStatusDto> _cache = {};
// 상태 관리
bool _isLoaded = false;
bool _isLoading = false;
DateTime? _lastUpdated;
String? _lastError;
// 설정 상수
static const int _cacheValidMinutes = 10; // 10분간 캐시 유효
static const int _maxRetryCount = 3; // 최대 재시도 횟수
/// 캐시 로딩 상태
bool get isLoaded => _isLoaded;
bool get isLoading => _isLoading;
DateTime? get lastUpdated => _lastUpdated;
String? get lastError => _lastError;
int get cachedCount => _cache.length;
/// 캐시 로드 (앱 시작 시 또는 필요 시 호출)
///
/// Returns:
/// - true: 로드 성공
/// - false: 로드 실패 (에러는 lastError에서 확인)
Future<bool> loadCache() async {
if (_isLoading) return _isLoaded; // 이미 로딩 중이면 현재 상태 반환
_isLoading = true;
_lastError = null;
try {
print('[EquipmentWarehouseCacheService] 재고 현황 로딩 시작...');
print('[EquipmentWarehouseCacheService] Repository: ${_repository.runtimeType}');
// Stock Status API 호출
final stocks = await _repository.getStockStatus();
print('[EquipmentWarehouseCacheService] API 응답 수신: ${stocks.length}개 항목');
// 캐시 업데이트
_cache.clear();
for (var stock in stocks) {
print('[EquipmentWarehouseCacheService] 캐시 추가: 장비${stock.equipmentId}${stock.warehouseName}');
_cache[stock.equipmentId] = stock;
}
// 상태 업데이트
_isLoaded = true;
_lastUpdated = DateTime.now();
print('[EquipmentWarehouseCacheService] 재고 현황 로딩 완료: ${_cache.length}개 장비');
print('[EquipmentWarehouseCacheService] 캐시된 장비 ID들: ${_cache.keys.toList()}');
return true;
} catch (e, stackTrace) {
_lastError = '재고 현황 로딩 실패: $e';
print('[EquipmentWarehouseCacheService] $_lastError');
print('[EquipmentWarehouseCacheService] StackTrace: $stackTrace');
return false;
} finally {
_isLoading = false;
}
}
/// 장비의 현재 창고 정보 조회
///
/// [equipmentId]: 조회할 장비 ID
///
/// Returns:
/// - StockStatusDto: 장비의 재고 현황 정보
/// - null: 캐시에 없는 경우
StockStatusDto? getEquipmentStock(int equipmentId) {
return _cache[equipmentId];
}
/// 장비의 현재 창고명 조회 (간편 메소드)
///
/// [equipmentId]: 조회할 장비 ID
/// [fallbackName]: 캐시에 없을 때 반환할 기본값
///
/// Returns: 창고명 또는 fallbackName
String getWarehouseName(int equipmentId, {String fallbackName = '위치 미확인'}) {
return _cache[equipmentId]?.warehouseName ?? fallbackName;
}
/// 장비의 현재 창고 ID 조회
///
/// [equipmentId]: 조회할 장비 ID
///
/// Returns: 창고 ID 또는 null
int? getWarehouseId(int equipmentId) {
return _cache[equipmentId]?.warehouseId;
}
/// 장비가 특정 창고에 있는지 확인
///
/// [equipmentId]: 확인할 장비 ID
/// [warehouseId]: 확인할 창고 ID
///
/// Returns: true if 장비가 해당 창고에 있음
bool isEquipmentInWarehouse(int equipmentId, int warehouseId) {
return _cache[equipmentId]?.warehouseId == warehouseId;
}
/// 특정 창고에 있는 모든 장비 목록 조회
///
/// [warehouseId]: 조회할 창고 ID
///
/// Returns: 해당 창고에 있는 장비들의 StockStatusDto 목록
List<StockStatusDto> getEquipmentsByWarehouse(int warehouseId) {
return _cache.values
.where((stock) => stock.warehouseId == warehouseId)
.toList();
}
/// 캐시 갱신 필요 여부 확인
///
/// Returns: true if 캐시 갱신이 필요함
bool needsRefresh() {
if (!_isLoaded || _lastUpdated == null) return true;
final difference = DateTime.now().difference(_lastUpdated!);
return difference.inMinutes >= _cacheValidMinutes;
}
/// 캐시 강제 갱신
///
/// 출고/입고 처리 후 호출하여 최신 재고 상태를 반영합니다.
///
/// Returns: true if 갱신 성공
Future<bool> refreshCache() async {
print('[EquipmentWarehouseCacheService] 강제 캐시 갱신 시작...');
// 강제로 갱신하도록 상태 초기화
_isLoaded = false;
_lastUpdated = null;
return await loadCache();
}
/// 캐시 무효화 (메모리 정리)
///
/// 로그아웃 시 또는 메모리 절약이 필요할 때 호출
void invalidateCache() {
_cache.clear();
_isLoaded = false;
_isLoading = false;
_lastUpdated = null;
_lastError = null;
print('[EquipmentWarehouseCacheService] 캐시 무효화 완료');
}
/// 캐시 통계 정보 조회 (디버깅용)
///
/// Returns: 캐시 상태 정보 맵
Map<String, dynamic> getCacheStats() {
return {
'isLoaded': _isLoaded,
'isLoading': _isLoading,
'cachedCount': _cache.length,
'lastUpdated': _lastUpdated?.toIso8601String(),
'lastError': _lastError,
'needsRefresh': needsRefresh(),
'cacheValidMinutes': _cacheValidMinutes,
};
}
/// 개발용: 캐시 상태 출력
void printCacheStats() {
final stats = getCacheStats();
print('[EquipmentWarehouseCacheService] Cache Stats:');
stats.forEach((key, value) {
print(' $key: $value');
});
}
}