- Replace dart:js with package:js in health_check_service_web.dart\n- Implement showHealthCheckNotification in web/index.html\n- Pin js dependency to ^0.6.7 for flutter_secure_storage_web compatibility auth: harden AuthInterceptor + tests - Allow overrideAuthRepository injection for testing\n- Normalize imports to package: paths\n- Add unit test covering token attach, 401→refresh→retry, and failure path\n- Add integration test skeleton gated by env vars ui/data: map User.companyName to list column - Add companyName to domain User\n- Map UserDto.company?.name\n- Render companyName in user_list cleanup: remove legacy equipment table + unused code; minor warnings - Remove _buildFlexibleTable and unused helpers\n- Remove unused zipcode details and cache retry constant\n- Fix null-aware and non-null assertions\n- Address child-last warnings in administrator dialog docs: update AGENTS.md session context
249 lines
8.6 KiB
Dart
249 lines
8.6 KiB
Dart
import 'package:get_it/get_it.dart';
|
|
import 'package:superport/data/models/inventory_history_view_model.dart';
|
|
import 'package:superport/data/models/equipment_history_dto.dart';
|
|
import 'package:superport/data/models/equipment/equipment_dto.dart';
|
|
import 'package:superport/data/repositories/equipment_history_repository.dart';
|
|
import 'package:superport/domain/usecases/equipment/get_equipment_detail_usecase.dart';
|
|
import 'package:superport/core/constants/app_constants.dart';
|
|
|
|
/// 재고 이력 관리 화면 전용 서비스
|
|
/// 백엔드 여러 API를 조합하여 화면용 데이터 제공
|
|
class InventoryHistoryService {
|
|
final EquipmentHistoryRepository _historyRepository;
|
|
final GetEquipmentDetailUseCase _equipmentDetailUseCase;
|
|
|
|
InventoryHistoryService({
|
|
EquipmentHistoryRepository? historyRepository,
|
|
GetEquipmentDetailUseCase? equipmentDetailUseCase,
|
|
}) : _historyRepository = historyRepository ?? GetIt.instance<EquipmentHistoryRepository>(),
|
|
_equipmentDetailUseCase = equipmentDetailUseCase ?? GetIt.instance<GetEquipmentDetailUseCase>();
|
|
|
|
/// 재고 이력 목록 로드 (여러 API 조합)
|
|
Future<InventoryHistoryListResponse> loadInventoryHistories({
|
|
int page = 1,
|
|
int pageSize = AppConstants.historyPageSize,
|
|
String? searchKeyword,
|
|
String? transactionType,
|
|
int? equipmentId,
|
|
int? warehouseId,
|
|
int? companyId,
|
|
DateTime? dateFrom,
|
|
DateTime? dateTo,
|
|
}) async {
|
|
try {
|
|
// 1. Equipment History 기본 데이터 로드
|
|
final historyResponse = await _historyRepository.getEquipmentHistories(
|
|
page: page,
|
|
pageSize: pageSize,
|
|
transactionType: transactionType,
|
|
equipmentsId: equipmentId,
|
|
warehousesId: warehouseId,
|
|
startDate: dateFrom?.toIso8601String(),
|
|
endDate: dateTo?.toIso8601String(),
|
|
);
|
|
|
|
// 2. 각 이력에 대해 추가 정보 조합
|
|
final List<InventoryHistoryViewModel> enrichedItems = [];
|
|
|
|
for (final history in historyResponse.items) {
|
|
try {
|
|
final viewModel = await _enrichHistoryWithDetails(history, searchKeyword);
|
|
if (viewModel != null) {
|
|
enrichedItems.add(viewModel);
|
|
}
|
|
} catch (e) {
|
|
print('[InventoryHistoryService] Failed to enrich history ${history.id}: $e');
|
|
// 에러 발생해도 기본 정보로라도 표시
|
|
final fallbackViewModel = _createFallbackViewModel(history);
|
|
enrichedItems.add(fallbackViewModel);
|
|
}
|
|
}
|
|
|
|
// 3. 검색 키워드 필터링 (서버 검색 후 추가 로컬 필터링)
|
|
List<InventoryHistoryViewModel> filteredItems = enrichedItems;
|
|
if (searchKeyword != null && searchKeyword.isNotEmpty) {
|
|
filteredItems = _applyLocalSearch(enrichedItems, searchKeyword);
|
|
}
|
|
|
|
return InventoryHistoryListResponse(
|
|
items: filteredItems,
|
|
totalCount: historyResponse.totalCount,
|
|
currentPage: historyResponse.currentPage,
|
|
totalPages: historyResponse.totalPages,
|
|
pageSize: historyResponse.pageSize,
|
|
);
|
|
} catch (e) {
|
|
print('[InventoryHistoryService] Error loading inventory histories: $e');
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
/// 특정 장비의 전체 이력 로드 (상세보기용)
|
|
Future<List<InventoryHistoryViewModel>> loadEquipmentHistory(int equipmentId) async {
|
|
try {
|
|
// 해당 장비의 모든 이력을 시간순(최신순)으로 로드
|
|
final historyResponse = await _historyRepository.getEquipmentHistories(
|
|
equipmentsId: equipmentId,
|
|
page: 1,
|
|
pageSize: AppConstants.maxBulkPageSize, // 모든 이력 로드
|
|
);
|
|
|
|
final List<InventoryHistoryViewModel> items = [];
|
|
for (final history in historyResponse.items) {
|
|
try {
|
|
final viewModel = await _enrichHistoryWithDetails(history);
|
|
if (viewModel != null) {
|
|
items.add(viewModel);
|
|
}
|
|
} catch (e) {
|
|
print('[InventoryHistoryService] Failed to enrich equipment history ${history.id}: $e');
|
|
final fallbackViewModel = _createFallbackViewModel(history);
|
|
items.add(fallbackViewModel);
|
|
}
|
|
}
|
|
|
|
// 시간순 정렬 (최신순)
|
|
items.sort((a, b) => b.changedDate.compareTo(a.changedDate));
|
|
|
|
return items;
|
|
} catch (e) {
|
|
print('[InventoryHistoryService] Error loading equipment history: $e');
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
/// History 데이터에 추가 정보를 조합하여 ViewModel 생성
|
|
Future<InventoryHistoryViewModel?> _enrichHistoryWithDetails(
|
|
EquipmentHistoryDto history,
|
|
[String? searchKeyword]
|
|
) async {
|
|
try {
|
|
// Equipment 상세 정보 로드
|
|
EquipmentDto? equipmentDetail;
|
|
if (history.equipmentsId != null) {
|
|
final equipmentResult = await _equipmentDetailUseCase(history.equipmentsId);
|
|
equipmentResult.fold(
|
|
(failure) {
|
|
print('[InventoryHistoryService] Failed to load equipment ${history.equipmentsId}: ${failure.message}');
|
|
},
|
|
(equipment) {
|
|
equipmentDetail = equipment;
|
|
},
|
|
);
|
|
}
|
|
|
|
// 장비명 결정 (Equipment API에서 가져오거나 fallback)
|
|
final String equipmentName = _determineEquipmentName(equipmentDetail, history);
|
|
|
|
// 시리얼번호 결정
|
|
final String serialNumber = _determineSerialNumber(equipmentDetail, history);
|
|
|
|
// 위치 결정 (transaction_type에 따라 다르게)
|
|
final String location = _determineLocation(history);
|
|
|
|
return InventoryHistoryViewModel(
|
|
historyId: history.id ?? 0,
|
|
equipmentId: history.equipmentsId,
|
|
equipmentName: equipmentName,
|
|
serialNumber: serialNumber,
|
|
location: location,
|
|
changedDate: history.transactedAt,
|
|
remark: history.remark,
|
|
transactionType: history.transactionType,
|
|
quantity: history.quantity,
|
|
originalHistory: history,
|
|
);
|
|
} catch (e) {
|
|
print('[InventoryHistoryService] Error enriching history ${history.id}: $e');
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// 장비명 결정 로직
|
|
String _determineEquipmentName(EquipmentDto? equipment, EquipmentHistoryDto history) {
|
|
if (equipment != null) {
|
|
// Equipment API에서 가져온 정보 우선 사용
|
|
if (equipment.modelName != null && equipment.vendorName != null) {
|
|
return '${equipment.vendorName} ${equipment.modelName}';
|
|
} else if (equipment.modelName != null) {
|
|
return equipment.modelName!;
|
|
}
|
|
}
|
|
|
|
// Fallback: History의 equipment_serial 사용
|
|
if (history.equipmentSerial != null) {
|
|
return history.equipmentSerial!;
|
|
}
|
|
|
|
return 'Unknown Equipment';
|
|
}
|
|
|
|
/// 시리얼번호 결정 로직
|
|
String _determineSerialNumber(EquipmentDto? equipment, EquipmentHistoryDto history) {
|
|
if (equipment != null) {
|
|
return equipment.serialNumber;
|
|
}
|
|
|
|
if (history.equipmentSerial != null) {
|
|
return history.equipmentSerial!;
|
|
}
|
|
|
|
return 'N/A';
|
|
}
|
|
|
|
/// 위치 결정 로직 (transaction_type에 따라 다르게)
|
|
String _determineLocation(EquipmentHistoryDto history) {
|
|
switch (history.transactionType) {
|
|
case 'O': // 출고
|
|
case 'R': // 대여
|
|
// 고객사 정보 사용
|
|
if (history.companies.isNotEmpty) {
|
|
final company = history.companies.first;
|
|
return company['name']?.toString() ?? 'Unknown Company';
|
|
}
|
|
return 'Unknown Company';
|
|
|
|
case 'I': // 입고
|
|
case 'D': // 폐기
|
|
// 창고 정보 사용
|
|
return history.warehouseName ?? 'Unknown Warehouse';
|
|
|
|
default:
|
|
return 'N/A';
|
|
}
|
|
}
|
|
|
|
/// 에러 발생 시 fallback ViewModel 생성
|
|
InventoryHistoryViewModel _createFallbackViewModel(EquipmentHistoryDto history) {
|
|
return InventoryHistoryViewModel(
|
|
historyId: history.id ?? 0,
|
|
equipmentId: history.equipmentsId,
|
|
equipmentName: history.equipmentSerial ?? 'Unknown Equipment',
|
|
serialNumber: history.equipmentSerial ?? 'N/A',
|
|
location: _determineLocation(history),
|
|
changedDate: history.transactedAt,
|
|
remark: history.remark,
|
|
transactionType: history.transactionType,
|
|
quantity: history.quantity,
|
|
originalHistory: history,
|
|
);
|
|
}
|
|
|
|
/// 로컬 검색 필터링 적용
|
|
List<InventoryHistoryViewModel> _applyLocalSearch(
|
|
List<InventoryHistoryViewModel> items,
|
|
String searchKeyword
|
|
) {
|
|
final keyword = searchKeyword.toLowerCase();
|
|
return items.where((item) {
|
|
return [
|
|
item.equipmentName,
|
|
item.serialNumber,
|
|
item.location,
|
|
item.remark ?? '',
|
|
item.transactionTypeDisplay,
|
|
].any((field) => field.toLowerCase().contains(keyword));
|
|
}).toList();
|
|
}
|
|
}
|