Files
superport/lib/screens/company/controllers/company_form_controller.dart
JiWoong Sul 6b31631cfb feat: 회사 관리 API 연동 구현
- CompanyService 및 RemoteDataSource 구현
- Company, Branch DTO 모델 생성 (Freezed)
- 의존성 주입 컨테이너 업데이트
- 회사 등록/수정 폼에 API 연동 로직 적용
- API 통합 계획 문서 업데이트
2025-07-24 17:56:06 +09:00

391 lines
12 KiB
Dart
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/// 회사 폼 컨트롤러
///
/// 회사 폼 화면의 비즈니스 로직을 담당하는 컨트롤러 클래스
/// 주요 기능:
/// - 회사 데이터 로드 및 저장
/// - 자동완성 처리
/// - 지점 정보 관리 (추가, 삭제, 수정)
/// - 전화번호 처리
/// - 중복 회사명 체크
/// - 회사 유형 관리
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: '지점 {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);
}
Company? checkDuplicateCompany() {
if (companyId != null) return null;
final name = nameController.text.trim();
if (name.isEmpty) return null;
return dataService.findCompanyByName(name);
}
Future<bool> 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 {
if (companyId == null) {
await _companyService.createCompany(company);
} else {
await _companyService.updateCompany(companyId!, company);
}
return true;
} on Failure catch (e) {
debugPrint('Failed to save company: ${e.message}');
return false;
}
} else {
if (companyId == null) {
dataService.addCompany(company);
} else {
dataService.updateCompany(company);
}
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<String> 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<String> 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<String> 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', // 기타
];
}