feat: 소프트 딜리트 기능 전면 구현 완료
## 주요 변경사항 - Company, Equipment, License, Warehouse Location 모든 화면에 소프트 딜리트 구현 - 관리자 권한으로 삭제된 데이터 조회 가능 (includeInactive 파라미터) - 데이터 무결성 보장을 위한 논리 삭제 시스템 완성 ## 기능 개선 - 각 리스트 컨트롤러에 toggleIncludeInactive() 메서드 추가 - UI에 "비활성 포함" 체크박스 추가 (관리자 전용) - API 데이터소스에 includeInactive 파라미터 지원 ## 문서 정리 - 불필요한 문서 파일 제거 및 재구성 - CLAUDE.md 프로젝트 상태 업데이트 (진행률 80%) - 테스트 결과 문서화 (test20250812v01.md) ## UI 컴포넌트 - Equipment 화면 위젯 모듈화 (custom_dropdown_field, equipment_basic_info_section) - 폼 유효성 검증 강화 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,176 +0,0 @@
|
||||
# 백엔드 API 구현 요청 사항
|
||||
|
||||
## 1. 시리얼 번호 중복 체크 API
|
||||
|
||||
### 요청 사항
|
||||
장비 입고 시 시리얼 번호 중복을 방지하기 위한 API가 필요합니다.
|
||||
|
||||
### API 스펙
|
||||
|
||||
#### Endpoint
|
||||
```
|
||||
POST /api/v1/equipment/check-serial
|
||||
```
|
||||
|
||||
#### Request Body
|
||||
```json
|
||||
{
|
||||
"serialNumber": "SN123456789"
|
||||
}
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
**성공 (200 OK) - 사용 가능한 시리얼 번호**
|
||||
```json
|
||||
{
|
||||
"available": true,
|
||||
"message": "사용 가능한 시리얼 번호입니다."
|
||||
}
|
||||
```
|
||||
|
||||
**성공 (200 OK) - 중복된 시리얼 번호**
|
||||
```json
|
||||
{
|
||||
"available": false,
|
||||
"message": "이미 등록된 시리얼 번호입니다.",
|
||||
"existingEquipment": {
|
||||
"id": 123,
|
||||
"name": "장비명",
|
||||
"companyName": "회사명",
|
||||
"warehouseLocation": "창고 위치"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**실패 (400 Bad Request) - 잘못된 요청**
|
||||
```json
|
||||
{
|
||||
"error": "시리얼 번호를 입력해주세요."
|
||||
}
|
||||
```
|
||||
|
||||
### 구현 요구사항
|
||||
|
||||
1. **중복 체크 로직**
|
||||
- equipment 테이블의 serial_number 컬럼에서 중복 확인
|
||||
- 대소문자 구분 없이 체크 (case-insensitive)
|
||||
- 공백 제거 후 비교 (trim)
|
||||
|
||||
2. **성능 고려사항**
|
||||
- serial_number 컬럼에 인덱스 필요
|
||||
- 빠른 응답을 위한 최적화
|
||||
|
||||
3. **보안 고려사항**
|
||||
- SQL Injection 방지
|
||||
- Rate limiting 적용 (분당 60회 제한)
|
||||
|
||||
### 프론트엔드 통합 코드
|
||||
|
||||
```dart
|
||||
// services/equipment_service.dart
|
||||
Future<bool> checkSerialNumberAvailability(String serialNumber) async {
|
||||
final response = await dio.post(
|
||||
'/equipment/check-serial',
|
||||
data: {'serialNumber': serialNumber},
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
return response.data['available'] ?? false;
|
||||
}
|
||||
throw Exception('시리얼 번호 확인 실패');
|
||||
}
|
||||
|
||||
// controllers/equipment_form_controller.dart
|
||||
Future<String?> validateSerialNumber(String? value) async {
|
||||
if (value == null || value.isEmpty) {
|
||||
return '시리얼 번호를 입력해주세요.';
|
||||
}
|
||||
|
||||
// 실시간 중복 체크
|
||||
final isAvailable = await _equipmentService.checkSerialNumberAvailability(value);
|
||||
if (!isAvailable) {
|
||||
return '이미 등록된 시리얼 번호입니다.';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
```
|
||||
|
||||
## 2. 벌크 시리얼 번호 체크 API (추가 제안)
|
||||
|
||||
### 요청 사항
|
||||
여러 장비를 한 번에 등록할 때 시리얼 번호들을 일괄 체크하는 API
|
||||
|
||||
### API 스펙
|
||||
|
||||
#### Endpoint
|
||||
```
|
||||
POST /api/v1/equipment/check-serial-bulk
|
||||
```
|
||||
|
||||
#### Request Body
|
||||
```json
|
||||
{
|
||||
"serialNumbers": ["SN001", "SN002", "SN003"]
|
||||
}
|
||||
```
|
||||
|
||||
#### Response
|
||||
```json
|
||||
{
|
||||
"results": [
|
||||
{
|
||||
"serialNumber": "SN001",
|
||||
"available": true
|
||||
},
|
||||
{
|
||||
"serialNumber": "SN002",
|
||||
"available": false,
|
||||
"existingEquipmentId": 456
|
||||
},
|
||||
{
|
||||
"serialNumber": "SN003",
|
||||
"available": true
|
||||
}
|
||||
],
|
||||
"summary": {
|
||||
"total": 3,
|
||||
"available": 2,
|
||||
"duplicates": 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 3. 시리얼 번호 유니크 제약 조건
|
||||
|
||||
### 데이터베이스 스키마 변경 요청
|
||||
|
||||
```sql
|
||||
-- equipment 테이블에 유니크 제약 조건 추가
|
||||
ALTER TABLE equipment
|
||||
ADD CONSTRAINT unique_serial_number
|
||||
UNIQUE (serial_number);
|
||||
|
||||
-- 성능을 위한 인덱스 추가 (이미 유니크 제약에 포함되지만 명시적으로)
|
||||
CREATE INDEX idx_equipment_serial_number
|
||||
ON equipment(LOWER(TRIM(serial_number)));
|
||||
```
|
||||
|
||||
## 구현 우선순위
|
||||
|
||||
1. **Phase 1 (즉시)**: 단일 시리얼 번호 체크 API
|
||||
2. **Phase 2 (선택)**: 벌크 시리얼 번호 체크 API
|
||||
3. **Phase 3 (필수)**: DB 유니크 제약 조건
|
||||
|
||||
## 예상 일정
|
||||
|
||||
- API 구현: 2-3시간
|
||||
- 테스트: 1시간
|
||||
- 배포: 30분
|
||||
|
||||
---
|
||||
|
||||
작성일: 2025-01-09
|
||||
작성자: Frontend Team
|
||||
상태: 백엔드 팀 검토 대기중
|
||||
@@ -1,120 +0,0 @@
|
||||
# Mock Service 제거 계획
|
||||
|
||||
> 작성일: 2025-01-09
|
||||
> 목적: Real API 전용으로 전환 (2025-01-07 결정사항)
|
||||
|
||||
## 📋 제거 대상 (25개 파일)
|
||||
|
||||
### Controllers - List (8개)
|
||||
- [x] `user_list_controller_refactored.dart` ✅
|
||||
- [x] `company_list_controller_refactored.dart` ✅
|
||||
- [x] `warehouse_location_list_controller_refactored.dart` ✅
|
||||
- [ ] `warehouse_location_list_controller.dart` (구버전)
|
||||
- [ ] `user_list_controller.dart` (구버전)
|
||||
- [ ] `company_list_controller.dart` (구버전)
|
||||
- [x] `equipment_list_controller_refactored.dart` (새로 생성) ✅
|
||||
- [ ] `license_list_controller.dart`
|
||||
|
||||
### Controllers - Form (5개)
|
||||
- [ ] `equipment_in_form_controller.dart`
|
||||
- [ ] `equipment_out_form_controller.dart`
|
||||
- [ ] `warehouse_location_form_controller.dart`
|
||||
- [ ] `user_form_controller.dart`
|
||||
- [ ] `license_form_controller.dart`
|
||||
|
||||
### Views (9개)
|
||||
- [ ] `company_list_redesign.dart`
|
||||
- [ ] `equipment_list_redesign.dart`
|
||||
- [ ] `license_list_redesign.dart`
|
||||
- [ ] `user_list_redesign.dart`
|
||||
- [ ] `equipment_in_form.dart`
|
||||
- [ ] `equipment_out_form.dart`
|
||||
- [ ] `company_form.dart`
|
||||
- [ ] `license_form.dart`
|
||||
- [ ] `user_form.dart`
|
||||
|
||||
### Core (4개)
|
||||
- [ ] `main.dart`
|
||||
- [x] `base_list_controller.dart` ✅
|
||||
- [ ] `auth_service.dart`
|
||||
- [ ] `mock_data_service.dart` (파일 삭제)
|
||||
|
||||
## 🔄 제거 패턴
|
||||
|
||||
### 1. Controller 수정 패턴
|
||||
```dart
|
||||
// BEFORE
|
||||
class SomeController extends ChangeNotifier {
|
||||
final MockDataService? dataService;
|
||||
bool _useApi = true;
|
||||
|
||||
Future<void> loadData() async {
|
||||
if (_useApi && _service != null) {
|
||||
// API 호출
|
||||
} else {
|
||||
// Mock 데이터 사용
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// AFTER
|
||||
class SomeController extends ChangeNotifier {
|
||||
// MockDataService 완전 제거
|
||||
// useApi 플래그 제거
|
||||
|
||||
Future<void> loadData() async {
|
||||
// API 호출만 유지
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. View 수정 패턴
|
||||
```dart
|
||||
// BEFORE
|
||||
ChangeNotifierProvider(
|
||||
create: (_) => SomeController(
|
||||
dataService: GetIt.instance<MockDataService>(),
|
||||
),
|
||||
)
|
||||
|
||||
// AFTER
|
||||
ChangeNotifierProvider(
|
||||
create: (_) => SomeController(),
|
||||
)
|
||||
```
|
||||
|
||||
### 3. BaseListController 수정
|
||||
```dart
|
||||
// useApi 파라미터 제거
|
||||
// Mock 관련 로직 제거
|
||||
```
|
||||
|
||||
## ⚠️ 주의사항
|
||||
|
||||
1. **GetIt 의존성**: MockDataService가 DI에 등록되지 않았으므로 직접 전달 부분만 제거
|
||||
2. **테스트 코드**: 테스트에서 Mock을 사용하는 경우 별도 처리 필요
|
||||
3. **Environment.useApi**: 환경변수 자체는 유지 (향후 완전 제거)
|
||||
4. **백업**: 중요 변경사항이므로 커밋 전 백업 필수
|
||||
|
||||
## 📊 진행 상황
|
||||
|
||||
- **시작**: 2025-01-09
|
||||
- **예상 완료**: 2025-01-09
|
||||
- **진행률**: 5/26 파일 (19%)
|
||||
- **완료 항목**:
|
||||
- BaseListController (useApi 파라미터 제거)
|
||||
- WarehouseLocationListControllerRefactored (Mock 코드 제거)
|
||||
- CompanyListControllerRefactored (Mock 코드 제거)
|
||||
- UserListControllerRefactored (Mock 코드 제거)
|
||||
- EquipmentListControllerRefactored (새로 생성, Mock 없이 구현)
|
||||
|
||||
## 🔧 실행 순서
|
||||
|
||||
1. BaseListController에서 useApi 관련 로직 제거
|
||||
2. Refactored Controllers 수정 (3개)
|
||||
3. 기존 Controllers 수정 (나머지)
|
||||
4. Form Controllers 수정
|
||||
5. Views 수정
|
||||
6. Core 파일 정리
|
||||
7. MockDataService 파일 삭제
|
||||
8. 테스트 실행 및 검증
|
||||
340
docs/superportPRD.md
Normal file
340
docs/superportPRD.md
Normal file
@@ -0,0 +1,340 @@
|
||||
**아래 내용 전체를 복사해서** `superportPRD.md` **파일로 저장하시면 됩니다.**
|
||||
|
||||
---
|
||||
|
||||
# supERPort ERP - 초기 프론트엔드 PRD (Flutter)
|
||||
|
||||
본 문서는 Flutter로 제작할 ERP 웹/앱 프론트엔드의 최소 기능 요구사항과 디렉터리 구조, 스타일 가이드, 그리고 AI 코드 생성에 활용 가능한 프롬프트를 포함하고 있습니다. 현재 계획은 확장 가능하며, 추후 대화와 요구사항 변경에 따라 자동으로 업데이트할 수 있습니다.
|
||||
|
||||
## 1. 개요
|
||||
|
||||
- **이름**: supERPort
|
||||
|
||||
- **기술 스택**: Flutter
|
||||
|
||||
- **주요 화면**:
|
||||
|
||||
1. 장비 입고
|
||||
|
||||
2. 장비 출고
|
||||
|
||||
3. 회사 등록
|
||||
|
||||
4. 사용자 등록
|
||||
|
||||
5. 라이센스 등록
|
||||
|
||||
|
||||
## 2. 공통 스타일 가이드
|
||||
|
||||
- **디자인 레퍼런스**: [Metronic Admin Template](https://themeforest.net/item/metronic-responsive-admin-dashboard-template/4021469)
|
||||
|
||||
- **디자인 레퍼런스를 철저히 검토 및 적용**: Metronic의 Layout, 컬러 팔레트, 컴포넌트, 인터랙션 가이드라인을 꼼꼼하게 분석하여, Flutter 및 Material Icons와 조화롭게 적용해야 합니다.
|
||||
- 하나의 화면에 중복되는 정보를 제공하지 않아야 합니다.
|
||||
|
||||
- **아이콘**: Material Icons
|
||||
|
||||
- **UI 기조**: 모던하고 직관적인 플랫 디자인, 적절한 그림자(Elevation)와 여백 사용
|
||||
|
||||
- **메인 컬러 예시**:
|
||||
|
||||
- Primary: #5867dd (Metronic 기본색)
|
||||
|
||||
- Secondary: #34bfa3
|
||||
|
||||
- Background: #f7f8fa
|
||||
|
||||
- **타이포그래피**: 가독성 높은 산세리프 폰트(예: Roboto, Noto Sans)
|
||||
|
||||
- **반응형 고려**: Web & Mobile 겸용, Responsive Layout 구성
|
||||
|
||||
|
||||
## 3. 기능 요구사항
|
||||
|
||||
### 3.1 장비 입고 화면
|
||||
|
||||
- **등록 후 리스트로 관리**
|
||||
|
||||
- 새로운 장비 입고 기록을 추가 (Form)
|
||||
|
||||
- 등록된 목록을 리스트(테이블)로 보여주고, 항목별 Editing 기능 제공
|
||||
|
||||
- **필수 데이터**
|
||||
|
||||
1. **제조사명** (varchar 필수)
|
||||
|
||||
2. **장비명** (varchar 필수)
|
||||
|
||||
3. **대분류 / 중분류 / 소분류** (varchar, Email 선택 UI 유사 방식)
|
||||
|
||||
4. **시리얼 넘버** (varchar)
|
||||
|
||||
5. **바코드 입력** (varchar, 차후에 바코드 스캔 기능과 연동 예정)
|
||||
|
||||
6. **물품 수** (int 필수)
|
||||
|
||||
- 시리얼 번호가 존재할 경우 1 고정 및 수정 불가
|
||||
|
||||
- 시리얼 번호가 없으면 직접 입력
|
||||
|
||||
7. **입고일** (datetime)
|
||||
|
||||
- 당일 날짜를 기본값으로 설정
|
||||
|
||||
- 캘린더에서 과거 날짜만 선택 가능
|
||||
|
||||
8. (DB 연계 시) **장비(장비이력) 테이블**과 매핑
|
||||
|
||||
- **입출고**는 “I”(입고)로 기록
|
||||
|
||||
- **발생시간**: 입고 시점(= 입고일)
|
||||
|
||||
|
||||
### 3.2 장비 출고 화면
|
||||
|
||||
- **등록 후 리스트로 관리**
|
||||
|
||||
- 새 출고 정보 등록 (Form)
|
||||
|
||||
- 출고 기록 목록 표시 및 편집 기능
|
||||
|
||||
- **필수 데이터**
|
||||
|
||||
1. **장비명** (varchar 필수)
|
||||
|
||||
2. **대분류 / 중분류 / 소분류** (출고 시점에 불러오고 수정 가능 여부는 정책 결정)
|
||||
|
||||
3. **시리얼 넘버** (varchar)
|
||||
|
||||
4. **바코드** (varchar, 바코드 스캔 기능 연동 예정)
|
||||
|
||||
5. **출고 수량** (int 필수)
|
||||
|
||||
6. **출고일** (datetime)
|
||||
|
||||
7. (DB 연계 시) **장비(장비이력) 테이블**과 매핑
|
||||
|
||||
- **입출고**는 “O”(출고)로 기록
|
||||
|
||||
- **발생시간**: 출고 시점(= 출고일)
|
||||
|
||||
|
||||
### 3.3 회사 등록 화면
|
||||
|
||||
- **등록 후 리스트로 관리**
|
||||
|
||||
- 새 회사 정보 등록 (Form)
|
||||
|
||||
- 회사 목록 조회 및 편집
|
||||
|
||||
- **필수 데이터**
|
||||
|
||||
1. **회사명** (varchar 필수)
|
||||
|
||||
2. **주소** (varchar)
|
||||
|
||||
3. (확장) **지점 정보** (Optional / 별도 화면 구성 가능)
|
||||
|
||||
- 지점명 (varchar)
|
||||
|
||||
- 대표전화번호 (varchar)
|
||||
|
||||
- 주소 (varchar)
|
||||
|
||||
4. (DB 연계 시) **고객(회사) / 고객(지점)** 테이블 매핑
|
||||
|
||||
- 회사 ID, 지점 ID 등 Primary Key, FK 관계
|
||||
|
||||
|
||||
### 3.4 사용자 등록 화면
|
||||
|
||||
- **등록 후 리스트로 관리**
|
||||
|
||||
- 사용자(직원) 가입/등록 Form
|
||||
|
||||
- 사용자 목록 조회 및 편집
|
||||
|
||||
- **필수 데이터**
|
||||
|
||||
1. **이름** (varchar 필수)
|
||||
|
||||
2. **소속 회사ID** (int)
|
||||
|
||||
3. **관리등급** (char) – 예) S(관리자), M(멤버)
|
||||
|
||||
4. (DB 연계 시) **서비스(직원)** 테이블 매핑
|
||||
|
||||
|
||||
### 3.5 라이센스 등록 화면
|
||||
|
||||
- **등록 후 리스트로 관리**
|
||||
|
||||
- 유지보수 라이센스 정보 등록 (Form)
|
||||
|
||||
- 등록된 라이센스 목록 표시 및 편집
|
||||
|
||||
- **필수 데이터**
|
||||
|
||||
1. **서비스(회사)ID** (int) – 라이센스가 적용될 회사/서비스 정보
|
||||
|
||||
2. **라이센스명** (varchar, 예: “1년 유지보수”)
|
||||
|
||||
3. **라이센스기간** (int, 월 단위)
|
||||
|
||||
4. **방문주기** (int, 유지보수 방문 주기)
|
||||
|
||||
5. (DB 연계 시) **유지(라이센스)** 테이블과 매핑
|
||||
|
||||
|
||||
## 4. 추천 디렉터리 구조
|
||||
|
||||
동일한 기능을 담은 모듈(파일)이 300라인을 초과하게 될 경우, **하위 파일로 분리**해 관리하는 것을 권장합니다.
|
||||
장비(Equipment) 모델과 장비 입고(In) / 출고(Out) 모델을 **분리**하여, 공통 필드와 입·출고 전용 필드를 구분해 유지보수를 용이하게 합니다.
|
||||
|
||||
lib/
|
||||
┣ models/
|
||||
┃ ┣ equipment_model.dart (장비(Equipment) 공통 모델)
|
||||
┃ ┣ equipment_in_model.dart (장비 입고 관련 모델, 장비 + 입고 필드)
|
||||
┃ ┣ equipment_out_model.dart (장비 출고 관련 모델, 장비 + 출고 필드)
|
||||
┃ ┣ company_model.dart (회사 정보 모델)
|
||||
┃ ┣ user_model.dart (사용자 정보 모델)
|
||||
┃ ┗ license_model.dart (유지보수 라이센스 정보 모델)
|
||||
┣ screens/
|
||||
┃ ┣ equipment_in/
|
||||
┃ ┃ ┣ equipment_in_list.dart (장비 입고 리스트 화면)
|
||||
┃ ┃ ┗ equipment_in_form.dart (장비 입고 등록/수정 폼)
|
||||
┃ ┣ equipment_out/
|
||||
┃ ┃ ┣ equipment_out_list.dart (장비 출고 리스트 화면)
|
||||
┃ ┃ ┗ equipment_out_form.dart (장비 출고 등록/수정 폼)
|
||||
┃ ┣ company/
|
||||
┃ ┃ ┣ company_list.dart (회사 목록 화면)
|
||||
┃ ┃ ┗ company_form.dart (회사 등록/수정 폼)
|
||||
┃ ┣ user/
|
||||
┃ ┃ ┣ user_list.dart (사용자 목록 화면)
|
||||
┃ ┃ ┗ user_form.dart (사용자 등록/수정 폼)
|
||||
┃ ┣ license/
|
||||
┃ ┃ ┣ license_list.dart (라이센스 목록 화면)
|
||||
┃ ┃ ┗ license_form.dart (라이센스 등록/수정 폼)
|
||||
┃ ┗ common/
|
||||
┃ ┣ custom_widgets.dart (공통 위젯)
|
||||
┃ ┗ theme.dart (스타일, 테마 정보)
|
||||
┣ services/
|
||||
┃ ┗ mock_data_service.dart (서버 없는 샘플 데이터 관리)
|
||||
┣ utils/
|
||||
┃ ┣ validators.dart (입력값 검증 함수)
|
||||
┃ ┗ constants.dart (상수 관리, 예: 라우트명, 컬러코드)
|
||||
┗ main.dart
|
||||
|
||||
## 5. 데이터베이스 설계
|
||||
|
||||
아래는 장비, 회사(고객), 서비스(직원/결제), 유지보수 라이센스 등에 대한 **예시** 테이블입니다. 실제 구현 시에는 필요에 따라 테이블을 통합하거나 컬럼명을 조정할 수 있습니다.
|
||||
|
||||
### 5.1 고객 관련 테이블
|
||||
|
||||
|테이블명|필드명|타입|예시|비고|
|
||||
|---|---|---|---|---|
|
||||
|고객(회사)|ID|Integer|1|Primary Key|
|
||||
||회사명|Varchar|LG전자|고객 회사명|
|
||||
||주소|Varchar|서울시 종로구|고객 회사 주소|
|
||||
|고객(지점)|ID|Integer|10|Primary Key|
|
||||
||회사ID|Integer|1|FK - 고객(회사)|
|
||||
||지점명|Varchar|본사|고객 지점명|
|
||||
||주소|Varchar|서울시 종로구|지점 주소|
|
||||
||대표전화번호|Varchar|02-3403-2222|지점 연락처|
|
||||
|
||||
### 5.2 서비스 관련 테이블
|
||||
|
||||
|테이블명|필드명|타입|예시|비고|
|
||||
|---|---|---|---|---|
|
||||
|서비스(직원)|ID|Integer|20|Primary Key|
|
||||
||회사ID|Integer|1|FK - 서비스(회사) 또는 고객(회사)|
|
||||
||이름|Varchar|홍길동|직원 이름|
|
||||
||관리등급|Char|M|S(관리자)/M(멤버)|
|
||||
|서비스(결제)|ID|Integer|30|Primary Key|
|
||||
||서비스(직원)ID|Integer|20|FK - 서비스(직원)|
|
||||
||결제여부|Char|A|승인(A)/반려(D)|
|
||||
||결제일|Datetime|2025-03-04 11:00|결제 일시|
|
||||
|
||||
### 5.3 장비 관련 테이블
|
||||
|
||||
|테이블명|필드명|타입|예시|비고|
|
||||
|---|---|---|---|---|
|
||||
|장비(장비정보)|ID|Integer|50|Primary Key|
|
||||
||장비(회사명)ID|Integer|1|FK - 장비(회사명) or 고객(회사)|
|
||||
||장비명|Varchar|라우터 123|장비 모델명|
|
||||
|장비(장비이력)|ID|Integer|60|Primary Key|
|
||||
||장비(장비바코드)ID|Integer|50|FK - 장비(장비바코드)|
|
||||
||입출고|Varchar|I|입고(I)/출고(O)/임대(R) 등 구분|
|
||||
||발생시간|Datetime|2025-03-04 11:00|이력 발생 시간|
|
||||
|
||||
### 5.4 유지보수 라이센스 관련 테이블
|
||||
|
||||
|테이블명|필드명|타입|예시|비고|
|
||||
|---|---|---|---|---|
|
||||
|유지(라이센스)|ID|Integer|70|Primary Key|
|
||||
||서비스(회사)ID|Integer|1|FK - 서비스(회사)|
|
||||
||라이센스명|Varchar|1년 유지보수|유지보수 라이센스명|
|
||||
||라이센스기간|Integer|12|월 단위|
|
||||
||방문주기|Integer|1|유지보수 방문 주기|
|
||||
|
||||
> 위 테이블들은 예시이며, 실제 구현 시에는 **API 요청/응답 형식**과 **화면 요구사항**에 따라 컬럼을 추가/수정/제거할 수 있습니다.
|
||||
|
||||
## 6. 코드 생성용 AI 프롬프트 예시
|
||||
|
||||
(아래 텍스트는 코드 자동생성용 프롬프트 작성 예시일 뿐, 실제 환경에 맞춰 수정해서 사용하세요.)
|
||||
|
||||
[System]
|
||||
You are Claude, a large language model trained by Anthropic.
|
||||
|
||||
[User]
|
||||
read document "doc/doc name" at first.
|
||||
|
||||
- 앱 이름: supERPort
|
||||
|
||||
- 화면 개요: 장비 입고, 장비 출고, 회사 등록, 사용자 등록
|
||||
|
||||
- 참조 스타일: Metronic Admin + Material Icons
|
||||
|
||||
- 필수 폴더 구조와 기능
|
||||
|
||||
|
||||
1. equipment_in (리스트 & 폼)
|
||||
|
||||
2. equipment_out (리스트 & 폼)
|
||||
|
||||
3. company (리스트 & 폼)
|
||||
|
||||
4. user (리스트 & 폼)
|
||||
|
||||
|
||||
Generate Flutter code with the above requirements.
|
||||
|
||||
- Use a consistent coding style.
|
||||
|
||||
- Provide minimal working code example for each screen.
|
||||
|
||||
- Utilize Material Icons.
|
||||
|
||||
- Implement basic validation in the forms.
|
||||
|
||||
|
||||
## 7. 추후 업데이트 사항
|
||||
|
||||
- 장비 출고 시 필수 데이터 상세
|
||||
|
||||
- 회사 등록/사용자 등록 시 필수 데이터 구조 정의 (지점 정보, 연락처 등 확장)
|
||||
|
||||
- Form 유효성 검사 규칙 세부화
|
||||
|
||||
- 권한(관리자/사용자)별 접근 제어
|
||||
|
||||
- 바코드 스캔 기능 연동
|
||||
|
||||
- API 연동 및 서버 연결
|
||||
|
||||
- **유지보수 라이센스** 관련 화면 및 기능 확대 (결제/계약 기간 연동 등)
|
||||
|
||||
|
||||
---
|
||||
|
||||
본 문서는 ERP 플랫폼 “supERPort”의 Flutter 프론트엔드 개발에 필요한 **최소 요구사항**, **디렉터리 구조**, **스타일 가이드**, 그리고 **데이터베이스 설계** 정보를 담고 있습니다. 여기서 정의되지 않은 사항은 추후 대화에서 확정된 후 문서에 자동 반영될 예정입니다. 필요에 따라 Markdown 형식으로 다운로드해, 버전 관리 시스템에 추가하거나 직접 열람할 수 있습니다.
|
||||
@@ -1,300 +0,0 @@
|
||||
# UseCase 패턴 가이드
|
||||
|
||||
## 📌 개요
|
||||
|
||||
UseCase 패턴은 Clean Architecture의 핵심 개념으로, 비즈니스 로직을 캡슐화하여 재사용성과 테스트 용이성을 높입니다.
|
||||
|
||||
## 🏗️ 구조
|
||||
|
||||
```
|
||||
lib/
|
||||
├── domain/
|
||||
│ └── usecases/
|
||||
│ ├── base_usecase.dart # 기본 UseCase 인터페이스
|
||||
│ ├── auth/ # 인증 관련 UseCase
|
||||
│ │ ├── login_usecase.dart
|
||||
│ │ ├── logout_usecase.dart
|
||||
│ │ └── ...
|
||||
│ ├── company/ # 회사 관리 UseCase
|
||||
│ │ ├── get_companies_usecase.dart
|
||||
│ │ ├── create_company_usecase.dart
|
||||
│ │ └── ...
|
||||
│ └── ...
|
||||
```
|
||||
|
||||
## 🔑 핵심 개념
|
||||
|
||||
### 1. UseCase 추상 클래스
|
||||
|
||||
```dart
|
||||
abstract class UseCase<Type, Params> {
|
||||
Future<Either<Failure, Type>> call(Params params);
|
||||
}
|
||||
```
|
||||
|
||||
- **Type**: 성공 시 반환할 데이터 타입
|
||||
- **Params**: UseCase 실행에 필요한 파라미터
|
||||
- **Either**: 실패(Left) 또는 성공(Right) 결과를 담는 컨테이너
|
||||
|
||||
### 2. Failure 클래스
|
||||
|
||||
```dart
|
||||
abstract class Failure {
|
||||
final String message;
|
||||
final String? code;
|
||||
final dynamic originalError;
|
||||
}
|
||||
```
|
||||
|
||||
다양한 실패 타입:
|
||||
- **ServerFailure**: 서버 에러
|
||||
- **NetworkFailure**: 네트워크 에러
|
||||
- **AuthFailure**: 인증 에러
|
||||
- **ValidationFailure**: 유효성 검증 에러
|
||||
- **PermissionFailure**: 권한 에러
|
||||
|
||||
## 📝 UseCase 구현 예시
|
||||
|
||||
### 1. 로그인 UseCase
|
||||
|
||||
```dart
|
||||
class LoginUseCase extends UseCase<LoginResponse, LoginParams> {
|
||||
final AuthService _authService;
|
||||
|
||||
LoginUseCase(this._authService);
|
||||
|
||||
@override
|
||||
Future<Either<Failure, LoginResponse>> call(LoginParams params) async {
|
||||
try {
|
||||
// 1. 유효성 검증
|
||||
if (!_isValidEmail(params.email)) {
|
||||
return Left(ValidationFailure(
|
||||
message: '올바른 이메일 형식이 아닙니다.',
|
||||
));
|
||||
}
|
||||
|
||||
// 2. 비즈니스 로직 실행
|
||||
final response = await _authService.login(
|
||||
LoginRequest(
|
||||
email: params.email,
|
||||
password: params.password,
|
||||
),
|
||||
);
|
||||
|
||||
// 3. 성공 결과 반환
|
||||
return Right(response);
|
||||
} on DioException catch (e) {
|
||||
// 4. 에러 처리
|
||||
return Left(_handleDioError(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 파라미터가 없는 UseCase
|
||||
|
||||
```dart
|
||||
class LogoutUseCase extends UseCase<void, NoParams> {
|
||||
final AuthService _authService;
|
||||
|
||||
LogoutUseCase(this._authService);
|
||||
|
||||
@override
|
||||
Future<Either<Failure, void>> call(NoParams params) async {
|
||||
try {
|
||||
await _authService.logout();
|
||||
return const Right(null);
|
||||
} catch (e) {
|
||||
return Left(UnknownFailure(
|
||||
message: '로그아웃 중 오류가 발생했습니다.',
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 🎯 Controller에서 UseCase 사용
|
||||
|
||||
### 1. UseCase 초기화
|
||||
|
||||
```dart
|
||||
class LoginControllerWithUseCase extends ChangeNotifier {
|
||||
late final LoginUseCase _loginUseCase;
|
||||
|
||||
LoginControllerWithUseCase() {
|
||||
final authService = inject<AuthService>();
|
||||
_loginUseCase = LoginUseCase(authService);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. UseCase 실행
|
||||
|
||||
```dart
|
||||
Future<bool> login() async {
|
||||
final params = LoginParams(
|
||||
email: emailController.text,
|
||||
password: passwordController.text,
|
||||
);
|
||||
|
||||
final result = await _loginUseCase(params);
|
||||
|
||||
return result.fold(
|
||||
(failure) {
|
||||
// 실패 처리
|
||||
_errorMessage = failure.message;
|
||||
notifyListeners();
|
||||
return false;
|
||||
},
|
||||
(loginResponse) {
|
||||
// 성공 처리
|
||||
return true;
|
||||
},
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## 🧪 테스트 작성
|
||||
|
||||
### 1. UseCase 단위 테스트
|
||||
|
||||
```dart
|
||||
void main() {
|
||||
late LoginUseCase loginUseCase;
|
||||
late MockAuthService mockAuthService;
|
||||
|
||||
setUp(() {
|
||||
mockAuthService = MockAuthService();
|
||||
loginUseCase = LoginUseCase(mockAuthService);
|
||||
});
|
||||
|
||||
test('로그인 성공 테스트', () async {
|
||||
// Given
|
||||
const params = LoginParams(
|
||||
email: 'test@example.com',
|
||||
password: 'password123',
|
||||
);
|
||||
final expectedResponse = LoginResponse(...);
|
||||
|
||||
when(mockAuthService.login(any))
|
||||
.thenAnswer((_) async => expectedResponse);
|
||||
|
||||
// When
|
||||
final result = await loginUseCase(params);
|
||||
|
||||
// Then
|
||||
expect(result.isRight(), true);
|
||||
result.fold(
|
||||
(failure) => fail('Should not fail'),
|
||||
(response) => expect(response, expectedResponse),
|
||||
);
|
||||
});
|
||||
|
||||
test('잘못된 이메일 형식 테스트', () async {
|
||||
// Given
|
||||
const params = LoginParams(
|
||||
email: 'invalid-email',
|
||||
password: 'password123',
|
||||
);
|
||||
|
||||
// When
|
||||
final result = await loginUseCase(params);
|
||||
|
||||
// Then
|
||||
expect(result.isLeft(), true);
|
||||
result.fold(
|
||||
(failure) => expect(failure, isA<ValidationFailure>()),
|
||||
(response) => fail('Should not succeed'),
|
||||
);
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Controller 테스트
|
||||
|
||||
```dart
|
||||
void main() {
|
||||
late LoginControllerWithUseCase controller;
|
||||
late MockLoginUseCase mockLoginUseCase;
|
||||
|
||||
setUp(() {
|
||||
mockLoginUseCase = MockLoginUseCase();
|
||||
controller = LoginControllerWithUseCase();
|
||||
controller._loginUseCase = mockLoginUseCase;
|
||||
});
|
||||
|
||||
test('로그인 버튼 클릭 시 UseCase 호출', () async {
|
||||
// Given
|
||||
controller.emailController.text = 'test@example.com';
|
||||
controller.passwordController.text = 'password123';
|
||||
|
||||
when(mockLoginUseCase(any))
|
||||
.thenAnswer((_) async => Right(LoginResponse()));
|
||||
|
||||
// When
|
||||
final result = await controller.login();
|
||||
|
||||
// Then
|
||||
expect(result, true);
|
||||
verify(mockLoginUseCase(any)).called(1);
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## 💡 장점
|
||||
|
||||
1. **단일 책임 원칙**: 각 UseCase는 하나의 비즈니스 로직만 담당
|
||||
2. **테스트 용이성**: 비즈니스 로직을 독립적으로 테스트 가능
|
||||
3. **재사용성**: 여러 Controller에서 동일한 UseCase 재사용
|
||||
4. **의존성 역전**: Controller가 구체적인 Service가 아닌 UseCase에 의존
|
||||
5. **에러 처리 표준화**: Either 패턴으로 일관된 에러 처리
|
||||
|
||||
## 📋 구현 체크리스트
|
||||
|
||||
### UseCase 생성 시
|
||||
- [ ] UseCase 클래스 생성 (base_usecase 상속)
|
||||
- [ ] 파라미터 클래스 정의 (필요한 경우)
|
||||
- [ ] 유효성 검증 로직 구현
|
||||
- [ ] 에러 처리 구현
|
||||
- [ ] 성공/실패 케이스 모두 처리
|
||||
|
||||
### Controller 리팩토링 시
|
||||
- [ ] UseCase 의존성 주입
|
||||
- [ ] 비즈니스 로직을 UseCase 호출로 대체
|
||||
- [ ] Either 패턴으로 결과 처리
|
||||
- [ ] 에러 메시지 사용자 친화적으로 변환
|
||||
|
||||
### 테스트 작성 시
|
||||
- [ ] UseCase 단위 테스트
|
||||
- [ ] 성공 케이스 테스트
|
||||
- [ ] 실패 케이스 테스트
|
||||
- [ ] 경계값 테스트
|
||||
- [ ] Controller 통합 테스트
|
||||
|
||||
## 🔄 마이그레이션 전략
|
||||
|
||||
### Phase 1: 핵심 기능부터 시작
|
||||
1. 인증 관련 기능 (로그인, 로그아웃)
|
||||
2. CRUD 기본 기능
|
||||
3. 복잡한 비즈니스 로직
|
||||
|
||||
### Phase 2: 점진적 확산
|
||||
1. 새로운 기능은 UseCase 패턴으로 구현
|
||||
2. 기존 코드는 리팩토링 시 UseCase 적용
|
||||
3. 테스트 커버리지 확보
|
||||
|
||||
### Phase 3: 완전 마이그레이션
|
||||
1. 모든 비즈니스 로직 UseCase화
|
||||
2. Service 레이어는 데이터 액세스만 담당
|
||||
3. Controller는 UI 로직만 담당
|
||||
|
||||
## 📚 참고 자료
|
||||
|
||||
- [Clean Architecture](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html)
|
||||
- [Either Pattern in Dart](https://pub.dev/packages/dartz)
|
||||
- [Flutter Clean Architecture](https://resocoder.com/flutter-clean-architecture-tdd/)
|
||||
|
||||
---
|
||||
|
||||
**작성일**: 2025-01-09
|
||||
**버전**: 1.0
|
||||
Reference in New Issue
Block a user