refactor: 프로젝트 구조 개선 및 테스트 시스템 강화
주요 변경사항: - CLAUDE.md: 프로젝트 규칙 v2.0으로 업데이트, 아키텍처 명확화 - 불필요한 문서 제거: NEXT_TASKS.md, TEST_PROGRESS.md, test_results 파일들 - 테스트 시스템 개선: 실제 API 테스트 스위트 추가 (15개 새 테스트 파일) - License 관리: DTO 모델 개선, API 응답 처리 최적화 - 에러 처리: Interceptor 로직 강화, 상세 로깅 추가 - Company/User/Warehouse 테스트: 자동화 테스트 안정성 향상 - Phone Utils: 전화번호 포맷팅 로직 개선 - Overview Controller: 대시보드 데이터 로딩 최적화 - Analysis Options: Flutter 린트 규칙 추가 테스트 개선: - company_real_api_test.dart: 실제 API 회사 관리 테스트 - equipment_in/out_real_api_test.dart: 장비 입출고 API 테스트 - license_real_api_test.dart: 라이선스 관리 API 테스트 - user_real_api_test.dart: 사용자 관리 API 테스트 - warehouse_location_real_api_test.dart: 창고 위치 API 테스트 - filter_sort_test.dart: 필터링/정렬 기능 테스트 - pagination_test.dart: 페이지네이션 테스트 - interactive_search_test.dart: 검색 기능 테스트 - overview_dashboard_test.dart: 대시보드 통합 테스트 코드 품질: - 모든 서비스에 에러 처리 강화 - DTO 모델 null safety 개선 - 테스트 커버리지 확대 - 불필요한 로그 파일 제거로 리포지토리 정리 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -2,7 +2,7 @@ import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
/// 서버와 클라이언트 간 장비 상태 코드 변환 유틸리티
|
||||
class EquipmentStatusConverter {
|
||||
/// 서버 상태 코드를 클라이언트 상태 코드로 변환
|
||||
/// 서버 상태 코드를 클라이언트 상태 코드로 변하
|
||||
static String serverToClient(String? serverStatus) {
|
||||
if (serverStatus == null) return 'E';
|
||||
|
||||
@@ -10,7 +10,7 @@ class EquipmentStatusConverter {
|
||||
case 'available':
|
||||
return 'I'; // 입고
|
||||
case 'inuse':
|
||||
return 'T'; // 대여
|
||||
return 'O'; // 출고 (사용 중인 장비는 출고 상태로 표시)
|
||||
case 'maintenance':
|
||||
return 'R'; // 수리
|
||||
case 'disposed':
|
||||
@@ -28,7 +28,7 @@ class EquipmentStatusConverter {
|
||||
case 'I': // 입고
|
||||
return 'available';
|
||||
case 'O': // 출고
|
||||
return 'available';
|
||||
return 'inuse'; // 출고된 장비는 사용 중(inuse) 상태
|
||||
case 'T': // 대여
|
||||
return 'inuse';
|
||||
case 'R': // 수리
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:dio/dio.dart';
|
||||
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';
|
||||
@@ -116,26 +117,42 @@ class CompanyRemoteDataSourceImpl implements CompanyRemoteDataSource {
|
||||
@override
|
||||
Future<CompanyResponse> createCompany(CreateCompanyRequest request) async {
|
||||
try {
|
||||
debugPrint('[CompanyRemoteDataSource] Sending POST request to ${ApiEndpoints.companies}');
|
||||
debugPrint('[CompanyRemoteDataSource] Request data: ${request.toJson()}');
|
||||
|
||||
final response = await _apiClient.post(
|
||||
ApiEndpoints.companies,
|
||||
data: request.toJson(),
|
||||
);
|
||||
|
||||
if (response.statusCode == 201) {
|
||||
final apiResponse = ApiResponse<CompanyResponse>.fromJson(
|
||||
response.data,
|
||||
(json) => CompanyResponse.fromJson(json as Map<String, dynamic>),
|
||||
);
|
||||
return apiResponse.data!;
|
||||
debugPrint('[CompanyRemoteDataSource] Response status: ${response.statusCode}');
|
||||
debugPrint('[CompanyRemoteDataSource] Response data: ${response.data}');
|
||||
|
||||
if (response.statusCode == 201 || response.statusCode == 200) {
|
||||
// API 응답 구조 확인
|
||||
final responseData = response.data;
|
||||
if (responseData != null && responseData['success'] == true && responseData['data'] != null) {
|
||||
// 직접 파싱
|
||||
return CompanyResponse.fromJson(responseData['data'] as Map<String, dynamic>);
|
||||
} else {
|
||||
// ApiResponse 형식으로 파싱 시도
|
||||
final apiResponse = ApiResponse<CompanyResponse>.fromJson(
|
||||
response.data,
|
||||
(json) => CompanyResponse.fromJson(json as Map<String, dynamic>),
|
||||
);
|
||||
return apiResponse.data!;
|
||||
}
|
||||
} else {
|
||||
throw ApiException(
|
||||
message: 'Failed to create company',
|
||||
message: 'Failed to create company - Status: ${response.statusCode}',
|
||||
statusCode: response.statusCode,
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
} catch (e, stackTrace) {
|
||||
debugPrint('[CompanyRemoteDataSource] Error creating company: $e');
|
||||
debugPrint('[CompanyRemoteDataSource] Stack trace: $stackTrace');
|
||||
if (e is ApiException) rethrow;
|
||||
throw ApiException(message: e.toString());
|
||||
throw ApiException(message: 'Error creating company: $e');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ abstract class EquipmentRemoteDataSource {
|
||||
String? status,
|
||||
int? companyId,
|
||||
int? warehouseLocationId,
|
||||
String? search,
|
||||
});
|
||||
|
||||
Future<EquipmentResponse> createEquipment(CreateEquipmentRequest request);
|
||||
@@ -49,6 +50,7 @@ class EquipmentRemoteDataSourceImpl implements EquipmentRemoteDataSource {
|
||||
String? status,
|
||||
int? companyId,
|
||||
int? warehouseLocationId,
|
||||
String? search,
|
||||
}) async {
|
||||
try {
|
||||
final queryParams = {
|
||||
@@ -57,6 +59,7 @@ class EquipmentRemoteDataSourceImpl implements EquipmentRemoteDataSource {
|
||||
if (status != null) 'status': status,
|
||||
if (companyId != null) 'company_id': companyId,
|
||||
if (warehouseLocationId != null) 'warehouse_location_id': warehouseLocationId,
|
||||
if (search != null && search.isNotEmpty) 'search': search,
|
||||
};
|
||||
|
||||
final response = await _apiClient.get(
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import '../../../../core/errors/exceptions.dart';
|
||||
import '../../../../core/constants/app_constants.dart';
|
||||
|
||||
@@ -69,9 +71,9 @@ class ErrorInterceptor extends Interceptor {
|
||||
|
||||
if (errorMessage.contains('cors') || errorString.contains('cors') ||
|
||||
errorMessage.contains('xmlhttprequest') || errorString.contains('xmlhttprequest')) {
|
||||
print('[ErrorInterceptor] CORS 에러 감지됨');
|
||||
print('[ErrorInterceptor] 요청 URL: ${err.requestOptions.uri}');
|
||||
print('[ErrorInterceptor] 에러 메시지: ${err.message}');
|
||||
debugPrint('[ErrorInterceptor] CORS 에러 감지됨');
|
||||
debugPrint('[ErrorInterceptor] 요청 URL: ${err.requestOptions.uri}');
|
||||
debugPrint('[ErrorInterceptor] 에러 메시지: ${err.message}');
|
||||
|
||||
handler.reject(
|
||||
DioException(
|
||||
@@ -84,10 +86,10 @@ class ErrorInterceptor extends Interceptor {
|
||||
),
|
||||
);
|
||||
} else {
|
||||
print('[ErrorInterceptor] 알 수 없는 에러');
|
||||
print('[ErrorInterceptor] 에러 타입: ${err.error?.runtimeType}');
|
||||
print('[ErrorInterceptor] 에러 메시지: ${err.message}');
|
||||
print('[ErrorInterceptor] 에러 내용: ${err.error}');
|
||||
debugPrint('[ErrorInterceptor] 알 수 없는 에러');
|
||||
debugPrint('[ErrorInterceptor] 에러 타입: ${err.error?.runtimeType}');
|
||||
debugPrint('[ErrorInterceptor] 에러 메시지: ${err.message}');
|
||||
debugPrint('[ErrorInterceptor] 에러 내용: ${err.error}');
|
||||
|
||||
handler.reject(
|
||||
DioException(
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
/// API 응답을 정규화하는 인터셉터
|
||||
///
|
||||
@@ -6,16 +7,16 @@ import 'package:dio/dio.dart';
|
||||
class ResponseInterceptor extends Interceptor {
|
||||
@override
|
||||
void onResponse(Response response, ResponseInterceptorHandler handler) {
|
||||
print('[ResponseInterceptor] 응답 수신: ${response.requestOptions.path}');
|
||||
print('[ResponseInterceptor] 상태 코드: ${response.statusCode}');
|
||||
print('[ResponseInterceptor] 응답 데이터 타입: ${response.data.runtimeType}');
|
||||
debugPrint('[ResponseInterceptor] 응답 수신: ${response.requestOptions.path}');
|
||||
debugPrint('[ResponseInterceptor] 상태 코드: ${response.statusCode}');
|
||||
debugPrint('[ResponseInterceptor] 응답 데이터 타입: ${response.data.runtimeType}');
|
||||
|
||||
// 장비 관련 API 응답 상세 로깅
|
||||
if (response.requestOptions.path.contains('equipment')) {
|
||||
print('[ResponseInterceptor] 장비 API 응답 전체: ${response.data}');
|
||||
debugPrint('[ResponseInterceptor] 장비 API 응답 전체: ${response.data}');
|
||||
if (response.data is List && (response.data as List).isNotEmpty) {
|
||||
final firstItem = (response.data as List).first;
|
||||
print('[ResponseInterceptor] 첫 번째 장비 상태: ${firstItem['status']}');
|
||||
debugPrint('[ResponseInterceptor] 첫 번째 장비 상태: ${firstItem['status']}');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +28,7 @@ class ResponseInterceptor extends Interceptor {
|
||||
|
||||
// 이미 정규화된 형식인지 확인
|
||||
if (data.containsKey('success') && data.containsKey('data')) {
|
||||
print('[ResponseInterceptor] 이미 정규화된 응답 형식');
|
||||
debugPrint('[ResponseInterceptor] 이미 정규화된 응답 형식');
|
||||
handler.next(response);
|
||||
return;
|
||||
}
|
||||
@@ -35,7 +36,7 @@ class ResponseInterceptor extends Interceptor {
|
||||
// API 응답이 직접 데이터를 반환하는 경우
|
||||
// (예: {accessToken: "...", refreshToken: "...", user: {...}})
|
||||
if (_isDirectDataResponse(data)) {
|
||||
print('[ResponseInterceptor] 직접 데이터 응답을 정규화된 형식으로 변환');
|
||||
debugPrint('[ResponseInterceptor] 직접 데이터 응답을 정규화된 형식으로 변환');
|
||||
|
||||
// 정규화된 응답으로 변환
|
||||
response.data = {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
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';
|
||||
@@ -63,7 +64,53 @@ class LicenseRemoteDataSourceImpl implements LicenseRemoteDataSource {
|
||||
);
|
||||
|
||||
if (response.data != null && response.data['success'] == true && response.data['data'] != null) {
|
||||
return LicenseListResponseDto.fromJson(response.data['data']);
|
||||
// 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',
|
||||
@@ -202,7 +249,35 @@ class LicenseRemoteDataSourceImpl implements LicenseRemoteDataSource {
|
||||
);
|
||||
|
||||
if (response.data != null && response.data['success'] == true && response.data['data'] != null) {
|
||||
return ExpiringLicenseListDto.fromJson(response.data['data']);
|
||||
// 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',
|
||||
|
||||
@@ -33,7 +33,34 @@ class UserRemoteDataSource {
|
||||
);
|
||||
|
||||
if (response.data != null && response.data['success'] == true && response.data['data'] != null) {
|
||||
return UserListDto.fromJson(response.data['data']);
|
||||
// API 응답이 배열인 경우와 객체인 경우를 모두 처리
|
||||
final data = response.data['data'];
|
||||
if (data is List) {
|
||||
// 배열 응답을 UserListDto 형식으로 변환
|
||||
// role이 null인 경우 기본값 설정
|
||||
final users = data.map((json) {
|
||||
if (json['role'] == null) {
|
||||
json['role'] = 'staff'; // 기본값
|
||||
}
|
||||
return UserDto.fromJson(json);
|
||||
}).toList();
|
||||
final pagination = response.data['pagination'] ?? {};
|
||||
return UserListDto(
|
||||
users: users,
|
||||
total: pagination['total'] ?? users.length,
|
||||
page: pagination['page'] ?? page,
|
||||
perPage: pagination['per_page'] ?? perPage,
|
||||
totalPages: pagination['total_pages'] ?? 1,
|
||||
);
|
||||
} else if (data['users'] != null) {
|
||||
// 이미 UserListDto 형식인 경우
|
||||
return UserListDto.fromJson(data);
|
||||
} else {
|
||||
// 예상치 못한 형식인 경우
|
||||
throw ApiException(
|
||||
message: 'Unexpected response format for user list',
|
||||
);
|
||||
}
|
||||
} else {
|
||||
throw ApiException(
|
||||
message: response.data?['error']?['message'] ?? '사용자 목록을 불러오는데 실패했습니다',
|
||||
|
||||
@@ -11,8 +11,8 @@ class CompanyListDto with _$CompanyListDto {
|
||||
required int id,
|
||||
required String name,
|
||||
required String address,
|
||||
@JsonKey(name: 'contact_name') required String contactName,
|
||||
@JsonKey(name: 'contact_phone') required String contactPhone,
|
||||
@JsonKey(name: 'contact_name') String? contactName,
|
||||
@JsonKey(name: 'contact_phone') String? contactPhone,
|
||||
@JsonKey(name: 'contact_email') String? contactEmail,
|
||||
@JsonKey(name: 'is_active') required bool isActive,
|
||||
@JsonKey(name: 'created_at') DateTime? createdAt,
|
||||
|
||||
@@ -24,9 +24,9 @@ mixin _$CompanyListDto {
|
||||
String get name => throw _privateConstructorUsedError;
|
||||
String get address => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: 'contact_name')
|
||||
String get contactName => throw _privateConstructorUsedError;
|
||||
String? get contactName => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: 'contact_phone')
|
||||
String get contactPhone => throw _privateConstructorUsedError;
|
||||
String? get contactPhone => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: 'contact_email')
|
||||
String? get contactEmail => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: 'is_active')
|
||||
@@ -56,8 +56,8 @@ abstract class $CompanyListDtoCopyWith<$Res> {
|
||||
{int id,
|
||||
String name,
|
||||
String address,
|
||||
@JsonKey(name: 'contact_name') String contactName,
|
||||
@JsonKey(name: 'contact_phone') String contactPhone,
|
||||
@JsonKey(name: 'contact_name') String? contactName,
|
||||
@JsonKey(name: 'contact_phone') String? contactPhone,
|
||||
@JsonKey(name: 'contact_email') String? contactEmail,
|
||||
@JsonKey(name: 'is_active') bool isActive,
|
||||
@JsonKey(name: 'created_at') DateTime? createdAt,
|
||||
@@ -82,8 +82,8 @@ class _$CompanyListDtoCopyWithImpl<$Res, $Val extends CompanyListDto>
|
||||
Object? id = null,
|
||||
Object? name = null,
|
||||
Object? address = null,
|
||||
Object? contactName = null,
|
||||
Object? contactPhone = null,
|
||||
Object? contactName = freezed,
|
||||
Object? contactPhone = freezed,
|
||||
Object? contactEmail = freezed,
|
||||
Object? isActive = null,
|
||||
Object? createdAt = freezed,
|
||||
@@ -102,14 +102,14 @@ class _$CompanyListDtoCopyWithImpl<$Res, $Val extends CompanyListDto>
|
||||
? _value.address
|
||||
: address // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
contactName: null == contactName
|
||||
contactName: freezed == contactName
|
||||
? _value.contactName
|
||||
: contactName // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
contactPhone: null == contactPhone
|
||||
as String?,
|
||||
contactPhone: freezed == contactPhone
|
||||
? _value.contactPhone
|
||||
: contactPhone // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
as String?,
|
||||
contactEmail: freezed == contactEmail
|
||||
? _value.contactEmail
|
||||
: contactEmail // ignore: cast_nullable_to_non_nullable
|
||||
@@ -142,8 +142,8 @@ abstract class _$$CompanyListDtoImplCopyWith<$Res>
|
||||
{int id,
|
||||
String name,
|
||||
String address,
|
||||
@JsonKey(name: 'contact_name') String contactName,
|
||||
@JsonKey(name: 'contact_phone') String contactPhone,
|
||||
@JsonKey(name: 'contact_name') String? contactName,
|
||||
@JsonKey(name: 'contact_phone') String? contactPhone,
|
||||
@JsonKey(name: 'contact_email') String? contactEmail,
|
||||
@JsonKey(name: 'is_active') bool isActive,
|
||||
@JsonKey(name: 'created_at') DateTime? createdAt,
|
||||
@@ -166,8 +166,8 @@ class __$$CompanyListDtoImplCopyWithImpl<$Res>
|
||||
Object? id = null,
|
||||
Object? name = null,
|
||||
Object? address = null,
|
||||
Object? contactName = null,
|
||||
Object? contactPhone = null,
|
||||
Object? contactName = freezed,
|
||||
Object? contactPhone = freezed,
|
||||
Object? contactEmail = freezed,
|
||||
Object? isActive = null,
|
||||
Object? createdAt = freezed,
|
||||
@@ -186,14 +186,14 @@ class __$$CompanyListDtoImplCopyWithImpl<$Res>
|
||||
? _value.address
|
||||
: address // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
contactName: null == contactName
|
||||
contactName: freezed == contactName
|
||||
? _value.contactName
|
||||
: contactName // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
contactPhone: null == contactPhone
|
||||
as String?,
|
||||
contactPhone: freezed == contactPhone
|
||||
? _value.contactPhone
|
||||
: contactPhone // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
as String?,
|
||||
contactEmail: freezed == contactEmail
|
||||
? _value.contactEmail
|
||||
: contactEmail // ignore: cast_nullable_to_non_nullable
|
||||
@@ -221,8 +221,8 @@ class _$CompanyListDtoImpl implements _CompanyListDto {
|
||||
{required this.id,
|
||||
required this.name,
|
||||
required this.address,
|
||||
@JsonKey(name: 'contact_name') required this.contactName,
|
||||
@JsonKey(name: 'contact_phone') required this.contactPhone,
|
||||
@JsonKey(name: 'contact_name') this.contactName,
|
||||
@JsonKey(name: 'contact_phone') this.contactPhone,
|
||||
@JsonKey(name: 'contact_email') this.contactEmail,
|
||||
@JsonKey(name: 'is_active') required this.isActive,
|
||||
@JsonKey(name: 'created_at') this.createdAt,
|
||||
@@ -239,10 +239,10 @@ class _$CompanyListDtoImpl implements _CompanyListDto {
|
||||
final String address;
|
||||
@override
|
||||
@JsonKey(name: 'contact_name')
|
||||
final String contactName;
|
||||
final String? contactName;
|
||||
@override
|
||||
@JsonKey(name: 'contact_phone')
|
||||
final String contactPhone;
|
||||
final String? contactPhone;
|
||||
@override
|
||||
@JsonKey(name: 'contact_email')
|
||||
final String? contactEmail;
|
||||
@@ -310,8 +310,8 @@ abstract class _CompanyListDto implements CompanyListDto {
|
||||
{required final int id,
|
||||
required final String name,
|
||||
required final String address,
|
||||
@JsonKey(name: 'contact_name') required final String contactName,
|
||||
@JsonKey(name: 'contact_phone') required final String contactPhone,
|
||||
@JsonKey(name: 'contact_name') final String? contactName,
|
||||
@JsonKey(name: 'contact_phone') final String? contactPhone,
|
||||
@JsonKey(name: 'contact_email') final String? contactEmail,
|
||||
@JsonKey(name: 'is_active') required final bool isActive,
|
||||
@JsonKey(name: 'created_at') final DateTime? createdAt,
|
||||
@@ -329,10 +329,10 @@ abstract class _CompanyListDto implements CompanyListDto {
|
||||
String get address;
|
||||
@override
|
||||
@JsonKey(name: 'contact_name')
|
||||
String get contactName;
|
||||
String? get contactName;
|
||||
@override
|
||||
@JsonKey(name: 'contact_phone')
|
||||
String get contactPhone;
|
||||
String? get contactPhone;
|
||||
@override
|
||||
@JsonKey(name: 'contact_email')
|
||||
String? get contactEmail;
|
||||
|
||||
@@ -11,8 +11,8 @@ _$CompanyListDtoImpl _$$CompanyListDtoImplFromJson(Map<String, dynamic> json) =>
|
||||
id: (json['id'] as num).toInt(),
|
||||
name: json['name'] as String,
|
||||
address: json['address'] as String,
|
||||
contactName: json['contact_name'] as String,
|
||||
contactPhone: json['contact_phone'] as String,
|
||||
contactName: json['contact_name'] as String?,
|
||||
contactPhone: json['contact_phone'] as String?,
|
||||
contactEmail: json['contact_email'] as String?,
|
||||
isActive: json['is_active'] as bool,
|
||||
createdAt: json['created_at'] == null
|
||||
|
||||
@@ -3,6 +3,44 @@ import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
part 'license_dto.freezed.dart';
|
||||
part 'license_dto.g.dart';
|
||||
|
||||
// 날짜를 YYYY-MM-DD 형식으로 변환하는 헬퍼 함수
|
||||
String? _dateToJson(DateTime? date) {
|
||||
if (date == null) return null;
|
||||
return '${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}';
|
||||
}
|
||||
|
||||
// YYYY-MM-DD 형식 문자열을 DateTime으로 변환하는 헬퍼 함수
|
||||
DateTime? _dateFromJson(String? dateStr) {
|
||||
if (dateStr == null || dateStr.isEmpty) return null;
|
||||
try {
|
||||
// YYYY-MM-DD 형식 파싱
|
||||
if (dateStr.contains('-') && dateStr.length == 10) {
|
||||
final parts = dateStr.split('-');
|
||||
return DateTime(int.parse(parts[0]), int.parse(parts[1]), int.parse(parts[2]));
|
||||
}
|
||||
// ISO 8601 형식도 지원
|
||||
return DateTime.parse(dateStr);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 필수 날짜 필드용 헬퍼 함수 (항상 non-null DateTime 반환)
|
||||
DateTime _requiredDateFromJson(String? dateStr) {
|
||||
if (dateStr == null || dateStr.isEmpty) return DateTime.now();
|
||||
try {
|
||||
// YYYY-MM-DD 형식 파싱
|
||||
if (dateStr.contains('-') && dateStr.length == 10) {
|
||||
final parts = dateStr.split('-');
|
||||
return DateTime(int.parse(parts[0]), int.parse(parts[1]), int.parse(parts[2]));
|
||||
}
|
||||
// ISO 8601 형식도 지원
|
||||
return DateTime.parse(dateStr);
|
||||
} catch (e) {
|
||||
return DateTime.now();
|
||||
}
|
||||
}
|
||||
|
||||
/// 라이선스 전체 정보 DTO
|
||||
@freezed
|
||||
class LicenseDto with _$LicenseDto {
|
||||
@@ -13,8 +51,8 @@ class LicenseDto with _$LicenseDto {
|
||||
String? vendor,
|
||||
@JsonKey(name: 'license_type') String? licenseType,
|
||||
@JsonKey(name: 'user_count') int? userCount,
|
||||
@JsonKey(name: 'purchase_date') DateTime? purchaseDate,
|
||||
@JsonKey(name: 'expiry_date') DateTime? expiryDate,
|
||||
@JsonKey(name: 'purchase_date', toJson: _dateToJson, fromJson: _dateFromJson) DateTime? purchaseDate,
|
||||
@JsonKey(name: 'expiry_date', toJson: _dateToJson, fromJson: _dateFromJson) DateTime? expiryDate,
|
||||
@JsonKey(name: 'purchase_price') double? purchasePrice,
|
||||
@JsonKey(name: 'company_id') int? companyId,
|
||||
@JsonKey(name: 'branch_id') int? branchId,
|
||||
@@ -54,10 +92,14 @@ class ExpiringLicenseDto with _$ExpiringLicenseDto {
|
||||
required int id,
|
||||
@JsonKey(name: 'license_key') required String licenseKey,
|
||||
@JsonKey(name: 'product_name') String? productName,
|
||||
@JsonKey(name: 'company_name') String? companyName,
|
||||
@JsonKey(name: 'expiry_date') required DateTime expiryDate,
|
||||
String? vendor,
|
||||
@JsonKey(name: 'expiry_date', fromJson: _requiredDateFromJson) required DateTime expiryDate,
|
||||
@JsonKey(name: 'days_until_expiry') required int daysUntilExpiry,
|
||||
@JsonKey(name: 'is_active') required bool isActive,
|
||||
@JsonKey(name: 'assigned_user_id') int? assignedUserId,
|
||||
@JsonKey(name: 'company_id') int? companyId,
|
||||
@JsonKey(name: 'company_name') String? companyName,
|
||||
@JsonKey(name: 'assigned_user_name') String? assignedUserName,
|
||||
@JsonKey(name: 'is_active', defaultValue: true) bool? isActive,
|
||||
}) = _ExpiringLicenseDto;
|
||||
|
||||
factory ExpiringLicenseDto.fromJson(Map<String, dynamic> json) =>
|
||||
|
||||
@@ -30,9 +30,9 @@ mixin _$LicenseDto {
|
||||
String? get licenseType => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: 'user_count')
|
||||
int? get userCount => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: 'purchase_date')
|
||||
@JsonKey(name: 'purchase_date', toJson: _dateToJson, fromJson: _dateFromJson)
|
||||
DateTime? get purchaseDate => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: 'expiry_date')
|
||||
@JsonKey(name: 'expiry_date', toJson: _dateToJson, fromJson: _dateFromJson)
|
||||
DateTime? get expiryDate => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: 'purchase_price')
|
||||
double? get purchasePrice => throw _privateConstructorUsedError;
|
||||
@@ -80,8 +80,12 @@ abstract class $LicenseDtoCopyWith<$Res> {
|
||||
String? vendor,
|
||||
@JsonKey(name: 'license_type') String? licenseType,
|
||||
@JsonKey(name: 'user_count') int? userCount,
|
||||
@JsonKey(name: 'purchase_date') DateTime? purchaseDate,
|
||||
@JsonKey(name: 'expiry_date') DateTime? expiryDate,
|
||||
@JsonKey(
|
||||
name: 'purchase_date', toJson: _dateToJson, fromJson: _dateFromJson)
|
||||
DateTime? purchaseDate,
|
||||
@JsonKey(
|
||||
name: 'expiry_date', toJson: _dateToJson, fromJson: _dateFromJson)
|
||||
DateTime? expiryDate,
|
||||
@JsonKey(name: 'purchase_price') double? purchasePrice,
|
||||
@JsonKey(name: 'company_id') int? companyId,
|
||||
@JsonKey(name: 'branch_id') int? branchId,
|
||||
@@ -226,8 +230,12 @@ abstract class _$$LicenseDtoImplCopyWith<$Res>
|
||||
String? vendor,
|
||||
@JsonKey(name: 'license_type') String? licenseType,
|
||||
@JsonKey(name: 'user_count') int? userCount,
|
||||
@JsonKey(name: 'purchase_date') DateTime? purchaseDate,
|
||||
@JsonKey(name: 'expiry_date') DateTime? expiryDate,
|
||||
@JsonKey(
|
||||
name: 'purchase_date', toJson: _dateToJson, fromJson: _dateFromJson)
|
||||
DateTime? purchaseDate,
|
||||
@JsonKey(
|
||||
name: 'expiry_date', toJson: _dateToJson, fromJson: _dateFromJson)
|
||||
DateTime? expiryDate,
|
||||
@JsonKey(name: 'purchase_price') double? purchasePrice,
|
||||
@JsonKey(name: 'company_id') int? companyId,
|
||||
@JsonKey(name: 'branch_id') int? branchId,
|
||||
@@ -365,8 +373,12 @@ class _$LicenseDtoImpl implements _LicenseDto {
|
||||
this.vendor,
|
||||
@JsonKey(name: 'license_type') this.licenseType,
|
||||
@JsonKey(name: 'user_count') this.userCount,
|
||||
@JsonKey(name: 'purchase_date') this.purchaseDate,
|
||||
@JsonKey(name: 'expiry_date') this.expiryDate,
|
||||
@JsonKey(
|
||||
name: 'purchase_date', toJson: _dateToJson, fromJson: _dateFromJson)
|
||||
this.purchaseDate,
|
||||
@JsonKey(
|
||||
name: 'expiry_date', toJson: _dateToJson, fromJson: _dateFromJson)
|
||||
this.expiryDate,
|
||||
@JsonKey(name: 'purchase_price') this.purchasePrice,
|
||||
@JsonKey(name: 'company_id') this.companyId,
|
||||
@JsonKey(name: 'branch_id') this.branchId,
|
||||
@@ -399,10 +411,10 @@ class _$LicenseDtoImpl implements _LicenseDto {
|
||||
@JsonKey(name: 'user_count')
|
||||
final int? userCount;
|
||||
@override
|
||||
@JsonKey(name: 'purchase_date')
|
||||
@JsonKey(name: 'purchase_date', toJson: _dateToJson, fromJson: _dateFromJson)
|
||||
final DateTime? purchaseDate;
|
||||
@override
|
||||
@JsonKey(name: 'expiry_date')
|
||||
@JsonKey(name: 'expiry_date', toJson: _dateToJson, fromJson: _dateFromJson)
|
||||
final DateTime? expiryDate;
|
||||
@override
|
||||
@JsonKey(name: 'purchase_price')
|
||||
@@ -534,8 +546,12 @@ abstract class _LicenseDto implements LicenseDto {
|
||||
final String? vendor,
|
||||
@JsonKey(name: 'license_type') final String? licenseType,
|
||||
@JsonKey(name: 'user_count') final int? userCount,
|
||||
@JsonKey(name: 'purchase_date') final DateTime? purchaseDate,
|
||||
@JsonKey(name: 'expiry_date') final DateTime? expiryDate,
|
||||
@JsonKey(
|
||||
name: 'purchase_date', toJson: _dateToJson, fromJson: _dateFromJson)
|
||||
final DateTime? purchaseDate,
|
||||
@JsonKey(
|
||||
name: 'expiry_date', toJson: _dateToJson, fromJson: _dateFromJson)
|
||||
final DateTime? expiryDate,
|
||||
@JsonKey(name: 'purchase_price') final double? purchasePrice,
|
||||
@JsonKey(name: 'company_id') final int? companyId,
|
||||
@JsonKey(name: 'branch_id') final int? branchId,
|
||||
@@ -569,10 +585,10 @@ abstract class _LicenseDto implements LicenseDto {
|
||||
@JsonKey(name: 'user_count')
|
||||
int? get userCount;
|
||||
@override
|
||||
@JsonKey(name: 'purchase_date')
|
||||
@JsonKey(name: 'purchase_date', toJson: _dateToJson, fromJson: _dateFromJson)
|
||||
DateTime? get purchaseDate;
|
||||
@override
|
||||
@JsonKey(name: 'expiry_date')
|
||||
@JsonKey(name: 'expiry_date', toJson: _dateToJson, fromJson: _dateFromJson)
|
||||
DateTime? get expiryDate;
|
||||
@override
|
||||
@JsonKey(name: 'purchase_price')
|
||||
@@ -886,14 +902,21 @@ mixin _$ExpiringLicenseDto {
|
||||
String get licenseKey => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: 'product_name')
|
||||
String? get productName => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: 'company_name')
|
||||
String? get companyName => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: 'expiry_date')
|
||||
String? get vendor => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: 'expiry_date', fromJson: _requiredDateFromJson)
|
||||
DateTime get expiryDate => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: 'days_until_expiry')
|
||||
int get daysUntilExpiry => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: 'is_active')
|
||||
bool get isActive => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: 'assigned_user_id')
|
||||
int? get assignedUserId => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: 'company_id')
|
||||
int? get companyId => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: 'company_name')
|
||||
String? get companyName => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: 'assigned_user_name')
|
||||
String? get assignedUserName => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: 'is_active', defaultValue: true)
|
||||
bool? get isActive => throw _privateConstructorUsedError;
|
||||
|
||||
/// Serializes this ExpiringLicenseDto to a JSON map.
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@@ -915,10 +938,15 @@ abstract class $ExpiringLicenseDtoCopyWith<$Res> {
|
||||
{int id,
|
||||
@JsonKey(name: 'license_key') String licenseKey,
|
||||
@JsonKey(name: 'product_name') String? productName,
|
||||
@JsonKey(name: 'company_name') String? companyName,
|
||||
@JsonKey(name: 'expiry_date') DateTime expiryDate,
|
||||
String? vendor,
|
||||
@JsonKey(name: 'expiry_date', fromJson: _requiredDateFromJson)
|
||||
DateTime expiryDate,
|
||||
@JsonKey(name: 'days_until_expiry') int daysUntilExpiry,
|
||||
@JsonKey(name: 'is_active') bool isActive});
|
||||
@JsonKey(name: 'assigned_user_id') int? assignedUserId,
|
||||
@JsonKey(name: 'company_id') int? companyId,
|
||||
@JsonKey(name: 'company_name') String? companyName,
|
||||
@JsonKey(name: 'assigned_user_name') String? assignedUserName,
|
||||
@JsonKey(name: 'is_active', defaultValue: true) bool? isActive});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@@ -939,10 +967,14 @@ class _$ExpiringLicenseDtoCopyWithImpl<$Res, $Val extends ExpiringLicenseDto>
|
||||
Object? id = null,
|
||||
Object? licenseKey = null,
|
||||
Object? productName = freezed,
|
||||
Object? companyName = freezed,
|
||||
Object? vendor = freezed,
|
||||
Object? expiryDate = null,
|
||||
Object? daysUntilExpiry = null,
|
||||
Object? isActive = null,
|
||||
Object? assignedUserId = freezed,
|
||||
Object? companyId = freezed,
|
||||
Object? companyName = freezed,
|
||||
Object? assignedUserName = freezed,
|
||||
Object? isActive = freezed,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
id: null == id
|
||||
@@ -957,9 +989,9 @@ class _$ExpiringLicenseDtoCopyWithImpl<$Res, $Val extends ExpiringLicenseDto>
|
||||
? _value.productName
|
||||
: productName // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
companyName: freezed == companyName
|
||||
? _value.companyName
|
||||
: companyName // ignore: cast_nullable_to_non_nullable
|
||||
vendor: freezed == vendor
|
||||
? _value.vendor
|
||||
: vendor // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
expiryDate: null == expiryDate
|
||||
? _value.expiryDate
|
||||
@@ -969,10 +1001,26 @@ class _$ExpiringLicenseDtoCopyWithImpl<$Res, $Val extends ExpiringLicenseDto>
|
||||
? _value.daysUntilExpiry
|
||||
: daysUntilExpiry // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
isActive: null == isActive
|
||||
assignedUserId: freezed == assignedUserId
|
||||
? _value.assignedUserId
|
||||
: assignedUserId // ignore: cast_nullable_to_non_nullable
|
||||
as int?,
|
||||
companyId: freezed == companyId
|
||||
? _value.companyId
|
||||
: companyId // ignore: cast_nullable_to_non_nullable
|
||||
as int?,
|
||||
companyName: freezed == companyName
|
||||
? _value.companyName
|
||||
: companyName // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
assignedUserName: freezed == assignedUserName
|
||||
? _value.assignedUserName
|
||||
: assignedUserName // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
isActive: freezed == isActive
|
||||
? _value.isActive
|
||||
: isActive // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
as bool?,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
@@ -989,10 +1037,15 @@ abstract class _$$ExpiringLicenseDtoImplCopyWith<$Res>
|
||||
{int id,
|
||||
@JsonKey(name: 'license_key') String licenseKey,
|
||||
@JsonKey(name: 'product_name') String? productName,
|
||||
@JsonKey(name: 'company_name') String? companyName,
|
||||
@JsonKey(name: 'expiry_date') DateTime expiryDate,
|
||||
String? vendor,
|
||||
@JsonKey(name: 'expiry_date', fromJson: _requiredDateFromJson)
|
||||
DateTime expiryDate,
|
||||
@JsonKey(name: 'days_until_expiry') int daysUntilExpiry,
|
||||
@JsonKey(name: 'is_active') bool isActive});
|
||||
@JsonKey(name: 'assigned_user_id') int? assignedUserId,
|
||||
@JsonKey(name: 'company_id') int? companyId,
|
||||
@JsonKey(name: 'company_name') String? companyName,
|
||||
@JsonKey(name: 'assigned_user_name') String? assignedUserName,
|
||||
@JsonKey(name: 'is_active', defaultValue: true) bool? isActive});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@@ -1011,10 +1064,14 @@ class __$$ExpiringLicenseDtoImplCopyWithImpl<$Res>
|
||||
Object? id = null,
|
||||
Object? licenseKey = null,
|
||||
Object? productName = freezed,
|
||||
Object? companyName = freezed,
|
||||
Object? vendor = freezed,
|
||||
Object? expiryDate = null,
|
||||
Object? daysUntilExpiry = null,
|
||||
Object? isActive = null,
|
||||
Object? assignedUserId = freezed,
|
||||
Object? companyId = freezed,
|
||||
Object? companyName = freezed,
|
||||
Object? assignedUserName = freezed,
|
||||
Object? isActive = freezed,
|
||||
}) {
|
||||
return _then(_$ExpiringLicenseDtoImpl(
|
||||
id: null == id
|
||||
@@ -1029,9 +1086,9 @@ class __$$ExpiringLicenseDtoImplCopyWithImpl<$Res>
|
||||
? _value.productName
|
||||
: productName // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
companyName: freezed == companyName
|
||||
? _value.companyName
|
||||
: companyName // ignore: cast_nullable_to_non_nullable
|
||||
vendor: freezed == vendor
|
||||
? _value.vendor
|
||||
: vendor // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
expiryDate: null == expiryDate
|
||||
? _value.expiryDate
|
||||
@@ -1041,10 +1098,26 @@ class __$$ExpiringLicenseDtoImplCopyWithImpl<$Res>
|
||||
? _value.daysUntilExpiry
|
||||
: daysUntilExpiry // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
isActive: null == isActive
|
||||
assignedUserId: freezed == assignedUserId
|
||||
? _value.assignedUserId
|
||||
: assignedUserId // ignore: cast_nullable_to_non_nullable
|
||||
as int?,
|
||||
companyId: freezed == companyId
|
||||
? _value.companyId
|
||||
: companyId // ignore: cast_nullable_to_non_nullable
|
||||
as int?,
|
||||
companyName: freezed == companyName
|
||||
? _value.companyName
|
||||
: companyName // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
assignedUserName: freezed == assignedUserName
|
||||
? _value.assignedUserName
|
||||
: assignedUserName // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
isActive: freezed == isActive
|
||||
? _value.isActive
|
||||
: isActive // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
as bool?,
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -1056,10 +1129,15 @@ class _$ExpiringLicenseDtoImpl implements _ExpiringLicenseDto {
|
||||
{required this.id,
|
||||
@JsonKey(name: 'license_key') required this.licenseKey,
|
||||
@JsonKey(name: 'product_name') this.productName,
|
||||
@JsonKey(name: 'company_name') this.companyName,
|
||||
@JsonKey(name: 'expiry_date') required this.expiryDate,
|
||||
this.vendor,
|
||||
@JsonKey(name: 'expiry_date', fromJson: _requiredDateFromJson)
|
||||
required this.expiryDate,
|
||||
@JsonKey(name: 'days_until_expiry') required this.daysUntilExpiry,
|
||||
@JsonKey(name: 'is_active') required this.isActive});
|
||||
@JsonKey(name: 'assigned_user_id') this.assignedUserId,
|
||||
@JsonKey(name: 'company_id') this.companyId,
|
||||
@JsonKey(name: 'company_name') this.companyName,
|
||||
@JsonKey(name: 'assigned_user_name') this.assignedUserName,
|
||||
@JsonKey(name: 'is_active', defaultValue: true) this.isActive});
|
||||
|
||||
factory _$ExpiringLicenseDtoImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$ExpiringLicenseDtoImplFromJson(json);
|
||||
@@ -1073,21 +1151,32 @@ class _$ExpiringLicenseDtoImpl implements _ExpiringLicenseDto {
|
||||
@JsonKey(name: 'product_name')
|
||||
final String? productName;
|
||||
@override
|
||||
@JsonKey(name: 'company_name')
|
||||
final String? companyName;
|
||||
final String? vendor;
|
||||
@override
|
||||
@JsonKey(name: 'expiry_date')
|
||||
@JsonKey(name: 'expiry_date', fromJson: _requiredDateFromJson)
|
||||
final DateTime expiryDate;
|
||||
@override
|
||||
@JsonKey(name: 'days_until_expiry')
|
||||
final int daysUntilExpiry;
|
||||
@override
|
||||
@JsonKey(name: 'is_active')
|
||||
final bool isActive;
|
||||
@JsonKey(name: 'assigned_user_id')
|
||||
final int? assignedUserId;
|
||||
@override
|
||||
@JsonKey(name: 'company_id')
|
||||
final int? companyId;
|
||||
@override
|
||||
@JsonKey(name: 'company_name')
|
||||
final String? companyName;
|
||||
@override
|
||||
@JsonKey(name: 'assigned_user_name')
|
||||
final String? assignedUserName;
|
||||
@override
|
||||
@JsonKey(name: 'is_active', defaultValue: true)
|
||||
final bool? isActive;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ExpiringLicenseDto(id: $id, licenseKey: $licenseKey, productName: $productName, companyName: $companyName, expiryDate: $expiryDate, daysUntilExpiry: $daysUntilExpiry, isActive: $isActive)';
|
||||
return 'ExpiringLicenseDto(id: $id, licenseKey: $licenseKey, productName: $productName, vendor: $vendor, expiryDate: $expiryDate, daysUntilExpiry: $daysUntilExpiry, assignedUserId: $assignedUserId, companyId: $companyId, companyName: $companyName, assignedUserName: $assignedUserName, isActive: $isActive)';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -1100,20 +1189,38 @@ class _$ExpiringLicenseDtoImpl implements _ExpiringLicenseDto {
|
||||
other.licenseKey == licenseKey) &&
|
||||
(identical(other.productName, productName) ||
|
||||
other.productName == productName) &&
|
||||
(identical(other.companyName, companyName) ||
|
||||
other.companyName == companyName) &&
|
||||
(identical(other.vendor, vendor) || other.vendor == vendor) &&
|
||||
(identical(other.expiryDate, expiryDate) ||
|
||||
other.expiryDate == expiryDate) &&
|
||||
(identical(other.daysUntilExpiry, daysUntilExpiry) ||
|
||||
other.daysUntilExpiry == daysUntilExpiry) &&
|
||||
(identical(other.assignedUserId, assignedUserId) ||
|
||||
other.assignedUserId == assignedUserId) &&
|
||||
(identical(other.companyId, companyId) ||
|
||||
other.companyId == companyId) &&
|
||||
(identical(other.companyName, companyName) ||
|
||||
other.companyName == companyName) &&
|
||||
(identical(other.assignedUserName, assignedUserName) ||
|
||||
other.assignedUserName == assignedUserName) &&
|
||||
(identical(other.isActive, isActive) ||
|
||||
other.isActive == isActive));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, id, licenseKey, productName,
|
||||
companyName, expiryDate, daysUntilExpiry, isActive);
|
||||
int get hashCode => Object.hash(
|
||||
runtimeType,
|
||||
id,
|
||||
licenseKey,
|
||||
productName,
|
||||
vendor,
|
||||
expiryDate,
|
||||
daysUntilExpiry,
|
||||
assignedUserId,
|
||||
companyId,
|
||||
companyName,
|
||||
assignedUserName,
|
||||
isActive);
|
||||
|
||||
/// Create a copy of ExpiringLicenseDto
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@@ -1137,11 +1244,16 @@ abstract class _ExpiringLicenseDto implements ExpiringLicenseDto {
|
||||
{required final int id,
|
||||
@JsonKey(name: 'license_key') required final String licenseKey,
|
||||
@JsonKey(name: 'product_name') final String? productName,
|
||||
@JsonKey(name: 'company_name') final String? companyName,
|
||||
@JsonKey(name: 'expiry_date') required final DateTime expiryDate,
|
||||
final String? vendor,
|
||||
@JsonKey(name: 'expiry_date', fromJson: _requiredDateFromJson)
|
||||
required final DateTime expiryDate,
|
||||
@JsonKey(name: 'days_until_expiry') required final int daysUntilExpiry,
|
||||
@JsonKey(name: 'is_active')
|
||||
required final bool isActive}) = _$ExpiringLicenseDtoImpl;
|
||||
@JsonKey(name: 'assigned_user_id') final int? assignedUserId,
|
||||
@JsonKey(name: 'company_id') final int? companyId,
|
||||
@JsonKey(name: 'company_name') final String? companyName,
|
||||
@JsonKey(name: 'assigned_user_name') final String? assignedUserName,
|
||||
@JsonKey(name: 'is_active', defaultValue: true)
|
||||
final bool? isActive}) = _$ExpiringLicenseDtoImpl;
|
||||
|
||||
factory _ExpiringLicenseDto.fromJson(Map<String, dynamic> json) =
|
||||
_$ExpiringLicenseDtoImpl.fromJson;
|
||||
@@ -1155,17 +1267,28 @@ abstract class _ExpiringLicenseDto implements ExpiringLicenseDto {
|
||||
@JsonKey(name: 'product_name')
|
||||
String? get productName;
|
||||
@override
|
||||
@JsonKey(name: 'company_name')
|
||||
String? get companyName;
|
||||
String? get vendor;
|
||||
@override
|
||||
@JsonKey(name: 'expiry_date')
|
||||
@JsonKey(name: 'expiry_date', fromJson: _requiredDateFromJson)
|
||||
DateTime get expiryDate;
|
||||
@override
|
||||
@JsonKey(name: 'days_until_expiry')
|
||||
int get daysUntilExpiry;
|
||||
@override
|
||||
@JsonKey(name: 'is_active')
|
||||
bool get isActive;
|
||||
@JsonKey(name: 'assigned_user_id')
|
||||
int? get assignedUserId;
|
||||
@override
|
||||
@JsonKey(name: 'company_id')
|
||||
int? get companyId;
|
||||
@override
|
||||
@JsonKey(name: 'company_name')
|
||||
String? get companyName;
|
||||
@override
|
||||
@JsonKey(name: 'assigned_user_name')
|
||||
String? get assignedUserName;
|
||||
@override
|
||||
@JsonKey(name: 'is_active', defaultValue: true)
|
||||
bool? get isActive;
|
||||
|
||||
/// Create a copy of ExpiringLicenseDto
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
|
||||
@@ -14,12 +14,8 @@ _$LicenseDtoImpl _$$LicenseDtoImplFromJson(Map<String, dynamic> json) =>
|
||||
vendor: json['vendor'] as String?,
|
||||
licenseType: json['license_type'] as String?,
|
||||
userCount: (json['user_count'] as num?)?.toInt(),
|
||||
purchaseDate: json['purchase_date'] == null
|
||||
? null
|
||||
: DateTime.parse(json['purchase_date'] as String),
|
||||
expiryDate: json['expiry_date'] == null
|
||||
? null
|
||||
: DateTime.parse(json['expiry_date'] as String),
|
||||
purchaseDate: _dateFromJson(json['purchase_date'] as String?),
|
||||
expiryDate: _dateFromJson(json['expiry_date'] as String?),
|
||||
purchasePrice: (json['purchase_price'] as num?)?.toDouble(),
|
||||
companyId: (json['company_id'] as num?)?.toInt(),
|
||||
branchId: (json['branch_id'] as num?)?.toInt(),
|
||||
@@ -41,8 +37,8 @@ Map<String, dynamic> _$$LicenseDtoImplToJson(_$LicenseDtoImpl instance) =>
|
||||
'vendor': instance.vendor,
|
||||
'license_type': instance.licenseType,
|
||||
'user_count': instance.userCount,
|
||||
'purchase_date': instance.purchaseDate?.toIso8601String(),
|
||||
'expiry_date': instance.expiryDate?.toIso8601String(),
|
||||
'purchase_date': _dateToJson(instance.purchaseDate),
|
||||
'expiry_date': _dateToJson(instance.expiryDate),
|
||||
'purchase_price': instance.purchasePrice,
|
||||
'company_id': instance.companyId,
|
||||
'branch_id': instance.branchId,
|
||||
@@ -84,10 +80,14 @@ _$ExpiringLicenseDtoImpl _$$ExpiringLicenseDtoImplFromJson(
|
||||
id: (json['id'] as num).toInt(),
|
||||
licenseKey: json['license_key'] as String,
|
||||
productName: json['product_name'] as String?,
|
||||
companyName: json['company_name'] as String?,
|
||||
expiryDate: DateTime.parse(json['expiry_date'] as String),
|
||||
vendor: json['vendor'] as String?,
|
||||
expiryDate: _requiredDateFromJson(json['expiry_date'] as String?),
|
||||
daysUntilExpiry: (json['days_until_expiry'] as num).toInt(),
|
||||
isActive: json['is_active'] as bool,
|
||||
assignedUserId: (json['assigned_user_id'] as num?)?.toInt(),
|
||||
companyId: (json['company_id'] as num?)?.toInt(),
|
||||
companyName: json['company_name'] as String?,
|
||||
assignedUserName: json['assigned_user_name'] as String?,
|
||||
isActive: json['is_active'] as bool? ?? true,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$ExpiringLicenseDtoImplToJson(
|
||||
@@ -96,9 +96,13 @@ Map<String, dynamic> _$$ExpiringLicenseDtoImplToJson(
|
||||
'id': instance.id,
|
||||
'license_key': instance.licenseKey,
|
||||
'product_name': instance.productName,
|
||||
'company_name': instance.companyName,
|
||||
'vendor': instance.vendor,
|
||||
'expiry_date': instance.expiryDate.toIso8601String(),
|
||||
'days_until_expiry': instance.daysUntilExpiry,
|
||||
'assigned_user_id': instance.assignedUserId,
|
||||
'company_id': instance.companyId,
|
||||
'company_name': instance.companyName,
|
||||
'assigned_user_name': instance.assignedUserName,
|
||||
'is_active': instance.isActive,
|
||||
};
|
||||
|
||||
|
||||
@@ -3,6 +3,28 @@ import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
part 'license_request_dto.freezed.dart';
|
||||
part 'license_request_dto.g.dart';
|
||||
|
||||
// 날짜를 YYYY-MM-DD 형식으로 변환하는 헬퍼 함수
|
||||
String? _dateToJson(DateTime? date) {
|
||||
if (date == null) return null;
|
||||
return '${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}';
|
||||
}
|
||||
|
||||
// YYYY-MM-DD 형식 문자열을 DateTime으로 변환하는 헬퍼 함수
|
||||
DateTime? _dateFromJson(String? dateStr) {
|
||||
if (dateStr == null || dateStr.isEmpty) return null;
|
||||
try {
|
||||
// YYYY-MM-DD 형식 파싱
|
||||
if (dateStr.contains('-') && dateStr.length == 10) {
|
||||
final parts = dateStr.split('-');
|
||||
return DateTime(int.parse(parts[0]), int.parse(parts[1]), int.parse(parts[2]));
|
||||
}
|
||||
// ISO 8601 형식도 지원
|
||||
return DateTime.parse(dateStr);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// 라이선스 생성 요청 DTO
|
||||
@freezed
|
||||
class CreateLicenseRequest with _$CreateLicenseRequest {
|
||||
@@ -12,8 +34,8 @@ class CreateLicenseRequest with _$CreateLicenseRequest {
|
||||
String? vendor,
|
||||
@JsonKey(name: 'license_type') String? licenseType,
|
||||
@JsonKey(name: 'user_count') int? userCount,
|
||||
@JsonKey(name: 'purchase_date') DateTime? purchaseDate,
|
||||
@JsonKey(name: 'expiry_date') DateTime? expiryDate,
|
||||
@JsonKey(name: 'purchase_date', toJson: _dateToJson, fromJson: _dateFromJson) DateTime? purchaseDate,
|
||||
@JsonKey(name: 'expiry_date', toJson: _dateToJson, fromJson: _dateFromJson) DateTime? expiryDate,
|
||||
@JsonKey(name: 'purchase_price') double? purchasePrice,
|
||||
@JsonKey(name: 'company_id') int? companyId,
|
||||
@JsonKey(name: 'branch_id') int? branchId,
|
||||
@@ -32,8 +54,8 @@ class UpdateLicenseRequest with _$UpdateLicenseRequest {
|
||||
String? vendor,
|
||||
@JsonKey(name: 'license_type') String? licenseType,
|
||||
@JsonKey(name: 'user_count') int? userCount,
|
||||
@JsonKey(name: 'purchase_date') DateTime? purchaseDate,
|
||||
@JsonKey(name: 'expiry_date') DateTime? expiryDate,
|
||||
@JsonKey(name: 'purchase_date', toJson: _dateToJson, fromJson: _dateFromJson) DateTime? purchaseDate,
|
||||
@JsonKey(name: 'expiry_date', toJson: _dateToJson, fromJson: _dateFromJson) DateTime? expiryDate,
|
||||
@JsonKey(name: 'purchase_price') double? purchasePrice,
|
||||
String? remark,
|
||||
@JsonKey(name: 'is_active') bool? isActive,
|
||||
|
||||
@@ -29,9 +29,9 @@ mixin _$CreateLicenseRequest {
|
||||
String? get licenseType => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: 'user_count')
|
||||
int? get userCount => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: 'purchase_date')
|
||||
@JsonKey(name: 'purchase_date', toJson: _dateToJson, fromJson: _dateFromJson)
|
||||
DateTime? get purchaseDate => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: 'expiry_date')
|
||||
@JsonKey(name: 'expiry_date', toJson: _dateToJson, fromJson: _dateFromJson)
|
||||
DateTime? get expiryDate => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: 'purchase_price')
|
||||
double? get purchasePrice => throw _privateConstructorUsedError;
|
||||
@@ -63,8 +63,12 @@ abstract class $CreateLicenseRequestCopyWith<$Res> {
|
||||
String? vendor,
|
||||
@JsonKey(name: 'license_type') String? licenseType,
|
||||
@JsonKey(name: 'user_count') int? userCount,
|
||||
@JsonKey(name: 'purchase_date') DateTime? purchaseDate,
|
||||
@JsonKey(name: 'expiry_date') DateTime? expiryDate,
|
||||
@JsonKey(
|
||||
name: 'purchase_date', toJson: _dateToJson, fromJson: _dateFromJson)
|
||||
DateTime? purchaseDate,
|
||||
@JsonKey(
|
||||
name: 'expiry_date', toJson: _dateToJson, fromJson: _dateFromJson)
|
||||
DateTime? expiryDate,
|
||||
@JsonKey(name: 'purchase_price') double? purchasePrice,
|
||||
@JsonKey(name: 'company_id') int? companyId,
|
||||
@JsonKey(name: 'branch_id') int? branchId,
|
||||
@@ -162,8 +166,12 @@ abstract class _$$CreateLicenseRequestImplCopyWith<$Res>
|
||||
String? vendor,
|
||||
@JsonKey(name: 'license_type') String? licenseType,
|
||||
@JsonKey(name: 'user_count') int? userCount,
|
||||
@JsonKey(name: 'purchase_date') DateTime? purchaseDate,
|
||||
@JsonKey(name: 'expiry_date') DateTime? expiryDate,
|
||||
@JsonKey(
|
||||
name: 'purchase_date', toJson: _dateToJson, fromJson: _dateFromJson)
|
||||
DateTime? purchaseDate,
|
||||
@JsonKey(
|
||||
name: 'expiry_date', toJson: _dateToJson, fromJson: _dateFromJson)
|
||||
DateTime? expiryDate,
|
||||
@JsonKey(name: 'purchase_price') double? purchasePrice,
|
||||
@JsonKey(name: 'company_id') int? companyId,
|
||||
@JsonKey(name: 'branch_id') int? branchId,
|
||||
@@ -253,8 +261,12 @@ class _$CreateLicenseRequestImpl implements _CreateLicenseRequest {
|
||||
this.vendor,
|
||||
@JsonKey(name: 'license_type') this.licenseType,
|
||||
@JsonKey(name: 'user_count') this.userCount,
|
||||
@JsonKey(name: 'purchase_date') this.purchaseDate,
|
||||
@JsonKey(name: 'expiry_date') this.expiryDate,
|
||||
@JsonKey(
|
||||
name: 'purchase_date', toJson: _dateToJson, fromJson: _dateFromJson)
|
||||
this.purchaseDate,
|
||||
@JsonKey(
|
||||
name: 'expiry_date', toJson: _dateToJson, fromJson: _dateFromJson)
|
||||
this.expiryDate,
|
||||
@JsonKey(name: 'purchase_price') this.purchasePrice,
|
||||
@JsonKey(name: 'company_id') this.companyId,
|
||||
@JsonKey(name: 'branch_id') this.branchId,
|
||||
@@ -278,10 +290,10 @@ class _$CreateLicenseRequestImpl implements _CreateLicenseRequest {
|
||||
@JsonKey(name: 'user_count')
|
||||
final int? userCount;
|
||||
@override
|
||||
@JsonKey(name: 'purchase_date')
|
||||
@JsonKey(name: 'purchase_date', toJson: _dateToJson, fromJson: _dateFromJson)
|
||||
final DateTime? purchaseDate;
|
||||
@override
|
||||
@JsonKey(name: 'expiry_date')
|
||||
@JsonKey(name: 'expiry_date', toJson: _dateToJson, fromJson: _dateFromJson)
|
||||
final DateTime? expiryDate;
|
||||
@override
|
||||
@JsonKey(name: 'purchase_price')
|
||||
@@ -368,8 +380,12 @@ abstract class _CreateLicenseRequest implements CreateLicenseRequest {
|
||||
final String? vendor,
|
||||
@JsonKey(name: 'license_type') final String? licenseType,
|
||||
@JsonKey(name: 'user_count') final int? userCount,
|
||||
@JsonKey(name: 'purchase_date') final DateTime? purchaseDate,
|
||||
@JsonKey(name: 'expiry_date') final DateTime? expiryDate,
|
||||
@JsonKey(
|
||||
name: 'purchase_date', toJson: _dateToJson, fromJson: _dateFromJson)
|
||||
final DateTime? purchaseDate,
|
||||
@JsonKey(
|
||||
name: 'expiry_date', toJson: _dateToJson, fromJson: _dateFromJson)
|
||||
final DateTime? expiryDate,
|
||||
@JsonKey(name: 'purchase_price') final double? purchasePrice,
|
||||
@JsonKey(name: 'company_id') final int? companyId,
|
||||
@JsonKey(name: 'branch_id') final int? branchId,
|
||||
@@ -393,10 +409,10 @@ abstract class _CreateLicenseRequest implements CreateLicenseRequest {
|
||||
@JsonKey(name: 'user_count')
|
||||
int? get userCount;
|
||||
@override
|
||||
@JsonKey(name: 'purchase_date')
|
||||
@JsonKey(name: 'purchase_date', toJson: _dateToJson, fromJson: _dateFromJson)
|
||||
DateTime? get purchaseDate;
|
||||
@override
|
||||
@JsonKey(name: 'expiry_date')
|
||||
@JsonKey(name: 'expiry_date', toJson: _dateToJson, fromJson: _dateFromJson)
|
||||
DateTime? get expiryDate;
|
||||
@override
|
||||
@JsonKey(name: 'purchase_price')
|
||||
@@ -433,9 +449,9 @@ mixin _$UpdateLicenseRequest {
|
||||
String? get licenseType => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: 'user_count')
|
||||
int? get userCount => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: 'purchase_date')
|
||||
@JsonKey(name: 'purchase_date', toJson: _dateToJson, fromJson: _dateFromJson)
|
||||
DateTime? get purchaseDate => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: 'expiry_date')
|
||||
@JsonKey(name: 'expiry_date', toJson: _dateToJson, fromJson: _dateFromJson)
|
||||
DateTime? get expiryDate => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: 'purchase_price')
|
||||
double? get purchasePrice => throw _privateConstructorUsedError;
|
||||
@@ -465,8 +481,12 @@ abstract class $UpdateLicenseRequestCopyWith<$Res> {
|
||||
String? vendor,
|
||||
@JsonKey(name: 'license_type') String? licenseType,
|
||||
@JsonKey(name: 'user_count') int? userCount,
|
||||
@JsonKey(name: 'purchase_date') DateTime? purchaseDate,
|
||||
@JsonKey(name: 'expiry_date') DateTime? expiryDate,
|
||||
@JsonKey(
|
||||
name: 'purchase_date', toJson: _dateToJson, fromJson: _dateFromJson)
|
||||
DateTime? purchaseDate,
|
||||
@JsonKey(
|
||||
name: 'expiry_date', toJson: _dateToJson, fromJson: _dateFromJson)
|
||||
DateTime? expiryDate,
|
||||
@JsonKey(name: 'purchase_price') double? purchasePrice,
|
||||
String? remark,
|
||||
@JsonKey(name: 'is_active') bool? isActive});
|
||||
@@ -558,8 +578,12 @@ abstract class _$$UpdateLicenseRequestImplCopyWith<$Res>
|
||||
String? vendor,
|
||||
@JsonKey(name: 'license_type') String? licenseType,
|
||||
@JsonKey(name: 'user_count') int? userCount,
|
||||
@JsonKey(name: 'purchase_date') DateTime? purchaseDate,
|
||||
@JsonKey(name: 'expiry_date') DateTime? expiryDate,
|
||||
@JsonKey(
|
||||
name: 'purchase_date', toJson: _dateToJson, fromJson: _dateFromJson)
|
||||
DateTime? purchaseDate,
|
||||
@JsonKey(
|
||||
name: 'expiry_date', toJson: _dateToJson, fromJson: _dateFromJson)
|
||||
DateTime? expiryDate,
|
||||
@JsonKey(name: 'purchase_price') double? purchasePrice,
|
||||
String? remark,
|
||||
@JsonKey(name: 'is_active') bool? isActive});
|
||||
@@ -643,8 +667,12 @@ class _$UpdateLicenseRequestImpl implements _UpdateLicenseRequest {
|
||||
this.vendor,
|
||||
@JsonKey(name: 'license_type') this.licenseType,
|
||||
@JsonKey(name: 'user_count') this.userCount,
|
||||
@JsonKey(name: 'purchase_date') this.purchaseDate,
|
||||
@JsonKey(name: 'expiry_date') this.expiryDate,
|
||||
@JsonKey(
|
||||
name: 'purchase_date', toJson: _dateToJson, fromJson: _dateFromJson)
|
||||
this.purchaseDate,
|
||||
@JsonKey(
|
||||
name: 'expiry_date', toJson: _dateToJson, fromJson: _dateFromJson)
|
||||
this.expiryDate,
|
||||
@JsonKey(name: 'purchase_price') this.purchasePrice,
|
||||
this.remark,
|
||||
@JsonKey(name: 'is_active') this.isActive});
|
||||
@@ -667,10 +695,10 @@ class _$UpdateLicenseRequestImpl implements _UpdateLicenseRequest {
|
||||
@JsonKey(name: 'user_count')
|
||||
final int? userCount;
|
||||
@override
|
||||
@JsonKey(name: 'purchase_date')
|
||||
@JsonKey(name: 'purchase_date', toJson: _dateToJson, fromJson: _dateFromJson)
|
||||
final DateTime? purchaseDate;
|
||||
@override
|
||||
@JsonKey(name: 'expiry_date')
|
||||
@JsonKey(name: 'expiry_date', toJson: _dateToJson, fromJson: _dateFromJson)
|
||||
final DateTime? expiryDate;
|
||||
@override
|
||||
@JsonKey(name: 'purchase_price')
|
||||
@@ -746,17 +774,21 @@ class _$UpdateLicenseRequestImpl implements _UpdateLicenseRequest {
|
||||
|
||||
abstract class _UpdateLicenseRequest implements UpdateLicenseRequest {
|
||||
const factory _UpdateLicenseRequest(
|
||||
{@JsonKey(name: 'license_key') final String? licenseKey,
|
||||
@JsonKey(name: 'product_name') final String? productName,
|
||||
final String? vendor,
|
||||
@JsonKey(name: 'license_type') final String? licenseType,
|
||||
@JsonKey(name: 'user_count') final int? userCount,
|
||||
@JsonKey(name: 'purchase_date') final DateTime? purchaseDate,
|
||||
@JsonKey(name: 'expiry_date') final DateTime? expiryDate,
|
||||
@JsonKey(name: 'purchase_price') final double? purchasePrice,
|
||||
final String? remark,
|
||||
@JsonKey(name: 'is_active') final bool? isActive}) =
|
||||
_$UpdateLicenseRequestImpl;
|
||||
{@JsonKey(name: 'license_key') final String? licenseKey,
|
||||
@JsonKey(name: 'product_name') final String? productName,
|
||||
final String? vendor,
|
||||
@JsonKey(name: 'license_type') final String? licenseType,
|
||||
@JsonKey(name: 'user_count') final int? userCount,
|
||||
@JsonKey(
|
||||
name: 'purchase_date', toJson: _dateToJson, fromJson: _dateFromJson)
|
||||
final DateTime? purchaseDate,
|
||||
@JsonKey(
|
||||
name: 'expiry_date', toJson: _dateToJson, fromJson: _dateFromJson)
|
||||
final DateTime? expiryDate,
|
||||
@JsonKey(name: 'purchase_price') final double? purchasePrice,
|
||||
final String? remark,
|
||||
@JsonKey(name: 'is_active')
|
||||
final bool? isActive}) = _$UpdateLicenseRequestImpl;
|
||||
|
||||
factory _UpdateLicenseRequest.fromJson(Map<String, dynamic> json) =
|
||||
_$UpdateLicenseRequestImpl.fromJson;
|
||||
@@ -776,10 +808,10 @@ abstract class _UpdateLicenseRequest implements UpdateLicenseRequest {
|
||||
@JsonKey(name: 'user_count')
|
||||
int? get userCount;
|
||||
@override
|
||||
@JsonKey(name: 'purchase_date')
|
||||
@JsonKey(name: 'purchase_date', toJson: _dateToJson, fromJson: _dateFromJson)
|
||||
DateTime? get purchaseDate;
|
||||
@override
|
||||
@JsonKey(name: 'expiry_date')
|
||||
@JsonKey(name: 'expiry_date', toJson: _dateToJson, fromJson: _dateFromJson)
|
||||
DateTime? get expiryDate;
|
||||
@override
|
||||
@JsonKey(name: 'purchase_price')
|
||||
|
||||
@@ -14,12 +14,8 @@ _$CreateLicenseRequestImpl _$$CreateLicenseRequestImplFromJson(
|
||||
vendor: json['vendor'] as String?,
|
||||
licenseType: json['license_type'] as String?,
|
||||
userCount: (json['user_count'] as num?)?.toInt(),
|
||||
purchaseDate: json['purchase_date'] == null
|
||||
? null
|
||||
: DateTime.parse(json['purchase_date'] as String),
|
||||
expiryDate: json['expiry_date'] == null
|
||||
? null
|
||||
: DateTime.parse(json['expiry_date'] as String),
|
||||
purchaseDate: _dateFromJson(json['purchase_date'] as String?),
|
||||
expiryDate: _dateFromJson(json['expiry_date'] as String?),
|
||||
purchasePrice: (json['purchase_price'] as num?)?.toDouble(),
|
||||
companyId: (json['company_id'] as num?)?.toInt(),
|
||||
branchId: (json['branch_id'] as num?)?.toInt(),
|
||||
@@ -34,8 +30,8 @@ Map<String, dynamic> _$$CreateLicenseRequestImplToJson(
|
||||
'vendor': instance.vendor,
|
||||
'license_type': instance.licenseType,
|
||||
'user_count': instance.userCount,
|
||||
'purchase_date': instance.purchaseDate?.toIso8601String(),
|
||||
'expiry_date': instance.expiryDate?.toIso8601String(),
|
||||
'purchase_date': _dateToJson(instance.purchaseDate),
|
||||
'expiry_date': _dateToJson(instance.expiryDate),
|
||||
'purchase_price': instance.purchasePrice,
|
||||
'company_id': instance.companyId,
|
||||
'branch_id': instance.branchId,
|
||||
@@ -50,12 +46,8 @@ _$UpdateLicenseRequestImpl _$$UpdateLicenseRequestImplFromJson(
|
||||
vendor: json['vendor'] as String?,
|
||||
licenseType: json['license_type'] as String?,
|
||||
userCount: (json['user_count'] as num?)?.toInt(),
|
||||
purchaseDate: json['purchase_date'] == null
|
||||
? null
|
||||
: DateTime.parse(json['purchase_date'] as String),
|
||||
expiryDate: json['expiry_date'] == null
|
||||
? null
|
||||
: DateTime.parse(json['expiry_date'] as String),
|
||||
purchaseDate: _dateFromJson(json['purchase_date'] as String?),
|
||||
expiryDate: _dateFromJson(json['expiry_date'] as String?),
|
||||
purchasePrice: (json['purchase_price'] as num?)?.toDouble(),
|
||||
remark: json['remark'] as String?,
|
||||
isActive: json['is_active'] as bool?,
|
||||
@@ -69,8 +61,8 @@ Map<String, dynamic> _$$UpdateLicenseRequestImplToJson(
|
||||
'vendor': instance.vendor,
|
||||
'license_type': instance.licenseType,
|
||||
'user_count': instance.userCount,
|
||||
'purchase_date': instance.purchaseDate?.toIso8601String(),
|
||||
'expiry_date': instance.expiryDate?.toIso8601String(),
|
||||
'purchase_date': _dateToJson(instance.purchaseDate),
|
||||
'expiry_date': _dateToJson(instance.expiryDate),
|
||||
'purchase_price': instance.purchasePrice,
|
||||
'remark': instance.remark,
|
||||
'is_active': instance.isActive,
|
||||
|
||||
@@ -256,10 +256,21 @@ class _CompanyFormScreenState extends State<CompanyFormScreen> {
|
||||
if (mounted) {
|
||||
Navigator.pop(context); // 로딩 다이얼로그 닫기
|
||||
if (success) {
|
||||
// 성공 메시지 표시
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(companyId != null ? '회사 정보가 수정되었습니다.' : '회사가 등록되었습니다.'),
|
||||
backgroundColor: Colors.green,
|
||||
),
|
||||
);
|
||||
// 리스트 화면으로 돌아가기
|
||||
Navigator.pop(context, true);
|
||||
} else {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('회사 저장에 실패했습니다.')),
|
||||
const SnackBar(
|
||||
content: Text('회사 저장에 실패했습니다.'),
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -267,7 +278,10 @@ class _CompanyFormScreenState extends State<CompanyFormScreen> {
|
||||
if (mounted) {
|
||||
Navigator.pop(context); // 로딩 다이얼로그 닫기
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('오류가 발생했습니다: $e')),
|
||||
SnackBar(
|
||||
content: Text('오류가 발생했습니다: $e'),
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -506,7 +506,17 @@ class _ContactInfoWidgetState extends State<ContactInfoWidget> {
|
||||
keyboardType: TextInputType.phone,
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.digitsOnly,
|
||||
PhoneUtils.phoneInputFormatter,
|
||||
// 접두사에 따른 동적 포맷팅
|
||||
TextInputFormatter.withFunction((oldValue, newValue) {
|
||||
final formatted = PhoneUtils.formatPhoneNumberByPrefix(
|
||||
widget.selectedPhonePrefix,
|
||||
newValue.text,
|
||||
);
|
||||
return TextEditingValue(
|
||||
text: formatted,
|
||||
selection: TextSelection.collapsed(offset: formatted.length),
|
||||
);
|
||||
}),
|
||||
],
|
||||
onTap: () {
|
||||
developer.log('전화번호 필드 터치됨', name: 'ContactInfoWidget');
|
||||
@@ -666,7 +676,17 @@ class _ContactInfoWidgetState extends State<ContactInfoWidget> {
|
||||
),
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.digitsOnly,
|
||||
PhoneUtils.phoneInputFormatter,
|
||||
// 접두사에 따른 동적 포맷팅
|
||||
TextInputFormatter.withFunction((oldValue, newValue) {
|
||||
final formatted = PhoneUtils.formatPhoneNumberByPrefix(
|
||||
widget.selectedPhonePrefix,
|
||||
newValue.text,
|
||||
);
|
||||
return TextEditingValue(
|
||||
text: formatted,
|
||||
selection: TextSelection.collapsed(offset: formatted.length),
|
||||
);
|
||||
}),
|
||||
],
|
||||
keyboardType: TextInputType.phone,
|
||||
onTap: _closeAllDropdowns,
|
||||
|
||||
@@ -4,8 +4,11 @@ import 'package:superport/models/equipment_unified_model.dart';
|
||||
import 'package:superport/models/company_model.dart';
|
||||
import 'package:superport/services/equipment_service.dart';
|
||||
import 'package:superport/services/mock_data_service.dart';
|
||||
import 'package:superport/services/warehouse_service.dart';
|
||||
import 'package:superport/services/company_service.dart';
|
||||
import 'package:superport/utils/constants.dart';
|
||||
import 'package:superport/core/errors/failures.dart';
|
||||
import 'package:superport/core/utils/debug_logger.dart';
|
||||
|
||||
/// 장비 입고 폼 컨트롤러
|
||||
///
|
||||
@@ -13,6 +16,8 @@ import 'package:superport/core/errors/failures.dart';
|
||||
class EquipmentInFormController extends ChangeNotifier {
|
||||
final MockDataService dataService;
|
||||
final EquipmentService _equipmentService = GetIt.instance<EquipmentService>();
|
||||
final WarehouseService _warehouseService = GetIt.instance<WarehouseService>();
|
||||
final CompanyService _companyService = GetIt.instance<CompanyService>();
|
||||
final int? equipmentInId;
|
||||
|
||||
bool _isLoading = false;
|
||||
@@ -55,6 +60,9 @@ class EquipmentInFormController extends ChangeNotifier {
|
||||
List<String> categories = [];
|
||||
List<String> subCategories = [];
|
||||
List<String> subSubCategories = [];
|
||||
|
||||
// 창고 위치 전체 데이터 (이름-ID 매핑용)
|
||||
Map<String, int> warehouseLocationMap = {};
|
||||
|
||||
// 편집 모드 여부
|
||||
bool isEditMode = false;
|
||||
@@ -108,19 +116,66 @@ class EquipmentInFormController extends ChangeNotifier {
|
||||
}
|
||||
|
||||
// 입고지 목록 로드
|
||||
void _loadWarehouseLocations() {
|
||||
warehouseLocations =
|
||||
dataService.getAllWarehouseLocations().map((e) => e.name).toList();
|
||||
void _loadWarehouseLocations() async {
|
||||
if (_useApi) {
|
||||
try {
|
||||
DebugLogger.log('입고지 목록 API 로드 시작', tag: 'EQUIPMENT_IN');
|
||||
final locations = await _warehouseService.getWarehouseLocations();
|
||||
warehouseLocations = locations.map((e) => e.name).toList();
|
||||
// 이름-ID 매핑 저장
|
||||
warehouseLocationMap = {for (var loc in locations) loc.name: loc.id};
|
||||
DebugLogger.log('입고지 목록 로드 성공', tag: 'EQUIPMENT_IN', data: {
|
||||
'count': warehouseLocations.length,
|
||||
'locations': warehouseLocations,
|
||||
'locationMap': warehouseLocationMap,
|
||||
});
|
||||
notifyListeners();
|
||||
} catch (e) {
|
||||
DebugLogger.logError('입고지 목록 로드 실패', error: e);
|
||||
// 실패 시 Mock 데이터 사용
|
||||
final mockLocations = dataService.getAllWarehouseLocations();
|
||||
warehouseLocations = mockLocations.map((e) => e.name).toList();
|
||||
warehouseLocationMap = {for (var loc in mockLocations) loc.name: loc.id};
|
||||
notifyListeners();
|
||||
}
|
||||
} else {
|
||||
final mockLocations = dataService.getAllWarehouseLocations();
|
||||
warehouseLocations = mockLocations.map((e) => e.name).toList();
|
||||
warehouseLocationMap = {for (var loc in mockLocations) loc.name: loc.id};
|
||||
}
|
||||
}
|
||||
|
||||
// 파트너사 목록 로드
|
||||
void _loadPartnerCompanies() {
|
||||
partnerCompanies =
|
||||
dataService
|
||||
.getAllCompanies()
|
||||
.where((c) => c.companyTypes.contains(CompanyType.partner))
|
||||
.map((c) => c.name)
|
||||
.toList();
|
||||
void _loadPartnerCompanies() async {
|
||||
if (_useApi) {
|
||||
try {
|
||||
DebugLogger.log('파트너사 목록 API 로드 시작', tag: 'EQUIPMENT_IN');
|
||||
final companies = await _companyService.getCompanies();
|
||||
partnerCompanies = companies.map((c) => c.name).toList();
|
||||
DebugLogger.log('파트너사 목록 로드 성공', tag: 'EQUIPMENT_IN', data: {
|
||||
'count': partnerCompanies.length,
|
||||
'companies': partnerCompanies,
|
||||
});
|
||||
notifyListeners();
|
||||
} catch (e) {
|
||||
DebugLogger.logError('파트너사 목록 로드 실패', error: e);
|
||||
// 실패 시 Mock 데이터 사용
|
||||
partnerCompanies =
|
||||
dataService
|
||||
.getAllCompanies()
|
||||
.where((c) => c.companyTypes.contains(CompanyType.partner))
|
||||
.map((c) => c.name)
|
||||
.toList();
|
||||
notifyListeners();
|
||||
}
|
||||
} else {
|
||||
partnerCompanies =
|
||||
dataService
|
||||
.getAllCompanies()
|
||||
.where((c) => c.companyTypes.contains(CompanyType.partner))
|
||||
.map((c) => c.name)
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
// 워런티 라이센스 목록 로드
|
||||
@@ -304,30 +359,50 @@ class EquipmentInFormController extends ChangeNotifier {
|
||||
await _equipmentService.updateEquipment(equipmentInId!, equipment);
|
||||
} else {
|
||||
// 생성 모드
|
||||
// 1. 먼저 장비 생성
|
||||
final createdEquipment = await _equipmentService.createEquipment(equipment);
|
||||
|
||||
// 2. 입고 처리 (warehouse location ID 필요)
|
||||
int? warehouseLocationId;
|
||||
if (warehouseLocation != null) {
|
||||
// TODO: 창고 위치 ID 가져오기 - 현재는 목 데이터에서 찾기
|
||||
try {
|
||||
final warehouse = dataService.getAllWarehouseLocations().firstWhere(
|
||||
(w) => w.name == warehouseLocation,
|
||||
);
|
||||
warehouseLocationId = warehouse.id;
|
||||
} catch (e) {
|
||||
// 창고를 찾을 수 없는 경우
|
||||
warehouseLocationId = null;
|
||||
try {
|
||||
// 1. 먼저 장비 생성
|
||||
DebugLogger.log('장비 생성 시작', tag: 'EQUIPMENT_IN', data: {
|
||||
'manufacturer': manufacturer,
|
||||
'name': name,
|
||||
'serialNumber': serialNumber,
|
||||
});
|
||||
|
||||
final createdEquipment = await _equipmentService.createEquipment(equipment);
|
||||
|
||||
DebugLogger.log('장비 생성 성공', tag: 'EQUIPMENT_IN', data: {
|
||||
'equipmentId': createdEquipment.id,
|
||||
});
|
||||
|
||||
// 2. 입고 처리 (warehouse location ID 필요)
|
||||
int? warehouseLocationId;
|
||||
if (warehouseLocation != null) {
|
||||
// 저장된 매핑에서 ID 가져오기
|
||||
warehouseLocationId = warehouseLocationMap[warehouseLocation];
|
||||
|
||||
if (warehouseLocationId == null) {
|
||||
DebugLogger.logError('창고 위치 ID를 찾을 수 없음', error: 'Warehouse: $warehouseLocation');
|
||||
}
|
||||
}
|
||||
|
||||
DebugLogger.log('입고 처리 시작', tag: 'EQUIPMENT_IN', data: {
|
||||
'equipmentId': createdEquipment.id,
|
||||
'quantity': quantity,
|
||||
'warehouseLocationId': warehouseLocationId,
|
||||
});
|
||||
|
||||
await _equipmentService.equipmentIn(
|
||||
equipmentId: createdEquipment.id!,
|
||||
quantity: quantity,
|
||||
warehouseLocationId: warehouseLocationId,
|
||||
notes: remarkController.text.trim(),
|
||||
);
|
||||
|
||||
DebugLogger.log('입고 처리 성공', tag: 'EQUIPMENT_IN');
|
||||
|
||||
} catch (e) {
|
||||
DebugLogger.logError('장비 입고 처리 실패', error: e);
|
||||
throw e; // 에러를 상위로 전파하여 적절한 에러 메시지 표시
|
||||
}
|
||||
|
||||
await _equipmentService.equipmentIn(
|
||||
equipmentId: createdEquipment.id!,
|
||||
quantity: quantity,
|
||||
warehouseLocationId: warehouseLocationId,
|
||||
notes: remarkController.text.trim(),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// Mock 데이터 사용
|
||||
|
||||
@@ -22,6 +22,7 @@ class EquipmentListController extends ChangeNotifier {
|
||||
|
||||
List<UnifiedEquipment> equipments = [];
|
||||
String? selectedStatusFilter;
|
||||
String searchKeyword = ''; // 검색어 추가
|
||||
final Set<String> selectedEquipmentIds = {}; // 'id:status' 형식
|
||||
|
||||
bool _isLoading = false;
|
||||
@@ -42,7 +43,7 @@ class EquipmentListController extends ChangeNotifier {
|
||||
EquipmentListController({required this.dataService});
|
||||
|
||||
// 데이터 로드 및 상태 필터 적용
|
||||
Future<void> loadData({bool isRefresh = false}) async {
|
||||
Future<void> loadData({bool isRefresh = false, String? search}) async {
|
||||
if (isRefresh) {
|
||||
_currentPage = 1;
|
||||
_hasMore = true;
|
||||
@@ -69,6 +70,7 @@ class EquipmentListController extends ChangeNotifier {
|
||||
page: _currentPage,
|
||||
perPage: _perPage,
|
||||
status: selectedStatusFilter != null ? EquipmentStatusConverter.clientToServer(selectedStatusFilter) : null,
|
||||
search: search ?? searchKeyword,
|
||||
);
|
||||
|
||||
DebugLogger.log('장비 목록 API 응답', tag: 'EQUIPMENT', data: {
|
||||
@@ -137,6 +139,12 @@ class EquipmentListController extends ChangeNotifier {
|
||||
selectedStatusFilter = status;
|
||||
await loadData(isRefresh: true);
|
||||
}
|
||||
|
||||
// 검색어 변경
|
||||
Future<void> updateSearchKeyword(String keyword) async {
|
||||
searchKeyword = keyword;
|
||||
await loadData(isRefresh: true, search: keyword);
|
||||
}
|
||||
|
||||
// 장비 선택/해제 (모든 상태 지원)
|
||||
void selectEquipment(int? id, String status, bool? isSelected) {
|
||||
|
||||
@@ -116,11 +116,12 @@ class _EquipmentListRedesignState extends State<EquipmentListRedesign> {
|
||||
}
|
||||
|
||||
/// 검색 실행
|
||||
void _onSearch() {
|
||||
void _onSearch() async {
|
||||
setState(() {
|
||||
_appliedSearchKeyword = _searchController.text;
|
||||
_currentPage = 1;
|
||||
});
|
||||
await _controller.updateSearchKeyword(_searchController.text);
|
||||
}
|
||||
|
||||
/// 장비 선택/해제
|
||||
|
||||
@@ -6,6 +6,15 @@ import 'package:superport/models/license_model.dart';
|
||||
import 'package:superport/services/license_service.dart';
|
||||
import 'package:superport/services/mock_data_service.dart';
|
||||
|
||||
// 라이센스 상태 필터
|
||||
enum LicenseStatusFilter {
|
||||
all,
|
||||
active,
|
||||
inactive,
|
||||
expiringSoon, // 30일 이내
|
||||
expired,
|
||||
}
|
||||
|
||||
// 라이센스 목록 화면의 상태 및 비즈니스 로직을 담당하는 컨트롤러
|
||||
class LicenseListController extends ChangeNotifier {
|
||||
final bool useApi;
|
||||
@@ -26,9 +35,22 @@ class LicenseListController extends ChangeNotifier {
|
||||
int? _selectedCompanyId;
|
||||
bool? _isActive;
|
||||
String? _licenseType;
|
||||
LicenseStatusFilter _statusFilter = LicenseStatusFilter.all;
|
||||
String _sortBy = 'expiry_date';
|
||||
String _sortOrder = 'asc';
|
||||
|
||||
// 선택된 라이선스 관리
|
||||
final Set<int> _selectedLicenseIds = {};
|
||||
|
||||
// 통계 데이터
|
||||
Map<String, int> _statistics = {
|
||||
'total': 0,
|
||||
'active': 0,
|
||||
'inactive': 0,
|
||||
'expiringSoon': 0,
|
||||
'expired': 0,
|
||||
};
|
||||
|
||||
// 검색 디바운스를 위한 타이머
|
||||
Timer? _debounceTimer;
|
||||
|
||||
@@ -49,6 +71,18 @@ class LicenseListController extends ChangeNotifier {
|
||||
int? get selectedCompanyId => _selectedCompanyId;
|
||||
bool? get isActive => _isActive;
|
||||
String? get licenseType => _licenseType;
|
||||
LicenseStatusFilter get statusFilter => _statusFilter;
|
||||
Set<int> get selectedLicenseIds => _selectedLicenseIds;
|
||||
Map<String, int> get statistics => _statistics;
|
||||
|
||||
// 선택된 라이선스 개수
|
||||
int get selectedCount => _selectedLicenseIds.length;
|
||||
|
||||
// 전체 선택 여부 확인
|
||||
bool get isAllSelected =>
|
||||
_filteredLicenses.isNotEmpty &&
|
||||
_filteredLicenses.where((l) => l.id != null)
|
||||
.every((l) => _selectedLicenseIds.contains(l.id));
|
||||
|
||||
// 데이터 로드
|
||||
Future<void> loadData({bool isInitialLoad = true}) async {
|
||||
@@ -67,6 +101,8 @@ class LicenseListController extends ChangeNotifier {
|
||||
|
||||
try {
|
||||
if (useApi && GetIt.instance.isRegistered<LicenseService>()) {
|
||||
debugPrint('📑 API 모드로 라이센스 로드 시작...');
|
||||
|
||||
// API 사용
|
||||
final fetchedLicenses = await _licenseService.getLicenses(
|
||||
page: _currentPage,
|
||||
@@ -75,21 +111,27 @@ class LicenseListController extends ChangeNotifier {
|
||||
companyId: _selectedCompanyId,
|
||||
licenseType: _licenseType,
|
||||
);
|
||||
|
||||
debugPrint('📑 API에서 ${fetchedLicenses.length}개 라이센스 받음');
|
||||
|
||||
if (isInitialLoad) {
|
||||
_licenses = fetchedLicenses;
|
||||
debugPrint('📑 초기 로드: _licenses에 ${_licenses.length}개 저장');
|
||||
} else {
|
||||
_licenses.addAll(fetchedLicenses);
|
||||
debugPrint('📑 추가 로드: _licenses에 총 ${_licenses.length}개');
|
||||
}
|
||||
|
||||
_hasMore = fetchedLicenses.length >= _pageSize;
|
||||
|
||||
// 전체 개수 조회
|
||||
debugPrint('📑 전체 개수 조회 시작...');
|
||||
_total = await _licenseService.getTotalLicenses(
|
||||
isActive: _isActive,
|
||||
companyId: _selectedCompanyId,
|
||||
licenseType: _licenseType,
|
||||
);
|
||||
debugPrint('📑 전체 개수: $_total');
|
||||
} else {
|
||||
// Mock 데이터 사용
|
||||
final allLicenses = mockDataService?.getAllLicenses() ?? [];
|
||||
@@ -124,11 +166,17 @@ class LicenseListController extends ChangeNotifier {
|
||||
_total = filtered.length;
|
||||
}
|
||||
|
||||
debugPrint('📑 _applySearchFilter 호출 전: _licenses=${_licenses.length}개');
|
||||
_applySearchFilter();
|
||||
_applyStatusFilter();
|
||||
await _updateStatistics();
|
||||
debugPrint('📑 _applySearchFilter 호출 후: _filteredLicenses=${_filteredLicenses.length}개');
|
||||
} catch (e) {
|
||||
debugPrint('❌ loadData 에러 발생: $e');
|
||||
_error = e.toString();
|
||||
} finally {
|
||||
_isLoading = false;
|
||||
debugPrint('📑 loadData 종료: _filteredLicenses=${_filteredLicenses.length}개');
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
@@ -162,21 +210,56 @@ class LicenseListController extends ChangeNotifier {
|
||||
|
||||
// 검색 필터 적용
|
||||
void _applySearchFilter() {
|
||||
debugPrint('🔎 _applySearchFilter 시작: _searchQuery="$_searchQuery", _licenses=${_licenses.length}개');
|
||||
|
||||
if (_searchQuery.isEmpty) {
|
||||
_filteredLicenses = List.from(_licenses);
|
||||
debugPrint('🔎 검색어 없음: 전체 복사 ${_filteredLicenses.length}개');
|
||||
} else {
|
||||
_filteredLicenses = _licenses.where((license) {
|
||||
final productName = license.productName?.toLowerCase() ?? '';
|
||||
final licenseKey = license.licenseKey.toLowerCase();
|
||||
final vendor = license.vendor?.toLowerCase() ?? '';
|
||||
final companyName = license.companyName?.toLowerCase() ?? '';
|
||||
final searchLower = _searchQuery.toLowerCase();
|
||||
|
||||
return productName.contains(searchLower) ||
|
||||
licenseKey.contains(searchLower) ||
|
||||
vendor.contains(searchLower);
|
||||
vendor.contains(searchLower) ||
|
||||
companyName.contains(searchLower);
|
||||
}).toList();
|
||||
debugPrint('🔎 검색 필터링 완료: ${_filteredLicenses.length}개');
|
||||
}
|
||||
}
|
||||
|
||||
// 상태 필터 적용
|
||||
void _applyStatusFilter() {
|
||||
if (_statusFilter == LicenseStatusFilter.all) return;
|
||||
|
||||
final now = DateTime.now();
|
||||
_filteredLicenses = _filteredLicenses.where((license) {
|
||||
switch (_statusFilter) {
|
||||
case LicenseStatusFilter.active:
|
||||
return license.isActive;
|
||||
case LicenseStatusFilter.inactive:
|
||||
return !license.isActive;
|
||||
case LicenseStatusFilter.expiringSoon:
|
||||
if (license.expiryDate != null) {
|
||||
final days = license.expiryDate!.difference(now).inDays;
|
||||
return days > 0 && days <= 30;
|
||||
}
|
||||
return false;
|
||||
case LicenseStatusFilter.expired:
|
||||
if (license.expiryDate != null) {
|
||||
return license.expiryDate!.isBefore(now);
|
||||
}
|
||||
return false;
|
||||
case LicenseStatusFilter.all:
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}).toList();
|
||||
}
|
||||
|
||||
// 필터 설정
|
||||
void setFilters({
|
||||
@@ -309,6 +392,162 @@ class LicenseListController extends ChangeNotifier {
|
||||
loadData();
|
||||
}
|
||||
|
||||
// 상태 필터 변경
|
||||
Future<void> changeStatusFilter(LicenseStatusFilter filter) async {
|
||||
_statusFilter = filter;
|
||||
await loadData();
|
||||
}
|
||||
|
||||
// 라이선스 선택/해제
|
||||
void selectLicense(int? id, bool? isSelected) {
|
||||
if (id == null) return;
|
||||
|
||||
if (isSelected == true) {
|
||||
_selectedLicenseIds.add(id);
|
||||
} else {
|
||||
_selectedLicenseIds.remove(id);
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// 전체 선택/해제
|
||||
void selectAll(bool? isSelected) {
|
||||
if (isSelected == true) {
|
||||
// 현재 필터링된 라이선스 모두 선택
|
||||
for (var license in _filteredLicenses) {
|
||||
if (license.id != null) {
|
||||
_selectedLicenseIds.add(license.id!);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 모두 해제
|
||||
_selectedLicenseIds.clear();
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// 선택된 라이선스 목록 반환
|
||||
List<License> getSelectedLicenses() {
|
||||
return _filteredLicenses
|
||||
.where((l) => l.id != null && _selectedLicenseIds.contains(l.id))
|
||||
.toList();
|
||||
}
|
||||
|
||||
// 선택 초기화
|
||||
void clearSelection() {
|
||||
_selectedLicenseIds.clear();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// 라이선스 할당
|
||||
Future<bool> assignLicense(int licenseId, int userId) async {
|
||||
try {
|
||||
if (useApi && GetIt.instance.isRegistered<LicenseService>()) {
|
||||
await _licenseService.assignLicense(licenseId, userId);
|
||||
await loadData();
|
||||
clearSelection();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} catch (e) {
|
||||
_error = e.toString();
|
||||
notifyListeners();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 라이선스 할당 해제
|
||||
Future<bool> unassignLicense(int licenseId) async {
|
||||
try {
|
||||
if (useApi && GetIt.instance.isRegistered<LicenseService>()) {
|
||||
await _licenseService.unassignLicense(licenseId);
|
||||
await loadData();
|
||||
clearSelection();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} catch (e) {
|
||||
_error = e.toString();
|
||||
notifyListeners();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 선택된 라이선스 일괄 삭제
|
||||
Future<void> deleteSelectedLicenses() async {
|
||||
if (_selectedLicenseIds.isEmpty) return;
|
||||
|
||||
final selectedIds = List<int>.from(_selectedLicenseIds);
|
||||
int successCount = 0;
|
||||
int failCount = 0;
|
||||
|
||||
for (var id in selectedIds) {
|
||||
try {
|
||||
await deleteLicense(id);
|
||||
successCount++;
|
||||
} catch (e) {
|
||||
failCount++;
|
||||
debugPrint('라이선스 $id 삭제 실패: $e');
|
||||
}
|
||||
}
|
||||
|
||||
_selectedLicenseIds.clear();
|
||||
await loadData();
|
||||
|
||||
if (successCount > 0) {
|
||||
debugPrint('✅ $successCount개 라이선스 삭제 완료');
|
||||
}
|
||||
if (failCount > 0) {
|
||||
debugPrint('❌ $failCount개 라이선스 삭제 실패');
|
||||
}
|
||||
}
|
||||
|
||||
// 통계 업데이트
|
||||
Future<void> _updateStatistics() async {
|
||||
try {
|
||||
final counts = await getLicenseStatusCounts();
|
||||
|
||||
final now = DateTime.now();
|
||||
int expiringSoonCount = 0;
|
||||
int expiredCount = 0;
|
||||
|
||||
for (var license in _licenses) {
|
||||
if (license.expiryDate != null) {
|
||||
final days = license.expiryDate!.difference(now).inDays;
|
||||
if (days <= 0) {
|
||||
expiredCount++;
|
||||
} else if (days <= 30) {
|
||||
expiringSoonCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_statistics = {
|
||||
'total': counts['total'] ?? 0,
|
||||
'active': counts['active'] ?? 0,
|
||||
'inactive': counts['inactive'] ?? 0,
|
||||
'expiringSoon': expiringSoonCount,
|
||||
'expired': expiredCount,
|
||||
};
|
||||
} catch (e) {
|
||||
debugPrint('❌ 통계 업데이트 오류: $e');
|
||||
// 오류 발생 시 기본값 사용
|
||||
_statistics = {
|
||||
'total': _licenses.length,
|
||||
'active': 0,
|
||||
'inactive': 0,
|
||||
'expiringSoon': 0,
|
||||
'expired': 0,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 만료일까지 남은 일수 계산
|
||||
int? getDaysUntilExpiry(License license) {
|
||||
if (license.expiryDate == null) return null;
|
||||
return license.expiryDate!.difference(DateTime.now()).inDays;
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_debounceTimer?.cancel();
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -52,12 +52,17 @@ class OverviewController extends ChangeNotifier {
|
||||
|
||||
// 데이터 로드
|
||||
Future<void> loadData() async {
|
||||
await Future.wait([
|
||||
_loadOverviewStats(),
|
||||
_loadRecentActivities(),
|
||||
_loadEquipmentStatus(),
|
||||
_loadExpiringLicenses(),
|
||||
]);
|
||||
try {
|
||||
await Future.wait([
|
||||
_loadOverviewStats(),
|
||||
_loadRecentActivities(),
|
||||
_loadEquipmentStatus(),
|
||||
_loadExpiringLicenses(),
|
||||
], eagerError: false); // 하나의 작업이 실패해도 다른 작업 계속 진행
|
||||
} catch (e) {
|
||||
DebugLogger.logError('대시보드 데이터 로드 중 오류', error: e);
|
||||
// 개별 에러는 각 메서드에서 처리하므로 여기서는 로그만 남김
|
||||
}
|
||||
}
|
||||
|
||||
// 대시보드 데이터 로드 (loadData의 alias)
|
||||
@@ -71,16 +76,55 @@ class OverviewController extends ChangeNotifier {
|
||||
_statsError = null;
|
||||
notifyListeners();
|
||||
|
||||
final result = await _dashboardService.getOverviewStats();
|
||||
|
||||
result.fold(
|
||||
(failure) {
|
||||
_statsError = failure.message;
|
||||
},
|
||||
(stats) {
|
||||
_overviewStats = stats;
|
||||
},
|
||||
);
|
||||
try {
|
||||
final result = await _dashboardService.getOverviewStats();
|
||||
|
||||
result.fold(
|
||||
(failure) {
|
||||
_statsError = failure.message;
|
||||
DebugLogger.logError('Overview 통계 로드 실패', error: failure.message);
|
||||
// 실패 시 기본값 설정
|
||||
_overviewStats = OverviewStats(
|
||||
totalCompanies: 0,
|
||||
activeCompanies: 0,
|
||||
totalUsers: 0,
|
||||
activeUsers: 0,
|
||||
totalEquipment: 0,
|
||||
availableEquipment: 0,
|
||||
inUseEquipment: 0,
|
||||
maintenanceEquipment: 0,
|
||||
totalLicenses: 0,
|
||||
activeLicenses: 0,
|
||||
expiringLicensesCount: 0,
|
||||
expiredLicensesCount: 0,
|
||||
totalWarehouseLocations: 0,
|
||||
activeWarehouseLocations: 0,
|
||||
);
|
||||
},
|
||||
(stats) {
|
||||
_overviewStats = stats;
|
||||
},
|
||||
);
|
||||
} catch (e) {
|
||||
_statsError = '통계 데이터를 불러올 수 없습니다';
|
||||
_overviewStats = OverviewStats(
|
||||
totalCompanies: 0,
|
||||
activeCompanies: 0,
|
||||
totalUsers: 0,
|
||||
activeUsers: 0,
|
||||
totalEquipment: 0,
|
||||
availableEquipment: 0,
|
||||
inUseEquipment: 0,
|
||||
maintenanceEquipment: 0,
|
||||
totalLicenses: 0,
|
||||
activeLicenses: 0,
|
||||
expiringLicensesCount: 0,
|
||||
expiredLicensesCount: 0,
|
||||
totalWarehouseLocations: 0,
|
||||
activeWarehouseLocations: 0,
|
||||
);
|
||||
DebugLogger.logError('Overview 통계 로드 예외', error: e);
|
||||
}
|
||||
|
||||
_isLoadingStats = false;
|
||||
notifyListeners();
|
||||
@@ -91,16 +135,24 @@ class OverviewController extends ChangeNotifier {
|
||||
_activitiesError = null;
|
||||
notifyListeners();
|
||||
|
||||
final result = await _dashboardService.getRecentActivities();
|
||||
|
||||
result.fold(
|
||||
(failure) {
|
||||
_activitiesError = failure.message;
|
||||
},
|
||||
(activities) {
|
||||
_recentActivities = activities;
|
||||
},
|
||||
);
|
||||
try {
|
||||
final result = await _dashboardService.getRecentActivities();
|
||||
|
||||
result.fold(
|
||||
(failure) {
|
||||
_activitiesError = failure.message;
|
||||
_recentActivities = []; // 실패 시 빈 리스트
|
||||
DebugLogger.logError('최근 활동 로드 실패', error: failure.message);
|
||||
},
|
||||
(activities) {
|
||||
_recentActivities = activities ?? [];
|
||||
},
|
||||
);
|
||||
} catch (e) {
|
||||
_activitiesError = '최근 활동을 불러올 수 없습니다';
|
||||
_recentActivities = [];
|
||||
DebugLogger.logError('최근 활동 로드 예외', error: e);
|
||||
}
|
||||
|
||||
_isLoadingActivities = false;
|
||||
notifyListeners();
|
||||
@@ -113,23 +165,41 @@ class OverviewController extends ChangeNotifier {
|
||||
|
||||
DebugLogger.log('장비 상태 분포 로드 시작', tag: 'DASHBOARD');
|
||||
|
||||
final result = await _dashboardService.getEquipmentStatusDistribution();
|
||||
|
||||
result.fold(
|
||||
(failure) {
|
||||
_equipmentStatusError = failure.message;
|
||||
DebugLogger.logError('장비 상태 분포 로드 실패', error: failure.message);
|
||||
},
|
||||
(status) {
|
||||
_equipmentStatus = status;
|
||||
DebugLogger.log('장비 상태 분포 로드 성공', tag: 'DASHBOARD', data: {
|
||||
'available': status.available,
|
||||
'inUse': status.inUse,
|
||||
'maintenance': status.maintenance,
|
||||
'disposed': status.disposed,
|
||||
});
|
||||
},
|
||||
);
|
||||
try {
|
||||
final result = await _dashboardService.getEquipmentStatusDistribution();
|
||||
|
||||
result.fold(
|
||||
(failure) {
|
||||
_equipmentStatusError = failure.message;
|
||||
DebugLogger.logError('장비 상태 분포 로드 실패', error: failure.message);
|
||||
// 실패 시 기본값 설정
|
||||
_equipmentStatus = EquipmentStatusDistribution(
|
||||
available: 0,
|
||||
inUse: 0,
|
||||
maintenance: 0,
|
||||
disposed: 0,
|
||||
);
|
||||
},
|
||||
(status) {
|
||||
_equipmentStatus = status;
|
||||
DebugLogger.log('장비 상태 분포 로드 성공', tag: 'DASHBOARD', data: {
|
||||
'available': status.available,
|
||||
'inUse': status.inUse,
|
||||
'maintenance': status.maintenance,
|
||||
'disposed': status.disposed,
|
||||
});
|
||||
},
|
||||
);
|
||||
} catch (e) {
|
||||
_equipmentStatusError = '장비 상태를 불러올 수 없습니다';
|
||||
_equipmentStatus = EquipmentStatusDistribution(
|
||||
available: 0,
|
||||
inUse: 0,
|
||||
maintenance: 0,
|
||||
disposed: 0,
|
||||
);
|
||||
DebugLogger.logError('장비 상태 로드 예외', error: e);
|
||||
}
|
||||
|
||||
_isLoadingEquipmentStatus = false;
|
||||
notifyListeners();
|
||||
@@ -140,16 +210,24 @@ class OverviewController extends ChangeNotifier {
|
||||
_licensesError = null;
|
||||
notifyListeners();
|
||||
|
||||
final result = await _dashboardService.getExpiringLicenses(days: 30);
|
||||
|
||||
result.fold(
|
||||
(failure) {
|
||||
_licensesError = failure.message;
|
||||
},
|
||||
(licenses) {
|
||||
_expiringLicenses = licenses;
|
||||
},
|
||||
);
|
||||
try {
|
||||
final result = await _dashboardService.getExpiringLicenses(days: 30);
|
||||
|
||||
result.fold(
|
||||
(failure) {
|
||||
_licensesError = failure.message;
|
||||
_expiringLicenses = []; // 실패 시 빈 리스트
|
||||
DebugLogger.logError('만료 라이선스 로드 실패', error: failure.message);
|
||||
},
|
||||
(licenses) {
|
||||
_expiringLicenses = licenses ?? [];
|
||||
},
|
||||
);
|
||||
} catch (e) {
|
||||
_licensesError = '라이선스 정보를 불러올 수 없습니다';
|
||||
_expiringLicenses = [];
|
||||
DebugLogger.logError('만료 라이선스 로드 예외', error: e);
|
||||
}
|
||||
|
||||
_isLoadingLicenses = false;
|
||||
notifyListeners();
|
||||
|
||||
@@ -125,13 +125,13 @@ class _OverviewScreenRedesignState extends State<OverviewScreenRedesign> {
|
||||
),
|
||||
_buildStatCard(
|
||||
'입고 장비',
|
||||
'${_controller.overviewStats?.availableEquipment ?? 0}',
|
||||
'${_controller.equipmentStatus?.available ?? 0}',
|
||||
Icons.inventory,
|
||||
ShadcnTheme.success,
|
||||
),
|
||||
_buildStatCard(
|
||||
'출고 장비',
|
||||
'${_controller.overviewStats?.inUseEquipment ?? 0}',
|
||||
'${_controller.equipmentStatus?.inUse ?? 0}',
|
||||
Icons.local_shipping,
|
||||
ShadcnTheme.warning,
|
||||
),
|
||||
@@ -300,7 +300,7 @@ class _OverviewScreenRedesignState extends State<OverviewScreenRedesign> {
|
||||
const SizedBox(height: 16),
|
||||
Consumer<OverviewController>(
|
||||
builder: (context, controller, child) {
|
||||
final activities = controller.recentActivities ?? [];
|
||||
final activities = controller.recentActivities;
|
||||
if (activities.isEmpty) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 20),
|
||||
@@ -435,15 +435,19 @@ class _OverviewScreenRedesignState extends State<OverviewScreenRedesign> {
|
||||
|
||||
Widget _buildActivityItem(dynamic activity) {
|
||||
// 아이콘 매핑
|
||||
IconData getActivityIcon(String type) {
|
||||
switch (type) {
|
||||
IconData getActivityIcon(String? type) {
|
||||
switch (type?.toLowerCase()) {
|
||||
case 'equipment_in':
|
||||
case '장비 입고':
|
||||
return Icons.inventory;
|
||||
case 'equipment_out':
|
||||
case '장비 출고':
|
||||
return Icons.local_shipping;
|
||||
case 'company':
|
||||
case '회사':
|
||||
return Icons.business;
|
||||
case 'user':
|
||||
case '사용자':
|
||||
return Icons.person_add;
|
||||
default:
|
||||
return Icons.settings;
|
||||
@@ -451,23 +455,31 @@ class _OverviewScreenRedesignState extends State<OverviewScreenRedesign> {
|
||||
}
|
||||
|
||||
// 색상 매핑
|
||||
Color getActivityColor(String type) {
|
||||
switch (type) {
|
||||
Color getActivityColor(String? type) {
|
||||
switch (type?.toLowerCase()) {
|
||||
case 'equipment_in':
|
||||
case '장비 입고':
|
||||
return ShadcnTheme.success;
|
||||
case 'equipment_out':
|
||||
case '장비 출고':
|
||||
return ShadcnTheme.warning;
|
||||
case 'company':
|
||||
case '회사':
|
||||
return ShadcnTheme.info;
|
||||
case 'user':
|
||||
case '사용자':
|
||||
return ShadcnTheme.primary;
|
||||
default:
|
||||
return ShadcnTheme.mutedForeground;
|
||||
}
|
||||
}
|
||||
|
||||
final color = getActivityColor(activity.activityType);
|
||||
final activityType = activity.activityType ?? '';
|
||||
final color = getActivityColor(activityType);
|
||||
final dateFormat = DateFormat('MM/dd HH:mm');
|
||||
final timestamp = activity.timestamp ?? DateTime.now();
|
||||
final entityName = activity.entityName ?? '이름 없음';
|
||||
final description = activity.description ?? '설명 없음';
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
@@ -480,7 +492,7 @@ class _OverviewScreenRedesignState extends State<OverviewScreenRedesign> {
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
child: Icon(
|
||||
getActivityIcon(activity.activityType),
|
||||
getActivityIcon(activityType),
|
||||
color: color,
|
||||
size: 16,
|
||||
),
|
||||
@@ -491,18 +503,20 @@ class _OverviewScreenRedesignState extends State<OverviewScreenRedesign> {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
activity.entityName,
|
||||
entityName,
|
||||
style: ShadcnTheme.bodyMedium,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
Text(
|
||||
activity.description,
|
||||
description,
|
||||
style: ShadcnTheme.bodySmall,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Text(
|
||||
dateFormat.format(activity.timestamp),
|
||||
dateFormat.format(timestamp),
|
||||
style: ShadcnTheme.bodySmall,
|
||||
),
|
||||
],
|
||||
|
||||
@@ -108,8 +108,32 @@ class _WarehouseLocationFormScreenState
|
||||
? null
|
||||
: () async {
|
||||
setState(() {}); // 저장 중 상태 갱신
|
||||
await _controller.save();
|
||||
final success = await _controller.save();
|
||||
setState(() {}); // 저장 완료 후 상태 갱신
|
||||
|
||||
if (success) {
|
||||
// 성공 메시지 표시
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(_controller.isEditMode ? '입고지가 수정되었습니다' : '입고지가 추가되었습니다'),
|
||||
backgroundColor: AppThemeTailwind.success,
|
||||
),
|
||||
);
|
||||
// 리스트 화면으로 돌아가기
|
||||
Navigator.of(context).pop(true);
|
||||
}
|
||||
} else {
|
||||
// 실패 메시지 표시
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(_controller.error ?? '저장에 실패했습니다'),
|
||||
backgroundColor: AppThemeTailwind.danger,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppThemeTailwind.primary,
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
@@ -47,16 +49,16 @@ class AuthServiceImpl implements AuthService {
|
||||
@override
|
||||
Future<Either<Failure, LoginResponse>> login(LoginRequest request) async {
|
||||
try {
|
||||
print('[AuthService] login 시작 - useApi: ${env.Environment.useApi}');
|
||||
debugPrint('[AuthService] login 시작 - useApi: ${env.Environment.useApi}');
|
||||
|
||||
// Mock 모드일 때
|
||||
if (!env.Environment.useApi) {
|
||||
print('[AuthService] Mock 모드로 로그인 처리');
|
||||
debugPrint('[AuthService] Mock 모드로 로그인 처리');
|
||||
return _mockLogin(request);
|
||||
}
|
||||
|
||||
// API 모드일 때
|
||||
print('[AuthService] API 모드로 로그인 처리');
|
||||
debugPrint('[AuthService] API 모드로 로그인 처리');
|
||||
final result = await _authRemoteDataSource.login(request);
|
||||
|
||||
return await result.fold(
|
||||
@@ -77,8 +79,8 @@ class AuthServiceImpl implements AuthService {
|
||||
},
|
||||
);
|
||||
} catch (e, stackTrace) {
|
||||
print('[AuthService] login 예외 발생: $e');
|
||||
print('[AuthService] Stack trace: $stackTrace');
|
||||
debugPrint('[AuthService] login 예외 발생: $e');
|
||||
debugPrint('[AuthService] Stack trace: $stackTrace');
|
||||
return Left(ServerFailure(message: '로그인 처리 중 오류가 발생했습니다.'));
|
||||
}
|
||||
}
|
||||
@@ -130,10 +132,10 @@ class AuthServiceImpl implements AuthService {
|
||||
// 인증 상태 변경 알림
|
||||
_authStateController.add(true);
|
||||
|
||||
print('[AuthService] Mock 로그인 성공');
|
||||
debugPrint('[AuthService] Mock 로그인 성공');
|
||||
return Right(loginResponse);
|
||||
} catch (e) {
|
||||
print('[AuthService] Mock 로그인 실패: $e');
|
||||
debugPrint('[AuthService] Mock 로그인 실패: $e');
|
||||
return Left(ServerFailure(message: '로그인 처리 중 오류가 발생했습니다.'));
|
||||
}
|
||||
}
|
||||
@@ -235,15 +237,15 @@ class AuthServiceImpl implements AuthService {
|
||||
try {
|
||||
final token = await _secureStorage.read(key: _accessTokenKey);
|
||||
if (token != null && token.length > 20) {
|
||||
print('[AuthService] getAccessToken: Found (${token.substring(0, 20)}...)');
|
||||
debugPrint('[AuthService] getAccessToken: Found (${token.substring(0, 20)}...)');
|
||||
} else if (token != null) {
|
||||
print('[AuthService] getAccessToken: Found (${token})');
|
||||
debugPrint('[AuthService] getAccessToken: Found (${token})');
|
||||
} else {
|
||||
print('[AuthService] getAccessToken: Not found');
|
||||
debugPrint('[AuthService] getAccessToken: Not found');
|
||||
}
|
||||
return token;
|
||||
} catch (e) {
|
||||
print('[AuthService] getAccessToken error: $e');
|
||||
debugPrint('[AuthService] getAccessToken error: $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -277,12 +279,12 @@ class AuthServiceImpl implements AuthService {
|
||||
String refreshToken,
|
||||
int expiresIn,
|
||||
) async {
|
||||
print('[AuthService] Saving tokens...');
|
||||
debugPrint('[AuthService] Saving tokens...');
|
||||
final accessTokenPreview = accessToken.length > 20 ? '${accessToken.substring(0, 20)}...' : accessToken;
|
||||
final refreshTokenPreview = refreshToken.length > 20 ? '${refreshToken.substring(0, 20)}...' : refreshToken;
|
||||
print('[AuthService] Access token: $accessTokenPreview');
|
||||
print('[AuthService] Refresh token: $refreshTokenPreview');
|
||||
print('[AuthService] Expires in: $expiresIn seconds');
|
||||
debugPrint('[AuthService] Access token: $accessTokenPreview');
|
||||
debugPrint('[AuthService] Refresh token: $refreshTokenPreview');
|
||||
debugPrint('[AuthService] Expires in: $expiresIn seconds');
|
||||
|
||||
await _secureStorage.write(key: _accessTokenKey, value: accessToken);
|
||||
await _secureStorage.write(key: _refreshTokenKey, value: refreshToken);
|
||||
@@ -294,7 +296,7 @@ class AuthServiceImpl implements AuthService {
|
||||
value: expiry.toIso8601String(),
|
||||
);
|
||||
|
||||
print('[AuthService] Tokens saved successfully');
|
||||
debugPrint('[AuthService] Tokens saved successfully');
|
||||
}
|
||||
|
||||
Future<void> _saveUser(AuthUser user) async {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
import 'package:superport/core/errors/exceptions.dart';
|
||||
import 'package:superport/core/errors/failures.dart';
|
||||
@@ -31,11 +32,11 @@ class CompanyService {
|
||||
|
||||
return response.items.map((dto) => _convertListDtoToCompany(dto)).toList();
|
||||
} on ApiException catch (e) {
|
||||
print('[CompanyService] ApiException: ${e.message}');
|
||||
debugPrint('[CompanyService] ApiException: ${e.message}');
|
||||
throw ServerFailure(message: e.message);
|
||||
} catch (e, stackTrace) {
|
||||
print('[CompanyService] Error loading companies: $e');
|
||||
print('[CompanyService] Stack trace: $stackTrace');
|
||||
debugPrint('[CompanyService] Error loading companies: $e');
|
||||
debugPrint('[CompanyService] Stack trace: $stackTrace');
|
||||
throw ServerFailure(message: 'Failed to fetch company list: $e');
|
||||
}
|
||||
}
|
||||
@@ -54,11 +55,16 @@ class CompanyService {
|
||||
remark: company.remark,
|
||||
);
|
||||
|
||||
debugPrint('[CompanyService] Creating company with request: ${request.toJson()}');
|
||||
final response = await _remoteDataSource.createCompany(request);
|
||||
debugPrint('[CompanyService] Company created with ID: ${response.id}');
|
||||
return _convertResponseToCompany(response);
|
||||
} on ApiException catch (e) {
|
||||
debugPrint('[CompanyService] ApiException during company creation: ${e.message}');
|
||||
throw ServerFailure(message: e.message);
|
||||
} catch (e) {
|
||||
} catch (e, stackTrace) {
|
||||
debugPrint('[CompanyService] Unexpected error during company creation: $e');
|
||||
debugPrint('[CompanyService] Stack trace: $stackTrace');
|
||||
throw ServerFailure(message: 'Failed to create company: $e');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ class EquipmentService {
|
||||
String? status,
|
||||
int? companyId,
|
||||
int? warehouseLocationId,
|
||||
String? search,
|
||||
}) async {
|
||||
try {
|
||||
final dtoList = await _remoteDataSource.getEquipments(
|
||||
@@ -29,6 +30,7 @@ class EquipmentService {
|
||||
status: status,
|
||||
companyId: companyId,
|
||||
warehouseLocationId: warehouseLocationId,
|
||||
search: search,
|
||||
);
|
||||
|
||||
return dtoList;
|
||||
@@ -46,6 +48,7 @@ class EquipmentService {
|
||||
String? status,
|
||||
int? companyId,
|
||||
int? warehouseLocationId,
|
||||
String? search,
|
||||
}) async {
|
||||
try {
|
||||
final dtoList = await _remoteDataSource.getEquipments(
|
||||
@@ -54,6 +57,7 @@ class EquipmentService {
|
||||
status: status,
|
||||
companyId: companyId,
|
||||
warehouseLocationId: warehouseLocationId,
|
||||
search: search,
|
||||
);
|
||||
|
||||
return dtoList.map((dto) => _convertListDtoToEquipment(dto)).toList();
|
||||
|
||||
@@ -20,14 +20,14 @@ class HealthCheckService {
|
||||
/// 헬스체크 API 호출
|
||||
Future<Map<String, dynamic>> checkHealth() async {
|
||||
try {
|
||||
print('=== 헬스체크 시작 ===');
|
||||
print('API Base URL: ${Environment.apiBaseUrl}');
|
||||
print('Full URL: ${Environment.apiBaseUrl}/health');
|
||||
debugPrint('=== 헬스체크 시작 ===');
|
||||
debugPrint('API Base URL: ${Environment.apiBaseUrl}');
|
||||
debugPrint('Full URL: ${Environment.apiBaseUrl}/health');
|
||||
|
||||
final response = await _apiClient.get('/health');
|
||||
|
||||
print('응답 상태 코드: ${response.statusCode}');
|
||||
print('응답 데이터: ${response.data}');
|
||||
debugPrint('응답 상태 코드: ${response.statusCode}');
|
||||
debugPrint('응답 데이터: ${response.data}');
|
||||
|
||||
return {
|
||||
'success': true,
|
||||
@@ -35,16 +35,16 @@ class HealthCheckService {
|
||||
'statusCode': response.statusCode,
|
||||
};
|
||||
} on DioException catch (e) {
|
||||
print('=== DioException 발생 ===');
|
||||
print('에러 타입: ${e.type}');
|
||||
print('에러 메시지: ${e.message}');
|
||||
print('에러 응답: ${e.response?.data}');
|
||||
print('에러 상태 코드: ${e.response?.statusCode}');
|
||||
debugPrint('=== DioException 발생 ===');
|
||||
debugPrint('에러 타입: ${e.type}');
|
||||
debugPrint('에러 메시지: ${e.message}');
|
||||
debugPrint('에러 응답: ${e.response?.data}');
|
||||
debugPrint('에러 상태 코드: ${e.response?.statusCode}');
|
||||
|
||||
// CORS 에러인지 확인
|
||||
if (e.type == DioExceptionType.connectionError ||
|
||||
e.type == DioExceptionType.unknown) {
|
||||
print('⚠️ CORS 또는 네트워크 연결 문제일 가능성이 있습니다.');
|
||||
debugPrint('⚠️ CORS 또는 네트워크 연결 문제일 가능성이 있습니다.');
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -55,8 +55,8 @@ class HealthCheckService {
|
||||
'responseData': e.response?.data,
|
||||
};
|
||||
} catch (e) {
|
||||
print('=== 일반 에러 발생 ===');
|
||||
print('에러: $e');
|
||||
debugPrint('=== 일반 에러 발생 ===');
|
||||
debugPrint('에러: $e');
|
||||
|
||||
return {
|
||||
'success': false,
|
||||
@@ -68,7 +68,7 @@ class HealthCheckService {
|
||||
/// 직접 Dio로 테스트 (인터셉터 없이)
|
||||
Future<Map<String, dynamic>> checkHealthDirect() async {
|
||||
try {
|
||||
print('=== 직접 Dio 헬스체크 시작 ===');
|
||||
debugPrint('=== 직접 Dio 헬스체크 시작 ===');
|
||||
|
||||
final dio = Dio(BaseOptions(
|
||||
baseUrl: Environment.apiBaseUrl,
|
||||
@@ -97,7 +97,7 @@ class HealthCheckService {
|
||||
'statusCode': response.statusCode,
|
||||
};
|
||||
} catch (e) {
|
||||
print('직접 Dio 에러: $e');
|
||||
debugPrint('직접 Dio 에러: $e');
|
||||
return {
|
||||
'success': false,
|
||||
'error': e.toString(),
|
||||
@@ -109,7 +109,7 @@ class HealthCheckService {
|
||||
void startPeriodicHealthCheck() {
|
||||
if (_isMonitoring) return;
|
||||
|
||||
print('=== 주기적 헬스체크 모니터링 시작 ===');
|
||||
debugPrint('=== 주기적 헬스체크 모니터링 시작 ===');
|
||||
_isMonitoring = true;
|
||||
|
||||
// 즉시 한 번 체크
|
||||
@@ -123,7 +123,7 @@ class HealthCheckService {
|
||||
|
||||
/// 주기적인 헬스체크 중지
|
||||
void stopPeriodicHealthCheck() {
|
||||
print('=== 주기적 헬스체크 모니터링 중지 ===');
|
||||
debugPrint('=== 주기적 헬스체크 모니터링 중지 ===');
|
||||
_isMonitoring = false;
|
||||
_healthCheckTimer?.cancel();
|
||||
_healthCheckTimer = null;
|
||||
@@ -146,9 +146,9 @@ class HealthCheckService {
|
||||
final status = result['data']?['status'] ?? 'unreachable';
|
||||
final message = result['error'] ?? 'Server status: $status';
|
||||
|
||||
print('=== 브라우저 알림 표시 ===');
|
||||
print('상태: $status');
|
||||
print('메시지: $message');
|
||||
debugPrint('=== 브라우저 알림 표시 ===');
|
||||
debugPrint('상태: $status');
|
||||
debugPrint('메시지: $message');
|
||||
|
||||
// 플랫폼별 알림 처리
|
||||
platform.showNotification(
|
||||
@@ -157,7 +157,7 @@ class HealthCheckService {
|
||||
status,
|
||||
);
|
||||
} catch (e) {
|
||||
print('브라우저 알림 표시 실패: $e');
|
||||
debugPrint('브라우저 알림 표시 실패: $e');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
/// 웹이 아닌 플랫폼을 위한 스텁 구현
|
||||
void showNotification(String title, String message, String status) {
|
||||
// 웹이 아닌 플랫폼에서는 아무것도 하지 않음
|
||||
print('Notification (non-web): $title - $message - $status');
|
||||
debugPrint('Notification (non-web): $title - $message - $status');
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
import 'dart:js' as js;
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
/// 웹 플랫폼을 위한 알림 구현
|
||||
void showNotification(String title, String message, String status) {
|
||||
try {
|
||||
@@ -10,6 +12,6 @@ void showNotification(String title, String message, String status) {
|
||||
status,
|
||||
]);
|
||||
} catch (e) {
|
||||
print('웹 알림 표시 실패: $e');
|
||||
debugPrint('웹 알림 표시 실패: $e');
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
import 'package:superport/core/errors/exceptions.dart';
|
||||
import 'package:superport/core/errors/failures.dart';
|
||||
@@ -22,6 +23,19 @@ class LicenseService {
|
||||
int? assignedUserId,
|
||||
String? licenseType,
|
||||
}) async {
|
||||
debugPrint('\n╔════════════════════════════════════════════════════════════');
|
||||
debugPrint('║ 📤 LICENSE API REQUEST');
|
||||
debugPrint('╟────────────────────────────────────────────────────────────');
|
||||
debugPrint('║ Endpoint: GET /licenses');
|
||||
debugPrint('║ Parameters:');
|
||||
debugPrint('║ - page: $page');
|
||||
debugPrint('║ - perPage: $perPage');
|
||||
if (isActive != null) debugPrint('║ - isActive: $isActive');
|
||||
if (companyId != null) debugPrint('║ - companyId: $companyId');
|
||||
if (assignedUserId != null) debugPrint('║ - assignedUserId: $assignedUserId');
|
||||
if (licenseType != null) debugPrint('║ - licenseType: $licenseType');
|
||||
debugPrint('╚════════════════════════════════════════════════════════════\n');
|
||||
|
||||
try {
|
||||
final response = await _remoteDataSource.getLicenses(
|
||||
page: page,
|
||||
@@ -32,10 +46,41 @@ class LicenseService {
|
||||
licenseType: licenseType,
|
||||
);
|
||||
|
||||
return response.items.map((dto) => _convertDtoToLicense(dto)).toList();
|
||||
final licenses = response.items.map((dto) => _convertDtoToLicense(dto)).toList();
|
||||
|
||||
debugPrint('\n╔════════════════════════════════════════════════════════════');
|
||||
debugPrint('║ 📥 LICENSE API RESPONSE');
|
||||
debugPrint('╟────────────────────────────────────────────────────────────');
|
||||
debugPrint('║ Status: SUCCESS');
|
||||
debugPrint('║ Total Items: ${response.total}');
|
||||
debugPrint('║ Current Page: ${response.page}');
|
||||
debugPrint('║ Total Pages: ${response.totalPages}');
|
||||
debugPrint('║ Returned Items: ${licenses.length}');
|
||||
if (licenses.isNotEmpty) {
|
||||
debugPrint('║ Sample Data:');
|
||||
final sample = licenses.first;
|
||||
debugPrint('║ - ID: ${sample.id}');
|
||||
debugPrint('║ - Product: ${sample.productName}');
|
||||
debugPrint('║ - Company: ${sample.companyName ?? "N/A"}');
|
||||
}
|
||||
debugPrint('╚════════════════════════════════════════════════════════════\n');
|
||||
|
||||
return licenses;
|
||||
} on ApiException catch (e) {
|
||||
debugPrint('\n╔════════════════════════════════════════════════════════════');
|
||||
debugPrint('║ ❌ LICENSE API ERROR');
|
||||
debugPrint('╟────────────────────────────────────────────────────────────');
|
||||
debugPrint('║ Type: ApiException');
|
||||
debugPrint('║ Message: ${e.message}');
|
||||
debugPrint('╚════════════════════════════════════════════════════════════\n');
|
||||
throw ServerFailure(message: e.message);
|
||||
} catch (e) {
|
||||
debugPrint('\n╔════════════════════════════════════════════════════════════');
|
||||
debugPrint('║ ❌ LICENSE API ERROR');
|
||||
debugPrint('╟────────────────────────────────────────────────────────────');
|
||||
debugPrint('║ Type: Unknown');
|
||||
debugPrint('║ Error: $e');
|
||||
debugPrint('╚════════════════════════════════════════════════════════════\n');
|
||||
throw ServerFailure(message: '라이선스 목록을 불러오는 데 실패했습니다: $e');
|
||||
}
|
||||
}
|
||||
@@ -54,6 +99,18 @@ class LicenseService {
|
||||
|
||||
// 라이선스 생성
|
||||
Future<License> createLicense(License license) async {
|
||||
debugPrint('\n╔════════════════════════════════════════════════════════════');
|
||||
debugPrint('║ 📤 LICENSE CREATE REQUEST');
|
||||
debugPrint('╟────────────────────────────────────────────────────────────');
|
||||
debugPrint('║ Endpoint: POST /licenses');
|
||||
debugPrint('║ Request Data:');
|
||||
debugPrint('║ - licenseKey: ${license.licenseKey}');
|
||||
debugPrint('║ - productName: ${license.productName}');
|
||||
debugPrint('║ - vendor: ${license.vendor}');
|
||||
debugPrint('║ - companyId: ${license.companyId}');
|
||||
debugPrint('║ - expiryDate: ${license.expiryDate?.toIso8601String()}');
|
||||
debugPrint('╚════════════════════════════════════════════════════════════\n');
|
||||
|
||||
try {
|
||||
final request = CreateLicenseRequest(
|
||||
licenseKey: license.licenseKey,
|
||||
@@ -70,10 +127,34 @@ class LicenseService {
|
||||
);
|
||||
|
||||
final dto = await _remoteDataSource.createLicense(request);
|
||||
return _convertDtoToLicense(dto);
|
||||
final createdLicense = _convertDtoToLicense(dto);
|
||||
|
||||
debugPrint('\n╔════════════════════════════════════════════════════════════');
|
||||
debugPrint('║ 📥 LICENSE CREATE RESPONSE');
|
||||
debugPrint('╟────────────────────────────────────────────────────────────');
|
||||
debugPrint('║ Status: SUCCESS');
|
||||
debugPrint('║ Created License:');
|
||||
debugPrint('║ - ID: ${createdLicense.id}');
|
||||
debugPrint('║ - Key: ${createdLicense.licenseKey}');
|
||||
debugPrint('║ - Product: ${createdLicense.productName}');
|
||||
debugPrint('╚════════════════════════════════════════════════════════════\n');
|
||||
|
||||
return createdLicense;
|
||||
} on ApiException catch (e) {
|
||||
debugPrint('\n╔════════════════════════════════════════════════════════════');
|
||||
debugPrint('║ ❌ LICENSE CREATE ERROR');
|
||||
debugPrint('╟────────────────────────────────────────────────────────────');
|
||||
debugPrint('║ Type: ApiException');
|
||||
debugPrint('║ Message: ${e.message}');
|
||||
debugPrint('╚════════════════════════════════════════════════════════════\n');
|
||||
throw ServerFailure(message: e.message);
|
||||
} catch (e) {
|
||||
debugPrint('\n╔════════════════════════════════════════════════════════════');
|
||||
debugPrint('║ ❌ LICENSE CREATE ERROR');
|
||||
debugPrint('╟────────────────────────────────────────────────────────────');
|
||||
debugPrint('║ Type: Unknown');
|
||||
debugPrint('║ Error: $e');
|
||||
debugPrint('╚════════════════════════════════════════════════════════════\n');
|
||||
throw ServerFailure(message: '라이선스 생성에 실패했습니다: $e');
|
||||
}
|
||||
}
|
||||
@@ -180,7 +261,7 @@ class LicenseService {
|
||||
branchId: dto.branchId,
|
||||
assignedUserId: dto.assignedUserId,
|
||||
remark: dto.remark,
|
||||
isActive: dto.isActive,
|
||||
isActive: dto.isActive ?? true,
|
||||
createdAt: dto.createdAt,
|
||||
updatedAt: dto.updatedAt,
|
||||
companyName: dto.companyName,
|
||||
@@ -205,7 +286,7 @@ class LicenseService {
|
||||
branchId: null,
|
||||
assignedUserId: null,
|
||||
remark: null,
|
||||
isActive: dto.isActive,
|
||||
isActive: dto.isActive ?? true,
|
||||
createdAt: null,
|
||||
updatedAt: null,
|
||||
companyName: dto.companyName,
|
||||
|
||||
@@ -209,7 +209,9 @@ class UserService {
|
||||
}
|
||||
|
||||
/// API 권한을 앱 형식으로 변환
|
||||
String _mapRoleFromApi(String role) {
|
||||
String _mapRoleFromApi(String? role) {
|
||||
if (role == null) return 'M'; // null인 경우 기본값
|
||||
|
||||
switch (role) {
|
||||
case 'admin':
|
||||
return 'S';
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
import 'package:superport/core/errors/exceptions.dart';
|
||||
@@ -26,11 +27,11 @@ class WarehouseService {
|
||||
|
||||
return response.items.map((dto) => _convertDtoToWarehouseLocation(dto)).toList();
|
||||
} on ApiException catch (e) {
|
||||
print('[WarehouseService] ApiException: ${e.message}');
|
||||
debugPrint('[WarehouseService] ApiException: ${e.message}');
|
||||
throw ServerFailure(message: e.message);
|
||||
} catch (e, stackTrace) {
|
||||
print('[WarehouseService] Error loading warehouse locations: $e');
|
||||
print('[WarehouseService] Stack trace: $stackTrace');
|
||||
debugPrint('[WarehouseService] Error loading warehouse locations: $e');
|
||||
debugPrint('[WarehouseService] Stack trace: $stackTrace');
|
||||
throw ServerFailure(message: '창고 위치 목록을 불러오는 데 실패했습니다: $e');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ class PhoneUtils {
|
||||
static final TextInputFormatter phoneInputFormatter =
|
||||
_PhoneTextInputFormatter();
|
||||
|
||||
/// 전화번호 포맷팅 (뒤 4자리 하이픈)
|
||||
/// 전화번호 포맷팅 (뒤 4자리 하이페)
|
||||
static String formatPhoneNumber(String phoneNumber) {
|
||||
final digitsOnly = phoneNumber.replaceAll(RegExp(r'[^\d]'), '');
|
||||
if (digitsOnly.isEmpty) return '';
|
||||
@@ -21,6 +21,46 @@ class PhoneUtils {
|
||||
}
|
||||
return digitsOnly;
|
||||
}
|
||||
|
||||
/// 접두사에 따른 전화번호 포맷팅
|
||||
/// 010, 070, 050 등 0x0 번호: 0000-0000
|
||||
/// 02, 031 등 지역번호: 000-0000 또는 0000-0000
|
||||
static String formatPhoneNumberByPrefix(String prefix, String phoneNumber) {
|
||||
final digitsOnly = phoneNumber.replaceAll(RegExp(r'[^\d]'), '');
|
||||
if (digitsOnly.isEmpty) return '';
|
||||
|
||||
// 0x0 형태의 번호 (010, 070, 050 등)
|
||||
if (prefix.length == 3 && prefix.startsWith('0') && prefix[2] == '0') {
|
||||
// 8자리 처리: 0000-0000
|
||||
if (digitsOnly.length == 8) {
|
||||
return '${digitsOnly.substring(0, 4)}-${digitsOnly.substring(4)}';
|
||||
} else if (digitsOnly.length > 8) {
|
||||
final trimmed = digitsOnly.substring(0, 8);
|
||||
return '${trimmed.substring(0, 4)}-${trimmed.substring(4)}';
|
||||
} else if (digitsOnly.length > 4) {
|
||||
return '${digitsOnly.substring(0, 4)}-${digitsOnly.substring(4)}';
|
||||
}
|
||||
}
|
||||
// 지역번호 (02, 031, 032 등)
|
||||
else {
|
||||
// 7자리: 000-0000
|
||||
if (digitsOnly.length == 7) {
|
||||
return '${digitsOnly.substring(0, 3)}-${digitsOnly.substring(3)}';
|
||||
}
|
||||
// 8자리: 0000-0000
|
||||
else if (digitsOnly.length == 8) {
|
||||
return '${digitsOnly.substring(0, 4)}-${digitsOnly.substring(4)}';
|
||||
}
|
||||
// 그 외: 마지막 4자리 앞에 하이픈
|
||||
else if (digitsOnly.length > 4) {
|
||||
final frontPart = digitsOnly.substring(0, digitsOnly.length - 4);
|
||||
final backPart = digitsOnly.substring(digitsOnly.length - 4);
|
||||
return '$frontPart-$backPart';
|
||||
}
|
||||
}
|
||||
|
||||
return digitsOnly;
|
||||
}
|
||||
|
||||
/// 포맷된 전화번호에서 숫자만 추출
|
||||
static String extractDigitsOnly(String formattedPhoneNumber) {
|
||||
|
||||
Reference in New Issue
Block a user