주요 변경사항: - CLAUDE.md: 프로젝트 규칙 v2.0으로 업데이트, 아키텍처 명확화 - 불필요한 문서 제거: NEXT_TASKS.md, TEST_PROGRESS.md, test_results 파일들 - 테스트 시스템 개선: 실제 API 테스트 스위트 추가 (15개 새 테스트 파일) - License 관리: DTO 모델 개선, API 응답 처리 최적화 - 에러 처리: Interceptor 로직 강화, 상세 로깅 추가 - Company/User/Warehouse 테스트: 자동화 테스트 안정성 향상 - Phone Utils: 전화번호 포맷팅 로직 개선 - Overview Controller: 대시보드 데이터 로딩 최적화 - Analysis Options: Flutter 린트 규칙 추가 테스트 개선: - company_real_api_test.dart: 실제 API 회사 관리 테스트 - equipment_in/out_real_api_test.dart: 장비 입출고 API 테스트 - license_real_api_test.dart: 라이선스 관리 API 테스트 - user_real_api_test.dart: 사용자 관리 API 테스트 - warehouse_location_real_api_test.dart: 창고 위치 API 테스트 - filter_sort_test.dart: 필터링/정렬 기능 테스트 - pagination_test.dart: 페이지네이션 테스트 - interactive_search_test.dart: 검색 기능 테스트 - overview_dashboard_test.dart: 대시보드 통합 테스트 코드 품질: - 모든 서비스에 에러 처리 강화 - DTO 모델 null safety 개선 - 테스트 커버리지 확대 - 불필요한 로그 파일 제거로 리포지토리 정리 Co-Authored-By: Claude <noreply@anthropic.com>
476 lines
14 KiB
Dart
476 lines
14 KiB
Dart
/// 회사 폼 컨트롤러
|
||
///
|
||
/// 회사 폼 화면의 비즈니스 로직을 담당하는 컨트롤러 클래스
|
||
/// 주요 기능:
|
||
/// - 회사 데이터 로드 및 저장
|
||
/// - 자동완성 처리
|
||
/// - 지점 정보 관리 (추가, 삭제, 수정)
|
||
/// - 전화번호 처리
|
||
/// - 중복 회사명 체크
|
||
/// - 회사 유형 관리
|
||
import 'package:flutter/material.dart';
|
||
import 'package:get_it/get_it.dart';
|
||
import 'package:superport/models/address_model.dart';
|
||
import 'package:superport/models/company_model.dart';
|
||
import 'package:superport/services/mock_data_service.dart';
|
||
import 'package:superport/services/company_service.dart';
|
||
import 'package:superport/core/errors/failures.dart';
|
||
import 'package:superport/utils/phone_utils.dart';
|
||
import 'dart:async';
|
||
import 'branch_form_controller.dart'; // 분리된 지점 컨트롤러 import
|
||
|
||
/// 회사 폼 컨트롤러 - 비즈니스 로직 처리
|
||
class CompanyFormController {
|
||
final MockDataService dataService;
|
||
final CompanyService _companyService = GetIt.instance<CompanyService>();
|
||
final int? companyId;
|
||
|
||
// Feature flag for API usage
|
||
bool _useApi = true;
|
||
|
||
final TextEditingController nameController = TextEditingController();
|
||
Address companyAddress = const Address();
|
||
final TextEditingController contactNameController = TextEditingController();
|
||
final TextEditingController contactPositionController =
|
||
TextEditingController();
|
||
final TextEditingController contactPhoneController = TextEditingController();
|
||
final TextEditingController contactEmailController = TextEditingController();
|
||
final TextEditingController remarkController = TextEditingController();
|
||
final FocusNode nameFocusNode = FocusNode();
|
||
final GlobalKey<FormState> formKey = GlobalKey<FormState>();
|
||
final ScrollController scrollController = ScrollController();
|
||
|
||
// 회사 유형 선택값 (복수)
|
||
List<CompanyType> selectedCompanyTypes = [CompanyType.customer];
|
||
|
||
List<String> companyNames = [];
|
||
List<String> filteredCompanyNames = [];
|
||
bool showCompanyNameDropdown = false;
|
||
|
||
// 분리된 BranchFormController 리스트로 관리
|
||
List<BranchFormController> branchControllers = [];
|
||
|
||
// 직책 목록 및 전화번호 접두사 목록(공통 상수)
|
||
final List<String> positions = [
|
||
'대표이사',
|
||
'사장',
|
||
'부사장',
|
||
'전무',
|
||
'상무',
|
||
'이사',
|
||
'부장',
|
||
'차장',
|
||
'팀장',
|
||
'과장',
|
||
'대리',
|
||
'사원',
|
||
'주임',
|
||
'기타',
|
||
];
|
||
final List<String> phonePrefixes = getCommonPhonePrefixes();
|
||
|
||
String selectedPhonePrefix = '010';
|
||
final List<String> phonePrefixesForMain = getCommonPhonePrefixes();
|
||
|
||
ValueNotifier<bool> showPositionDropdownNotifier = ValueNotifier<bool>(false);
|
||
Timer? debounceTimer;
|
||
bool preventAutoFocus = false;
|
||
final Map<int, bool> isNewlyAddedBranch = {};
|
||
|
||
CompanyFormController({required this.dataService, this.companyId}) {
|
||
_setupFocusNodes();
|
||
_setupControllerListeners();
|
||
// 비동기 초기화는 별도로 호출해야 함
|
||
Future.microtask(() => _initializeAsync());
|
||
}
|
||
|
||
Future<void> _initializeAsync() async {
|
||
final isEditMode = companyId != null;
|
||
await _loadCompanyNames();
|
||
if (isEditMode) {
|
||
await _loadCompanyData();
|
||
}
|
||
}
|
||
|
||
void dispose() {
|
||
debounceTimer?.cancel();
|
||
scrollController.dispose();
|
||
showPositionDropdownNotifier.dispose();
|
||
for (final branchController in branchControllers) {
|
||
branchController.dispose();
|
||
}
|
||
nameController.dispose();
|
||
contactNameController.dispose();
|
||
contactPositionController.dispose();
|
||
contactPhoneController.dispose();
|
||
contactEmailController.dispose();
|
||
remarkController.dispose();
|
||
nameFocusNode.dispose();
|
||
}
|
||
|
||
void _setupFocusNodes() {
|
||
nameFocusNode.addListener(() {
|
||
if (nameFocusNode.hasFocus) {
|
||
showCompanyNameDropdown = filteredCompanyNames.isNotEmpty;
|
||
} else {
|
||
showCompanyNameDropdown = false;
|
||
}
|
||
});
|
||
}
|
||
|
||
void _setupControllerListeners() {
|
||
nameController.addListener(_onCompanyNameTextChanged);
|
||
}
|
||
|
||
Future<void> _loadCompanyNames() async {
|
||
if (_useApi) {
|
||
try {
|
||
final companyNamesList = await _companyService.getCompanyNames();
|
||
companyNames = companyNamesList.map((item) => item['name'] as String).toList();
|
||
} on Failure catch (e) {
|
||
// 오류 시 빈 목록 사용
|
||
companyNames = [];
|
||
debugPrint('Failed to load company names: ${e.message}');
|
||
}
|
||
} else {
|
||
companyNames = dataService.getAllCompanyNames();
|
||
}
|
||
filteredCompanyNames = List.from(companyNames);
|
||
}
|
||
|
||
Future<void> _loadCompanyData() async {
|
||
if (companyId == null) return;
|
||
|
||
Company? company;
|
||
if (_useApi) {
|
||
try {
|
||
company = await _companyService.getCompanyWithBranches(companyId!);
|
||
} on Failure catch (e) {
|
||
debugPrint('Failed to load company data: ${e.message}');
|
||
return;
|
||
}
|
||
} else {
|
||
company = dataService.getCompanyById(companyId!);
|
||
}
|
||
|
||
if (company != null) {
|
||
nameController.text = company.name;
|
||
companyAddress = company.address;
|
||
selectedCompanyTypes = List.from(company.companyTypes); // 복수 유형 지원
|
||
contactNameController.text = company.contactName ?? '';
|
||
contactPositionController.text = company.contactPosition ?? '';
|
||
selectedPhonePrefix = extractPhonePrefix(
|
||
company.contactPhone ?? '',
|
||
phonePrefixesForMain,
|
||
);
|
||
contactPhoneController.text = extractPhoneNumberWithoutPrefix(
|
||
company.contactPhone ?? '',
|
||
phonePrefixesForMain,
|
||
);
|
||
contactEmailController.text = company.contactEmail ?? '';
|
||
remarkController.text = company.remark ?? '';
|
||
// 지점 컨트롤러 생성
|
||
branchControllers.clear();
|
||
final branches = company.branches?.toList() ?? [];
|
||
if (branches.isEmpty) {
|
||
_addInitialBranch();
|
||
} else {
|
||
for (final branch in branches) {
|
||
branchControllers.add(
|
||
BranchFormController(
|
||
branch: branch,
|
||
positions: positions,
|
||
phonePrefixes: phonePrefixes,
|
||
),
|
||
);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
void _addInitialBranch() {
|
||
final newBranch = Branch(
|
||
companyId: companyId ?? 0,
|
||
name: '본사',
|
||
address: const Address(),
|
||
);
|
||
branchControllers.add(
|
||
BranchFormController(
|
||
branch: newBranch,
|
||
positions: positions,
|
||
phonePrefixes: phonePrefixes,
|
||
),
|
||
);
|
||
isNewlyAddedBranch[branchControllers.length - 1] = true;
|
||
}
|
||
|
||
void updateCompanyAddress(Address address) {
|
||
companyAddress = address;
|
||
}
|
||
|
||
void updateBranchAddress(int index, Address address) {
|
||
if (index >= 0 && index < branchControllers.length) {
|
||
branchControllers[index].updateAddress(address);
|
||
}
|
||
}
|
||
|
||
void _onCompanyNameTextChanged() {
|
||
final query = nameController.text.toLowerCase();
|
||
if (query.isEmpty) {
|
||
filteredCompanyNames = List.from(companyNames);
|
||
} else {
|
||
filteredCompanyNames =
|
||
companyNames
|
||
.where((name) => name.toLowerCase().contains(query))
|
||
.toList();
|
||
}
|
||
showCompanyNameDropdown =
|
||
nameFocusNode.hasFocus && filteredCompanyNames.isNotEmpty;
|
||
}
|
||
|
||
void selectCompanyName(String name) {
|
||
nameController.text = name;
|
||
showCompanyNameDropdown = false;
|
||
}
|
||
|
||
// 지점 추가
|
||
void addBranch() {
|
||
final newBranch = Branch(
|
||
companyId: companyId ?? 0,
|
||
name: '지점 |