결재 템플릿 단계 적용 구현

- ApprovalTemplate 엔티티·DTO·원격 리포지토리 추가
- ApprovalController에 템플릿 로딩/적용 상태와 assignSteps 호출 연동
- ApprovalPage 단계 탭에 템플릿 선택 UI 및 적용 확인 다이얼로그 구현
- 템플릿 적용 단위 테스트와 IMPLEMENTATION_TASKS 현황 갱신
This commit is contained in:
JiWoong Sul
2025-09-25 00:21:12 +09:00
parent b6e50464d2
commit c3010965ad
63 changed files with 10179 additions and 1436 deletions

View File

@@ -0,0 +1,208 @@
import 'package:flutter/foundation.dart';
import 'package:superport_v2/core/common/models/paginated_result.dart';
import '../../../group/domain/entities/group.dart';
import '../../../group/domain/repositories/group_repository.dart';
import '../../../menu/domain/entities/menu.dart';
import '../../../menu/domain/repositories/menu_repository.dart';
import '../../domain/entities/group_permission.dart';
import '../../domain/repositories/group_permission_repository.dart';
enum GroupPermissionStatusFilter { all, activeOnly, inactiveOnly }
/// 그룹-메뉴 권한 화면용 컨트롤러
///
/// - 목록/필터 상태를 관리하고, CRUD/복구 동작을 래핑한다.
/// - 그룹/메뉴 선택을 위해 참조 데이터를 로드한다.
class GroupPermissionController extends ChangeNotifier {
GroupPermissionController({
required GroupPermissionRepository permissionRepository,
required GroupRepository groupRepository,
required MenuRepository menuRepository,
}) : _permissionRepository = permissionRepository,
_groupRepository = groupRepository,
_menuRepository = menuRepository;
final GroupPermissionRepository _permissionRepository;
final GroupRepository _groupRepository;
final MenuRepository _menuRepository;
PaginatedResult<GroupPermission>? _result;
bool _isLoading = false;
bool _isSubmitting = false;
bool _isLoadingGroups = false;
bool _isLoadingMenus = false;
String? _errorMessage;
GroupPermissionStatusFilter _statusFilter = GroupPermissionStatusFilter.all;
int? _groupFilter;
int? _menuFilter;
bool _includeDeleted = false;
final List<Group> _groups = [];
final List<MenuItem> _menus = [];
PaginatedResult<GroupPermission>? get result => _result;
bool get isLoading => _isLoading;
bool get isSubmitting => _isSubmitting;
bool get isLoadingGroups => _isLoadingGroups;
bool get isLoadingMenus => _isLoadingMenus;
String? get errorMessage => _errorMessage;
GroupPermissionStatusFilter get statusFilter => _statusFilter;
int? get groupFilter => _groupFilter;
int? get menuFilter => _menuFilter;
bool get includeDeleted => _includeDeleted;
List<Group> get groups => List.unmodifiable(_groups);
List<MenuItem> get menus => List.unmodifiable(_menus);
Future<void> loadGroups() async {
_isLoadingGroups = true;
notifyListeners();
try {
final response = await _groupRepository.list(page: 1, pageSize: 200);
_groups
..clear()
..addAll(response.items);
} catch (e) {
_errorMessage = e.toString();
} finally {
_isLoadingGroups = false;
notifyListeners();
}
}
Future<void> loadMenus() async {
_isLoadingMenus = true;
notifyListeners();
try {
final response = await _menuRepository.list(
page: 1,
pageSize: 200,
includeDeleted: false,
);
_menus
..clear()
..addAll(response.items);
} catch (e) {
_errorMessage = e.toString();
} finally {
_isLoadingMenus = false;
notifyListeners();
}
}
Future<void> fetch({int page = 1}) async {
_isLoading = true;
_errorMessage = null;
notifyListeners();
try {
final isActive = switch (_statusFilter) {
GroupPermissionStatusFilter.all => null,
GroupPermissionStatusFilter.activeOnly => true,
GroupPermissionStatusFilter.inactiveOnly => false,
};
final response = await _permissionRepository.list(
page: page,
pageSize: _result?.pageSize ?? 20,
groupId: _groupFilter,
menuId: _menuFilter,
isActive: isActive,
includeDeleted: _includeDeleted,
);
_result = response;
} catch (e) {
_errorMessage = e.toString();
} finally {
_isLoading = false;
notifyListeners();
}
}
void updateGroupFilter(int? groupId) {
_groupFilter = groupId;
notifyListeners();
}
void updateMenuFilter(int? menuId) {
_menuFilter = menuId;
notifyListeners();
}
void updateStatusFilter(GroupPermissionStatusFilter filter) {
_statusFilter = filter;
notifyListeners();
}
void updateIncludeDeleted(bool value) {
_includeDeleted = value;
notifyListeners();
}
Future<GroupPermission?> create(GroupPermissionInput input) async {
_setSubmitting(true);
try {
final created = await _permissionRepository.create(input);
await fetch(page: 1);
return created;
} catch (e) {
_errorMessage = e.toString();
notifyListeners();
return null;
} finally {
_setSubmitting(false);
}
}
Future<GroupPermission?> update(int id, GroupPermissionInput input) async {
_setSubmitting(true);
try {
final updated = await _permissionRepository.update(id, input);
await fetch(page: _result?.page ?? 1);
return updated;
} catch (e) {
_errorMessage = e.toString();
notifyListeners();
return null;
} finally {
_setSubmitting(false);
}
}
Future<bool> delete(int id) async {
_setSubmitting(true);
try {
await _permissionRepository.delete(id);
await fetch(page: _result?.page ?? 1);
return true;
} catch (e) {
_errorMessage = e.toString();
notifyListeners();
return false;
} finally {
_setSubmitting(false);
}
}
Future<GroupPermission?> restore(int id) async {
_setSubmitting(true);
try {
final restored = await _permissionRepository.restore(id);
await fetch(page: _result?.page ?? 1);
return restored;
} catch (e) {
_errorMessage = e.toString();
notifyListeners();
return null;
} finally {
_setSubmitting(false);
}
}
void clearError() {
_errorMessage = null;
notifyListeners();
}
void _setSubmitting(bool value) {
_isSubmitting = value;
notifyListeners();
}
}