사용하지 않는 파일 정리 전 백업 (Phase 10 완료 후 상태)

This commit is contained in:
JiWoong Sul
2025-08-29 15:11:59 +09:00
parent a740ff10c8
commit d916b281a7
333 changed files with 53617 additions and 22574 deletions

View 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();
}
}