feat: 소프트 딜리트 기능 전면 구현 완료
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

## 주요 변경사항
- Company, Equipment, License, Warehouse Location 모든 화면에 소프트 딜리트 구현
- 관리자 권한으로 삭제된 데이터 조회 가능 (includeInactive 파라미터)
- 데이터 무결성 보장을 위한 논리 삭제 시스템 완성

## 기능 개선
- 각 리스트 컨트롤러에 toggleIncludeInactive() 메서드 추가
- UI에 "비활성 포함" 체크박스 추가 (관리자 전용)
- API 데이터소스에 includeInactive 파라미터 지원

## 문서 정리
- 불필요한 문서 파일 제거 및 재구성
- CLAUDE.md 프로젝트 상태 업데이트 (진행률 80%)
- 테스트 결과 문서화 (test20250812v01.md)

## UI 컴포넌트
- Equipment 화면 위젯 모듈화 (custom_dropdown_field, equipment_basic_info_section)
- 폼 유효성 검증 강화

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
JiWoong Sul
2025-08-12 20:02:54 +09:00
parent 1645182b38
commit e7860ae028
48 changed files with 2096 additions and 1242 deletions

View File

@@ -6,6 +6,7 @@ import 'package:superport/core/constants/app_constants.dart';
import 'package:superport/core/utils/error_handler.dart';
import 'package:superport/models/license_model.dart';
import 'package:superport/services/license_service.dart';
import 'package:superport/services/dashboard_service.dart';
import 'package:superport/data/models/common/pagination_params.dart';
/// 라이센스 상태 필터
@@ -21,6 +22,7 @@ enum LicenseStatusFilter {
/// BaseListController를 상속받아 공통 기능을 재사용
class LicenseListController extends BaseListController<License> {
late final LicenseService _licenseService;
late final DashboardService _dashboardService;
// 라이선스 특화 필터 상태
int? _selectedCompanyId;
@@ -29,6 +31,7 @@ class LicenseListController extends BaseListController<License> {
LicenseStatusFilter _statusFilter = LicenseStatusFilter.all;
String _sortBy = 'expiry_date';
String _sortOrder = 'asc';
bool _includeInactive = false; // 비활성 라이선스 포함 여부
// 선택된 라이선스 관리
final Set<int> _selectedLicenseIds = {};
@@ -54,6 +57,7 @@ class LicenseListController extends BaseListController<License> {
Set<int> get selectedLicenseIds => _selectedLicenseIds;
Map<String, int> get statistics => _statistics;
int get selectedCount => _selectedLicenseIds.length;
bool get includeInactive => _includeInactive;
// 전체 선택 여부 확인
bool get isAllSelected =>
@@ -67,6 +71,12 @@ class LicenseListController extends BaseListController<License> {
} else {
throw Exception('LicenseService not registered in GetIt');
}
if (GetIt.instance.isRegistered<DashboardService>()) {
_dashboardService = GetIt.instance<DashboardService>();
} else {
throw Exception('DashboardService not registered in GetIt');
}
}
@override
@@ -82,6 +92,7 @@ class LicenseListController extends BaseListController<License> {
isActive: _isActive,
companyId: _selectedCompanyId,
licenseType: _licenseType,
includeInactive: _includeInactive,
),
onError: (failure) {
throw failure;
@@ -102,8 +113,8 @@ class LicenseListController extends BaseListController<License> {
);
}
// 통계 업데이트
await _updateStatistics(response.items);
// 통계 업데이트 (전체 데이터 기반)
await _updateStatistics();
// PaginatedResponse를 PagedResult로 변환
final meta = PaginationMeta(
@@ -187,6 +198,12 @@ class LicenseListController extends BaseListController<License> {
_licenseType = licenseType;
loadData(isRefresh: true);
}
/// 비활성 포함 토글
void toggleIncludeInactive() {
_includeInactive = !_includeInactive;
loadData(isRefresh: true);
}
/// 필터 초기화
void clearFilters() {
@@ -219,11 +236,14 @@ class LicenseListController extends BaseListController<License> {
},
);
// BaseListController의 removeItemLocally 활용
removeItemLocally((l) => l.id == id);
// BaseListController의 removeItemLocally 활용 대신 서버에서 새로고침
// removeItemLocally((l) => l.id == id);
// 선택 목록에서도 제거
_selectedLicenseIds.remove(id);
// 삭제 후 리스트 새로고침 (서버에서 10개 다시 가져오기)
await refresh();
}
/// 라이선스 선택/해제
@@ -308,28 +328,42 @@ class LicenseListController extends BaseListController<License> {
await updateLicense(updatedLicense);
}
/// 통계 데이터 업데이트
Future<void> _updateStatistics(List<License> licenses) async {
final now = DateTime.now();
/// 통계 데이터 업데이트 (전체 데이터 기반)
Future<void> _updateStatistics() async {
// 전체 라이선스 통계를 위해 getLicenseExpirySummary API 호출
final result = await _dashboardService.getLicenseExpirySummary();
_statistics = {
'total': licenses.length,
'active': licenses.where((l) => l.isActive).length,
'inactive': licenses.where((l) => !l.isActive).length,
'expiringSoon': licenses.where((l) {
if (l.expiryDate != null) {
final days = l.expiryDate!.difference(now).inDays;
return days > 0 && days <= 30;
}
return false;
}).length,
'expired': licenses.where((l) {
if (l.expiryDate != null) {
return l.expiryDate!.isBefore(now);
}
return false;
}).length,
};
result.fold(
(failure) {
// 실패 시 기본값 유지
debugPrint('[ERROR] 라이선스 통계 로드 실패: $failure');
_statistics = {
'total': 0,
'active': 0,
'inactive': 0,
'expiringSoon': 0,
'expired': 0,
};
},
(summary) {
// API 응답 데이터로 통계 업데이트
_statistics = {
'total': summary.totalActive + summary.expired, // 전체 = 활성 + 만료
'active': summary.totalActive, // 활성 라이선스 총계
'inactive': 0, // API에서 제공하지 않으므로 0
'expiringSoon': summary.within30Days, // 30일 내 만료
'expired': summary.expired, // 만료된 라이선스
};
debugPrint('[DEBUG] 라이선스 통계 업데이트 완료');
debugPrint('[DEBUG] 전체: ${_statistics['total']}');
debugPrint('[DEBUG] 활성: ${_statistics['active']}');
debugPrint('[DEBUG] 30일 내 만료: ${_statistics['expiringSoon']}');
debugPrint('[DEBUG] 만료: ${_statistics['expired']}');
},
);
notifyListeners();
}
/// 라이선스 만료일별 그룹핑