feat: API 연동 개선 및 라이선스 모델 확장
- 라이선스 모델 전면 개편 (상세 필드 추가, 계산 필드 구현) - API 응답 처리 개선 (HTTP 상태 코드 기반) - 장비 출고 폼 컨트롤러 추가 - 회사 지점 정보 모델 추가 - 공통 데이터 모델 구조 추가 - 전체 서비스 레이어 API 호출 방식 통일 - UI 컴포넌트 마이너 개선
This commit is contained in:
@@ -171,7 +171,7 @@ class EquipmentInFormController extends ChangeNotifier {
|
||||
// API 실패 시 Mock 데이터 사용
|
||||
_loadFromMockData(equipmentIn);
|
||||
}
|
||||
} else {
|
||||
} else if (equipmentIn != null) {
|
||||
_loadFromMockData(equipmentIn);
|
||||
}
|
||||
} else {
|
||||
@@ -311,11 +311,15 @@ class EquipmentInFormController extends ChangeNotifier {
|
||||
int? warehouseLocationId;
|
||||
if (warehouseLocation != null) {
|
||||
// TODO: 창고 위치 ID 가져오기 - 현재는 목 데이터에서 찾기
|
||||
final warehouse = dataService.getAllWarehouseLocations().firstWhere(
|
||||
(w) => w.name == warehouseLocation,
|
||||
orElse: () => null,
|
||||
);
|
||||
warehouseLocationId = warehouse?.id;
|
||||
try {
|
||||
final warehouse = dataService.getAllWarehouseLocations().firstWhere(
|
||||
(w) => w.name == warehouseLocation,
|
||||
);
|
||||
warehouseLocationId = warehouse.id;
|
||||
} catch (e) {
|
||||
// 창고를 찾을 수 없는 경우
|
||||
warehouseLocationId = null;
|
||||
}
|
||||
}
|
||||
|
||||
await _equipmentService.equipmentIn(
|
||||
|
||||
@@ -63,13 +63,12 @@ class EquipmentListController extends ChangeNotifier {
|
||||
);
|
||||
|
||||
// API 모델을 UnifiedEquipment로 변환
|
||||
final unifiedEquipments = apiEquipments.map((equipment) {
|
||||
final List<UnifiedEquipment> unifiedEquipments = apiEquipments.map((equipment) {
|
||||
return UnifiedEquipment(
|
||||
id: equipment.id,
|
||||
equipment: equipment,
|
||||
quantity: equipment.quantity,
|
||||
date: DateTime.now(), // 실제로는 API에서 날짜 정보를 가져와야 함
|
||||
status: EquipmentStatus.in_, // 기본값, 실제로는 API에서 가져와야 함
|
||||
locationTrack: LocationTrack.inStock,
|
||||
);
|
||||
}).toList();
|
||||
|
||||
|
||||
@@ -0,0 +1,231 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:superport/models/equipment_unified_model.dart';
|
||||
import 'package:superport/models/company_model.dart';
|
||||
import 'package:superport/models/company_branch_info.dart';
|
||||
import 'package:superport/models/address_model.dart';
|
||||
import 'package:superport/services/mock_data_service.dart';
|
||||
import 'package:superport/utils/constants.dart';
|
||||
|
||||
/// 장비 출고 폼 컨트롤러
|
||||
///
|
||||
/// 폼의 전체 상태, 유효성, 저장, 데이터 로딩 등 비즈니스 로직을 담당한다.
|
||||
class EquipmentOutFormController extends ChangeNotifier {
|
||||
final MockDataService dataService;
|
||||
int? equipmentOutId;
|
||||
|
||||
// 편집 모드 여부
|
||||
bool isEditMode = false;
|
||||
|
||||
// 상태 관리
|
||||
bool _isLoading = false;
|
||||
String? _errorMessage;
|
||||
|
||||
// Getters
|
||||
bool get isLoading => _isLoading;
|
||||
String? get errorMessage => _errorMessage;
|
||||
|
||||
// 폼 키
|
||||
final GlobalKey<FormState> formKey = GlobalKey<FormState>();
|
||||
|
||||
// 선택된 장비 정보
|
||||
Equipment? selectedEquipment;
|
||||
int? selectedEquipmentInId;
|
||||
List<Map<String, dynamic>>? selectedEquipments;
|
||||
|
||||
// 출고처 정보
|
||||
List<String?> selectedCompanies = [null];
|
||||
List<bool> hasManagersPerCompany = [false];
|
||||
List<List<String>> filteredManagersPerCompany = [[]];
|
||||
List<String?> selectedManagersPerCompany = [null];
|
||||
|
||||
// 라이선스 정보
|
||||
String? selectedLicense;
|
||||
|
||||
// 출고 타입
|
||||
String outType = 'O'; // 기본값: 출고
|
||||
|
||||
// 드롭다운 데이터
|
||||
List<CompanyBranchInfo> companies = [];
|
||||
List<String> licenses = [];
|
||||
|
||||
// 날짜
|
||||
DateTime outDate = DateTime.now();
|
||||
|
||||
// 회사 정보 (지점 포함)
|
||||
List<CompanyBranchInfo> get companiesWithBranches => companies;
|
||||
|
||||
// 비고
|
||||
final TextEditingController remarkController = TextEditingController();
|
||||
|
||||
EquipmentOutFormController({
|
||||
required this.dataService,
|
||||
this.equipmentOutId,
|
||||
}) {
|
||||
isEditMode = equipmentOutId != null;
|
||||
}
|
||||
|
||||
// 이용 가능한 회사 목록 (선택된 회사 제외)
|
||||
List<List<CompanyBranchInfo>> get availableCompaniesPerDropdown {
|
||||
return List.generate(selectedCompanies.length, (index) {
|
||||
final selectedBefore = selectedCompanies.sublist(0, index).whereType<String>().toSet();
|
||||
return companies.where((company) => !selectedBefore.contains(company.name)).toList();
|
||||
});
|
||||
}
|
||||
|
||||
// 드롭다운 데이터 로드
|
||||
void loadDropdownData() {
|
||||
// 회사 목록 로드 (출고처 가능한 회사만)
|
||||
companies = dataService.getAllCompanies()
|
||||
.where((c) => c.companyTypes.contains(CompanyType.customer))
|
||||
.map((c) => CompanyBranchInfo(
|
||||
id: c.id,
|
||||
name: c.name,
|
||||
originalName: c.name,
|
||||
isMainCompany: true,
|
||||
companyId: c.id,
|
||||
branchId: null,
|
||||
))
|
||||
.toList();
|
||||
|
||||
// 라이선스 목록 로드
|
||||
licenses = dataService.getAllLicenses().map((l) => l.name).toList();
|
||||
}
|
||||
|
||||
// 선택된 장비로 초기화
|
||||
void initializeWithSelectedEquipment(Equipment equipment) {
|
||||
selectedEquipment = equipment;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// 회사 선택 시 담당자 목록 필터링
|
||||
void filterManagersForCompany(int index) {
|
||||
if (index >= selectedCompanies.length || selectedCompanies[index] == null) {
|
||||
hasManagersPerCompany[index] = false;
|
||||
filteredManagersPerCompany[index] = [];
|
||||
return;
|
||||
}
|
||||
|
||||
// Mock 데이터에서 회사별 담당자 목록 가져오기
|
||||
final company = dataService.getAllCompanies().firstWhere(
|
||||
(c) => c.name == selectedCompanies[index],
|
||||
orElse: () => Company(
|
||||
name: '',
|
||||
companyTypes: [],
|
||||
),
|
||||
);
|
||||
|
||||
if (company.name.isNotEmpty && company.contactName != null && company.contactName!.isNotEmpty) {
|
||||
// 회사의 담당자 정보
|
||||
hasManagersPerCompany[index] = true;
|
||||
filteredManagersPerCompany[index] = [company.contactName!];
|
||||
} else {
|
||||
hasManagersPerCompany[index] = false;
|
||||
filteredManagersPerCompany[index] = ['없음'];
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// 인덱스별 담당자 필터링
|
||||
void filterManagersByCompanyAtIndex(int index) {
|
||||
filterManagersForCompany(index);
|
||||
}
|
||||
|
||||
// 회사 추가
|
||||
void addCompany() {
|
||||
selectedCompanies.add(null);
|
||||
hasManagersPerCompany.add(false);
|
||||
filteredManagersPerCompany.add([]);
|
||||
selectedManagersPerCompany.add(null);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// 회사 제거
|
||||
void removeCompany(int index) {
|
||||
if (selectedCompanies.length > 1) {
|
||||
selectedCompanies.removeAt(index);
|
||||
hasManagersPerCompany.removeAt(index);
|
||||
filteredManagersPerCompany.removeAt(index);
|
||||
selectedManagersPerCompany.removeAt(index);
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
// 회사 선택 초기화
|
||||
void resetCompanySelection() {
|
||||
selectedCompanies = [null];
|
||||
hasManagersPerCompany = [false];
|
||||
filteredManagersPerCompany = [[]];
|
||||
selectedManagersPerCompany = [null];
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// 에러 초기화
|
||||
void clearError() {
|
||||
_errorMessage = null;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// 이용 가능한 회사 목록 업데이트
|
||||
void updateAvailableCompanies() {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// 날짜 포맷팅
|
||||
String formatDate(DateTime date) {
|
||||
return DateFormat('yyyy-MM-dd').format(date);
|
||||
}
|
||||
|
||||
// 출고 정보 저장
|
||||
Future<bool> saveEquipmentOut(BuildContext context, {String? note}) async {
|
||||
// 유효성 검사
|
||||
if (selectedCompanies.isEmpty || selectedCompanies[0] == null) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('출고처를 선택해주세요.')),
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (selectedEquipment == null && (selectedEquipments == null || selectedEquipments!.isEmpty)) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('출고할 장비를 선택해주세요.')),
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
// TODO: 실제 저장 로직 구현
|
||||
// 현재는 Mock 데이터 서비스에 저장
|
||||
|
||||
if (isEditMode) {
|
||||
// 수정 모드
|
||||
// dataService.updateEquipmentOut(...)
|
||||
} else {
|
||||
// 생성 모드
|
||||
if (selectedEquipments != null && selectedEquipments!.isNotEmpty) {
|
||||
// 다중 장비 출고
|
||||
for (var equipmentData in selectedEquipments!) {
|
||||
// dataService.addEquipmentOut(...)
|
||||
}
|
||||
} else if (selectedEquipment != null) {
|
||||
// 단일 장비 출고
|
||||
// dataService.addEquipmentOut(...)
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (e) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('저장 중 오류가 발생했습니다: $e')),
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
remarkController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import 'package:flutter/services.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:superport/models/equipment_unified_model.dart';
|
||||
import 'package:superport/models/company_model.dart';
|
||||
import 'package:superport/models/company_branch_info.dart';
|
||||
import 'package:superport/models/address_model.dart';
|
||||
import 'package:superport/screens/common/custom_widgets.dart';
|
||||
import 'package:superport/screens/common/theme_tailwind.dart';
|
||||
@@ -406,25 +407,17 @@ class _EquipmentOutFormScreenState extends State<EquipmentOutFormScreen> {
|
||||
}
|
||||
}
|
||||
|
||||
controller.saveEquipmentOut(
|
||||
(msg) {
|
||||
controller.saveEquipmentOut(context).then((success) {
|
||||
if (success) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(msg),
|
||||
duration: const Duration(seconds: 2),
|
||||
const SnackBar(
|
||||
content: Text('출고가 완료되었습니다.'),
|
||||
duration: Duration(seconds: 2),
|
||||
),
|
||||
);
|
||||
Navigator.pop(context, true);
|
||||
},
|
||||
(err) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(err),
|
||||
duration: const Duration(seconds: 2),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
: null,
|
||||
style:
|
||||
@@ -510,8 +503,8 @@ class _EquipmentOutFormScreenState extends State<EquipmentOutFormScreen> {
|
||||
controller.availableCompaniesPerDropdown[index]
|
||||
.map(
|
||||
(item) => DropdownMenuItem<String>(
|
||||
value: item,
|
||||
child: _buildCompanyDropdownItem(item, controller),
|
||||
value: item.name,
|
||||
child: _buildCompanyDropdownItem(item.name, controller),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
@@ -526,10 +519,7 @@ class _EquipmentOutFormScreenState extends State<EquipmentOutFormScreen> {
|
||||
controller.selectedCompanies[index - 1] != null)
|
||||
? (value) {
|
||||
controller.selectedCompanies[index] = value;
|
||||
controller.filterManagersByCompanyAtIndex(
|
||||
value,
|
||||
index,
|
||||
);
|
||||
controller.filterManagersByCompanyAtIndex(index);
|
||||
controller.updateAvailableCompanies();
|
||||
}
|
||||
: null,
|
||||
|
||||
Reference in New Issue
Block a user