사용하지 않는 파일 정리 전 백업 (Phase 10 완료 후 상태)

This commit is contained in:
JiWoong Sul
2025-08-29 15:11:59 +09:00
parent a740ff10c8
commit d916b281a7
333 changed files with 53617 additions and 22574 deletions

View File

@@ -0,0 +1,251 @@
import 'package:dio/dio.dart';
import 'package:injectable/injectable.dart';
import 'package:superport/core/errors/exceptions.dart';
import 'package:superport/data/datasources/remote/api_client.dart';
import 'package:superport/data/models/administrator_dto.dart';
/// 관리자 원격 데이터 소스 (백엔드 Administrator 테이블)
/// 엔드포인트: /api/v1/administrators
abstract class AdministratorRemoteDataSource {
/// 관리자 목록 조회 (페이지네이션 지원)
Future<AdministratorListResponse> getAdministrators({
int page = 1,
int pageSize = 20,
String? search,
});
/// 단일 관리자 조회
Future<AdministratorDto> getAdministrator(int id);
/// 관리자 생성
Future<AdministratorDto> createAdministrator(AdministratorRequestDto request);
/// 관리자 정보 수정
Future<AdministratorDto> updateAdministrator(int id, AdministratorUpdateRequestDto request);
/// 관리자 삭제
Future<void> deleteAdministrator(int id);
/// 이메일 중복 확인
Future<bool> checkEmailAvailability(String email, {int? excludeId});
/// 관리자 인증 (로그인)
Future<AdministratorDto> authenticateAdministrator(String email, String password);
}
@LazySingleton(as: AdministratorRemoteDataSource)
class AdministratorRemoteDataSourceImpl implements AdministratorRemoteDataSource {
final ApiClient _apiClient;
AdministratorRemoteDataSourceImpl(this._apiClient);
/// 관리자 목록 조회
@override
Future<AdministratorListResponse> getAdministrators({
int page = 1,
int pageSize = 20,
String? search,
}) async {
try {
final queryParams = <String, dynamic>{
'page': page,
'page_size': pageSize,
};
if (search != null && search.isNotEmpty) {
queryParams['search'] = search;
}
final response = await _apiClient.get(
'/administrators',
queryParameters: queryParams,
);
if (response.data != null) {
return AdministratorListResponse.fromJson(response.data);
} else {
throw ApiException(
message: '관리자 목록 조회 응답이 비어있습니다',
statusCode: response.statusCode ?? 500,
);
}
} on DioException catch (e) {
throw ApiException(
message: e.message ?? '관리자 목록 조회 중 오류 발생',
statusCode: e.response?.statusCode,
);
} catch (e) {
throw ApiException(
message: '관리자 목록 조회 중 오류 발생: ${e.toString()}',
statusCode: 500,
);
}
}
/// 단일 관리자 조회
@override
Future<AdministratorDto> getAdministrator(int id) async {
try {
final response = await _apiClient.get('/administrators/$id');
if (response.data != null) {
return AdministratorDto.fromJson(response.data);
} else {
throw ApiException(
message: '관리자 정보 조회 응답이 비어있습니다',
statusCode: response.statusCode ?? 500,
);
}
} on DioException catch (e) {
throw ApiException(
message: e.message ?? 'API 호출 중 오류 발생',
statusCode: e.response?.statusCode,
);
} catch (e) {
throw ApiException(
message: '관리자 정보 조회 중 오류 발생: ${e.toString()}',
statusCode: 500,
);
}
}
/// 관리자 생성
@override
Future<AdministratorDto> createAdministrator(AdministratorRequestDto request) async {
try {
final response = await _apiClient.post(
'/administrators',
data: request.toJson(),
);
if (response.data != null) {
return AdministratorDto.fromJson(response.data);
} else {
throw ApiException(
message: '관리자 생성 응답이 비어있습니다',
statusCode: response.statusCode ?? 500,
);
}
} on DioException catch (e) {
throw ApiException(
message: e.message ?? 'API 호출 중 오류 발생',
statusCode: e.response?.statusCode,
);
} catch (e) {
throw ApiException(
message: '관리자 생성 중 오류 발생: ${e.toString()}',
statusCode: 500,
);
}
}
/// 관리자 정보 수정
@override
Future<AdministratorDto> updateAdministrator(int id, AdministratorUpdateRequestDto request) async {
try {
final response = await _apiClient.put(
'/administrators/$id',
data: request.toJson(),
);
if (response.data != null) {
return AdministratorDto.fromJson(response.data);
} else {
throw ApiException(
message: '관리자 정보 수정 응답이 비어있습니다',
statusCode: response.statusCode ?? 500,
);
}
} on DioException catch (e) {
throw ApiException(
message: e.message ?? 'API 호출 중 오류 발생',
statusCode: e.response?.statusCode,
);
} catch (e) {
throw ApiException(
message: '관리자 정보 수정 중 오류 발생: ${e.toString()}',
statusCode: 500,
);
}
}
/// 관리자 삭제
@override
Future<void> deleteAdministrator(int id) async {
try {
await _apiClient.delete('/administrators/$id');
} on DioException catch (e) {
throw ApiException(
message: e.message ?? 'API 호출 중 오류 발생',
statusCode: e.response?.statusCode,
);
} catch (e) {
throw ApiException(
message: '관리자 삭제 중 오류 발생: ${e.toString()}',
statusCode: 500,
);
}
}
/// 이메일 중복 확인 (미래에 백엔드에서 지원될 수 있는 기능)
@override
Future<bool> checkEmailAvailability(String email, {int? excludeId}) async {
try {
// 현재는 단순히 true 반환 (중복되지 않음으로 가정)
// 실제 백엔드 구현 시 아래와 같이 호출
/*
final queryParams = <String, dynamic>{
'email': email,
if (excludeId != null) 'exclude_id': excludeId,
};
final response = await _apiClient.get(
'/administrators/check-email',
queryParameters: queryParams,
);
return response.data['available'] ?? false;
*/
return true; // 임시로 항상 사용 가능으로 반환
} catch (e) {
// 에러 발생 시 안전하게 false 반환 (사용 불가로 처리)
return false;
}
}
/// 관리자 인증 (로그인)
@override
Future<AdministratorDto> authenticateAdministrator(String email, String password) async {
try {
final response = await _apiClient.post(
'/auth/login',
data: {
'email': email,
'password': password,
},
);
if (response.data != null && response.data['user'] != null) {
return AdministratorDto.fromJson(response.data['user']);
} else if (response.data != null) {
return AdministratorDto.fromJson(response.data);
} else {
throw ApiException(
message: '로그인 응답이 비어있습니다',
statusCode: response.statusCode ?? 500,
);
}
} on DioException catch (e) {
throw ApiException(
message: e.message ?? 'API 호출 중 오류 발생',
statusCode: e.response?.statusCode,
);
} catch (e) {
throw ApiException(
message: '관리자 인증 중 오류 발생: ${e.toString()}',
statusCode: 500,
);
}
}
}

View File

@@ -51,7 +51,7 @@ class AuthRemoteDataSourceImpl implements AuthRemoteDataSource {
final dataFields = responseData['data'] as Map<String, dynamic>;
DebugLogger.validateResponseStructure(
dataFields,
['access_token', 'refresh_token', 'user'],
['access_token', 'refresh_token', 'admin'], // API returns 'admin' instead of 'user'
responseName: 'LoginResponse.data',
);
@@ -81,7 +81,7 @@ class AuthRemoteDataSourceImpl implements AuthRemoteDataSource {
// 응답 데이터 구조 검증 (snake_case 키 확인)
DebugLogger.validateResponseStructure(
responseData as Map<String, dynamic>,
['access_token', 'refresh_token', 'user'],
['access_token', 'refresh_token', 'admin'], // API returns 'admin' instead of 'user'
responseName: 'LoginResponse',
);

View File

@@ -19,13 +19,13 @@ abstract class CompanyRemoteDataSource {
bool? isActive,
});
Future<CompanyResponse> createCompany(CreateCompanyRequest request);
Future<CompanyDto> createCompany(CompanyRequestDto request);
Future<CompanyResponse> getCompanyDetail(int id);
Future<CompanyDto> getCompanyDetail(int id);
Future<CompanyWithChildren> getCompanyWithChildren(int id);
Future<CompanyResponse> updateCompany(int id, UpdateCompanyRequest request);
Future<CompanyDto> updateCompany(int id, CompanyUpdateRequestDto request);
Future<void> deleteCompany(int id);
@@ -123,7 +123,7 @@ class CompanyRemoteDataSourceImpl implements CompanyRemoteDataSource {
}
@override
Future<CompanyResponse> createCompany(CreateCompanyRequest request) async {
Future<CompanyDto> createCompany(CompanyRequestDto request) async {
try {
debugPrint('[CompanyRemoteDataSource] Sending POST request to ${ApiEndpoints.companies}');
debugPrint('[CompanyRemoteDataSource] Request data: ${request.toJson()}');
@@ -141,12 +141,12 @@ class CompanyRemoteDataSourceImpl implements CompanyRemoteDataSource {
final responseData = response.data;
if (responseData != null && responseData['success'] == true && responseData['data'] != null) {
// 직접 파싱
return CompanyResponse.fromJson(responseData['data'] as Map<String, dynamic>);
return CompanyDto.fromJson(responseData['data'] as Map<String, dynamic>);
} else {
// ApiResponse 형식으로 파싱 시도
final apiResponse = ApiResponse<CompanyResponse>.fromJson(
final apiResponse = ApiResponse<CompanyDto>.fromJson(
response.data,
(json) => CompanyResponse.fromJson(json as Map<String, dynamic>),
(json) => CompanyDto.fromJson(json as Map<String, dynamic>),
);
return apiResponse.data!;
}
@@ -165,13 +165,13 @@ class CompanyRemoteDataSourceImpl implements CompanyRemoteDataSource {
}
@override
Future<CompanyResponse> getCompanyDetail(int id) async {
Future<CompanyDto> getCompanyDetail(int id) async {
try {
final response = await _apiClient.dio.get(
'${ApiEndpoints.companies}/$id',
);
return CompanyResponse.fromJson(response.data['data']);
return CompanyDto.fromJson(response.data['data']);
} on DioException catch (e) {
throw ServerException(
message: e.response?.data['message'] ?? 'Failed to fetch company detail',
@@ -197,14 +197,14 @@ class CompanyRemoteDataSourceImpl implements CompanyRemoteDataSource {
}
@override
Future<CompanyResponse> updateCompany(int id, UpdateCompanyRequest request) async {
Future<CompanyDto> updateCompany(int id, CompanyUpdateRequestDto request) async {
try {
final response = await _apiClient.dio.put(
'${ApiEndpoints.companies}/$id',
data: request.toJson(),
);
return CompanyResponse.fromJson(response.data['data']);
return CompanyDto.fromJson(response.data['data']);
} on DioException catch (e) {
throw ServerException(
message: e.response?.data['message'] ?? 'Failed to update company',

View File

@@ -3,66 +3,38 @@ import 'package:get_it/get_it.dart';
import 'package:superport/core/constants/api_endpoints.dart';
import 'package:superport/core/errors/exceptions.dart';
import 'package:superport/data/datasources/remote/api_client.dart';
import 'package:superport/data/models/equipment/equipment_history_dto.dart';
import 'package:superport/data/models/equipment/equipment_in_request.dart';
import 'package:superport/data/models/equipment/equipment_io_response.dart';
import 'package:superport/data/models/equipment/equipment_list_dto.dart';
import 'package:superport/data/models/equipment/equipment_out_request.dart';
import 'package:superport/data/models/equipment/equipment_request.dart';
import 'package:superport/data/models/equipment/equipment_response.dart';
import 'package:superport/data/models/equipment/equipment_dto.dart';
abstract class EquipmentRemoteDataSource {
Future<EquipmentListResponseDto> getEquipments({
Future<EquipmentListResponse> getEquipments({
int page = 1,
int perPage = 20,
String? status,
int? companyId,
int? warehouseLocationId,
String? search,
bool? isActive,
});
Future<EquipmentResponse> createEquipment(CreateEquipmentRequest request);
Future<EquipmentDto> createEquipment(EquipmentRequestDto request);
Future<EquipmentResponse> getEquipmentDetail(int id);
Future<EquipmentDto> getEquipmentDetail(int id);
Future<EquipmentResponse> updateEquipment(int id, UpdateEquipmentRequest request);
Future<EquipmentDto> updateEquipment(int id, EquipmentUpdateRequestDto request);
Future<void> deleteEquipment(int id);
Future<EquipmentResponse> changeEquipmentStatus(int id, String status, String? reason);
Future<EquipmentHistoryDto> addEquipmentHistory(int equipmentId, CreateHistoryRequest request);
Future<List<EquipmentHistoryDto>> getEquipmentHistory(int equipmentId, {int page = 1, int perPage = 20});
Future<EquipmentIoResponse> equipmentIn(EquipmentInRequest request);
Future<EquipmentIoResponse> equipmentOut(EquipmentOutRequest request);
}
class EquipmentRemoteDataSourceImpl implements EquipmentRemoteDataSource {
final ApiClient _apiClient = GetIt.instance<ApiClient>();
@override
Future<EquipmentListResponseDto> getEquipments({
Future<EquipmentListResponse> getEquipments({
int page = 1,
int perPage = 20,
String? status,
int? companyId,
int? warehouseLocationId,
String? search,
bool? isActive,
}) async {
try {
final queryParams = {
final queryParams = <String, dynamic>{
'page': page,
'per_page': perPage,
if (status != null) 'status': status,
if (companyId != null) 'company_id': companyId,
if (warehouseLocationId != null) 'warehouse_location_id': warehouseLocationId,
'page_size': perPage,
if (search != null && search.isNotEmpty) 'search': search,
if (isActive != null) 'is_active': isActive,
};
final response = await _apiClient.get(
@@ -70,25 +42,11 @@ class EquipmentRemoteDataSourceImpl implements EquipmentRemoteDataSource {
queryParameters: queryParams,
);
if (response.data['success'] == true && response.data['data'] != null) {
// API 응답 구조를 DTO에 맞게 변환 (백엔드 실제 응답 구조에 맞춤)
final List<dynamic> dataList = response.data['data'];
final pagination = response.data['pagination'] ?? {};
final listData = {
'items': dataList,
'total': pagination['total'] ?? 0,
'page': pagination['page'] ?? 1, // 백엔드는 'page' 사용 ('current_page' 아님)
'per_page': pagination['per_page'] ?? 20,
'total_pages': pagination['total_pages'] ?? 1,
};
return EquipmentListResponseDto.fromJson(listData);
} else {
throw ServerException(
message: response.data['message'] ?? 'Failed to fetch equipment list',
);
}
print('[Equipment API] Response: ${response.data}');
// 백엔드 응답은 직접 data 배열과 페이지네이션 정보 반환
final Map<String, dynamic> responseData = response.data;
return EquipmentListResponse.fromJson(responseData);
} on DioException catch (e) {
throw ServerException(
message: e.response?.data['message'] ?? 'Network error occurred',
@@ -98,20 +56,15 @@ class EquipmentRemoteDataSourceImpl implements EquipmentRemoteDataSource {
}
@override
Future<EquipmentResponse> createEquipment(CreateEquipmentRequest request) async {
Future<EquipmentDto> createEquipment(EquipmentRequestDto request) async {
try {
final response = await _apiClient.post(
ApiEndpoints.equipment,
data: request.toJson(),
);
if (response.data['success'] == true && response.data['data'] != null) {
return EquipmentResponse.fromJson(response.data['data']);
} else {
throw ServerException(
message: response.data['message'] ?? 'Failed to create equipment',
);
}
print('[Equipment API] Create Response: ${response.data}');
return EquipmentDto.fromJson(response.data);
} on DioException catch (e) {
throw ServerException(
message: e.response?.data['message'] ?? 'Network error occurred',
@@ -121,17 +74,12 @@ class EquipmentRemoteDataSourceImpl implements EquipmentRemoteDataSource {
}
@override
Future<EquipmentResponse> getEquipmentDetail(int id) async {
Future<EquipmentDto> getEquipmentDetail(int id) async {
try {
final response = await _apiClient.get('${ApiEndpoints.equipment}/$id');
if (response.data['success'] == true && response.data['data'] != null) {
return EquipmentResponse.fromJson(response.data['data']);
} else {
throw ServerException(
message: response.data['message'] ?? 'Failed to fetch equipment detail',
);
}
print('[Equipment API] Detail Response: ${response.data}');
return EquipmentDto.fromJson(response.data);
} on DioException catch (e) {
throw ServerException(
message: e.response?.data['message'] ?? 'Network error occurred',
@@ -141,20 +89,15 @@ class EquipmentRemoteDataSourceImpl implements EquipmentRemoteDataSource {
}
@override
Future<EquipmentResponse> updateEquipment(int id, UpdateEquipmentRequest request) async {
Future<EquipmentDto> updateEquipment(int id, EquipmentUpdateRequestDto request) async {
try {
final response = await _apiClient.put(
'${ApiEndpoints.equipment}/$id',
data: request.toJson(),
);
if (response.data['success'] == true && response.data['data'] != null) {
return EquipmentResponse.fromJson(response.data['data']);
} else {
throw ServerException(
message: response.data['message'] ?? 'Failed to update equipment',
);
}
print('[Equipment API] Update Response: ${response.data}');
return EquipmentDto.fromJson(response.data);
} on DioException catch (e) {
throw ServerException(
message: e.response?.data['message'] ?? 'Network error occurred',
@@ -166,192 +109,7 @@ class EquipmentRemoteDataSourceImpl implements EquipmentRemoteDataSource {
@override
Future<void> deleteEquipment(int id) async {
try {
final response = await _apiClient.delete('${ApiEndpoints.equipment}/$id');
if (response.data['success'] != true) {
throw ServerException(
message: response.data['message'] ?? 'Failed to delete equipment',
);
}
} on DioException catch (e) {
throw ServerException(
message: e.response?.data['message'] ?? 'Network error occurred',
statusCode: e.response?.statusCode,
);
}
}
@override
Future<EquipmentResponse> changeEquipmentStatus(int id, String status, String? reason) async {
try {
final response = await _apiClient.patch(
'${ApiEndpoints.equipment}/$id/status',
data: {
'status': status,
if (reason != null) 'reason': reason,
},
);
if (response.data['success'] == true && response.data['data'] != null) {
return EquipmentResponse.fromJson(response.data['data']);
} else {
throw ServerException(
message: response.data['message'] ?? 'Failed to change equipment status',
);
}
} on DioException catch (e) {
throw ServerException(
message: e.response?.data['message'] ?? 'Network error occurred',
statusCode: e.response?.statusCode,
);
}
}
@override
Future<EquipmentHistoryDto> addEquipmentHistory(int equipmentId, CreateHistoryRequest request) async {
try {
final response = await _apiClient.post(
'${ApiEndpoints.equipment}/$equipmentId/history',
data: request.toJson(),
);
if (response.data['success'] == true && response.data['data'] != null) {
return EquipmentHistoryDto.fromJson(response.data['data']);
} else {
throw ServerException(
message: response.data['message'] ?? 'Failed to add equipment history',
);
}
} on DioException catch (e) {
throw ServerException(
message: e.response?.data['message'] ?? 'Network error occurred',
statusCode: e.response?.statusCode,
);
}
}
@override
Future<List<EquipmentHistoryDto>> getEquipmentHistory(int equipmentId, {int page = 1, int perPage = 20}) async {
try {
final queryParams = {
'page': page,
'per_page': perPage,
};
print('[API] Requesting equipment history: ${ApiEndpoints.equipment}/$equipmentId/history');
print('[API] Query params: $queryParams');
final response = await _apiClient.get(
'${ApiEndpoints.equipment}/$equipmentId/history',
queryParameters: queryParams,
);
print('[API] Response status: ${response.statusCode}');
print('[API] Response data type: ${response.data.runtimeType}');
print('[API] Full response: ${response.data}');
// API 응답 구조 확인
if (response.data == null) {
print('[API ERROR] Response data is null');
throw ServerException(message: 'Empty response from server');
}
// 응답이 Map인지 확인
if (response.data is! Map) {
print('[API ERROR] Response is not a Map: ${response.data.runtimeType}');
throw ServerException(message: 'Invalid response format');
}
// success 필드 확인
final success = response.data['success'];
print('[API] Success field: $success');
if (success == true) {
final responseData = response.data['data'];
print('[API] Data field type: ${responseData?.runtimeType}');
if (responseData == null) {
print('[API] No data field, returning empty list');
return [];
}
if (responseData is! List) {
print('[API ERROR] Data is not a List: ${responseData.runtimeType}');
throw ServerException(message: 'Invalid data format');
}
final List<dynamic> data = responseData;
print('[API] History data count: ${data.length}');
if (data.isEmpty) {
print('[API] Empty history data');
return [];
}
print('[API] First history item: ${data.first}');
try {
final histories = data.map((json) {
print('[API] Parsing history item: $json');
return EquipmentHistoryDto.fromJson(json);
}).toList();
print('[API] Successfully parsed ${histories.length} history items');
return histories;
} catch (e) {
print('[API ERROR] Failed to parse history data: $e');
throw ServerException(message: 'Failed to parse history data: $e');
}
} else {
final errorMessage = response.data['message'] ?? response.data['error'] ?? 'Failed to fetch equipment history';
print('[API ERROR] Request failed: $errorMessage');
throw ServerException(message: errorMessage);
}
} on DioException catch (e) {
throw ServerException(
message: e.response?.data['message'] ?? 'Network error occurred',
statusCode: e.response?.statusCode,
);
}
}
@override
Future<EquipmentIoResponse> equipmentIn(EquipmentInRequest request) async {
try {
final response = await _apiClient.post(
'${ApiEndpoints.equipment}/in',
data: request.toJson(),
);
if (response.data['success'] == true && response.data['data'] != null) {
return EquipmentIoResponse.fromJson(response.data['data']);
} else {
throw ServerException(
message: response.data['message'] ?? 'Failed to process equipment in',
);
}
} on DioException catch (e) {
throw ServerException(
message: e.response?.data['message'] ?? 'Network error occurred',
statusCode: e.response?.statusCode,
);
}
}
@override
Future<EquipmentIoResponse> equipmentOut(EquipmentOutRequest request) async {
try {
final response = await _apiClient.post(
'${ApiEndpoints.equipment}/out',
data: request.toJson(),
);
if (response.data['success'] == true && response.data['data'] != null) {
return EquipmentIoResponse.fromJson(response.data['data']);
} else {
throw ServerException(
message: response.data['message'] ?? 'Failed to process equipment out',
);
}
await _apiClient.delete('${ApiEndpoints.equipment}/$id');
} on DioException catch (e) {
throw ServerException(
message: e.response?.data['message'] ?? 'Network error occurred',

View File

@@ -52,6 +52,22 @@ class ResponseInterceptor extends Interceptor {
/// 직접 데이터 응답인지 확인
bool _isDirectDataResponse(Map<String, dynamic> data) {
// 페이지네이션 응답은 변형하지 않음 (이미 올바른 구조)
// data, total, page 등이 있으면 페이지네이션 응답으로 간주
if (data.containsKey('data') &&
data.containsKey('total') &&
data.containsKey('page')) {
return false; // 페이지네이션 응답은 변형 안함
}
// 엔티티 단일 응답 패턴 (vendor, model, equipment 등)
// id, name이 있으면서 registered_at 또는 created_at이 있으면 엔티티 응답으로 간주
if (data.containsKey('id') &&
data.containsKey('name') &&
(data.containsKey('registered_at') || data.containsKey('created_at'))) {
return false; // 엔티티 응답은 변형 안함
}
// 로그인 응답 패턴
if (data.containsKey('accessToken') ||
data.containsKey('access_token') ||
@@ -65,10 +81,9 @@ class ResponseInterceptor extends Interceptor {
return true;
}
// 리스트 응답 패턴
// 단순 배열 응답 패턴 (페이지네이션 정보 없이 배열만 반환)
if (data.containsKey('items') ||
data.containsKey('results') ||
data.containsKey('data') && data['data'] is List) {
data.containsKey('results')) {
return true;
}

View File

@@ -1,298 +0,0 @@
import 'package:flutter/foundation.dart';
import 'package:injectable/injectable.dart';
import 'package:superport/core/constants/api_endpoints.dart';
import 'package:superport/core/errors/exceptions.dart';
import 'package:superport/data/datasources/remote/api_client.dart';
import 'package:superport/data/models/license/license_dto.dart';
import 'package:superport/data/models/license/license_request_dto.dart';
abstract class LicenseRemoteDataSource {
Future<LicenseListResponseDto> getLicenses({
int page = 1,
int perPage = 20,
bool? isActive,
int? companyId,
int? assignedUserId,
String? licenseType,
});
Future<LicenseDto> getLicenseById(int id);
Future<LicenseDto> createLicense(CreateLicenseRequest request);
Future<LicenseDto> updateLicense(int id, UpdateLicenseRequest request);
Future<void> deleteLicense(int id);
Future<LicenseDto> assignLicense(int id, AssignLicenseRequest request);
Future<LicenseDto> unassignLicense(int id);
Future<ExpiringLicenseListDto> getExpiringLicenses({
int days = 30,
int page = 1,
int perPage = 20,
});
}
@LazySingleton(as: LicenseRemoteDataSource)
class LicenseRemoteDataSourceImpl implements LicenseRemoteDataSource {
final ApiClient _apiClient;
LicenseRemoteDataSourceImpl({
required ApiClient apiClient,
}) : _apiClient = apiClient;
@override
Future<LicenseListResponseDto> getLicenses({
int page = 1,
int perPage = 20,
bool? isActive,
int? companyId,
int? assignedUserId,
String? licenseType,
}) async {
try {
final queryParams = <String, dynamic>{
'page': page,
'per_page': perPage,
};
if (isActive != null) queryParams['is_active'] = isActive;
if (companyId != null) queryParams['company_id'] = companyId;
if (assignedUserId != null) queryParams['assigned_user_id'] = assignedUserId;
if (licenseType != null) queryParams['license_type'] = licenseType;
final response = await _apiClient.get(
ApiEndpoints.licenses,
queryParameters: queryParams,
);
if (response.data != null && response.data['success'] == true && response.data['data'] != null) {
// API 응답이 배열인 경우와 객체인 경우를 모두 처리
final data = response.data['data'];
if (data is List) {
// 배열 응답을 LicenseListResponseDto 형식으로 변환
final List<LicenseDto> licenses = [];
for (int i = 0; i < data.length; i++) {
try {
final item = data[i];
debugPrint('📑 Parsing license item $i: ${item['license_key']}');
// null 검사 및 기본값 설정
final licenseDto = LicenseDto.fromJson({
...item,
// 필수 필드 보장
'license_key': item['license_key'] ?? '',
'is_active': item['is_active'] ?? true,
'created_at': item['created_at'] ?? DateTime.now().toIso8601String(),
'updated_at': item['updated_at'] ?? DateTime.now().toIso8601String(),
});
licenses.add(licenseDto);
} catch (e, stackTrace) {
debugPrint('❌ Error parsing license item $i: $e');
debugPrint('Item data: ${data[i]}');
debugPrint('Stack trace: $stackTrace');
// 파싱 실패한 항목은 건너뛰고 계속
continue;
}
}
final pagination = response.data['pagination'] ?? {};
return LicenseListResponseDto(
items: licenses,
total: pagination['total'] ?? licenses.length,
page: pagination['page'] ?? page,
perPage: pagination['per_page'] ?? perPage,
totalPages: pagination['total_pages'] ?? 1,
);
} else if (data['items'] != null) {
// 이미 LicenseListResponseDto 형식인 경우
return LicenseListResponseDto.fromJson(data);
} else {
// 예상치 못한 형식인 경우
throw ApiException(
message: 'Unexpected response format for license list',
);
}
} else {
throw ApiException(
message: response.data?['error']?['message'] ?? 'Failed to fetch licenses',
);
}
} catch (e) {
throw _handleError(e);
}
}
@override
Future<LicenseDto> getLicenseById(int id) async {
try {
final response = await _apiClient.get(
'${ApiEndpoints.licenses}/$id',
);
if (response.data != null && response.data['success'] == true && response.data['data'] != null) {
return LicenseDto.fromJson(response.data['data']);
} else {
throw ApiException(
message: response.data?['error']?['message'] ?? 'Failed to fetch license',
);
}
} catch (e) {
throw _handleError(e);
}
}
@override
Future<LicenseDto> createLicense(CreateLicenseRequest request) async {
try {
final response = await _apiClient.post(
ApiEndpoints.licenses,
data: request.toJson(),
);
if (response.data != null && response.data['success'] == true && response.data['data'] != null) {
return LicenseDto.fromJson(response.data['data']);
} else {
throw ApiException(
message: response.data?['error']?['message'] ?? 'Failed to fetch license',
);
}
} catch (e) {
throw _handleError(e);
}
}
@override
Future<LicenseDto> updateLicense(int id, UpdateLicenseRequest request) async {
try {
final response = await _apiClient.put(
'${ApiEndpoints.licenses}/$id',
data: request.toJson(),
);
if (response.data != null && response.data['success'] == true && response.data['data'] != null) {
return LicenseDto.fromJson(response.data['data']);
} else {
throw ApiException(
message: response.data?['error']?['message'] ?? 'Failed to fetch license',
);
}
} catch (e) {
throw _handleError(e);
}
}
@override
Future<void> deleteLicense(int id) async {
try {
await _apiClient.delete(
'${ApiEndpoints.licenses}/$id',
);
} catch (e) {
throw _handleError(e);
}
}
@override
Future<LicenseDto> assignLicense(int id, AssignLicenseRequest request) async {
try {
final response = await _apiClient.patch(
'${ApiEndpoints.licenses}/$id/assign',
data: request.toJson(),
);
if (response.data != null && response.data['success'] == true && response.data['data'] != null) {
return LicenseDto.fromJson(response.data['data']);
} else {
throw ApiException(
message: response.data?['error']?['message'] ?? 'Failed to fetch license',
);
}
} catch (e) {
throw _handleError(e);
}
}
@override
Future<LicenseDto> unassignLicense(int id) async {
try {
final response = await _apiClient.patch(
'${ApiEndpoints.licenses}/$id/unassign',
);
if (response.data != null && response.data['success'] == true && response.data['data'] != null) {
return LicenseDto.fromJson(response.data['data']);
} else {
throw ApiException(
message: response.data?['error']?['message'] ?? 'Failed to fetch license',
);
}
} catch (e) {
throw _handleError(e);
}
}
@override
Future<ExpiringLicenseListDto> getExpiringLicenses({
int days = 30,
int page = 1,
int perPage = 20,
}) async {
try {
final queryParams = <String, dynamic>{
'days': days,
'page': page,
'per_page': perPage,
};
final response = await _apiClient.get(
'${ApiEndpoints.licenses}/expiring',
queryParameters: queryParams,
);
if (response.data != null && response.data['success'] == true && response.data['data'] != null) {
// API 응답이 배열 형태인 경우 처리
final data = response.data['data'];
final pagination = response.data['pagination'] ?? {};
if (data is List) {
// 배열 응답을 ExpiringLicenseListDto 형식으로 변환
final List<ExpiringLicenseDto> licenses = [];
for (var item in data) {
try {
licenses.add(ExpiringLicenseDto.fromJson(item));
} catch (e) {
debugPrint('❌ Error parsing expiring license: $e');
debugPrint('Item data: $item');
continue;
}
}
return ExpiringLicenseListDto(
items: licenses,
total: pagination['total'] ?? licenses.length,
page: pagination['page'] ?? page,
perPage: pagination['per_page'] ?? perPage,
totalPages: pagination['total_pages'] ?? 1,
);
} else {
// 이미 올바른 형식인 경우
return ExpiringLicenseListDto.fromJson(data);
}
} else {
throw ApiException(
message: response.data?['error']?['message'] ?? 'Failed to fetch expiring licenses',
);
}
} catch (e) {
throw _handleError(e);
}
}
Exception _handleError(dynamic error) {
if (error is ApiException) {
return error;
}
return ApiException(
message: error.toString(),
);
}
}

View File

@@ -19,10 +19,10 @@ abstract class UserRemoteDataSource {
Future<UserDto> getUser(int id);
/// 사용자 생성
Future<UserDto> createUser(CreateUserRequest request);
Future<UserDto> createUser(UserRequestDto request);
/// 사용자 정보 수정 (비밀번호 포함)
Future<UserDto> updateUser(int id, UpdateUserRequest request);
Future<UserDto> updateUser(int id, UserUpdateRequestDto request);
/// 사용자 소프트 삭제 (is_active = false)
Future<void> deleteUser(int id);
@@ -146,7 +146,7 @@ class UserRemoteDataSourceImpl implements UserRemoteDataSource {
/// 사용자 생성 (POST /api/v1/users)
@override
Future<UserDto> createUser(CreateUserRequest request) async {
Future<UserDto> createUser(UserRequestDto request) async {
try {
final response = await _apiClient.post(
'/users',
@@ -175,7 +175,7 @@ class UserRemoteDataSourceImpl implements UserRemoteDataSource {
/// 사용자 수정 (PUT /api/v1/users/{id})
@override
Future<UserDto> updateUser(int id, UpdateUserRequest request) async {
Future<UserDto> updateUser(int id, UserUpdateRequestDto request) async {
try {
// null이나 빈 값 필터링하여 실제로 변경된 필드만 전송
final requestData = request.toJson();

View File

@@ -13,9 +13,9 @@ abstract class WarehouseLocationRemoteDataSource {
Map<String, dynamic>? filters,
});
Future<WarehouseLocationDto> getWarehouseLocationDetail(int id);
Future<WarehouseLocationDto> createWarehouseLocation(CreateWarehouseLocationRequest request);
Future<WarehouseLocationDto> updateWarehouseLocation(int id, UpdateWarehouseLocationRequest request);
Future<WarehouseDto> getWarehouseLocationDetail(int id);
Future<WarehouseDto> createWarehouseLocation(WarehouseRequestDto request);
Future<WarehouseDto> updateWarehouseLocation(int id, WarehouseUpdateRequestDto request);
Future<void> deleteWarehouseLocation(int id);
Future<WarehouseCapacityInfo> getWarehouseCapacity(int id);
Future<WarehouseEquipmentListDto> getWarehouseEquipment({
@@ -65,60 +65,46 @@ class WarehouseLocationRemoteDataSourceImpl implements WarehouseLocationRemoteDa
}
final response = await _apiClient.get(
ApiEndpoints.warehouseLocations,
ApiEndpoints.warehouses,
queryParameters: queryParams,
);
if (response.data != null && response.data['success'] == true && response.data['data'] != null) {
// API 응답이 배열인 경우와 객체인 경우를 모두 처리
if (response.data != null) {
// 백엔드는 직접 페이지네이션 구조를 반환
final data = response.data['data'];
if (data is List) {
// 배열 응답을 WarehouseLocationListDto 형식으로 변환
final List<WarehouseLocationDto> warehouses = [];
if (data != null && data is List) {
final List<WarehouseDto> warehouses = [];
for (int i = 0; i < data.length; i++) {
try {
final item = data[i];
debugPrint('📦 Parsing warehouse location item $i: ${item['name']}');
// null 검사 및 기본값 설정
final warehouseDto = WarehouseLocationDto.fromJson({
...item,
// 필수 필드 보장
'name': item['name'] ?? '',
'is_active': item['is_active'] ?? true,
'created_at': item['created_at'] ?? DateTime.now().toIso8601String(),
});
final warehouseDto = WarehouseDto.fromJson(item);
warehouses.add(warehouseDto);
} catch (e, stackTrace) {
debugPrint('❌ Error parsing warehouse location item $i: $e');
debugPrint('Item data: ${data[i]}');
debugPrint('Stack trace: $stackTrace');
// 파싱 실패한 항목은 건너뛰고 계속
continue;
}
}
final pagination = response.data['pagination'] ?? {};
return WarehouseLocationListDto(
items: warehouses,
total: pagination['total'] ?? warehouses.length,
page: pagination['page'] ?? page,
perPage: pagination['per_page'] ?? perPage,
totalPages: pagination['total_pages'] ?? 1,
total: response.data['total'] ?? warehouses.length,
page: response.data['page'] ?? page,
perPage: response.data['page_size'] ?? perPage,
totalPages: response.data['total_pages'] ?? 1,
);
} else if (data['items'] != null) {
// 이미 WarehouseLocationListDto 형식인 경우
return WarehouseLocationListDto.fromJson(data);
} else {
// 예상치 못한 형식인 경우
throw ApiException(
message: 'Unexpected response format for warehouse location list',
message: 'Invalid response format: expected data array',
);
}
} else {
throw ApiException(
message: response.data?['error']?['message'] ?? 'Failed to fetch warehouse locations',
message: 'Empty response from server',
);
}
} catch (e) {
@@ -127,17 +113,17 @@ class WarehouseLocationRemoteDataSourceImpl implements WarehouseLocationRemoteDa
}
@override
Future<WarehouseLocationDto> getWarehouseLocationDetail(int id) async {
Future<WarehouseDto> getWarehouseLocationDetail(int id) async {
try {
final response = await _apiClient.get(
'${ApiEndpoints.warehouseLocations}/$id',
'${ApiEndpoints.warehouses}/$id',
);
if (response.data != null && response.data['success'] == true && response.data['data'] != null) {
return WarehouseLocationDto.fromJson(response.data['data']);
if (response.data != null) {
return WarehouseDto.fromJson(response.data);
} else {
throw ApiException(
message: response.data?['error']?['message'] ?? 'Failed to fetch warehouse location',
message: 'Failed to fetch warehouse location',
);
}
} catch (e) {
@@ -146,18 +132,18 @@ class WarehouseLocationRemoteDataSourceImpl implements WarehouseLocationRemoteDa
}
@override
Future<WarehouseLocationDto> createWarehouseLocation(CreateWarehouseLocationRequest request) async {
Future<WarehouseDto> createWarehouseLocation(WarehouseRequestDto request) async {
try {
final response = await _apiClient.post(
ApiEndpoints.warehouseLocations,
ApiEndpoints.warehouses,
data: request.toJson(),
);
if (response.data != null && response.data['success'] == true && response.data['data'] != null) {
return WarehouseLocationDto.fromJson(response.data['data']);
if (response.data != null) {
return WarehouseDto.fromJson(response.data);
} else {
throw ApiException(
message: response.data?['error']?['message'] ?? 'Failed to create warehouse location',
message: 'Failed to create warehouse location',
);
}
} catch (e) {
@@ -166,18 +152,18 @@ class WarehouseLocationRemoteDataSourceImpl implements WarehouseLocationRemoteDa
}
@override
Future<WarehouseLocationDto> updateWarehouseLocation(int id, UpdateWarehouseLocationRequest request) async {
Future<WarehouseDto> updateWarehouseLocation(int id, WarehouseUpdateRequestDto request) async {
try {
final response = await _apiClient.put(
'${ApiEndpoints.warehouseLocations}/$id',
'${ApiEndpoints.warehouses}/$id',
data: request.toJson(),
);
if (response.data != null && response.data['success'] == true && response.data['data'] != null) {
return WarehouseLocationDto.fromJson(response.data['data']);
if (response.data != null) {
return WarehouseDto.fromJson(response.data);
} else {
throw ApiException(
message: response.data?['error']?['message'] ?? 'Failed to update warehouse location',
message: 'Failed to update warehouse location',
);
}
} catch (e) {
@@ -189,7 +175,7 @@ class WarehouseLocationRemoteDataSourceImpl implements WarehouseLocationRemoteDa
Future<void> deleteWarehouseLocation(int id) async {
try {
await _apiClient.delete(
'${ApiEndpoints.warehouseLocations}/$id',
'${ApiEndpoints.warehouses}/$id',
);
} catch (e) {
throw _handleError(e);
@@ -200,7 +186,7 @@ class WarehouseLocationRemoteDataSourceImpl implements WarehouseLocationRemoteDa
Future<WarehouseCapacityInfo> getWarehouseCapacity(int id) async {
try {
final response = await _apiClient.get(
'${ApiEndpoints.warehouseLocations}/$id/capacity',
'${ApiEndpoints.warehouses}/$id/capacity',
);
if (response.data != null && response.data['success'] == true && response.data['data'] != null) {
@@ -228,7 +214,7 @@ class WarehouseLocationRemoteDataSourceImpl implements WarehouseLocationRemoteDa
};
final response = await _apiClient.get(
'${ApiEndpoints.warehouseLocations}/$warehouseId/equipment',
'${ApiEndpoints.warehouses}/$warehouseId/equipment',
queryParameters: queryParams,
);
@@ -253,9 +239,6 @@ class WarehouseLocationRemoteDataSourceImpl implements WarehouseLocationRemoteDa
return WarehouseEquipmentListDto(
items: equipment,
total: pagination['total'] ?? equipment.length,
page: pagination['page'] ?? page,
perPage: pagination['per_page'] ?? perPage,
totalPages: pagination['total_pages'] ?? 1,
);
} else {
// 이미 올바른 형식인 경우
@@ -276,7 +259,7 @@ class WarehouseLocationRemoteDataSourceImpl implements WarehouseLocationRemoteDa
Future<void> updateWarehouseLocationStatus(int id, bool isActive) async {
try {
await _apiClient.patch(
'${ApiEndpoints.warehouseLocations}/$id/status',
'${ApiEndpoints.warehouses}/$id/status',
data: {'is_active': isActive},
);
} catch (e) {
@@ -288,7 +271,7 @@ class WarehouseLocationRemoteDataSourceImpl implements WarehouseLocationRemoteDa
Future<bool> checkWarehouseHasEquipment(int id) async {
try {
final response = await _apiClient.get(
'${ApiEndpoints.warehouseLocations}/$id/has-equipment',
'${ApiEndpoints.warehouses}/$id/has-equipment',
);
if (response.data != null && response.data['success'] == true) {
@@ -306,7 +289,7 @@ class WarehouseLocationRemoteDataSourceImpl implements WarehouseLocationRemoteDa
Future<bool> checkDuplicateWarehouseName(String name) async {
try {
final response = await _apiClient.get(
'${ApiEndpoints.warehouseLocations}/check-duplicate',
'${ApiEndpoints.warehouses}/check-duplicate',
queryParameters: {'name': name},
);

View File

@@ -13,9 +13,9 @@ abstract class WarehouseRemoteDataSource {
bool includeInactive = false,
});
Future<WarehouseLocationDto> getWarehouseLocationById(int id);
Future<WarehouseLocationDto> createWarehouseLocation(CreateWarehouseLocationRequest request);
Future<WarehouseLocationDto> updateWarehouseLocation(int id, UpdateWarehouseLocationRequest request);
Future<WarehouseDto> getWarehouseLocationById(int id);
Future<WarehouseDto> createWarehouseLocation(WarehouseRequestDto request);
Future<WarehouseDto> updateWarehouseLocation(int id, WarehouseUpdateRequestDto request);
Future<void> deleteWarehouseLocation(int id);
Future<WarehouseEquipmentListDto> getWarehouseEquipment(
int warehouseId, {
@@ -23,7 +23,7 @@ abstract class WarehouseRemoteDataSource {
int perPage = 20,
});
Future<WarehouseCapacityInfo> getWarehouseCapacity(int id);
Future<List<WarehouseLocationDto>> getInUseWarehouseLocations();
Future<List<WarehouseDto>> getInUseWarehouseLocations();
}
@LazySingleton(as: WarehouseRemoteDataSource)
@@ -53,7 +53,7 @@ class WarehouseRemoteDataSourceImpl implements WarehouseRemoteDataSource {
queryParams['include_inactive'] = includeInactive;
final response = await _apiClient.get(
ApiEndpoints.warehouseLocations,
ApiEndpoints.warehouses,
queryParameters: queryParams,
);
@@ -82,14 +82,14 @@ class WarehouseRemoteDataSourceImpl implements WarehouseRemoteDataSource {
}
@override
Future<WarehouseLocationDto> getWarehouseLocationById(int id) async {
Future<WarehouseDto> getWarehouseLocationById(int id) async {
try {
final response = await _apiClient.get(
'${ApiEndpoints.warehouseLocations}/$id',
'${ApiEndpoints.warehouses}/$id',
);
if (response.data != null && response.data['success'] == true && response.data['data'] != null) {
return WarehouseLocationDto.fromJson(response.data['data']);
return WarehouseDto.fromJson(response.data['data']);
} else {
throw ApiException(
message: response.data?['error']?['message'] ?? 'Failed to fetch warehouse location',
@@ -101,15 +101,15 @@ class WarehouseRemoteDataSourceImpl implements WarehouseRemoteDataSource {
}
@override
Future<WarehouseLocationDto> createWarehouseLocation(CreateWarehouseLocationRequest request) async {
Future<WarehouseDto> createWarehouseLocation(WarehouseRequestDto request) async {
try {
final response = await _apiClient.post(
ApiEndpoints.warehouseLocations,
ApiEndpoints.warehouses,
data: request.toJson(),
);
if (response.data != null && response.data['success'] == true && response.data['data'] != null) {
return WarehouseLocationDto.fromJson(response.data['data']);
return WarehouseDto.fromJson(response.data['data']);
} else {
throw ApiException(
message: response.data?['error']?['message'] ?? 'Failed to fetch warehouse location',
@@ -121,15 +121,15 @@ class WarehouseRemoteDataSourceImpl implements WarehouseRemoteDataSource {
}
@override
Future<WarehouseLocationDto> updateWarehouseLocation(int id, UpdateWarehouseLocationRequest request) async {
Future<WarehouseDto> updateWarehouseLocation(int id, WarehouseUpdateRequestDto request) async {
try {
final response = await _apiClient.put(
'${ApiEndpoints.warehouseLocations}/$id',
'${ApiEndpoints.warehouses}/$id',
data: request.toJson(),
);
if (response.data != null && response.data['success'] == true && response.data['data'] != null) {
return WarehouseLocationDto.fromJson(response.data['data']);
return WarehouseDto.fromJson(response.data['data']);
} else {
throw ApiException(
message: response.data?['error']?['message'] ?? 'Failed to fetch warehouse location',
@@ -144,7 +144,7 @@ class WarehouseRemoteDataSourceImpl implements WarehouseRemoteDataSource {
Future<void> deleteWarehouseLocation(int id) async {
try {
await _apiClient.delete(
'${ApiEndpoints.warehouseLocations}/$id',
'${ApiEndpoints.warehouses}/$id',
);
} catch (e) {
throw _handleError(e);
@@ -200,15 +200,15 @@ class WarehouseRemoteDataSourceImpl implements WarehouseRemoteDataSource {
}
@override
Future<List<WarehouseLocationDto>> getInUseWarehouseLocations() async {
Future<List<WarehouseDto>> getInUseWarehouseLocations() async {
try {
final response = await _apiClient.get(
'${ApiEndpoints.warehouseLocations}/in-use',
'${ApiEndpoints.warehouses}/in-use',
);
if (response.data != null && response.data['success'] == true && response.data['data'] is List) {
return (response.data['data'] as List)
.map((item) => WarehouseLocationDto.fromJson(item))
.map((item) => WarehouseDto.fromJson(item))
.toList();
} else {
throw ApiException(message: 'Invalid response format');