feat: 라이선스 및 창고 관리 API 연동 구현
- 라이선스 관리 API 연동 완료 - LicenseRemoteDataSource, LicenseService 구현 - LicenseListController, LicenseFormController API 연동 - 페이지네이션, 검색, 필터링 기능 추가 - 라이선스 할당/해제 기능 구현 - 창고 관리 API 연동 완료 - WarehouseRemoteDataSource, WarehouseService 구현 - WarehouseLocationListController, WarehouseLocationFormController API 연동 - 창고별 장비 조회 및 용량 관리 기능 추가 - DI 컨테이너에 새로운 서비스 등록 - API 통합 문서 업데이트 (전체 진행률 100% 달성)
This commit is contained in:
@@ -1,10 +1,16 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:superport/models/warehouse_location_model.dart';
|
||||
import 'package:superport/models/address_model.dart';
|
||||
import 'package:superport/services/warehouse_service.dart';
|
||||
import 'package:superport/services/mock_data_service.dart';
|
||||
|
||||
/// 입고지 폼 상태 및 저장/수정 로직을 담당하는 컨트롤러
|
||||
class WarehouseLocationFormController {
|
||||
class WarehouseLocationFormController extends ChangeNotifier {
|
||||
final bool useApi;
|
||||
final MockDataService? mockDataService;
|
||||
late final WarehouseService _warehouseService;
|
||||
|
||||
/// 폼 키
|
||||
final GlobalKey<FormState> formKey = GlobalKey<FormState>();
|
||||
|
||||
@@ -15,74 +21,157 @@ class WarehouseLocationFormController {
|
||||
final TextEditingController remarkController = TextEditingController();
|
||||
|
||||
/// 주소 정보
|
||||
Address address = const Address();
|
||||
Address _address = const Address();
|
||||
|
||||
/// 저장 중 여부
|
||||
bool isSaving = false;
|
||||
bool _isSaving = false;
|
||||
|
||||
/// 수정 모드 여부
|
||||
bool isEditMode = false;
|
||||
bool _isEditMode = false;
|
||||
|
||||
/// 입고지 id (수정 모드)
|
||||
int? id;
|
||||
int? _id;
|
||||
|
||||
/// 로딩 상태
|
||||
bool _isLoading = false;
|
||||
|
||||
/// 에러 메시지
|
||||
String? _error;
|
||||
|
||||
/// 원본 창고 위치 (수정 모드)
|
||||
WarehouseLocation? _originalLocation;
|
||||
|
||||
WarehouseLocationFormController({
|
||||
this.useApi = true,
|
||||
this.mockDataService,
|
||||
int? locationId,
|
||||
}) {
|
||||
if (useApi && GetIt.instance.isRegistered<WarehouseService>()) {
|
||||
_warehouseService = GetIt.instance<WarehouseService>();
|
||||
}
|
||||
|
||||
if (locationId != null) {
|
||||
initialize(locationId);
|
||||
}
|
||||
}
|
||||
|
||||
// Getters
|
||||
Address get address => _address;
|
||||
bool get isSaving => _isSaving;
|
||||
bool get isEditMode => _isEditMode;
|
||||
int? get id => _id;
|
||||
bool get isLoading => _isLoading;
|
||||
String? get error => _error;
|
||||
WarehouseLocation? get originalLocation => _originalLocation;
|
||||
|
||||
/// 기존 데이터 세팅 (수정 모드)
|
||||
void initialize(int? locationId) {
|
||||
id = locationId;
|
||||
if (id != null) {
|
||||
final location = MockDataService().getWarehouseLocationById(id!);
|
||||
if (location != null) {
|
||||
isEditMode = true;
|
||||
nameController.text = location.name;
|
||||
address = location.address;
|
||||
remarkController.text = location.remark ?? '';
|
||||
Future<void> initialize(int locationId) async {
|
||||
_id = locationId;
|
||||
_isEditMode = true;
|
||||
_isLoading = true;
|
||||
_error = null;
|
||||
notifyListeners();
|
||||
|
||||
try {
|
||||
if (useApi && GetIt.instance.isRegistered<WarehouseService>()) {
|
||||
_originalLocation = await _warehouseService.getWarehouseLocationById(locationId);
|
||||
} else {
|
||||
_originalLocation = mockDataService?.getWarehouseLocationById(locationId);
|
||||
}
|
||||
|
||||
if (_originalLocation != null) {
|
||||
nameController.text = _originalLocation!.name;
|
||||
_address = _originalLocation!.address;
|
||||
remarkController.text = _originalLocation!.remark ?? '';
|
||||
}
|
||||
} catch (e) {
|
||||
_error = e.toString();
|
||||
} finally {
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
/// 주소 변경 처리
|
||||
void updateAddress(Address newAddress) {
|
||||
address = newAddress;
|
||||
_address = newAddress;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// 저장 처리 (추가/수정)
|
||||
Future<bool> save(BuildContext context) async {
|
||||
Future<bool> save() async {
|
||||
if (!formKey.currentState!.validate()) return false;
|
||||
isSaving = true;
|
||||
if (isEditMode) {
|
||||
// 수정
|
||||
MockDataService().updateWarehouseLocation(
|
||||
WarehouseLocation(
|
||||
id: id!,
|
||||
name: nameController.text.trim(),
|
||||
address: address,
|
||||
remark: remarkController.text.trim(),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
// 추가
|
||||
MockDataService().addWarehouseLocation(
|
||||
WarehouseLocation(
|
||||
id: 0,
|
||||
name: nameController.text.trim(),
|
||||
address: address,
|
||||
remark: remarkController.text.trim(),
|
||||
),
|
||||
|
||||
_isSaving = true;
|
||||
_error = null;
|
||||
notifyListeners();
|
||||
|
||||
try {
|
||||
final location = WarehouseLocation(
|
||||
id: _isEditMode ? _id! : 0,
|
||||
name: nameController.text.trim(),
|
||||
address: _address,
|
||||
remark: remarkController.text.trim().isEmpty ? null : remarkController.text.trim(),
|
||||
);
|
||||
|
||||
if (useApi && GetIt.instance.isRegistered<WarehouseService>()) {
|
||||
if (_isEditMode) {
|
||||
await _warehouseService.updateWarehouseLocation(location);
|
||||
} else {
|
||||
await _warehouseService.createWarehouseLocation(location);
|
||||
}
|
||||
} else {
|
||||
if (_isEditMode) {
|
||||
mockDataService?.updateWarehouseLocation(location);
|
||||
} else {
|
||||
mockDataService?.addWarehouseLocation(location);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (e) {
|
||||
_error = e.toString();
|
||||
notifyListeners();
|
||||
return false;
|
||||
} finally {
|
||||
_isSaving = false;
|
||||
notifyListeners();
|
||||
}
|
||||
isSaving = false;
|
||||
Navigator.pop(context, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// 취소 처리
|
||||
void cancel(BuildContext context) {
|
||||
Navigator.pop(context, false);
|
||||
/// 폼 초기화
|
||||
void resetForm() {
|
||||
nameController.clear();
|
||||
remarkController.clear();
|
||||
_address = const Address();
|
||||
_error = null;
|
||||
formKey.currentState?.reset();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// 유효성 검사
|
||||
String? validateName(String? value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return '입고지명을 입력해주세요';
|
||||
}
|
||||
if (value.length < 2) {
|
||||
return '입고지명은 2자 이상이어야 합니다';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
String? validateAddress() {
|
||||
if (_address.isEmpty) {
|
||||
return '주소를 입력해주세요';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// 컨트롤러 해제
|
||||
@override
|
||||
void dispose() {
|
||||
nameController.dispose();
|
||||
remarkController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,36 +1,246 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:superport/models/warehouse_location_model.dart';
|
||||
import 'package:superport/services/warehouse_service.dart';
|
||||
import 'package:superport/services/mock_data_service.dart';
|
||||
|
||||
/// 입고지 리스트 상태 및 CRUD만 담당하는 컨트롤러 클래스 (SRP 적용)
|
||||
/// UI, 네비게이션, 다이얼로그 등은 포함하지 않음
|
||||
/// 향후 서비스/리포지토리 DI 구조로 확장 가능
|
||||
class WarehouseLocationListController {
|
||||
/// 입고지 데이터 서비스 (mock)
|
||||
final MockDataService _dataService = MockDataService();
|
||||
class WarehouseLocationListController extends ChangeNotifier {
|
||||
final bool useApi;
|
||||
final MockDataService? mockDataService;
|
||||
late final WarehouseService _warehouseService;
|
||||
|
||||
List<WarehouseLocation> _warehouseLocations = [];
|
||||
List<WarehouseLocation> _filteredLocations = [];
|
||||
bool _isLoading = false;
|
||||
String? _error;
|
||||
String _searchQuery = '';
|
||||
int _currentPage = 1;
|
||||
final int _pageSize = 20;
|
||||
bool _hasMore = true;
|
||||
int _total = 0;
|
||||
|
||||
/// 전체 입고지 목록
|
||||
List<WarehouseLocation> warehouseLocations = [];
|
||||
// 필터 옵션
|
||||
bool? _isActive;
|
||||
|
||||
WarehouseLocationListController({this.useApi = true, this.mockDataService}) {
|
||||
if (useApi && GetIt.instance.isRegistered<WarehouseService>()) {
|
||||
_warehouseService = GetIt.instance<WarehouseService>();
|
||||
}
|
||||
}
|
||||
|
||||
// Getters
|
||||
List<WarehouseLocation> get warehouseLocations => _filteredLocations;
|
||||
bool get isLoading => _isLoading;
|
||||
String? get error => _error;
|
||||
String get searchQuery => _searchQuery;
|
||||
int get currentPage => _currentPage;
|
||||
bool get hasMore => _hasMore;
|
||||
int get total => _total;
|
||||
bool? get isActive => _isActive;
|
||||
|
||||
/// 데이터 로드
|
||||
void loadWarehouseLocations() {
|
||||
warehouseLocations = _dataService.getAllWarehouseLocations();
|
||||
Future<void> loadWarehouseLocations({bool isInitialLoad = true}) async {
|
||||
if (_isLoading) return;
|
||||
|
||||
_isLoading = true;
|
||||
_error = null;
|
||||
|
||||
if (isInitialLoad) {
|
||||
_currentPage = 1;
|
||||
_warehouseLocations.clear();
|
||||
_hasMore = true;
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
|
||||
try {
|
||||
if (useApi && GetIt.instance.isRegistered<WarehouseService>()) {
|
||||
// API 사용
|
||||
final fetchedLocations = await _warehouseService.getWarehouseLocations(
|
||||
page: _currentPage,
|
||||
perPage: _pageSize,
|
||||
isActive: _isActive,
|
||||
);
|
||||
|
||||
if (isInitialLoad) {
|
||||
_warehouseLocations = fetchedLocations;
|
||||
} else {
|
||||
_warehouseLocations.addAll(fetchedLocations);
|
||||
}
|
||||
|
||||
_hasMore = fetchedLocations.length >= _pageSize;
|
||||
|
||||
// 전체 개수 조회
|
||||
_total = await _warehouseService.getTotalWarehouseLocations(
|
||||
isActive: _isActive,
|
||||
);
|
||||
} else {
|
||||
// Mock 데이터 사용
|
||||
final allLocations = mockDataService?.getAllWarehouseLocations() ?? [];
|
||||
|
||||
// 필터링 적용
|
||||
var filtered = allLocations;
|
||||
if (_isActive != null) {
|
||||
// Mock 데이터에는 isActive 필드가 없으므로 모두 활성으로 처리
|
||||
filtered = _isActive! ? allLocations : [];
|
||||
}
|
||||
|
||||
// 페이지네이션 적용
|
||||
final startIndex = (_currentPage - 1) * _pageSize;
|
||||
final endIndex = startIndex + _pageSize;
|
||||
|
||||
if (startIndex < filtered.length) {
|
||||
final pageLocations = filtered.sublist(
|
||||
startIndex,
|
||||
endIndex > filtered.length ? filtered.length : endIndex,
|
||||
);
|
||||
|
||||
if (isInitialLoad) {
|
||||
_warehouseLocations = pageLocations;
|
||||
} else {
|
||||
_warehouseLocations.addAll(pageLocations);
|
||||
}
|
||||
|
||||
_hasMore = endIndex < filtered.length;
|
||||
} else {
|
||||
_hasMore = false;
|
||||
}
|
||||
|
||||
_total = filtered.length;
|
||||
}
|
||||
|
||||
_applySearchFilter();
|
||||
|
||||
if (!isInitialLoad) {
|
||||
_currentPage++;
|
||||
}
|
||||
} catch (e) {
|
||||
_error = e.toString();
|
||||
} finally {
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
// 다음 페이지 로드
|
||||
Future<void> loadNextPage() async {
|
||||
if (!_hasMore || _isLoading) return;
|
||||
_currentPage++;
|
||||
await loadWarehouseLocations(isInitialLoad: false);
|
||||
}
|
||||
|
||||
// 검색
|
||||
void search(String query) {
|
||||
_searchQuery = query;
|
||||
_applySearchFilter();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// 검색 필터 적용
|
||||
void _applySearchFilter() {
|
||||
if (_searchQuery.isEmpty) {
|
||||
_filteredLocations = List.from(_warehouseLocations);
|
||||
} else {
|
||||
_filteredLocations = _warehouseLocations.where((location) {
|
||||
return location.name.toLowerCase().contains(_searchQuery.toLowerCase()) ||
|
||||
location.address.toString().toLowerCase().contains(_searchQuery.toLowerCase());
|
||||
}).toList();
|
||||
}
|
||||
}
|
||||
|
||||
// 필터 설정
|
||||
void setFilters({bool? isActive}) {
|
||||
_isActive = isActive;
|
||||
loadWarehouseLocations();
|
||||
}
|
||||
|
||||
// 필터 초기화
|
||||
void clearFilters() {
|
||||
_isActive = null;
|
||||
_searchQuery = '';
|
||||
loadWarehouseLocations();
|
||||
}
|
||||
|
||||
/// 입고지 추가
|
||||
void addWarehouseLocation(WarehouseLocation location) {
|
||||
_dataService.addWarehouseLocation(location);
|
||||
loadWarehouseLocations();
|
||||
Future<void> addWarehouseLocation(WarehouseLocation location) async {
|
||||
try {
|
||||
if (useApi && GetIt.instance.isRegistered<WarehouseService>()) {
|
||||
await _warehouseService.createWarehouseLocation(location);
|
||||
} else {
|
||||
mockDataService?.addWarehouseLocation(location);
|
||||
}
|
||||
|
||||
// 목록 새로고침
|
||||
await loadWarehouseLocations();
|
||||
} catch (e) {
|
||||
_error = e.toString();
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
/// 입고지 수정
|
||||
void updateWarehouseLocation(WarehouseLocation location) {
|
||||
_dataService.updateWarehouseLocation(location);
|
||||
loadWarehouseLocations();
|
||||
Future<void> updateWarehouseLocation(WarehouseLocation location) async {
|
||||
try {
|
||||
if (useApi && GetIt.instance.isRegistered<WarehouseService>()) {
|
||||
await _warehouseService.updateWarehouseLocation(location);
|
||||
} else {
|
||||
mockDataService?.updateWarehouseLocation(location);
|
||||
}
|
||||
|
||||
// 목록에서 업데이트
|
||||
final index = _warehouseLocations.indexWhere((l) => l.id == location.id);
|
||||
if (index != -1) {
|
||||
_warehouseLocations[index] = location;
|
||||
_applySearchFilter();
|
||||
notifyListeners();
|
||||
}
|
||||
} catch (e) {
|
||||
_error = e.toString();
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
/// 입고지 삭제
|
||||
void deleteWarehouseLocation(int id) {
|
||||
_dataService.deleteWarehouseLocation(id);
|
||||
loadWarehouseLocations();
|
||||
Future<void> deleteWarehouseLocation(int id) async {
|
||||
try {
|
||||
if (useApi && GetIt.instance.isRegistered<WarehouseService>()) {
|
||||
await _warehouseService.deleteWarehouseLocation(id);
|
||||
} else {
|
||||
mockDataService?.deleteWarehouseLocation(id);
|
||||
}
|
||||
|
||||
// 목록에서 제거
|
||||
_warehouseLocations.removeWhere((l) => l.id == id);
|
||||
_applySearchFilter();
|
||||
_total--;
|
||||
notifyListeners();
|
||||
} catch (e) {
|
||||
_error = e.toString();
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
// 새로고침
|
||||
Future<void> refresh() async {
|
||||
await loadWarehouseLocations();
|
||||
}
|
||||
|
||||
// 사용 중인 창고 위치 조회
|
||||
Future<List<WarehouseLocation>> getInUseWarehouseLocations() async {
|
||||
try {
|
||||
if (useApi && GetIt.instance.isRegistered<WarehouseService>()) {
|
||||
return await _warehouseService.getInUseWarehouseLocations();
|
||||
} else {
|
||||
// Mock 데이터에서는 모든 창고가 사용 중으로 간주
|
||||
return mockDataService?.getAllWarehouseLocations() ?? [];
|
||||
}
|
||||
} catch (e) {
|
||||
_error = e.toString();
|
||||
notifyListeners();
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user