/// 회사 폼 컨트롤러 /// /// 회사 폼 화면의 비즈니스 로직을 담당하는 컨트롤러 클래스 /// 주요 기능: /// - 회사 데이터 로드 및 저장 /// - 자동완성 처리 /// - 지점 정보 관리 (추가, 삭제, 수정) /// - 전화번호 처리 /// - 중복 회사명 체크 /// - 회사 유형 관리 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(); 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 formKey = GlobalKey(); final ScrollController scrollController = ScrollController(); // 회사 유형 선택값 (복수) List selectedCompanyTypes = [CompanyType.customer]; List companyNames = []; List filteredCompanyNames = []; bool showCompanyNameDropdown = false; // 분리된 BranchFormController 리스트로 관리 List branchControllers = []; // 직책 목록 및 전화번호 접두사 목록(공통 상수) final List positions = [ '대표이사', '사장', '부사장', '전무', '상무', '이사', '부장', '차장', '팀장', '과장', '대리', '사원', '주임', '기타', ]; final List phonePrefixes = getCommonPhonePrefixes(); String selectedPhonePrefix = '010'; final List phonePrefixesForMain = getCommonPhonePrefixes(); ValueNotifier showPositionDropdownNotifier = ValueNotifier(false); Timer? debounceTimer; bool preventAutoFocus = false; final Map isNewlyAddedBranch = {}; CompanyFormController({required this.dataService, this.companyId}) { _setupFocusNodes(); _setupControllerListeners(); // 비동기 초기화는 별도로 호출해야 함 Future.microtask(() => _initializeAsync()); } Future _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 _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 _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: '지점 {branchControllers.length + 1}', address: const Address(), ); branchControllers.add( BranchFormController( branch: newBranch, positions: positions, phonePrefixes: phonePrefixes, ), ); isNewlyAddedBranch[branchControllers.length - 1] = true; } // 지점 삭제 void removeBranch(int index) { if (index < 0 || index >= branchControllers.length) return; branchControllers[index].dispose(); branchControllers.removeAt(index); isNewlyAddedBranch.remove(index); } Future checkDuplicateCompany() async { if (companyId != null) return null; // 수정 모드에서는 체크하지 않음 final name = nameController.text.trim(); if (name.isEmpty) return null; if (_useApi) { try { // 회사명 목록을 조회하여 중복 확인 final companies = await _companyService.getCompanies(search: name); // 정확히 일치하는 회사명이 있는지 확인 for (final company in companies) { if (company.name.toLowerCase() == name.toLowerCase()) { return company; } } return null; } on Failure catch (e) { debugPrint('Failed to check duplicate company: ${e.message}'); // 오류 발생 시 중복 없음으로 처리 return null; } } else { return dataService.findCompanyByName(name); } } Future saveCompany() async { if (!formKey.currentState!.validate()) { return false; } // 저장 직전, remarkController의 값을 branch.remark에 동기화 for (final c in branchControllers) { c.updateField('remark', c.remarkController.text); } final company = Company( id: companyId, name: nameController.text.trim(), address: companyAddress, contactName: contactNameController.text.trim(), contactPosition: contactPositionController.text.trim(), contactPhone: getFullPhoneNumber( selectedPhonePrefix, contactPhoneController.text.trim(), ), contactEmail: contactEmailController.text.trim(), remark: remarkController.text.trim(), branches: branchControllers.isEmpty ? null : branchControllers.map((c) => c.branch).toList(), companyTypes: List.from(selectedCompanyTypes), // 복수 유형 저장 ); if (_useApi) { try { Company savedCompany; if (companyId == null) { // 새 회사 생성 savedCompany = await _companyService.createCompany(company); debugPrint('Company created successfully with ID: ${savedCompany.id}'); // 지점이 있으면 별도로 생성 if (branchControllers.isNotEmpty && savedCompany.id != null) { for (final branchController in branchControllers) { try { final branch = branchController.branch.copyWith( companyId: savedCompany.id!, ); await _companyService.createBranch(savedCompany.id!, branch); debugPrint('Branch created successfully: ${branch.name}'); } catch (e) { debugPrint('Failed to create branch: $e'); // 지점 생성 실패는 경고만 하고 계속 진행 } } } } else { // 기존 회사 수정 savedCompany = await _companyService.updateCompany(companyId!, company); debugPrint('Company updated successfully'); // 지점 업데이트는 별도 처리 필요 (현재는 수정 시 지점 추가/삭제 미지원) } return true; } on Failure catch (e) { debugPrint('Failed to save company: ${e.message}'); return false; } catch (e) { debugPrint('Unexpected error saving company: $e'); return false; } } else { if (companyId == null) { dataService.addCompany(company); } else { dataService.updateCompany(company); } return true; } } // 지점 저장 Future saveBranch(int branchId) async { if (!formKey.currentState!.validate()) { return false; } formKey.currentState!.save(); // 지점 정보 생성 final branch = Branch( id: branchId, companyId: companyId!, name: nameController.text.trim(), address: companyAddress, contactName: contactNameController.text.trim(), contactPosition: contactPositionController.text.trim(), contactPhone: getFullPhoneNumber( selectedPhonePrefix, contactPhoneController.text.trim(), ), contactEmail: contactEmailController.text.trim(), remark: remarkController.text.trim(), ); if (_useApi) { try { // API를 사용하여 지점 업데이트 await _companyService.updateBranch(companyId!, branchId, branch); return true; } on Failure catch (e) { debugPrint('Failed to save branch: ${e.message}'); return false; } } else { // Mock 데이터 서비스 사용 dataService.updateBranch(companyId!, branch); return true; } } // 회사 유형 체크박스 토글 함수 void toggleCompanyType(CompanyType type, bool checked) { if (checked) { if (!selectedCompanyTypes.contains(type)) { selectedCompanyTypes.add(type); } } else { selectedCompanyTypes.remove(type); if (selectedCompanyTypes.isEmpty) { // 최소 1개는 선택되도록 강제 selectedCompanyTypes.add(CompanyType.customer); } } } } // 전화번호 관련 유틸리티 메서드 // 전화번호 접두사 추출 String extractPhonePrefix(String phoneNumber, List prefixes) { if (phoneNumber.isEmpty) return '010'; // 하이픈 제거 String cleanNumber = phoneNumber.replaceAll('-', ''); // 접두사 확인 for (String prefix in prefixes) { if (cleanNumber.startsWith(prefix)) { return prefix; } } return '010'; // 기본값 } // 접두사 제외 전화번호 추출 String extractPhoneNumberWithoutPrefix( String phoneNumber, List prefixes, ) { if (phoneNumber.isEmpty) return ''; // 하이픈 제거 String cleanNumber = phoneNumber.replaceAll('-', ''); // 접두사 제거 for (String prefix in prefixes) { if (cleanNumber.startsWith(prefix)) { return cleanNumber.substring(prefix.length); } } return cleanNumber; } // 전체 전화번호 생성 (접두사 + 번호) String getFullPhoneNumber(String prefix, String number) { if (number.isEmpty) return ''; // 하이픈 제거 String cleanNumber = number.replaceAll('-', ''); return '$prefix-$cleanNumber'; } // 일반적인 전화번호 접두사 목록 List getCommonPhonePrefixes() { return [ '010', '011', '016', '017', '018', '019', // 휴대폰 '02', '031', '032', '033', '041', '042', '043', '044', '051', '052', '053', '054', '055', '061', '062', '063', '064', // 지역번호 '070', '080', '1588', '1566', '1544', '1644', '1661', '1599', // 기타 ]; }