backup: 사용하지 않는 파일 삭제 전 복구 지점

- 전체 371개 파일 중 82개 미사용 파일 식별
- Phase 1: 33개 파일 삭제 예정 (100% 안전)
- Phase 2: 30개 파일 삭제 검토 예정
- Phase 3: 19개 파일 수동 검토 예정

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
JiWoong Sul
2025-09-02 19:51:40 +09:00
parent 650cd4be55
commit c419f8f458
149 changed files with 12934 additions and 3644 deletions

View File

@@ -0,0 +1,299 @@
import 'package:flutter/material.dart';
import 'package:injectable/injectable.dart';
import '../../../models/company_model.dart';
import '../../../domain/usecases/company/get_companies_usecase.dart';
import '../../../domain/usecases/company/get_company_detail_usecase.dart';
import '../../../domain/usecases/company/create_company_usecase.dart';
import '../../../domain/usecases/company/update_company_usecase.dart';
import '../../../domain/usecases/company/delete_company_usecase.dart';
import '../../../domain/usecases/company/restore_company_usecase.dart';
import '../../../core/constants/app_constants.dart';
@injectable
class CompanyController with ChangeNotifier {
final GetCompaniesUseCase _getCompaniesUseCase;
final GetCompanyDetailUseCase _getCompanyDetailUseCase;
final CreateCompanyUseCase _createCompanyUseCase;
final UpdateCompanyUseCase _updateCompanyUseCase;
final DeleteCompanyUseCase _deleteCompanyUseCase;
final RestoreCompanyUseCase _restoreCompanyUseCase;
// 상태 관리
bool _isLoading = false;
String? _error;
List<Company> _companies = [];
Company? _selectedCompany;
// 페이지네이션
int _currentPage = 1;
int _totalPages = 0;
int _totalItems = 0;
final int _pageSize = AppConstants.companyPageSize;
// 필터
String? _searchQuery;
bool _includeDeleted = false;
CompanyController(
this._getCompaniesUseCase,
this._getCompanyDetailUseCase,
this._createCompanyUseCase,
this._updateCompanyUseCase,
this._deleteCompanyUseCase,
this._restoreCompanyUseCase,
);
// Getters
bool get isLoading => _isLoading;
String? get error => _error;
bool get hasError => _error != null;
List<Company> get companies => _companies;
Company? get selectedCompany => _selectedCompany;
int get currentPage => _currentPage;
int get totalPages => _totalPages;
int get totalItems => _totalItems;
int get pageSize => _pageSize;
String? get searchQuery => _searchQuery;
bool get includeDeleted => _includeDeleted;
void _setLoading(bool loading) {
_isLoading = loading;
notifyListeners();
}
void _setError(String? error) {
_error = error;
notifyListeners();
}
void clearError() {
_error = null;
notifyListeners();
}
/// 회사 목록 조회
Future<void> loadCompanies({
int page = 1,
int perPage = AppConstants.companyPageSize,
String? search,
bool includeDeleted = false,
bool refresh = false,
}) async {
try {
if (refresh) {
_companies.clear();
_currentPage = 1;
notifyListeners();
}
_setLoading(true);
final params = GetCompaniesParams(
page: page,
perPage: perPage,
search: search,
isActive: includeDeleted ? null : true,
);
final response = await _getCompaniesUseCase(params);
response.fold(
(failure) => _setError('회사 목록을 불러오는데 실패했습니다: ${failure.message}'),
(data) {
_companies = data.items;
_currentPage = page;
_totalPages = data.totalPages;
_totalItems = data.totalElements;
_searchQuery = search;
_includeDeleted = includeDeleted;
clearError();
},
);
} catch (e) {
_setError('회사 목록을 불러오는데 실패했습니다: $e');
} finally {
_setLoading(false);
}
}
/// 회사 상세 조회
Future<void> loadCompany(int id) async {
try {
_setLoading(true);
final params = GetCompanyDetailParams(id: id);
final response = await _getCompanyDetailUseCase(params);
response.fold(
(failure) => _setError('회사 정보를 불러오는데 실패했습니다: ${failure.message}'),
(company) {
_selectedCompany = company;
clearError();
},
);
} catch (e) {
_setError('회사 정보를 불러오는데 실패했습니다: $e');
} finally {
_setLoading(false);
}
}
/// 회사 생성
Future<bool> createCompany(Company company) async {
try {
_setLoading(true);
final params = CreateCompanyParams(company: company);
final response = await _createCompanyUseCase(params);
return response.fold(
(failure) {
_setError('회사 생성에 실패했습니다: ${failure.message}');
return false;
},
(company) {
// 목록 새로고침
loadCompanies(refresh: true);
clearError();
return true;
},
);
} catch (e) {
_setError('회사 생성에 실패했습니다: $e');
return false;
} finally {
_setLoading(false);
}
}
/// 회사 수정
Future<bool> updateCompany(int id, Company company) async {
try {
_setLoading(true);
final params = UpdateCompanyParams(id: id, company: company);
final response = await _updateCompanyUseCase(params);
return response.fold(
(failure) {
_setError('회사 수정에 실패했습니다: ${failure.message}');
return false;
},
(company) {
// 목록 새로고침
loadCompanies(refresh: true);
clearError();
return true;
},
);
} catch (e) {
_setError('회사 수정에 실패했습니다: $e');
return false;
} finally {
_setLoading(false);
}
}
/// 회사 삭제 (Soft Delete)
Future<bool> deleteCompany(int id) async {
try {
_setLoading(true);
final params = DeleteCompanyParams(id: id);
final response = await _deleteCompanyUseCase(params);
return response.fold(
(failure) {
_setError('회사 삭제에 실패했습니다: ${failure.message}');
return false;
},
(_) {
// 목록 새로고침
loadCompanies(refresh: true);
clearError();
return true;
},
);
} catch (e) {
_setError('회사 삭제에 실패했습니다: $e');
return false;
} finally {
_setLoading(false);
}
}
/// 회사 복구
Future<bool> restoreCompany(int id) async {
try {
_setLoading(true);
final response = await _restoreCompanyUseCase(id);
return response.fold(
(failure) {
_setError('회사 복구에 실패했습니다: ${failure.message}');
return false;
},
(company) {
// 목록 새로고침
loadCompanies(refresh: true);
clearError();
return true;
},
);
} catch (e) {
_setError('회사 복구에 실패했습니다: $e');
return false;
} finally {
_setLoading(false);
}
}
/// 페이지 변경
Future<void> goToPage(int page) async {
if (page < 1 || page > _totalPages || page == _currentPage) return;
await loadCompanies(
page: page,
search: _searchQuery,
includeDeleted: _includeDeleted,
);
}
/// 검색 설정
void setSearch(String? search) {
_searchQuery = search;
loadCompanies(
search: search,
includeDeleted: _includeDeleted,
refresh: true,
);
}
/// 삭제된 항목 포함 여부 토글
void toggleIncludeDeleted() {
_includeDeleted = !_includeDeleted;
loadCompanies(
search: _searchQuery,
includeDeleted: _includeDeleted,
refresh: true,
);
}
/// 새로고침
Future<void> refresh() async {
await loadCompanies(
page: 1,
search: _searchQuery,
includeDeleted: _includeDeleted,
refresh: true,
);
}
/// 선택된 회사 초기화
void clearSelectedCompany() {
_selectedCompany = null;
notifyListeners();
}
}

View File

@@ -19,6 +19,8 @@ import 'package:superport/core/errors/failures.dart';
import 'dart:async';
import 'branch_form_controller.dart'; // 분리된 지점 컨트롤러 import
import 'package:superport/data/models/zipcode_dto.dart';
import 'package:superport/data/datasources/remote/api_client.dart';
import 'package:dio/dio.dart';
/// 회사 폼 컨트롤러 - 비즈니스 로직 처리
class CompanyFormController {
@@ -46,9 +48,9 @@ class CompanyFormController {
// 회사 유형 선택값 (복수)
List<CompanyType> selectedCompanyTypes = [CompanyType.customer];
// 부모 회사 선택
// 부모 회사 선택 (단순화)
int? selectedParentCompanyId;
List<Company> availableParentCompanies = [];
Map<int, String> availableParentCompanies = {};
List<String> companyNames = [];
List<String> filteredCompanyNames = [];
@@ -89,37 +91,65 @@ class CompanyFormController {
_setupFocusNodes();
_setupControllerListeners();
// 비동기 초기화는 별도로 호출해야 함
Future.microtask(() => _initializeAsync());
Future.microtask(() => loadParentCompanies());
}
Future<void> _initializeAsync() async {
await _loadCompanyNames();
// loadCompanyData는 별도로 호출됨 (company_form.dart에서)
}
// 회사명 목록 로드 (자동완성용)
Future<void> _loadCompanyNames() async {
// 부모 회사 목록 로드 (LOOKUP COMPANIES API 직접 호출)
Future<void> loadParentCompanies() async {
try {
List<Company> companies;
// API만 사용 (PaginatedResponse에서 items 추출)
final response = await _companyService.getCompanies(page: 1, perPage: 1000);
companies = response.items;
companyNames = companies.map((c) => c.name).toList();
filteredCompanyNames = companyNames;
debugPrint('📝 부모 회사 목록 로드 시작 - LOOKUP /companies API 직접 호출');
// 부모 회사 목록도 설정 (자기 자신은 제외)
if (companyId != null) {
availableParentCompanies = companies.where((c) => c.id != companyId).toList();
// API 직접 호출 (GetIt DI 사용)
final apiClient = GetIt.instance<ApiClient>();
debugPrint('📞 === LOOKUP COMPANIES API 요청 ===');
debugPrint('📞 URL: /lookups/companies');
final response = await apiClient.get('/lookups/companies');
debugPrint('📊 === LOOKUP COMPANIES API 응답 ===');
debugPrint('📊 Status Code: ${response.statusCode}');
debugPrint('📊 Response Data: ${response.data}');
if (response.statusCode == 200 && response.data != null) {
final List<dynamic> companiesJson = response.data as List<dynamic>;
debugPrint('🎯 === LOOKUP COMPANIES API 성공 ===');
debugPrint('📊 받은 회사 총 개수: ${companiesJson.length}');
if (companiesJson.isNotEmpty) {
debugPrint('📝 Lookup 회사 목록:');
for (int i = 0; i < companiesJson.length && i < 15; i++) {
final company = companiesJson[i];
debugPrint(' ${i + 1}. ID: ${company['id']}, 이름: ${company['name']}');
}
if (companiesJson.length > 15) {
debugPrint(' ... 외 ${companiesJson.length - 15}개 더');
}
}
// ===== 부모회사 드롭다운 구성 =====
availableParentCompanies = {};
for (final companyJson in companiesJson) {
final id = companyJson['id'] as int?;
final name = companyJson['name'] as String?;
if (id != null && name != null && id != companyId) {
availableParentCompanies[id] = name;
}
}
debugPrint('✅ 부모 회사 목록 로드 완료: ${availableParentCompanies.length}');
debugPrint('📝 드롭다운에 표시될 회사들: ${availableParentCompanies.values.take(5).join(", ")}${availableParentCompanies.length > 5 ? "..." : ""}');
} else {
availableParentCompanies = companies;
debugPrint('❌ Lookup Companies API 실패: 상태코드 ${response.statusCode}');
availableParentCompanies = {};
}
} catch (e) {
debugPrint('❌ 회사 목록 로드 실패: $e');
companyNames = [];
filteredCompanyNames = [];
availableParentCompanies = [];
debugPrint(' 부모 회사 목록 로드 예외: $e');
availableParentCompanies = {};
}
}
@@ -171,17 +201,18 @@ class CompanyFormController {
remarkController.text = company.remark ?? '';
debugPrint('📝 비고 설정: "${remarkController.text}"');
// 전화번호 처리
// 전화번호 처리 - 수정 모드에서는 전체 전화번호를 그대로 사용
if (company.contactPhone != null && company.contactPhone!.isNotEmpty) {
// 통합 필드를 위해 전체 전화번호를 그대로 저장
contactPhoneController.text = company.contactPhone!;
// 기존 분리 로직은 참고용으로만 유지
selectedPhonePrefix = extractPhonePrefix(
company.contactPhone!,
phonePrefixes,
);
contactPhoneController.text = extractPhoneNumberWithoutPrefix(
company.contactPhone!,
phonePrefixes,
);
debugPrint('📝 전화번호 설정: $selectedPhonePrefix-${contactPhoneController.text}');
debugPrint('📝 전화번호 설정 (전체): ${contactPhoneController.text}');
debugPrint('📝 전화번호 접두사 (참고): $selectedPhonePrefix');
}
// 회사 유형 설정
@@ -315,24 +346,51 @@ class CompanyFormController {
// 회사명 중복 검사 (저장 시점에만 수행)
Future<bool> checkDuplicateName(String name) async {
try {
// 수정 모드일 때는 자기 자신을 제외하고 검사
final response = await _companyService.getCompanies(search: name);
debugPrint('🔍 === 중복 검사 시작 (LOOKUPS API 사용) ===');
debugPrint('🔍 검사할 회사명: "$name"');
debugPrint('🔍 현재 회사 ID: $companyId');
for (final company in response.items) {
// 정확히 일치하는 회사명이 있는지 확인 (대소문자 구분 없이)
if (company.name.toLowerCase() == name.toLowerCase()) {
// 수정 모드일 때는 자기 자신은 제외
if (companyId != null && company.id == companyId) {
continue;
// LOOKUPS API로 전체 회사 목록 조회 (페이지네이션 없음)
final apiClient = GetIt.instance<ApiClient>();
final response = await apiClient.get('/lookups/companies');
if (response.statusCode == 200 && response.data != null) {
final List<dynamic> companiesJson = response.data as List<dynamic>;
debugPrint('🔍 전체 회사 수 (LOOKUPS): ${companiesJson.length}');
for (final companyJson in companiesJson) {
final id = companyJson['id'] as int?;
final companyName = companyJson['name'] as String?;
if (id != null && companyName != null) {
debugPrint('🔍 비교: "$companyName" vs "$name"');
debugPrint(' - 회사 ID: $id');
debugPrint(' - 소문자 비교: "${companyName.toLowerCase()}" == "${name.toLowerCase()}"');
// 정확히 일치하는 회사명이 있는지 확인 (대소문자 구분 없이)
if (companyName.toLowerCase() == name.toLowerCase()) {
// 수정 모드일 때는 자기 자신은 제외
if (companyId != null && id == companyId) {
debugPrint('✅ 자기 자신이므로 제외');
continue;
}
debugPrint('❌ 중복 발견! 기존 회사: ID $id, 이름: "$companyName"');
return true; // 중복 발견
}
}
return true; // 중복 발견
}
debugPrint('✅ 중복 없음');
return false; // 중복 없음
} else {
debugPrint('❌ LOOKUPS API 호출 실패: ${response.statusCode}');
return true; // 안전장치
}
return false; // 중복 없음
} catch (e) {
debugPrint('회사명 중복 검사 실패: $e');
// 네트워크 오류 시 중복 음으로 처리 (저장 진행)
return false;
debugPrint('회사명 중복 검사 실패: $e');
// 네트워크 오류 시 중복 음으로 처리 (안전장치)
return true;
}
}
@@ -378,10 +436,7 @@ class CompanyFormController {
address: companyAddress,
contactName: contactNameController.text.trim(),
contactPosition: contactPositionController.text.trim(),
contactPhone: getFullPhoneNumber(
selectedPhonePrefix,
contactPhoneController.text.trim(),
),
contactPhone: contactPhoneController.text.trim(),
contactEmail: contactEmailController.text.trim(),
remark: remarkController.text.trim(),
branches:
@@ -399,6 +454,11 @@ class CompanyFormController {
Company savedCompany;
if (companyId == null) {
// 새 회사 생성
debugPrint('💾 회사 생성 요청 데이터:');
debugPrint(' - 회사명: ${company.name}');
debugPrint(' - 이메일: ${company.contactEmail}');
debugPrint(' - 부모회사ID: ${company.parentCompanyId}');
savedCompany = await _companyService.createCompany(company);
debugPrint(
'Company created successfully with ID: ${savedCompany.id}',
@@ -423,6 +483,11 @@ class CompanyFormController {
}
} else {
// 기존 회사 수정
debugPrint('💾 회사 수정 요청 데이터:');
debugPrint(' - 회사명: ${company.name}');
debugPrint(' - 이메일: ${company.contactEmail}');
debugPrint(' - 부모회사ID: ${company.parentCompanyId}');
savedCompany = await _companyService.updateCompany(
companyId!,
company,
@@ -481,11 +546,55 @@ class CompanyFormController {
}
}
return true;
} on DioException catch (e) {
debugPrint('❌ === COMPANY SAVE DIO 에러 ===');
debugPrint('❌ 에러 타입: ${e.type}');
debugPrint('❌ 상태 코드: ${e.response?.statusCode}');
debugPrint('❌ 에러 메시지: ${e.message}');
debugPrint('❌ 응답 데이터 타입: ${e.response?.data.runtimeType}');
debugPrint('❌ 응답 데이터: ${e.response?.data}');
debugPrint('❌ 응답 헤더: ${e.response?.headers}');
if (e.response?.statusCode == 409) {
// 409 Conflict - 중복 데이터
final responseData = e.response?.data;
String errorMessage = '중복된 데이터가 있습니다';
debugPrint('🔍 === 409 CONFLICT 상세 분석 ===');
debugPrint('🔍 응답 데이터 분석:');
if (responseData is Map<String, dynamic>) {
debugPrint('🔍 Map 형태 응답:');
responseData.forEach((key, value) {
debugPrint(' - $key: $value');
});
// 백엔드 응답 형식에 맞게 메시지 추출
// 백엔드: {"error": {"code": 409, "message": "...", "type": "DUPLICATE_ERROR"}}
errorMessage = responseData['error']?['message'] ??
responseData['message'] ??
responseData['detail'] ??
responseData['msg'] ??
errorMessage;
} else if (responseData is String) {
debugPrint('🔍 String 형태 응답: $responseData');
errorMessage = responseData;
} else {
debugPrint('🔍 기타 형태 응답: ${responseData.toString()}');
}
debugPrint('🔄 최종 에러 메시지: $errorMessage');
// 이 오류는 UI에서 처리하도록 다시 throw
throw Exception('CONFLICT: $errorMessage');
}
debugPrint('❌ 회사 저장 실패 (DioException): ${e.message}');
return false;
} on Failure catch (e) {
debugPrint('Failed to save company: ${e.message}');
debugPrint('❌ 회사 저장 실패 (Failure): ${e.message}');
return false;
} catch (e) {
debugPrint('Unexpected error saving company: $e');
debugPrint('❌ 예상치 못한 오류: $e');
return false;
}
} else {