사용하지 않는 파일 정리 전 백업 (Phase 10 완료 후 상태)
This commit is contained in:
345
lib/core/utils/hierarchy_validator.dart
Normal file
345
lib/core/utils/hierarchy_validator.dart
Normal file
@@ -0,0 +1,345 @@
|
||||
import 'package:superport/data/models/company/company_dto.dart';
|
||||
import 'package:superport/domain/entities/company_hierarchy.dart';
|
||||
|
||||
/// Company 계층 구조 검증 유틸리티
|
||||
class HierarchyValidator {
|
||||
static const int maxHierarchyDepth = 5;
|
||||
|
||||
/// 순환 참조 검증
|
||||
static HierarchyValidationResult validateCircularReference({
|
||||
required int companyId,
|
||||
required int? newParentId,
|
||||
required List<CompanyDto> allCompanies,
|
||||
}) {
|
||||
if (newParentId == null) {
|
||||
return HierarchyValidationResult.valid();
|
||||
}
|
||||
|
||||
// 자기 자신을 부모로 설정하려는 경우
|
||||
if (companyId == newParentId) {
|
||||
return HierarchyValidationResult.invalid(
|
||||
message: '회사는 자기 자신을 상위 회사로 설정할 수 없습니다.',
|
||||
errors: ['Self-reference detected'],
|
||||
);
|
||||
}
|
||||
|
||||
// 자손을 부모로 설정하려는 경우 검증
|
||||
final descendants = _getDescendants(companyId, allCompanies);
|
||||
if (descendants.contains(newParentId)) {
|
||||
return HierarchyValidationResult.invalid(
|
||||
message: '하위 회사를 상위 회사로 설정할 수 없습니다. 순환 참조가 발생합니다.',
|
||||
errors: ['Circular reference detected'],
|
||||
);
|
||||
}
|
||||
|
||||
return HierarchyValidationResult.valid();
|
||||
}
|
||||
|
||||
/// 계층 깊이 검증
|
||||
static HierarchyValidationResult validateDepth({
|
||||
required int? parentId,
|
||||
required List<CompanyDto> allCompanies,
|
||||
}) {
|
||||
if (parentId == null) {
|
||||
return HierarchyValidationResult.valid();
|
||||
}
|
||||
|
||||
final depth = _calculateDepth(parentId, allCompanies);
|
||||
if (depth >= maxHierarchyDepth) {
|
||||
return HierarchyValidationResult.invalid(
|
||||
message: '최대 계층 깊이($maxHierarchyDepth 레벨)를 초과했습니다.',
|
||||
errors: ['Maximum hierarchy depth exceeded'],
|
||||
warnings: ['Current depth: $depth'],
|
||||
);
|
||||
}
|
||||
|
||||
return HierarchyValidationResult.valid();
|
||||
}
|
||||
|
||||
/// 삭제 가능 여부 검증
|
||||
static HierarchyValidationResult validateDeletion({
|
||||
required int companyId,
|
||||
required List<CompanyDto> allCompanies,
|
||||
}) {
|
||||
final children = _getDirectChildren(companyId, allCompanies);
|
||||
|
||||
if (children.isNotEmpty) {
|
||||
return HierarchyValidationResult.invalid(
|
||||
message: '하위 회사가 있는 회사는 삭제할 수 없습니다. 먼저 하위 회사를 이동하거나 삭제하세요.',
|
||||
errors: ['Cannot delete company with children'],
|
||||
warnings: ['${children.length} child companies found'],
|
||||
);
|
||||
}
|
||||
|
||||
return HierarchyValidationResult.valid();
|
||||
}
|
||||
|
||||
/// 부모 변경 가능 여부 검증
|
||||
static HierarchyValidationResult validateParentChange({
|
||||
required int companyId,
|
||||
required int? newParentId,
|
||||
required List<CompanyDto> allCompanies,
|
||||
}) {
|
||||
// 순환 참조 검증
|
||||
final circularResult = validateCircularReference(
|
||||
companyId: companyId,
|
||||
newParentId: newParentId,
|
||||
allCompanies: allCompanies,
|
||||
);
|
||||
|
||||
if (!circularResult.isValid) {
|
||||
return circularResult;
|
||||
}
|
||||
|
||||
// 계층 깊이 검증
|
||||
if (newParentId != null) {
|
||||
final depthResult = validateDepth(
|
||||
parentId: newParentId,
|
||||
allCompanies: allCompanies,
|
||||
);
|
||||
|
||||
if (!depthResult.isValid) {
|
||||
return depthResult;
|
||||
}
|
||||
|
||||
// 새 부모의 자손들 깊이 검증
|
||||
final descendants = _getDescendants(companyId, allCompanies);
|
||||
|
||||
if (descendants.isNotEmpty) {
|
||||
final maxDescendantDepth = _getMaxDescendantDepth(companyId, allCompanies);
|
||||
final newParentDepth = _calculateDepth(newParentId, allCompanies);
|
||||
|
||||
if (newParentDepth + maxDescendantDepth + 1 > maxHierarchyDepth) {
|
||||
return HierarchyValidationResult.invalid(
|
||||
message: '이동 시 하위 회사들이 최대 깊이를 초과하게 됩니다.',
|
||||
errors: ['Would exceed maximum depth after move'],
|
||||
warnings: ['New total depth: ${newParentDepth + maxDescendantDepth + 1}'],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return HierarchyValidationResult.valid();
|
||||
}
|
||||
|
||||
/// 계층 구조 트리 생성
|
||||
static CompanyHierarchy buildHierarchyTree({
|
||||
required List<CompanyDto> companies,
|
||||
int? rootParentId,
|
||||
}) {
|
||||
// 루트 회사들 찾기
|
||||
final rootCompanies = companies.where((c) => c.parentCompanyId == rootParentId).toList();
|
||||
|
||||
if (rootCompanies.isEmpty) {
|
||||
return const CompanyHierarchy(
|
||||
id: '0',
|
||||
name: 'Root',
|
||||
children: [],
|
||||
);
|
||||
}
|
||||
|
||||
// 트리 구축
|
||||
final children = rootCompanies
|
||||
.map((company) => _buildCompanyNode(company, companies, 0))
|
||||
.toList();
|
||||
|
||||
return CompanyHierarchy(
|
||||
id: '0',
|
||||
name: 'Root',
|
||||
children: children,
|
||||
totalDescendants: _countAllDescendants(children),
|
||||
);
|
||||
}
|
||||
|
||||
/// 회사 노드 생성 (재귀)
|
||||
static CompanyHierarchy _buildCompanyNode(
|
||||
CompanyDto company,
|
||||
List<CompanyDto> allCompanies,
|
||||
int level,
|
||||
) {
|
||||
final children = allCompanies
|
||||
.where((c) => c.parentCompanyId == company.id)
|
||||
.map((child) => _buildCompanyNode(child, allCompanies, level + 1))
|
||||
.toList();
|
||||
|
||||
final path = _buildPath(company.id ?? 0, allCompanies);
|
||||
|
||||
return CompanyHierarchy(
|
||||
id: company.id.toString(),
|
||||
name: company.name,
|
||||
parentId: company.parentCompanyId?.toString(),
|
||||
children: children,
|
||||
level: level,
|
||||
fullPath: path,
|
||||
totalDescendants: _countAllDescendants(children),
|
||||
);
|
||||
}
|
||||
|
||||
/// 자손 회사 목록 가져오기
|
||||
static List<int> _getDescendants(int companyId, List<CompanyDto> allCompanies) {
|
||||
final descendants = <int>[];
|
||||
final directChildren = _getDirectChildren(companyId, allCompanies);
|
||||
|
||||
for (final childId in directChildren) {
|
||||
descendants.add(childId);
|
||||
descendants.addAll(_getDescendants(childId, allCompanies));
|
||||
}
|
||||
|
||||
return descendants;
|
||||
}
|
||||
|
||||
/// 직접 자식 회사 목록 가져오기
|
||||
static List<int> _getDirectChildren(int companyId, List<CompanyDto> allCompanies) {
|
||||
return allCompanies
|
||||
.where((c) => c.parentCompanyId == companyId)
|
||||
.map((c) => c.id ?? 0)
|
||||
.where((id) => id > 0)
|
||||
.toList();
|
||||
}
|
||||
|
||||
/// 계층 깊이 계산
|
||||
static int _calculateDepth(int companyId, List<CompanyDto> allCompanies) {
|
||||
int depth = 0;
|
||||
int? currentId = companyId;
|
||||
|
||||
while (currentId != null && depth < maxHierarchyDepth + 1) {
|
||||
final company = allCompanies.firstWhere(
|
||||
(c) => c.id == currentId,
|
||||
orElse: () => CompanyDto(
|
||||
id: 0,
|
||||
name: '',
|
||||
contactName: '',
|
||||
contactPhone: '',
|
||||
contactEmail: '',
|
||||
address: '', // 필수 필드 추가
|
||||
isActive: false,
|
||||
registeredAt: DateTime.now(), // createdAt → registeredAt
|
||||
),
|
||||
);
|
||||
|
||||
if (company.id == 0) break;
|
||||
|
||||
currentId = company.parentCompanyId;
|
||||
if (currentId != null) {
|
||||
depth++;
|
||||
}
|
||||
}
|
||||
|
||||
return depth;
|
||||
}
|
||||
|
||||
/// 자손 최대 깊이 계산
|
||||
static int _getMaxDescendantDepth(int companyId, List<CompanyDto> allCompanies) {
|
||||
final directChildren = _getDirectChildren(companyId, allCompanies);
|
||||
|
||||
if (directChildren.isEmpty) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int maxDepth = 0;
|
||||
for (final childId in directChildren) {
|
||||
final childDepth = 1 + _getMaxDescendantDepth(childId, allCompanies);
|
||||
if (childDepth > maxDepth) {
|
||||
maxDepth = childDepth;
|
||||
}
|
||||
}
|
||||
|
||||
return maxDepth;
|
||||
}
|
||||
|
||||
/// 경로 생성
|
||||
static String _buildPath(int companyId, List<CompanyDto> allCompanies) {
|
||||
final path = <String>[];
|
||||
int? currentId = companyId;
|
||||
|
||||
while (currentId != null) {
|
||||
final company = allCompanies.firstWhere(
|
||||
(c) => c.id == currentId,
|
||||
orElse: () => CompanyDto(
|
||||
id: 0,
|
||||
name: '',
|
||||
contactName: '',
|
||||
contactPhone: '',
|
||||
contactEmail: '',
|
||||
address: '', // 필수 필드 추가
|
||||
isActive: false,
|
||||
registeredAt: DateTime.now(), // createdAt → registeredAt
|
||||
),
|
||||
);
|
||||
|
||||
if (company.id == 0) break;
|
||||
|
||||
path.insert(0, company.name);
|
||||
currentId = company.parentCompanyId;
|
||||
}
|
||||
|
||||
return '/${path.join('/')}';
|
||||
}
|
||||
|
||||
/// 모든 자손 수 계산
|
||||
static int _countAllDescendants(List<CompanyHierarchy> children) {
|
||||
int count = children.length;
|
||||
|
||||
for (final child in children) {
|
||||
count += child.totalDescendants;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/// 계층 구조 일관성 검증
|
||||
static HierarchyValidationResult validateConsistency({
|
||||
required List<CompanyDto> allCompanies,
|
||||
}) {
|
||||
final errors = <String>[];
|
||||
final warnings = <String>[];
|
||||
|
||||
for (final company in allCompanies) {
|
||||
// 부모가 존재하는지 확인
|
||||
if (company.parentCompanyId != null) {
|
||||
final parentExists = allCompanies.any((c) => c.id == company.parentCompanyId);
|
||||
if (!parentExists) {
|
||||
errors.add('Company ${company.id} references non-existent parent ${company.parentCompanyId}');
|
||||
}
|
||||
}
|
||||
|
||||
// 순환 참조 확인
|
||||
final visited = <int>{};
|
||||
int? currentId = company.parentCompanyId;
|
||||
|
||||
while (currentId != null) {
|
||||
if (visited.contains(currentId)) {
|
||||
errors.add('Circular reference detected starting from company ${company.id}');
|
||||
break;
|
||||
}
|
||||
|
||||
visited.add(currentId);
|
||||
final parent = allCompanies.firstWhere(
|
||||
(c) => c.id == currentId,
|
||||
orElse: () => CompanyDto(
|
||||
id: 0,
|
||||
name: '',
|
||||
contactName: '',
|
||||
contactPhone: '',
|
||||
contactEmail: '',
|
||||
address: '',
|
||||
isActive: false,
|
||||
registeredAt: DateTime.now(),
|
||||
),
|
||||
);
|
||||
|
||||
currentId = parent.id == 0 ? null : parent.parentCompanyId;
|
||||
}
|
||||
}
|
||||
|
||||
if (errors.isNotEmpty) {
|
||||
return HierarchyValidationResult.invalid(
|
||||
message: '계층 구조에 일관성 문제가 발견되었습니다.',
|
||||
errors: errors,
|
||||
warnings: warnings,
|
||||
);
|
||||
}
|
||||
|
||||
return HierarchyValidationResult.valid();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user