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,326 @@
import 'package:flutter/material.dart';
import 'package:superport/data/models/inventory_history_view_model.dart';
import 'package:superport/services/inventory_history_service.dart';
import 'package:superport/core/constants/app_constants.dart';
/// 재고 이력 관리 화면 전용 컨트롤러
/// InventoryHistoryService를 통해 여러 API를 조합한 데이터 관리
class InventoryHistoryController extends ChangeNotifier {
final InventoryHistoryService _service;
InventoryHistoryController({
InventoryHistoryService? service,
}) : _service = service ?? InventoryHistoryService();
// 상태 변수
List<InventoryHistoryViewModel> _historyItems = [];
bool _isLoading = false;
String? _error;
// 페이지네이션
int _currentPage = 1;
int _pageSize = AppConstants.historyPageSize;
int _totalCount = 0;
int _totalPages = 0;
// 검색 및 필터
String _searchKeyword = '';
String? _selectedTransactionType;
int? _selectedEquipmentId;
int? _selectedWarehouseId;
int? _selectedCompanyId;
DateTime? _dateFrom;
DateTime? _dateTo;
// Getters
List<InventoryHistoryViewModel> get historyItems => _historyItems;
bool get isLoading => _isLoading;
String? get error => _error;
int get currentPage => _currentPage;
int get totalPages => _totalPages;
int get totalCount => _totalCount;
int get pageSize => _pageSize;
String get searchKeyword => _searchKeyword;
String? get selectedTransactionType => _selectedTransactionType;
// 통계 정보
int get totalTransactions => _historyItems.length;
int get inStockCount => _historyItems.where((item) => item.transactionType == 'I').length;
int get outStockCount => _historyItems.where((item) => item.transactionType == 'O').length;
int get rentCount => _historyItems.where((item) => item.transactionType == 'R').length;
int get disposeCount => _historyItems.where((item) => item.transactionType == 'D').length;
/// 재고 이력 목록 로드
Future<void> loadHistories({bool refresh = false}) async {
if (refresh) {
_currentPage = 1;
_historyItems.clear();
}
_isLoading = true;
_error = null;
notifyListeners();
try {
print('[InventoryHistoryController] Loading histories - Page: $_currentPage, Search: "$_searchKeyword", Type: $_selectedTransactionType');
final response = await _service.loadInventoryHistories(
page: _currentPage,
pageSize: _pageSize,
searchKeyword: _searchKeyword.isEmpty ? null : _searchKeyword,
transactionType: _selectedTransactionType,
equipmentId: _selectedEquipmentId,
warehouseId: _selectedWarehouseId,
companyId: _selectedCompanyId,
dateFrom: _dateFrom,
dateTo: _dateTo,
);
if (refresh) {
_historyItems = response.items;
} else {
_historyItems.addAll(response.items);
}
_totalCount = response.totalCount;
_totalPages = response.totalPages;
print('[InventoryHistoryController] Loaded ${response.items.length} items, Total: $_totalCount');
} catch (e) {
_error = e.toString();
print('[InventoryHistoryController] Error loading histories: $e');
} finally {
_isLoading = false;
notifyListeners();
}
}
/// 특정 장비의 전체 이력 로드 (상세보기용)
Future<List<InventoryHistoryViewModel>> loadEquipmentHistory(int equipmentId) async {
try {
print('[InventoryHistoryController] Loading equipment history for ID: $equipmentId');
final histories = await _service.loadEquipmentHistory(equipmentId);
print('[InventoryHistoryController] Loaded ${histories.length} equipment histories');
return histories;
} catch (e) {
print('[InventoryHistoryController] Error loading equipment history: $e');
rethrow;
}
}
/// 검색 키워드 설정
void setSearchKeyword(String keyword) {
if (_searchKeyword != keyword) {
_searchKeyword = keyword;
_currentPage = 1;
loadHistories(refresh: true);
}
}
/// 거래 유형 필터 설정
void setTransactionTypeFilter(String? transactionType) {
if (_selectedTransactionType != transactionType) {
_selectedTransactionType = transactionType;
_currentPage = 1;
loadHistories(refresh: true);
}
}
/// 장비 필터 설정
void setEquipmentFilter(int? equipmentId) {
if (_selectedEquipmentId != equipmentId) {
_selectedEquipmentId = equipmentId;
_currentPage = 1;
loadHistories(refresh: true);
}
}
/// 창고 필터 설정
void setWarehouseFilter(int? warehouseId) {
if (_selectedWarehouseId != warehouseId) {
_selectedWarehouseId = warehouseId;
_currentPage = 1;
loadHistories(refresh: true);
}
}
/// 고객사 필터 설정
void setCompanyFilter(int? companyId) {
if (_selectedCompanyId != companyId) {
_selectedCompanyId = companyId;
_currentPage = 1;
loadHistories(refresh: true);
}
}
/// 날짜 범위 필터 설정
void setDateRangeFilter(DateTime? dateFrom, DateTime? dateTo) {
if (_dateFrom != dateFrom || _dateTo != dateTo) {
_dateFrom = dateFrom;
_dateTo = dateTo;
_currentPage = 1;
loadHistories(refresh: true);
}
}
/// 복합 필터 설정 (한 번에 여러 필터 적용)
void setFilters({
String? searchKeyword,
String? transactionType,
int? equipmentId,
int? warehouseId,
int? companyId,
DateTime? dateFrom,
DateTime? dateTo,
}) {
bool hasChanges = false;
if (searchKeyword != null && _searchKeyword != searchKeyword) {
_searchKeyword = searchKeyword;
hasChanges = true;
}
if (_selectedTransactionType != transactionType) {
_selectedTransactionType = transactionType;
hasChanges = true;
}
if (_selectedEquipmentId != equipmentId) {
_selectedEquipmentId = equipmentId;
hasChanges = true;
}
if (_selectedWarehouseId != warehouseId) {
_selectedWarehouseId = warehouseId;
hasChanges = true;
}
if (_selectedCompanyId != companyId) {
_selectedCompanyId = companyId;
hasChanges = true;
}
if (_dateFrom != dateFrom || _dateTo != dateTo) {
_dateFrom = dateFrom;
_dateTo = dateTo;
hasChanges = true;
}
if (hasChanges) {
_currentPage = 1;
loadHistories(refresh: true);
}
}
/// 모든 필터 초기화
void clearFilters() {
_searchKeyword = '';
_selectedTransactionType = null;
_selectedEquipmentId = null;
_selectedWarehouseId = null;
_selectedCompanyId = null;
_dateFrom = null;
_dateTo = null;
_currentPage = 1;
loadHistories(refresh: true);
}
/// 다음 페이지 로드
Future<void> loadNextPage() async {
if (_currentPage < _totalPages && !_isLoading) {
_currentPage++;
await loadHistories();
}
}
/// 이전 페이지 로드
Future<void> loadPreviousPage() async {
if (_currentPage > 1 && !_isLoading) {
_currentPage--;
await loadHistories();
}
}
/// 특정 페이지로 이동
Future<void> goToPage(int page) async {
if (page > 0 && page <= _totalPages && page != _currentPage && !_isLoading) {
_currentPage = page;
await loadHistories();
}
}
/// 데이터 새로고침
Future<void> refresh() async {
await loadHistories(refresh: true);
}
/// 에러 초기화
void clearError() {
_error = null;
notifyListeners();
}
/// 통계 정보 맵 형태로 반환
Map<String, dynamic> getStatistics() {
return {
'total': totalCount,
'current_page_count': totalTransactions,
'in_stock': inStockCount,
'out_stock': outStockCount,
'rent': rentCount,
'dispose': disposeCount,
};
}
/// 검색 상태 확인
bool get hasActiveFilters {
return _searchKeyword.isNotEmpty ||
_selectedTransactionType != null ||
_selectedEquipmentId != null ||
_selectedWarehouseId != null ||
_selectedCompanyId != null ||
_dateFrom != null ||
_dateTo != null;
}
/// 필터 상태 텍스트
String get filterStatusText {
List<String> filters = [];
if (_searchKeyword.isNotEmpty) {
filters.add('검색: "$_searchKeyword"');
}
if (_selectedTransactionType != null) {
final typeMap = {
'I': '입고',
'O': '출고',
'R': '대여',
'D': '폐기',
};
filters.add('유형: ${typeMap[_selectedTransactionType]}');
}
if (_dateFrom != null || _dateTo != null) {
String dateFilter = '기간: ';
if (_dateFrom != null) {
dateFilter += '${_dateFrom!.toString().substring(0, 10)}';
}
if (_dateTo != null) {
dateFilter += ' ~ ${_dateTo!.toString().substring(0, 10)}';
}
filters.add(dateFilter);
}
return filters.join(', ');
}
@override
void dispose() {
_historyItems.clear();
super.dispose();
}
}