docs: 플로우 테스트 파일 이동 및 계획 문서 추가
- api에서 플로우 테스트 JSON 파일들 이동 - 더미 데이터 시딩 계획 추가 - 견적 자동 계산 개발 계획 추가 - 기존 계획 문서 업데이트
This commit is contained in:
1574
plans/dummy-data-seeding-plan.md
Normal file
1574
plans/dummy-data-seeding-plan.md
Normal file
File diff suppressed because it is too large
Load Diff
143
plans/flow-tests/account-management-flow.json
Normal file
143
plans/flow-tests/account-management-flow.json
Normal file
@@ -0,0 +1,143 @@
|
||||
{
|
||||
"name": "계정 관리 플로우",
|
||||
"description": "이용 약관 조회/동의 → 계정 일시 정지 → 회원 탈퇴 플로우 테스트",
|
||||
"version": "1.0",
|
||||
"config": {
|
||||
"baseUrl": "",
|
||||
"timeout": 30000,
|
||||
"stopOnFailure": true
|
||||
},
|
||||
"variables": {
|
||||
"user_id": "{{$env.FLOW_TESTER_USER_ID}}",
|
||||
"user_pwd": "{{$env.FLOW_TESTER_USER_PWD}}"
|
||||
},
|
||||
"steps": [
|
||||
{
|
||||
"id": "login",
|
||||
"name": "로그인",
|
||||
"method": "POST",
|
||||
"endpoint": "/api/v1/login",
|
||||
"body": {
|
||||
"user_id": "{{user_id}}",
|
||||
"user_pwd": "{{user_pwd}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.access_token": "@isString"
|
||||
}
|
||||
},
|
||||
"extract": {
|
||||
"token": "$.access_token"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "get_agreements",
|
||||
"name": "이용 약관 목록 조회",
|
||||
"method": "GET",
|
||||
"endpoint": "/api/v1/account/agreements",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true
|
||||
}
|
||||
},
|
||||
"extract": {
|
||||
"agreements": "$.data"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "update_agreements",
|
||||
"name": "이용 약관 동의 상태 수정",
|
||||
"method": "PUT",
|
||||
"endpoint": "/api/v1/account/agreements",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"body": {
|
||||
"agreements": [
|
||||
{
|
||||
"type": "terms",
|
||||
"agreed": true
|
||||
},
|
||||
{
|
||||
"type": "privacy",
|
||||
"agreed": true
|
||||
},
|
||||
{
|
||||
"type": "marketing",
|
||||
"agreed": false
|
||||
}
|
||||
]
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "verify_agreements",
|
||||
"name": "약관 동의 상태 확인",
|
||||
"method": "GET",
|
||||
"endpoint": "/api/v1/account/agreements",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "suspend_account",
|
||||
"name": "계정 일시 정지",
|
||||
"description": "계정을 일시 정지 상태로 변경",
|
||||
"method": "POST",
|
||||
"endpoint": "/api/v1/account/suspend",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"body": {
|
||||
"reason": "테스트용 일시 정지",
|
||||
"duration_days": 30
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true
|
||||
}
|
||||
},
|
||||
"continueOnFailure": true
|
||||
},
|
||||
{
|
||||
"id": "withdraw_account",
|
||||
"name": "회원 탈퇴 (테스트 스킵)",
|
||||
"description": "회원 탈퇴 API - 실제 실행 시 계정 삭제됨 (주의)",
|
||||
"method": "POST",
|
||||
"endpoint": "/api/v1/account/withdraw",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"body": {
|
||||
"reason": "서비스 불만족",
|
||||
"feedback": "테스트용 피드백입니다"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200, 400, 422],
|
||||
"jsonPath": {
|
||||
"$.success": "@isBoolean"
|
||||
}
|
||||
},
|
||||
"continueOnFailure": true,
|
||||
"skip": true,
|
||||
"skipReason": "실제 탈퇴 실행 방지 - 수동 테스트 필요"
|
||||
}
|
||||
]
|
||||
}
|
||||
227
plans/flow-tests/attendance-api-crud.json
Normal file
227
plans/flow-tests/attendance-api-crud.json
Normal file
@@ -0,0 +1,227 @@
|
||||
{
|
||||
"name": "Attendance API 근태관리 테스트",
|
||||
"description": "근태 CRUD, 출퇴근 기록, 월간 통계 테스트",
|
||||
"version": "1.0",
|
||||
"config": {
|
||||
"baseUrl": "",
|
||||
"timeout": 30000,
|
||||
"stopOnFailure": true
|
||||
},
|
||||
"variables": {
|
||||
"user_id": "{{$env.FLOW_TESTER_USER_ID}}",
|
||||
"user_pwd": "{{$env.FLOW_TESTER_USER_PWD}}",
|
||||
"test_date": "{{$date}}"
|
||||
},
|
||||
"steps": [
|
||||
{
|
||||
"id": "login",
|
||||
"name": "로그인",
|
||||
"method": "POST",
|
||||
"endpoint": "/login",
|
||||
"body": {
|
||||
"user_id": "{{user_id}}",
|
||||
"user_pwd": "{{user_pwd}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.message": "로그인 성공",
|
||||
"$.access_token": "@isString"
|
||||
}
|
||||
},
|
||||
"extract": {
|
||||
"token": "$.access_token",
|
||||
"current_user_id": "$.user.id"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "check_in",
|
||||
"name": "출근 기록",
|
||||
"method": "POST",
|
||||
"endpoint": "/attendances/check-in",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"body": {
|
||||
"check_in": "09:00:00",
|
||||
"gps_data": {
|
||||
"latitude": 37.5665,
|
||||
"longitude": 126.978,
|
||||
"accuracy": 10
|
||||
}
|
||||
},
|
||||
"expect": {
|
||||
"status": [200, 201],
|
||||
"jsonPath": {
|
||||
"$.success": true,
|
||||
"$.data.id": "@isNumber",
|
||||
"$.data.status": "@isString"
|
||||
}
|
||||
},
|
||||
"extract": {
|
||||
"attendance_id": "$.data.id"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "show_attendance",
|
||||
"name": "근태 상세 조회",
|
||||
"method": "GET",
|
||||
"endpoint": "/attendances/{{check_in.attendance_id}}",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true,
|
||||
"$.data.id": "{{check_in.attendance_id}}",
|
||||
"$.data.base_date": "@isString"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "check_out",
|
||||
"name": "퇴근 기록",
|
||||
"method": "POST",
|
||||
"endpoint": "/attendances/check-out",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"body": {
|
||||
"check_out": "18:00:00",
|
||||
"gps_data": {
|
||||
"latitude": 37.5665,
|
||||
"longitude": 126.978,
|
||||
"accuracy": 15
|
||||
}
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true,
|
||||
"$.data.id": "@isNumber"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "list_attendances",
|
||||
"name": "근태 목록 조회",
|
||||
"method": "GET",
|
||||
"endpoint": "/attendances",
|
||||
"query": {
|
||||
"page": 1,
|
||||
"per_page": 10,
|
||||
"date": "{{test_date}}"
|
||||
},
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true,
|
||||
"$.data.data": "@isArray"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "monthly_stats",
|
||||
"name": "월간 통계 조회",
|
||||
"method": "GET",
|
||||
"endpoint": "/attendances/monthly-stats",
|
||||
"query": {
|
||||
"year": 2025,
|
||||
"month": 12
|
||||
},
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "create_attendance",
|
||||
"name": "근태 수동 등록 (관리자)",
|
||||
"method": "POST",
|
||||
"endpoint": "/attendances",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"body": {
|
||||
"user_id": "{{login.current_user_id}}",
|
||||
"base_date": "2025-12-01",
|
||||
"status": "onTime",
|
||||
"json_details": {
|
||||
"check_in": "09:00:00",
|
||||
"check_out": "18:00:00",
|
||||
"work_minutes": 480
|
||||
},
|
||||
"remarks": "Flow Tester 테스트 데이터"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200, 201],
|
||||
"jsonPath": {
|
||||
"$.success": true,
|
||||
"$.data.id": "@isNumber"
|
||||
}
|
||||
},
|
||||
"extract": {
|
||||
"manual_attendance_id": "$.data.id"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "update_attendance",
|
||||
"name": "근태 수정",
|
||||
"method": "PATCH",
|
||||
"endpoint": "/attendances/{{create_attendance.manual_attendance_id}}",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"body": {
|
||||
"status": "late",
|
||||
"remarks": "수정된 테스트 데이터"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true,
|
||||
"$.data.status": "late"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "delete_manual_attendance",
|
||||
"name": "수동 등록 근태 삭제",
|
||||
"method": "DELETE",
|
||||
"endpoint": "/attendances/{{create_attendance.manual_attendance_id}}",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "delete_checkin_attendance",
|
||||
"name": "출퇴근 기록 삭제 (정리)",
|
||||
"method": "DELETE",
|
||||
"endpoint": "/attendances/{{check_in.attendance_id}}",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
85
plans/flow-tests/auth-legacy-flow.json
Normal file
85
plans/flow-tests/auth-legacy-flow.json
Normal file
@@ -0,0 +1,85 @@
|
||||
{
|
||||
"name": "인증 플로우 테스트",
|
||||
"description": "로그인, 프로필 조회, 토큰 갱신, 로그아웃 플로우를 테스트합니다.",
|
||||
"version": "1.0",
|
||||
"config": {
|
||||
"apiKey": "42Jfwc6EaRQ04GNRmLR5kzJp5UudSOzGGqjmdk1a",
|
||||
"baseUrl": "https://api.sam.kr/api/v1",
|
||||
"timeout": 30000,
|
||||
"stopOnFailure": true
|
||||
},
|
||||
"variables": {
|
||||
"user_id": "codebridgex",
|
||||
"user_pwd": "code1234"
|
||||
},
|
||||
"steps": [
|
||||
{
|
||||
"id": "login",
|
||||
"name": "로그인",
|
||||
"method": "POST",
|
||||
"endpoint": "/login",
|
||||
"body": {
|
||||
"user_id": "{{variables.user_id}}",
|
||||
"user_pwd": "{{variables.user_pwd}}"
|
||||
},
|
||||
"extract": {
|
||||
"accessToken": "$.access_token",
|
||||
"refreshToken": "$.refresh_token"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.access_token": "@isString"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "get_profile",
|
||||
"name": "프로필 조회",
|
||||
"method": "GET",
|
||||
"endpoint": "/users/me",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.accessToken}}"
|
||||
},
|
||||
"dependsOn": ["login"],
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "refresh_token",
|
||||
"name": "토큰 갱신",
|
||||
"method": "POST",
|
||||
"endpoint": "/refresh",
|
||||
"body": {
|
||||
"refresh_token": "{{login.refreshToken}}"
|
||||
},
|
||||
"dependsOn": ["get_profile"],
|
||||
"extract": {
|
||||
"newToken": "$.access_token"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.access_token": "@isString"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "logout",
|
||||
"name": "로그아웃",
|
||||
"method": "POST",
|
||||
"endpoint": "/logout",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{refresh_token.newToken}}"
|
||||
},
|
||||
"dependsOn": ["refresh_token"],
|
||||
"expect": {
|
||||
"status": [200, 204]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
233
plans/flow-tests/bad-debt-flow.json
Normal file
233
plans/flow-tests/bad-debt-flow.json
Normal file
@@ -0,0 +1,233 @@
|
||||
{
|
||||
"name": "부실채권 관리 플로우",
|
||||
"description": "부실채권 등록 → 문서 첨부 → 메모 추가 → 상태 변경 → 삭제 플로우 테스트",
|
||||
"version": "1.0",
|
||||
"config": {
|
||||
"baseUrl": "",
|
||||
"timeout": 30000,
|
||||
"stopOnFailure": true
|
||||
},
|
||||
"variables": {
|
||||
"user_id": "{{$env.FLOW_TESTER_USER_ID}}",
|
||||
"user_pwd": "{{$env.FLOW_TESTER_USER_PWD}}"
|
||||
},
|
||||
"steps": [
|
||||
{
|
||||
"id": "login",
|
||||
"name": "로그인",
|
||||
"method": "POST",
|
||||
"endpoint": "/api/v1/login",
|
||||
"body": {
|
||||
"user_id": "{{user_id}}",
|
||||
"user_pwd": "{{user_pwd}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.access_token": "@isString"
|
||||
}
|
||||
},
|
||||
"extract": {
|
||||
"token": "$.access_token"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "list_bad_debts",
|
||||
"name": "부실채권 목록 조회",
|
||||
"method": "GET",
|
||||
"endpoint": "/api/v1/bad-debts",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"queryParams": {
|
||||
"page": 1,
|
||||
"per_page": 10
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true
|
||||
}
|
||||
},
|
||||
"extract": {
|
||||
"existing_count": "$.data.total"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "create_bad_debt",
|
||||
"name": "부실채권 등록",
|
||||
"method": "POST",
|
||||
"endpoint": "/api/v1/bad-debts",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"body": {
|
||||
"client_id": 1,
|
||||
"sale_id": 1,
|
||||
"amount": 5000000,
|
||||
"occurred_at": "2025-01-01",
|
||||
"reason": "연체 90일 초과",
|
||||
"status": "pending",
|
||||
"description": "Flow 테스트용 부실채권"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200, 201],
|
||||
"jsonPath": {
|
||||
"$.success": true
|
||||
}
|
||||
},
|
||||
"extract": {
|
||||
"bad_debt_id": "$.data.id"
|
||||
},
|
||||
"continueOnFailure": true
|
||||
},
|
||||
{
|
||||
"id": "get_bad_debt_detail",
|
||||
"name": "부실채권 상세 조회",
|
||||
"method": "GET",
|
||||
"endpoint": "/api/v1/bad-debts/{{create_bad_debt.bad_debt_id}}",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true,
|
||||
"$.data.id": "@isNumber"
|
||||
}
|
||||
},
|
||||
"continueOnFailure": true
|
||||
},
|
||||
{
|
||||
"id": "add_document",
|
||||
"name": "관련 문서 첨부",
|
||||
"method": "POST",
|
||||
"endpoint": "/api/v1/bad-debts/{{create_bad_debt.bad_debt_id}}/documents",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"body": {
|
||||
"file_id": 1,
|
||||
"document_type": "collection_notice",
|
||||
"description": "독촉장 발송 증빙"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200, 201],
|
||||
"jsonPath": {
|
||||
"$.success": true
|
||||
}
|
||||
},
|
||||
"continueOnFailure": true
|
||||
},
|
||||
{
|
||||
"id": "list_documents",
|
||||
"name": "첨부 문서 목록 조회",
|
||||
"method": "GET",
|
||||
"endpoint": "/api/v1/bad-debts/{{create_bad_debt.bad_debt_id}}/documents",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true
|
||||
}
|
||||
},
|
||||
"continueOnFailure": true
|
||||
},
|
||||
{
|
||||
"id": "add_memo",
|
||||
"name": "메모 추가",
|
||||
"method": "POST",
|
||||
"endpoint": "/api/v1/bad-debts/{{create_bad_debt.bad_debt_id}}/memos",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"body": {
|
||||
"content": "1차 독촉장 발송 완료",
|
||||
"memo_type": "collection"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200, 201],
|
||||
"jsonPath": {
|
||||
"$.success": true
|
||||
}
|
||||
},
|
||||
"extract": {
|
||||
"memo_id": "$.data.id"
|
||||
},
|
||||
"continueOnFailure": true
|
||||
},
|
||||
{
|
||||
"id": "list_memos",
|
||||
"name": "메모 목록 조회",
|
||||
"method": "GET",
|
||||
"endpoint": "/api/v1/bad-debts/{{create_bad_debt.bad_debt_id}}/memos",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true
|
||||
}
|
||||
},
|
||||
"continueOnFailure": true
|
||||
},
|
||||
{
|
||||
"id": "toggle_status",
|
||||
"name": "상태 토글 (처리중)",
|
||||
"method": "POST",
|
||||
"endpoint": "/api/v1/bad-debts/{{create_bad_debt.bad_debt_id}}/toggle",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"body": {
|
||||
"status": "in_progress"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true
|
||||
}
|
||||
},
|
||||
"continueOnFailure": true
|
||||
},
|
||||
{
|
||||
"id": "update_bad_debt",
|
||||
"name": "부실채권 수정",
|
||||
"method": "PUT",
|
||||
"endpoint": "/api/v1/bad-debts/{{create_bad_debt.bad_debt_id}}",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"body": {
|
||||
"description": "Flow 테스트 - 수정됨",
|
||||
"status": "resolved"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true
|
||||
}
|
||||
},
|
||||
"continueOnFailure": true
|
||||
},
|
||||
{
|
||||
"id": "delete_bad_debt",
|
||||
"name": "부실채권 삭제",
|
||||
"method": "DELETE",
|
||||
"endpoint": "/api/v1/bad-debts/{{create_bad_debt.bad_debt_id}}",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true
|
||||
}
|
||||
},
|
||||
"continueOnFailure": true
|
||||
}
|
||||
]
|
||||
}
|
||||
166
plans/flow-tests/branching-example-flow.json
Normal file
166
plans/flow-tests/branching-example-flow.json
Normal file
@@ -0,0 +1,166 @@
|
||||
{
|
||||
"$schema": "flow-tester-schema.json",
|
||||
"name": "분기 테스트 플로우 예시",
|
||||
"description": "Flow Tester의 조건 분기 기능을 보여주는 예시 플로우입니다. 로그인 성공/실패에 따른 분기, 권한에 따른 분기, 조건부 의존성 등을 테스트합니다.",
|
||||
"version": "1.0.0",
|
||||
"author": "SAM Team",
|
||||
"category": "examples",
|
||||
"tags": ["branching", "condition", "if-else", "example"],
|
||||
"config": {
|
||||
"baseUrl": "",
|
||||
"timeout": 30000,
|
||||
"stopOnFailure": false
|
||||
},
|
||||
"variables": {
|
||||
"user_id": "{{$env.FLOW_TESTER_USER_ID}}",
|
||||
"user_pwd": "{{$env.FLOW_TESTER_USER_PWD}}"
|
||||
},
|
||||
"steps": [
|
||||
{
|
||||
"id": "login",
|
||||
"name": "1. 로그인 시도",
|
||||
"description": "사용자 인증을 시도합니다",
|
||||
"method": "POST",
|
||||
"endpoint": "/api/v1/login",
|
||||
"body": {
|
||||
"user_id": "{{variables.user_id}}",
|
||||
"user_pwd": "{{variables.user_pwd}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200, 401]
|
||||
},
|
||||
"extract": {
|
||||
"token": "$.access_token",
|
||||
"role": "$.user.role",
|
||||
"permissions": "$.user.permissions"
|
||||
},
|
||||
"continueOnFailure": true
|
||||
},
|
||||
|
||||
{
|
||||
"id": "login_success_path",
|
||||
"name": "2-A. 로그인 성공 처리",
|
||||
"description": "로그인이 성공했을 때만 실행됩니다",
|
||||
"condition": {"stepResult": "login", "is": "success"},
|
||||
"dependsOn": ["login"],
|
||||
"method": "GET",
|
||||
"endpoint": "/api/v1/users/me",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200]
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"id": "login_failure_path",
|
||||
"name": "2-B. 로그인 실패 처리",
|
||||
"description": "로그인이 실패했을 때만 실행됩니다",
|
||||
"condition": {"stepResult": "login", "is": "failure"},
|
||||
"dependsOn": [{"step": "login", "onlyIf": "any"}],
|
||||
"method": "POST",
|
||||
"endpoint": "/api/v1/auth/password-reset-request",
|
||||
"body": {
|
||||
"email": "{{variables.user_id}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200, 404]
|
||||
},
|
||||
"continueOnFailure": true
|
||||
},
|
||||
|
||||
{
|
||||
"id": "admin_dashboard",
|
||||
"name": "3-A. 관리자 대시보드",
|
||||
"description": "관리자 권한이 있을 때만 실행됩니다",
|
||||
"condition": "{{login.role}} == 'admin'",
|
||||
"dependsOn": ["login_success_path"],
|
||||
"method": "GET",
|
||||
"endpoint": "/api/v1/admin/dashboard",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200]
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"id": "user_dashboard",
|
||||
"name": "3-B. 일반 사용자 대시보드",
|
||||
"description": "일반 사용자 권한일 때만 실행됩니다",
|
||||
"condition": "{{login.role}} == 'user'",
|
||||
"dependsOn": ["login_success_path"],
|
||||
"method": "GET",
|
||||
"endpoint": "/api/v1/user/dashboard",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200]
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"id": "premium_features",
|
||||
"name": "3-C. 프리미엄 기능",
|
||||
"description": "프리미엄 권한이 있을 때만 실행됩니다",
|
||||
"condition": {
|
||||
"and": [
|
||||
{"stepResult": "login", "is": "success"},
|
||||
{"left": "{{login.permissions}}", "op": "contains", "right": "premium"}
|
||||
]
|
||||
},
|
||||
"dependsOn": ["login_success_path"],
|
||||
"method": "GET",
|
||||
"endpoint": "/api/v1/premium/features",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200]
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"id": "create_report",
|
||||
"name": "4. 리포트 생성 (관리자만)",
|
||||
"description": "관리자만 리포트를 생성할 수 있습니다",
|
||||
"condition": {"stepResult": "admin_dashboard", "is": "success"},
|
||||
"dependsOn": [{"step": "admin_dashboard", "onlyIf": "executed"}],
|
||||
"method": "POST",
|
||||
"endpoint": "/api/v1/admin/reports",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"body": {
|
||||
"type": "daily",
|
||||
"date": "{{$faker.date}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [201]
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"id": "cleanup_always",
|
||||
"name": "5. 정리 작업 (항상 실행)",
|
||||
"description": "성공/실패와 무관하게 항상 실행되는 정리 작업입니다",
|
||||
"dependsOn": [
|
||||
{"step": "login", "onlyIf": "any"},
|
||||
{"step": "admin_dashboard", "onlyIf": "any"},
|
||||
{"step": "user_dashboard", "onlyIf": "any"}
|
||||
],
|
||||
"method": "POST",
|
||||
"endpoint": "/api/v1/logout",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200, 401]
|
||||
},
|
||||
"continueOnFailure": true
|
||||
}
|
||||
]
|
||||
}
|
||||
215
plans/flow-tests/client-legacy-flow.json
Normal file
215
plans/flow-tests/client-legacy-flow.json
Normal file
@@ -0,0 +1,215 @@
|
||||
{
|
||||
"name": "Client API CRUD 테스트",
|
||||
"description": "거래처(Client) API 전체 CRUD 테스트 - 생성, 조회, 수정, 토글, 삭제 포함. business_no, business_type, business_item 신규 필드 검증 포함.",
|
||||
"version": "1.0",
|
||||
"config": {
|
||||
"baseUrl": "https://api.sam.kr/api/v1",
|
||||
"apiKey": "{{$env.FLOW_TESTER_API_KEY}}",
|
||||
"timeout": 30000,
|
||||
"stopOnFailure": true
|
||||
},
|
||||
"variables": {
|
||||
"user_id": "{{$env.FLOW_TESTER_USER_ID}}",
|
||||
"user_pwd": "{{$env.FLOW_TESTER_USER_PWD}}",
|
||||
"test_client_code": "TEST_CLIENT_{{$timestamp}}"
|
||||
},
|
||||
"steps": [
|
||||
{
|
||||
"id": "login",
|
||||
"name": "1. 로그인 - 토큰 획득",
|
||||
"method": "POST",
|
||||
"endpoint": "/login",
|
||||
"body": {
|
||||
"user_id": "{{variables.user_id}}",
|
||||
"user_pwd": "{{variables.user_pwd}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.access_token": "@isString"
|
||||
}
|
||||
},
|
||||
"extract": {
|
||||
"token": "$.access_token"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "create_client",
|
||||
"name": "2. 거래처 생성 (신규 필드 포함)",
|
||||
"method": "POST",
|
||||
"endpoint": "/clients",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"body": {
|
||||
"client_code": "{{variables.test_client_code}}",
|
||||
"name": "테스트 거래처",
|
||||
"contact_person": "홍길동",
|
||||
"phone": "02-1234-5678",
|
||||
"email": "test@example.com",
|
||||
"address": "서울시 강남구 테헤란로 123",
|
||||
"business_no": "123-45-67890",
|
||||
"business_type": "제조업",
|
||||
"business_item": "전자부품",
|
||||
"is_active": "Y"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true,
|
||||
"$.data.id": "@isNumber",
|
||||
"$.data.client_code": "{{variables.test_client_code}}",
|
||||
"$.data.name": "테스트 거래처",
|
||||
"$.data.business_no": "123-45-67890",
|
||||
"$.data.business_type": "제조업",
|
||||
"$.data.business_item": "전자부품"
|
||||
}
|
||||
},
|
||||
"extract": {
|
||||
"client_id": "$.data.id"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "list_clients",
|
||||
"name": "3. 거래처 목록 조회",
|
||||
"method": "GET",
|
||||
"endpoint": "/clients?page=1&size=20&q=테스트",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true,
|
||||
"$.data.data": "@isArray",
|
||||
"$.data.current_page": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "show_client",
|
||||
"name": "4. 거래처 단건 조회",
|
||||
"method": "GET",
|
||||
"endpoint": "/clients/{{create_client.client_id}}",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true,
|
||||
"$.data.id": "{{create_client.client_id}}",
|
||||
"$.data.client_code": "{{variables.test_client_code}}",
|
||||
"$.data.business_no": "123-45-67890",
|
||||
"$.data.business_type": "제조업",
|
||||
"$.data.business_item": "전자부품"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "update_client",
|
||||
"name": "5. 거래처 수정 (신규 필드 변경)",
|
||||
"method": "PUT",
|
||||
"endpoint": "/clients/{{create_client.client_id}}",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"body": {
|
||||
"name": "테스트 거래처 (수정됨)",
|
||||
"contact_person": "김철수",
|
||||
"business_no": "987-65-43210",
|
||||
"business_type": "도소매업",
|
||||
"business_item": "IT솔루션"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true,
|
||||
"$.data.name": "테스트 거래처 (수정됨)",
|
||||
"$.data.contact_person": "김철수",
|
||||
"$.data.business_no": "987-65-43210",
|
||||
"$.data.business_type": "도소매업",
|
||||
"$.data.business_item": "IT솔루션"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "toggle_client",
|
||||
"name": "6. 거래처 활성/비활성 토글 (N으로)",
|
||||
"method": "PATCH",
|
||||
"endpoint": "/clients/{{create_client.client_id}}/toggle",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true,
|
||||
"$.data.is_active": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "toggle_client_back",
|
||||
"name": "7. 거래처 토글 복원 (Y로)",
|
||||
"method": "PATCH",
|
||||
"endpoint": "/clients/{{create_client.client_id}}/toggle",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true,
|
||||
"$.data.is_active": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "list_active_only",
|
||||
"name": "8. 활성 거래처만 조회",
|
||||
"method": "GET",
|
||||
"endpoint": "/clients?only_active=1",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true,
|
||||
"$.data.data": "@isArray"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "delete_client",
|
||||
"name": "9. 거래처 삭제",
|
||||
"method": "DELETE",
|
||||
"endpoint": "/clients/{{create_client.client_id}}",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "verify_deleted",
|
||||
"name": "10. 삭제 확인 (404 예상)",
|
||||
"method": "GET",
|
||||
"endpoint": "/clients/{{create_client.client_id}}",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [404],
|
||||
"jsonPath": {
|
||||
"$.success": false
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
200
plans/flow-tests/company-request-flow.json
Normal file
200
plans/flow-tests/company-request-flow.json
Normal file
@@ -0,0 +1,200 @@
|
||||
{
|
||||
"name": "회사 가입 신청 플로우",
|
||||
"description": "사업자번호 확인 → 가입 신청 → 내 신청 목록 → 승인/거절 플로우 테스트",
|
||||
"version": "1.0",
|
||||
"config": {
|
||||
"baseUrl": "",
|
||||
"timeout": 30000,
|
||||
"stopOnFailure": true
|
||||
},
|
||||
"variables": {
|
||||
"user_id": "{{$env.FLOW_TESTER_USER_ID}}",
|
||||
"user_pwd": "{{$env.FLOW_TESTER_USER_PWD}}",
|
||||
"test_business_number": "123-45-67890"
|
||||
},
|
||||
"steps": [
|
||||
{
|
||||
"id": "login",
|
||||
"name": "로그인",
|
||||
"method": "POST",
|
||||
"endpoint": "/api/v1/login",
|
||||
"body": {
|
||||
"user_id": "{{user_id}}",
|
||||
"user_pwd": "{{user_pwd}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.access_token": "@isString"
|
||||
}
|
||||
},
|
||||
"extract": {
|
||||
"token": "$.access_token"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "check_business_number",
|
||||
"name": "사업자번호 확인",
|
||||
"description": "가입 가능한 회사인지 사업자번호로 확인",
|
||||
"method": "POST",
|
||||
"endpoint": "/api/v1/companies/check",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"body": {
|
||||
"business_number": "{{test_business_number}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true
|
||||
}
|
||||
},
|
||||
"extract": {
|
||||
"company_exists": "$.data.exists",
|
||||
"company_id": "$.data.company_id"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "submit_request",
|
||||
"name": "회사 가입 신청",
|
||||
"method": "POST",
|
||||
"endpoint": "/api/v1/companies/request",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"body": {
|
||||
"company_id": "{{check_business_number.company_id}}",
|
||||
"department": "개발팀",
|
||||
"position": "개발자",
|
||||
"message": "Flow 테스트용 가입 신청입니다."
|
||||
},
|
||||
"expect": {
|
||||
"status": [200, 201],
|
||||
"jsonPath": {
|
||||
"$.success": true
|
||||
}
|
||||
},
|
||||
"extract": {
|
||||
"request_id": "$.data.id"
|
||||
},
|
||||
"continueOnFailure": true
|
||||
},
|
||||
{
|
||||
"id": "get_my_requests",
|
||||
"name": "내 신청 목록 조회",
|
||||
"method": "GET",
|
||||
"endpoint": "/api/v1/companies/my-requests",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "get_request_detail",
|
||||
"name": "신청 상세 조회",
|
||||
"method": "GET",
|
||||
"endpoint": "/api/v1/companies/requests/{{submit_request.request_id}}",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true,
|
||||
"$.data.id": "@isNumber"
|
||||
}
|
||||
},
|
||||
"continueOnFailure": true
|
||||
},
|
||||
{
|
||||
"id": "approve_request",
|
||||
"name": "신청 승인 (관리자)",
|
||||
"description": "관리자 권한으로 가입 신청 승인",
|
||||
"method": "POST",
|
||||
"endpoint": "/api/v1/companies/requests/{{submit_request.request_id}}/approve",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"body": {
|
||||
"role_id": 2,
|
||||
"department_id": 1,
|
||||
"approved_message": "승인되었습니다."
|
||||
},
|
||||
"expect": {
|
||||
"status": [200, 403],
|
||||
"jsonPath": {
|
||||
"$.success": "@isBoolean"
|
||||
}
|
||||
},
|
||||
"continueOnFailure": true
|
||||
},
|
||||
{
|
||||
"id": "submit_another_request",
|
||||
"name": "거절 테스트용 신청",
|
||||
"method": "POST",
|
||||
"endpoint": "/api/v1/companies/request",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"body": {
|
||||
"company_id": "{{check_business_number.company_id}}",
|
||||
"department": "영업팀",
|
||||
"position": "매니저",
|
||||
"message": "거절 테스트용 신청입니다."
|
||||
},
|
||||
"expect": {
|
||||
"status": [200, 201, 400, 422],
|
||||
"jsonPath": {
|
||||
"$.success": "@isBoolean"
|
||||
}
|
||||
},
|
||||
"extract": {
|
||||
"reject_request_id": "$.data.id"
|
||||
},
|
||||
"continueOnFailure": true
|
||||
},
|
||||
{
|
||||
"id": "reject_request",
|
||||
"name": "신청 거절 (관리자)",
|
||||
"description": "관리자 권한으로 가입 신청 거절",
|
||||
"method": "POST",
|
||||
"endpoint": "/api/v1/companies/requests/{{submit_another_request.reject_request_id}}/reject",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"body": {
|
||||
"reason": "테스트 거절 사유"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200, 403],
|
||||
"jsonPath": {
|
||||
"$.success": "@isBoolean"
|
||||
}
|
||||
},
|
||||
"continueOnFailure": true
|
||||
},
|
||||
{
|
||||
"id": "verify_rejection",
|
||||
"name": "거절 상태 확인",
|
||||
"method": "GET",
|
||||
"endpoint": "/api/v1/companies/requests/{{submit_another_request.reject_request_id}}",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true,
|
||||
"$.data.status": "rejected"
|
||||
}
|
||||
},
|
||||
"continueOnFailure": true
|
||||
}
|
||||
]
|
||||
}
|
||||
87
plans/flow-tests/department-tree-api.json
Normal file
87
plans/flow-tests/department-tree-api.json
Normal file
@@ -0,0 +1,87 @@
|
||||
{
|
||||
"name": "Department Tree API 테스트",
|
||||
"description": "부서 트리 조회 테스트",
|
||||
"version": "1.0",
|
||||
"config": {
|
||||
"baseUrl": "",
|
||||
"timeout": 30000,
|
||||
"stopOnFailure": true
|
||||
},
|
||||
"variables": {
|
||||
"user_id": "{{$env.FLOW_TESTER_USER_ID}}",
|
||||
"user_pwd": "{{$env.FLOW_TESTER_USER_PWD}}"
|
||||
},
|
||||
"steps": [
|
||||
{
|
||||
"id": "login",
|
||||
"name": "로그인",
|
||||
"method": "POST",
|
||||
"endpoint": "/login",
|
||||
"body": {
|
||||
"user_id": "{{user_id}}",
|
||||
"user_pwd": "{{user_pwd}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.message": "로그인 성공",
|
||||
"$.access_token": "@isString"
|
||||
}
|
||||
},
|
||||
"extract": {
|
||||
"token": "$.access_token"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "get_tree",
|
||||
"name": "부서 트리 조회",
|
||||
"method": "GET",
|
||||
"endpoint": "/departments/tree",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true,
|
||||
"$.data": "@isArray"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "get_tree_with_users",
|
||||
"name": "부서 트리 조회 (사용자 포함)",
|
||||
"method": "GET",
|
||||
"endpoint": "/departments/tree",
|
||||
"query": {
|
||||
"with_users": true
|
||||
},
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true,
|
||||
"$.data": "@isArray"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "list_departments",
|
||||
"name": "부서 목록 조회",
|
||||
"method": "GET",
|
||||
"endpoint": "/departments",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true,
|
||||
"$.data": "@isArray"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
188
plans/flow-tests/employee-api-crud.json
Normal file
188
plans/flow-tests/employee-api-crud.json
Normal file
@@ -0,0 +1,188 @@
|
||||
{
|
||||
"name": "Employee API CRUD 테스트",
|
||||
"description": "사원 관리 API 전체 CRUD 및 계정 생성 테스트",
|
||||
"version": "1.0",
|
||||
"config": {
|
||||
"baseUrl": "",
|
||||
"timeout": 30000,
|
||||
"stopOnFailure": true
|
||||
},
|
||||
"variables": {
|
||||
"user_id": "{{$env.FLOW_TESTER_USER_ID}}",
|
||||
"user_pwd": "{{$env.FLOW_TESTER_USER_PWD}}",
|
||||
"test_email": "test.employee.{{$timestamp}}@example.com",
|
||||
"test_name": "테스트사원{{$random:4}}"
|
||||
},
|
||||
"steps": [
|
||||
{
|
||||
"id": "login",
|
||||
"name": "로그인",
|
||||
"method": "POST",
|
||||
"endpoint": "/login",
|
||||
"body": {
|
||||
"user_id": "{{user_id}}",
|
||||
"user_pwd": "{{user_pwd}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.message": "로그인 성공",
|
||||
"$.access_token": "@isString"
|
||||
}
|
||||
},
|
||||
"extract": {
|
||||
"token": "$.access_token",
|
||||
"current_user_id": "$.user.id"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "get_stats",
|
||||
"name": "사원 통계 조회",
|
||||
"method": "GET",
|
||||
"endpoint": "/employees/stats",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true,
|
||||
"$.data.total": "@isNumber",
|
||||
"$.data.active": "@isNumber",
|
||||
"$.data.leave": "@isNumber",
|
||||
"$.data.resigned": "@isNumber"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "list_employees",
|
||||
"name": "사원 목록 조회",
|
||||
"method": "GET",
|
||||
"endpoint": "/employees",
|
||||
"query": {
|
||||
"page": 1,
|
||||
"per_page": 10
|
||||
},
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true,
|
||||
"$.data.data": "@isArray"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "create_employee",
|
||||
"name": "사원 등록",
|
||||
"method": "POST",
|
||||
"endpoint": "/employees",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"body": {
|
||||
"name": "{{test_name}}",
|
||||
"email": "{{test_email}}",
|
||||
"phone": "010-1234-5678",
|
||||
"employee_number": "EMP{{$random:6}}",
|
||||
"employee_status": "active",
|
||||
"position": "사원",
|
||||
"hire_date": "{{$date}}",
|
||||
"json_extra": {
|
||||
"emergency_contact": "010-9999-8888",
|
||||
"address": "서울시 강남구"
|
||||
}
|
||||
},
|
||||
"expect": {
|
||||
"status": [200, 201],
|
||||
"jsonPath": {
|
||||
"$.success": true,
|
||||
"$.data.id": "@isNumber",
|
||||
"$.data.user.name": "{{test_name}}"
|
||||
}
|
||||
},
|
||||
"extract": {
|
||||
"employee_id": "$.data.id",
|
||||
"user_id": "$.data.user_id"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "show_employee",
|
||||
"name": "사원 상세 조회",
|
||||
"method": "GET",
|
||||
"endpoint": "/employees/{{create_employee.employee_id}}",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true,
|
||||
"$.data.id": "{{create_employee.employee_id}}",
|
||||
"$.data.employee_status": "active"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "update_employee",
|
||||
"name": "사원 정보 수정",
|
||||
"method": "PATCH",
|
||||
"endpoint": "/employees/{{create_employee.employee_id}}",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"body": {
|
||||
"position": "대리",
|
||||
"employee_status": "active",
|
||||
"json_extra": {
|
||||
"emergency_contact": "010-1111-2222",
|
||||
"address": "서울시 서초구",
|
||||
"skills": ["Laravel", "React"]
|
||||
}
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true,
|
||||
"$.data.id": "@isNumber"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "list_filtered",
|
||||
"name": "사원 필터 조회 (재직자)",
|
||||
"method": "GET",
|
||||
"endpoint": "/employees",
|
||||
"query": {
|
||||
"status": "active",
|
||||
"q": "{{test_name}}"
|
||||
},
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "delete_employee",
|
||||
"name": "사원 삭제",
|
||||
"method": "DELETE",
|
||||
"endpoint": "/employees/{{create_employee.employee_id}}",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
287
plans/flow-tests/item-delete-legacy-flow.json
Normal file
287
plans/flow-tests/item-delete-legacy-flow.json
Normal file
@@ -0,0 +1,287 @@
|
||||
{
|
||||
"name": "품목 삭제 API 테스트",
|
||||
"description": "품목 삭제 시 참조 무결성 체크 및 soft delete 동작을 테스트합니다.",
|
||||
"version": "1.0",
|
||||
"config": {
|
||||
"apiKey": "42Jfwc6EaRQ04GNRmLR5kzJp5UudSOzGGqjmdk1a",
|
||||
"baseUrl": "https://api.sam.kr/api/v1",
|
||||
"timeout": 30000,
|
||||
"stopOnFailure": false
|
||||
},
|
||||
"variables": {
|
||||
"user_id": "codebridgex",
|
||||
"user_pwd": "code1234",
|
||||
"testProductCode": "TEST-DEL-001",
|
||||
"testProductName": "삭제 테스트용 품목",
|
||||
"testBomParentCode": "TEST-BOM-PARENT-001",
|
||||
"testBomParentName": "BOM 부모 품목"
|
||||
},
|
||||
"steps": [
|
||||
{
|
||||
"id": "login",
|
||||
"name": "로그인",
|
||||
"description": "API 테스트를 위한 로그인",
|
||||
"method": "POST",
|
||||
"endpoint": "/login",
|
||||
"body": {
|
||||
"user_id": "{{variables.user_id}}",
|
||||
"user_pwd": "{{variables.user_pwd}}"
|
||||
},
|
||||
"extract": {
|
||||
"accessToken": "$.access_token",
|
||||
"refreshToken": "$.refresh_token"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.access_token": "@isString"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "create_product_for_delete",
|
||||
"name": "삭제 테스트용 품목 생성",
|
||||
"description": "단순 삭제 테스트용 품목 생성 (BOM에 미사용)",
|
||||
"method": "POST",
|
||||
"endpoint": "/items",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.accessToken}}"
|
||||
},
|
||||
"body": {
|
||||
"code": "{{variables.testProductCode}}",
|
||||
"name": "{{variables.testProductName}}",
|
||||
"unit": "EA",
|
||||
"product_type": "FG",
|
||||
"is_active": true
|
||||
},
|
||||
"dependsOn": ["login"],
|
||||
"extract": {
|
||||
"createdProductId": "$.data.id",
|
||||
"createdProductCode": "$.data.code"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200, 201],
|
||||
"jsonPath": {
|
||||
"$.success": true,
|
||||
"$.data.id": "@isNumber"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "get_items_list",
|
||||
"name": "품목 목록 조회",
|
||||
"description": "생성된 품목이 목록에 있는지 확인",
|
||||
"method": "GET",
|
||||
"endpoint": "/items?type=FG&search={{create_product_for_delete.createdProductCode}}",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.accessToken}}"
|
||||
},
|
||||
"dependsOn": ["create_product_for_delete"],
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "delete_product_success",
|
||||
"name": "품목 삭제 (정상)",
|
||||
"description": "BOM에서 사용되지 않는 품목 삭제 - 성공해야 함",
|
||||
"method": "DELETE",
|
||||
"endpoint": "/items/{{create_product_for_delete.createdProductId}}",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.accessToken}}"
|
||||
},
|
||||
"dependsOn": ["get_items_list"],
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "verify_deleted_not_in_list",
|
||||
"name": "삭제된 품목 목록 미포함 확인",
|
||||
"description": "삭제된 품목이 기본 목록에서 제외되는지 확인",
|
||||
"method": "GET",
|
||||
"endpoint": "/items?type=FG&search={{create_product_for_delete.createdProductCode}}",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.accessToken}}"
|
||||
},
|
||||
"dependsOn": ["delete_product_success"],
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true
|
||||
}
|
||||
},
|
||||
"note": "data.total이 0이어야 함 (삭제된 품목 미포함)"
|
||||
},
|
||||
{
|
||||
"id": "delete_already_deleted_item",
|
||||
"name": "이미 삭제된 품목 재삭제 시도",
|
||||
"description": "soft delete된 품목을 다시 삭제하면 404 반환해야 함",
|
||||
"method": "DELETE",
|
||||
"endpoint": "/items/{{create_product_for_delete.createdProductId}}",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.accessToken}}"
|
||||
},
|
||||
"dependsOn": ["verify_deleted_not_in_list"],
|
||||
"expect": {
|
||||
"status": [404],
|
||||
"jsonPath": {
|
||||
"$.success": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "create_bom_parent",
|
||||
"name": "BOM 부모 품목 생성",
|
||||
"description": "BOM 테스트를 위한 부모 품목 생성",
|
||||
"method": "POST",
|
||||
"endpoint": "/items",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.accessToken}}"
|
||||
},
|
||||
"body": {
|
||||
"code": "{{variables.testBomParentCode}}",
|
||||
"name": "{{variables.testBomParentName}}",
|
||||
"unit": "EA",
|
||||
"product_type": "FG",
|
||||
"is_active": true
|
||||
},
|
||||
"dependsOn": ["login"],
|
||||
"extract": {
|
||||
"bomParentId": "$.data.id"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200, 201],
|
||||
"jsonPath": {
|
||||
"$.success": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "create_bom_child",
|
||||
"name": "BOM 자식 품목 생성",
|
||||
"description": "BOM 구성품으로 사용될 품목 생성",
|
||||
"method": "POST",
|
||||
"endpoint": "/items",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.accessToken}}"
|
||||
},
|
||||
"body": {
|
||||
"code": "TEST-BOM-CHILD-001",
|
||||
"name": "BOM 자식 품목",
|
||||
"unit": "EA",
|
||||
"product_type": "PT",
|
||||
"is_active": true
|
||||
},
|
||||
"dependsOn": ["create_bom_parent"],
|
||||
"extract": {
|
||||
"bomChildId": "$.data.id"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200, 201],
|
||||
"jsonPath": {
|
||||
"$.success": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "add_bom_component",
|
||||
"name": "BOM 구성품 추가",
|
||||
"description": "부모 품목에 자식 품목을 BOM으로 등록",
|
||||
"method": "POST",
|
||||
"endpoint": "/items/{{create_bom_parent.bomParentId}}/bom",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.accessToken}}"
|
||||
},
|
||||
"body": {
|
||||
"items": [
|
||||
{
|
||||
"ref_type": "PRODUCT",
|
||||
"ref_id": "{{create_bom_child.bomChildId}}",
|
||||
"quantity": 2,
|
||||
"sort_order": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
"dependsOn": ["create_bom_child"],
|
||||
"expect": {
|
||||
"status": [200, 201],
|
||||
"jsonPath": {
|
||||
"$.success": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "delete_bom_used_item_fail",
|
||||
"name": "BOM 사용 중인 품목 삭제 시도",
|
||||
"description": "다른 BOM에서 구성품으로 사용 중인 품목 삭제 - 400 에러 반환해야 함",
|
||||
"method": "DELETE",
|
||||
"endpoint": "/items/{{create_bom_child.bomChildId}}",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.accessToken}}"
|
||||
},
|
||||
"dependsOn": ["add_bom_component"],
|
||||
"expect": {
|
||||
"status": [400],
|
||||
"jsonPath": {
|
||||
"$.success": false,
|
||||
"$.message": "@contains:BOM"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "cleanup_bom",
|
||||
"name": "BOM 구성품 제거",
|
||||
"description": "테스트 정리 - BOM 구성품 제거",
|
||||
"method": "DELETE",
|
||||
"endpoint": "/items/{{create_bom_parent.bomParentId}}/bom",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.accessToken}}"
|
||||
},
|
||||
"dependsOn": ["delete_bom_used_item_fail"],
|
||||
"expect": {
|
||||
"status": [200, 204]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "delete_bom_child_after_cleanup",
|
||||
"name": "BOM 해제 후 자식 품목 삭제",
|
||||
"description": "BOM에서 제거된 품목은 삭제 가능해야 함",
|
||||
"method": "DELETE",
|
||||
"endpoint": "/items/{{create_bom_child.bomChildId}}",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.accessToken}}"
|
||||
},
|
||||
"dependsOn": ["cleanup_bom"],
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "cleanup_bom_parent",
|
||||
"name": "BOM 부모 품목 삭제",
|
||||
"description": "테스트 정리 - 부모 품목 삭제",
|
||||
"method": "DELETE",
|
||||
"endpoint": "/items/{{create_bom_parent.bomParentId}}",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.accessToken}}"
|
||||
},
|
||||
"dependsOn": ["delete_bom_child_after_cleanup"],
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
172
plans/flow-tests/item-fields-is-active-test.json
Normal file
172
plans/flow-tests/item-fields-is-active-test.json
Normal file
@@ -0,0 +1,172 @@
|
||||
{
|
||||
"name": "ItemField is_active 컬럼 검증 테스트",
|
||||
"description": "item_fields 테이블에 추가된 is_active 컬럼의 기능을 검증합니다. 필드 생성 시 기본값(true), 수정, 조회 시 is_active 필드 포함 여부를 테스트합니다.",
|
||||
"version": "1.0",
|
||||
"config": {
|
||||
"baseUrl": "https://api.sam.kr/api/v1",
|
||||
"apiKey": "{{$env.FLOW_TESTER_API_KEY}}",
|
||||
"timeout": 30000,
|
||||
"stopOnFailure": true
|
||||
},
|
||||
"variables": {
|
||||
"user_id": "{{$env.FLOW_TESTER_USER_ID}}",
|
||||
"user_pwd": "{{$env.FLOW_TESTER_USER_PWD}}"
|
||||
},
|
||||
"steps": [
|
||||
{
|
||||
"id": "login",
|
||||
"name": "1. 로그인 - 인증 토큰 획득",
|
||||
"method": "POST",
|
||||
"endpoint": "/login",
|
||||
"body": {
|
||||
"user_id": "{{user_id}}",
|
||||
"user_pwd": "{{user_pwd}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.message": "로그인 성공",
|
||||
"$.access_token": "@isString"
|
||||
}
|
||||
},
|
||||
"extract": {
|
||||
"token": "$.access_token"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "get_fields_list",
|
||||
"name": "2. 필드 목록 조회 - is_active 필드 포함 확인",
|
||||
"method": "GET",
|
||||
"endpoint": "/item-master/fields",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true,
|
||||
"$.data": "@isArray"
|
||||
}
|
||||
},
|
||||
"extract": {
|
||||
"existingFieldId": "$.data[0].id",
|
||||
"fieldCount": "$.data.length"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "create_field",
|
||||
"name": "3. 독립 필드 생성 - is_active 기본값 true 확인",
|
||||
"method": "POST",
|
||||
"endpoint": "/item-master/fields",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"body": {
|
||||
"field_name": "[테스트] is_active 검증 필드",
|
||||
"field_type": "textbox",
|
||||
"field_key": "test_is_active",
|
||||
"is_required": false,
|
||||
"placeholder": "is_active 기본값 테스트",
|
||||
"description": "API Flow Tester에서 생성한 테스트 필드"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200, 201],
|
||||
"jsonPath": {
|
||||
"$.success": true,
|
||||
"$.data.id": "@isNumber",
|
||||
"$.data.field_name": "[테스트] is_active 검증 필드",
|
||||
"$.data.is_active": true
|
||||
}
|
||||
},
|
||||
"extract": {
|
||||
"newFieldId": "$.data.id"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "verify_field_created",
|
||||
"name": "4. 생성된 필드 상세 확인 - is_active=true",
|
||||
"method": "GET",
|
||||
"endpoint": "/item-master/fields",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true
|
||||
}
|
||||
},
|
||||
"extract": {
|
||||
"allFields": "$.data"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "update_field_inactive",
|
||||
"name": "5. 필드 비활성화 - is_active=false로 수정",
|
||||
"method": "PUT",
|
||||
"endpoint": "/item-master/fields/{{create_field.newFieldId}}",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"body": {
|
||||
"is_active": false
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true,
|
||||
"$.data.is_active": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "verify_field_inactive",
|
||||
"name": "6. 비활성화 상태 확인",
|
||||
"method": "GET",
|
||||
"endpoint": "/item-master/fields",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "update_field_active",
|
||||
"name": "7. 필드 재활성화 - is_active=true로 수정",
|
||||
"method": "PUT",
|
||||
"endpoint": "/item-master/fields/{{create_field.newFieldId}}",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"body": {
|
||||
"is_active": true
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true,
|
||||
"$.data.is_active": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "delete_test_field",
|
||||
"name": "8. 테스트 필드 삭제 (정리)",
|
||||
"method": "DELETE",
|
||||
"endpoint": "/item-master/fields/{{create_field.newFieldId}}",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
157
plans/flow-tests/item-master-legacy-flow.json
Normal file
157
plans/flow-tests/item-master-legacy-flow.json
Normal file
@@ -0,0 +1,157 @@
|
||||
{
|
||||
"name": "품목기준관리 통합 테스트",
|
||||
"description": "품목기준관리 API의 전체 CRUD 플로우를 테스트합니다.",
|
||||
"version": "1.0",
|
||||
"config": {
|
||||
"apiKey": "42Jfwc6EaRQ04GNRmLR5kzJp5UudSOzGGqjmdk1a",
|
||||
"baseUrl": "https://api.sam.kr/api/v1",
|
||||
"timeout": 30000,
|
||||
"stopOnFailure": true
|
||||
},
|
||||
"variables": {
|
||||
"user_id": "codebridgex",
|
||||
"user_pwd": "code1234",
|
||||
"testItemCode": "TEST-ITEM-{{$timestamp}}",
|
||||
"testItemName": "테스트 품목",
|
||||
"testItemSpec": "100x100x10",
|
||||
"updatedItemName": "수정된 테스트 품목",
|
||||
"updatedItemSpec": "200x200x20"
|
||||
},
|
||||
"steps": [
|
||||
{
|
||||
"id": "login",
|
||||
"name": "로그인",
|
||||
"method": "POST",
|
||||
"endpoint": "/login",
|
||||
"body": {
|
||||
"user_id": "{{variables.user_id}}",
|
||||
"user_pwd": "{{variables.user_pwd}}"
|
||||
},
|
||||
"extract": {
|
||||
"accessToken": "$.access_token"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.access_token": "@isString"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "create_item",
|
||||
"name": "품목 생성",
|
||||
"method": "POST",
|
||||
"endpoint": "/item-master-data",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.accessToken}}"
|
||||
},
|
||||
"body": {
|
||||
"item_code": "{{variables.testItemCode}}",
|
||||
"item_name": "{{variables.testItemName}}",
|
||||
"item_spec": "{{variables.testItemSpec}}",
|
||||
"item_type": "PRODUCT",
|
||||
"unit": "EA",
|
||||
"is_active": true
|
||||
},
|
||||
"dependsOn": ["login"],
|
||||
"extract": {
|
||||
"createdItemId": "$.data.id",
|
||||
"createdItemCode": "$.data.item_code"
|
||||
},
|
||||
"expect": {
|
||||
"status": [201],
|
||||
"jsonPath": {
|
||||
"$.data.id": "@exists"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "get_item",
|
||||
"name": "품목 단건 조회",
|
||||
"method": "GET",
|
||||
"endpoint": "/item-master-data/{{create_item.createdItemId}}",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.accessToken}}"
|
||||
},
|
||||
"dependsOn": ["create_item"],
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.data.id": "@exists"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "list_items",
|
||||
"name": "품목 목록 조회",
|
||||
"method": "GET",
|
||||
"endpoint": "/item-master-data",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.accessToken}}"
|
||||
},
|
||||
"dependsOn": ["create_item"],
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.data": "@isArray"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "update_item",
|
||||
"name": "품목 수정",
|
||||
"method": "PUT",
|
||||
"endpoint": "/item-master-data/{{create_item.createdItemId}}",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.accessToken}}"
|
||||
},
|
||||
"body": {
|
||||
"item_name": "{{variables.updatedItemName}}",
|
||||
"item_spec": "{{variables.updatedItemSpec}}"
|
||||
},
|
||||
"dependsOn": ["get_item"],
|
||||
"expect": {
|
||||
"status": [200]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "verify_update",
|
||||
"name": "수정 확인",
|
||||
"method": "GET",
|
||||
"endpoint": "/item-master-data/{{create_item.createdItemId}}",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.accessToken}}"
|
||||
},
|
||||
"dependsOn": ["update_item"],
|
||||
"expect": {
|
||||
"status": [200]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "delete_item",
|
||||
"name": "품목 삭제",
|
||||
"method": "DELETE",
|
||||
"endpoint": "/item-master-data/{{create_item.createdItemId}}",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.accessToken}}"
|
||||
},
|
||||
"dependsOn": ["verify_update"],
|
||||
"expect": {
|
||||
"status": [200]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "verify_delete",
|
||||
"name": "삭제 확인",
|
||||
"method": "GET",
|
||||
"endpoint": "/item-master-data/{{create_item.createdItemId}}",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.accessToken}}"
|
||||
},
|
||||
"dependsOn": ["delete_item"],
|
||||
"expect": {
|
||||
"status": [404]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
254
plans/flow-tests/notification-settings-flow.json
Normal file
254
plans/flow-tests/notification-settings-flow.json
Normal file
@@ -0,0 +1,254 @@
|
||||
{
|
||||
"name": "알림 설정 조회/수정 플로우",
|
||||
"description": "알림 설정 조회 → 그룹별 설정 변경 → 저장 → 확인 플로우 테스트",
|
||||
"version": "1.0",
|
||||
"config": {
|
||||
"baseUrl": "",
|
||||
"timeout": 30000,
|
||||
"stopOnFailure": true
|
||||
},
|
||||
"variables": {
|
||||
"user_id": "{{$env.FLOW_TESTER_USER_ID}}",
|
||||
"user_pwd": "{{$env.FLOW_TESTER_USER_PWD}}"
|
||||
},
|
||||
"steps": [
|
||||
{
|
||||
"id": "login",
|
||||
"name": "로그인",
|
||||
"method": "POST",
|
||||
"endpoint": "/api/v1/login",
|
||||
"body": {
|
||||
"user_id": "{{user_id}}",
|
||||
"user_pwd": "{{user_pwd}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.access_token": "@isString"
|
||||
}
|
||||
},
|
||||
"extract": {
|
||||
"token": "$.access_token"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "get_settings",
|
||||
"name": "알림 설정 조회 (그룹 기반)",
|
||||
"method": "GET",
|
||||
"endpoint": "/api/v1/settings/notifications",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true
|
||||
}
|
||||
},
|
||||
"extract": {
|
||||
"original_notice_enabled": "$.data.notice.enabled",
|
||||
"original_approval_enabled": "$.data.approval.enabled"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "update_notice_group",
|
||||
"name": "공지사항 그룹 설정 수정",
|
||||
"method": "PUT",
|
||||
"endpoint": "/api/v1/settings/notifications",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"body": {
|
||||
"notice": {
|
||||
"enabled": true,
|
||||
"notice": {
|
||||
"enabled": true,
|
||||
"email": true
|
||||
},
|
||||
"event": {
|
||||
"enabled": true,
|
||||
"email": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "verify_notice_update",
|
||||
"name": "공지사항 설정 변경 확인",
|
||||
"method": "GET",
|
||||
"endpoint": "/api/v1/settings/notifications",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true,
|
||||
"$.data.notice.enabled": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "update_approval_group",
|
||||
"name": "결재 그룹 설정 수정",
|
||||
"method": "PUT",
|
||||
"endpoint": "/api/v1/settings/notifications",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"body": {
|
||||
"approval": {
|
||||
"enabled": true,
|
||||
"approvalRequest": {
|
||||
"enabled": true,
|
||||
"email": true
|
||||
},
|
||||
"draftApproved": {
|
||||
"enabled": true,
|
||||
"email": false
|
||||
},
|
||||
"draftRejected": {
|
||||
"enabled": true,
|
||||
"email": true
|
||||
},
|
||||
"draftCompleted": {
|
||||
"enabled": false,
|
||||
"email": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "update_multiple_groups",
|
||||
"name": "여러 그룹 동시 수정",
|
||||
"method": "PUT",
|
||||
"endpoint": "/api/v1/settings/notifications",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"body": {
|
||||
"order": {
|
||||
"enabled": true,
|
||||
"salesOrder": {
|
||||
"enabled": true,
|
||||
"email": false
|
||||
},
|
||||
"purchaseOrder": {
|
||||
"enabled": true,
|
||||
"email": false
|
||||
}
|
||||
},
|
||||
"production": {
|
||||
"enabled": false,
|
||||
"safetyStock": {
|
||||
"enabled": false,
|
||||
"email": false
|
||||
},
|
||||
"productionComplete": {
|
||||
"enabled": false,
|
||||
"email": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "get_flat_settings",
|
||||
"name": "플랫 구조 알림 설정 조회",
|
||||
"method": "GET",
|
||||
"endpoint": "/api/v1/users/me/notification-settings",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "update_single_type",
|
||||
"name": "단일 알림 유형 수정 (플랫)",
|
||||
"method": "PUT",
|
||||
"endpoint": "/api/v1/users/me/notification-settings",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"body": {
|
||||
"notification_type": "approval",
|
||||
"push_enabled": true,
|
||||
"email_enabled": false,
|
||||
"sms_enabled": false,
|
||||
"in_app_enabled": true
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "bulk_update",
|
||||
"name": "일괄 업데이트 (플랫)",
|
||||
"method": "PUT",
|
||||
"endpoint": "/api/v1/users/me/notification-settings/bulk",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"body": {
|
||||
"settings": [
|
||||
{
|
||||
"notification_type": "order",
|
||||
"push_enabled": true,
|
||||
"email_enabled": false
|
||||
},
|
||||
{
|
||||
"notification_type": "deposit",
|
||||
"push_enabled": true,
|
||||
"email_enabled": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "final_verify",
|
||||
"name": "최종 설정 확인",
|
||||
"method": "GET",
|
||||
"endpoint": "/api/v1/settings/notifications",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
260
plans/flow-tests/payment-flow.json
Normal file
260
plans/flow-tests/payment-flow.json
Normal file
@@ -0,0 +1,260 @@
|
||||
{
|
||||
"name": "결제 관리 플로우",
|
||||
"description": "결제 등록 → 완료 처리 → 명세서 조회 → 취소 → 환불 플로우 테스트",
|
||||
"version": "1.0",
|
||||
"config": {
|
||||
"baseUrl": "",
|
||||
"timeout": 30000,
|
||||
"stopOnFailure": true
|
||||
},
|
||||
"variables": {
|
||||
"user_id": "{{$env.FLOW_TESTER_USER_ID}}",
|
||||
"user_pwd": "{{$env.FLOW_TESTER_USER_PWD}}"
|
||||
},
|
||||
"steps": [
|
||||
{
|
||||
"id": "login",
|
||||
"name": "로그인",
|
||||
"method": "POST",
|
||||
"endpoint": "/api/v1/login",
|
||||
"body": {
|
||||
"user_id": "{{user_id}}",
|
||||
"user_pwd": "{{user_pwd}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.access_token": "@isString"
|
||||
}
|
||||
},
|
||||
"extract": {
|
||||
"token": "$.access_token"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "list_payments",
|
||||
"name": "결제 목록 조회",
|
||||
"method": "GET",
|
||||
"endpoint": "/api/v1/payments",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"queryParams": {
|
||||
"page": 1,
|
||||
"per_page": 10
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "create_payment",
|
||||
"name": "결제 등록",
|
||||
"method": "POST",
|
||||
"endpoint": "/api/v1/payments",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"body": {
|
||||
"subscription_id": 1,
|
||||
"amount": 99000,
|
||||
"payment_method": "card",
|
||||
"billing_name": "테스트 결제",
|
||||
"billing_email": "test@example.com"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200, 201],
|
||||
"jsonPath": {
|
||||
"$.success": true
|
||||
}
|
||||
},
|
||||
"extract": {
|
||||
"payment_id": "$.data.id"
|
||||
},
|
||||
"continueOnFailure": true
|
||||
},
|
||||
{
|
||||
"id": "get_payment_detail",
|
||||
"name": "결제 상세 조회",
|
||||
"method": "GET",
|
||||
"endpoint": "/api/v1/payments/{{create_payment.payment_id}}",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true,
|
||||
"$.data.id": "@isNumber"
|
||||
}
|
||||
},
|
||||
"continueOnFailure": true
|
||||
},
|
||||
{
|
||||
"id": "complete_payment",
|
||||
"name": "결제 완료 처리",
|
||||
"method": "POST",
|
||||
"endpoint": "/api/v1/payments/{{create_payment.payment_id}}/complete",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"body": {
|
||||
"transaction_id": "TXN_{{$timestamp}}",
|
||||
"pg_response": {
|
||||
"code": "0000",
|
||||
"message": "성공"
|
||||
}
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true
|
||||
}
|
||||
},
|
||||
"continueOnFailure": true
|
||||
},
|
||||
{
|
||||
"id": "verify_completed",
|
||||
"name": "완료 상태 확인",
|
||||
"method": "GET",
|
||||
"endpoint": "/api/v1/payments/{{create_payment.payment_id}}",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true,
|
||||
"$.data.status": "completed"
|
||||
}
|
||||
},
|
||||
"continueOnFailure": true
|
||||
},
|
||||
{
|
||||
"id": "get_statement",
|
||||
"name": "결제 명세서 조회",
|
||||
"method": "GET",
|
||||
"endpoint": "/api/v1/payments/{{create_payment.payment_id}}/statement",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true
|
||||
}
|
||||
},
|
||||
"continueOnFailure": true
|
||||
},
|
||||
{
|
||||
"id": "cancel_payment",
|
||||
"name": "결제 취소 요청",
|
||||
"method": "POST",
|
||||
"endpoint": "/api/v1/payments/{{create_payment.payment_id}}/cancel",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"body": {
|
||||
"reason": "테스트 취소",
|
||||
"cancel_type": "full"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true
|
||||
}
|
||||
},
|
||||
"continueOnFailure": true
|
||||
},
|
||||
{
|
||||
"id": "create_payment_for_refund",
|
||||
"name": "환불 테스트용 결제 등록",
|
||||
"method": "POST",
|
||||
"endpoint": "/api/v1/payments",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"body": {
|
||||
"subscription_id": 1,
|
||||
"amount": 50000,
|
||||
"payment_method": "card",
|
||||
"billing_name": "환불 테스트",
|
||||
"billing_email": "refund@example.com"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200, 201],
|
||||
"jsonPath": {
|
||||
"$.success": true
|
||||
}
|
||||
},
|
||||
"extract": {
|
||||
"refund_payment_id": "$.data.id"
|
||||
},
|
||||
"continueOnFailure": true
|
||||
},
|
||||
{
|
||||
"id": "complete_refund_payment",
|
||||
"name": "환불 테스트용 결제 완료",
|
||||
"method": "POST",
|
||||
"endpoint": "/api/v1/payments/{{create_payment_for_refund.refund_payment_id}}/complete",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"body": {
|
||||
"transaction_id": "TXN_REFUND_{{$timestamp}}",
|
||||
"pg_response": {
|
||||
"code": "0000",
|
||||
"message": "성공"
|
||||
}
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true
|
||||
}
|
||||
},
|
||||
"continueOnFailure": true
|
||||
},
|
||||
{
|
||||
"id": "request_refund",
|
||||
"name": "환불 요청",
|
||||
"method": "POST",
|
||||
"endpoint": "/api/v1/payments/{{create_payment_for_refund.refund_payment_id}}/refund",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"body": {
|
||||
"refund_amount": 50000,
|
||||
"reason": "테스트 환불",
|
||||
"refund_method": "original"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true
|
||||
}
|
||||
},
|
||||
"continueOnFailure": true
|
||||
},
|
||||
{
|
||||
"id": "verify_refunded",
|
||||
"name": "환불 상태 확인",
|
||||
"method": "GET",
|
||||
"endpoint": "/api/v1/payments/{{create_payment_for_refund.refund_payment_id}}",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true,
|
||||
"$.data.status": "refunded"
|
||||
}
|
||||
},
|
||||
"continueOnFailure": true
|
||||
}
|
||||
]
|
||||
}
|
||||
188
plans/flow-tests/popup-flow.json
Normal file
188
plans/flow-tests/popup-flow.json
Normal file
@@ -0,0 +1,188 @@
|
||||
{
|
||||
"name": "팝업 관리 플로우",
|
||||
"description": "팝업 등록 → 목록 조회 → 활성 팝업 조회 → 수정 → 삭제 플로우 테스트",
|
||||
"version": "1.0",
|
||||
"config": {
|
||||
"baseUrl": "",
|
||||
"timeout": 30000,
|
||||
"stopOnFailure": true
|
||||
},
|
||||
"variables": {
|
||||
"user_id": "{{$env.FLOW_TESTER_USER_ID}}",
|
||||
"user_pwd": "{{$env.FLOW_TESTER_USER_PWD}}"
|
||||
},
|
||||
"steps": [
|
||||
{
|
||||
"id": "login",
|
||||
"name": "로그인",
|
||||
"method": "POST",
|
||||
"endpoint": "/api/v1/login",
|
||||
"body": {
|
||||
"user_id": "{{user_id}}",
|
||||
"user_pwd": "{{user_pwd}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.access_token": "@isString"
|
||||
}
|
||||
},
|
||||
"extract": {
|
||||
"token": "$.access_token"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "list_popups",
|
||||
"name": "팝업 목록 조회",
|
||||
"method": "GET",
|
||||
"endpoint": "/api/v1/popups",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"queryParams": {
|
||||
"page": 1,
|
||||
"per_page": 10
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "create_popup",
|
||||
"name": "팝업 등록",
|
||||
"method": "POST",
|
||||
"endpoint": "/api/v1/popups",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"body": {
|
||||
"title": "Flow 테스트 팝업",
|
||||
"content": "<p>테스트용 팝업 콘텐츠입니다.</p>",
|
||||
"popup_type": "notice",
|
||||
"position": "center",
|
||||
"width": 500,
|
||||
"height": 400,
|
||||
"start_at": "2025-01-01T00:00:00Z",
|
||||
"end_at": "2025-12-31T23:59:59Z",
|
||||
"is_active": true,
|
||||
"show_today_close": true,
|
||||
"target_pages": ["dashboard", "main"]
|
||||
},
|
||||
"expect": {
|
||||
"status": [200, 201],
|
||||
"jsonPath": {
|
||||
"$.success": true
|
||||
}
|
||||
},
|
||||
"extract": {
|
||||
"popup_id": "$.data.id"
|
||||
},
|
||||
"continueOnFailure": true
|
||||
},
|
||||
{
|
||||
"id": "get_popup_detail",
|
||||
"name": "팝업 상세 조회",
|
||||
"method": "GET",
|
||||
"endpoint": "/api/v1/popups/{{create_popup.popup_id}}",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true,
|
||||
"$.data.id": "@isNumber"
|
||||
}
|
||||
},
|
||||
"continueOnFailure": true
|
||||
},
|
||||
{
|
||||
"id": "get_active_popups",
|
||||
"name": "활성 팝업 목록 조회",
|
||||
"description": "현재 표시 중인 팝업 목록",
|
||||
"method": "GET",
|
||||
"endpoint": "/api/v1/popups/active",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"queryParams": {
|
||||
"page": "dashboard"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "update_popup",
|
||||
"name": "팝업 수정",
|
||||
"method": "PUT",
|
||||
"endpoint": "/api/v1/popups/{{create_popup.popup_id}}",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"body": {
|
||||
"title": "Flow 테스트 팝업 (수정됨)",
|
||||
"is_active": false
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true
|
||||
}
|
||||
},
|
||||
"continueOnFailure": true
|
||||
},
|
||||
{
|
||||
"id": "verify_inactive",
|
||||
"name": "비활성 상태 확인",
|
||||
"method": "GET",
|
||||
"endpoint": "/api/v1/popups/{{create_popup.popup_id}}",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true,
|
||||
"$.data.is_active": false
|
||||
}
|
||||
},
|
||||
"continueOnFailure": true
|
||||
},
|
||||
{
|
||||
"id": "delete_popup",
|
||||
"name": "팝업 삭제",
|
||||
"method": "DELETE",
|
||||
"endpoint": "/api/v1/popups/{{create_popup.popup_id}}",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true
|
||||
}
|
||||
},
|
||||
"continueOnFailure": true
|
||||
},
|
||||
{
|
||||
"id": "verify_deleted",
|
||||
"name": "삭제 확인",
|
||||
"method": "GET",
|
||||
"endpoint": "/api/v1/popups/{{create_popup.popup_id}}",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [404]
|
||||
},
|
||||
"continueOnFailure": true
|
||||
}
|
||||
]
|
||||
}
|
||||
277
plans/flow-tests/pricing-crud-flow.json
Normal file
277
plans/flow-tests/pricing-crud-flow.json
Normal file
@@ -0,0 +1,277 @@
|
||||
{
|
||||
"name": "단가 관리 CRUD 테스트",
|
||||
"description": "단가(Pricing) API의 생성, 조회, 수정, 확정, 삭제 전체 플로우 테스트",
|
||||
"version": "1.0",
|
||||
"config": {
|
||||
"baseUrl": "",
|
||||
"timeout": 30000,
|
||||
"stopOnFailure": true
|
||||
},
|
||||
"variables": {
|
||||
"user_id": "{{$env.FLOW_TESTER_USER_ID}}",
|
||||
"user_pwd": "{{$env.FLOW_TESTER_USER_PWD}}"
|
||||
},
|
||||
"steps": [
|
||||
{
|
||||
"id": "login",
|
||||
"name": "로그인",
|
||||
"method": "POST",
|
||||
"endpoint": "/login",
|
||||
"body": {
|
||||
"user_id": "{{user_id}}",
|
||||
"user_pwd": "{{user_pwd}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.message": "로그인 성공",
|
||||
"$.access_token": "@isString"
|
||||
}
|
||||
},
|
||||
"extract": {
|
||||
"token": "$.access_token"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "list_prices",
|
||||
"name": "단가 목록 조회",
|
||||
"method": "GET",
|
||||
"endpoint": "/pricing",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"query": {
|
||||
"per_page": 10,
|
||||
"page": 1
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true,
|
||||
"$.data.data": "@isArray"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "create_price",
|
||||
"name": "단가 생성 (MATERIAL)",
|
||||
"method": "POST",
|
||||
"endpoint": "/pricing",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"body": {
|
||||
"item_type_code": "MATERIAL",
|
||||
"item_id": 1,
|
||||
"client_group_id": null,
|
||||
"purchase_price": 10000,
|
||||
"processing_cost": 500,
|
||||
"loss_rate": 5,
|
||||
"margin_rate": 20,
|
||||
"sales_price": 12600,
|
||||
"rounding_rule": "round",
|
||||
"rounding_unit": 100,
|
||||
"supplier": "테스트 공급업체",
|
||||
"effective_from": "2025-01-01",
|
||||
"effective_to": "2025-12-31",
|
||||
"note": "API Flow 테스트용 단가",
|
||||
"status": "draft"
|
||||
},
|
||||
"expect": {
|
||||
"status": [201],
|
||||
"jsonPath": {
|
||||
"$.success": true,
|
||||
"$.data.id": "@isNumber",
|
||||
"$.data.item_type_code": "MATERIAL",
|
||||
"$.data.purchase_price": 10000,
|
||||
"$.data.status": "draft"
|
||||
}
|
||||
},
|
||||
"extract": {
|
||||
"price_id": "$.data.id"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "show_price",
|
||||
"name": "생성된 단가 상세 조회",
|
||||
"method": "GET",
|
||||
"endpoint": "/pricing/{{create_price.price_id}}",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true,
|
||||
"$.data.id": "{{create_price.price_id}}",
|
||||
"$.data.item_type_code": "MATERIAL",
|
||||
"$.data.supplier": "테스트 공급업체"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "update_price",
|
||||
"name": "단가 수정 (가격 변경)",
|
||||
"method": "PUT",
|
||||
"endpoint": "/pricing/{{create_price.price_id}}",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"body": {
|
||||
"purchase_price": 11000,
|
||||
"processing_cost": 600,
|
||||
"margin_rate": 25,
|
||||
"sales_price": 14500,
|
||||
"note": "단가 수정 테스트",
|
||||
"change_reason": "원가 인상으로 인한 가격 조정",
|
||||
"status": "active"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true,
|
||||
"$.data.purchase_price": 11000,
|
||||
"$.data.processing_cost": 600,
|
||||
"$.data.status": "active"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "get_revisions",
|
||||
"name": "변경 이력 조회",
|
||||
"method": "GET",
|
||||
"endpoint": "/pricing/{{create_price.price_id}}/revisions",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true,
|
||||
"$.data": "@isArray"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "get_cost",
|
||||
"name": "원가 조회 (receipt > standard 폴백)",
|
||||
"method": "GET",
|
||||
"endpoint": "/pricing/cost",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"query": {
|
||||
"item_type_code": "MATERIAL",
|
||||
"item_id": 1,
|
||||
"date": "2025-06-15"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true,
|
||||
"$.data.item_type_code": "MATERIAL",
|
||||
"$.data.item_id": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "by_items",
|
||||
"name": "다중 품목 단가 조회",
|
||||
"method": "POST",
|
||||
"endpoint": "/pricing/by-items",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"body": {
|
||||
"items": [
|
||||
{
|
||||
"item_type_code": "MATERIAL",
|
||||
"item_id": 1
|
||||
}
|
||||
],
|
||||
"date": "2025-06-15"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true,
|
||||
"$.data": "@isArray"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "create_price_for_finalize",
|
||||
"name": "확정 테스트용 단가 생성",
|
||||
"method": "POST",
|
||||
"endpoint": "/pricing",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"body": {
|
||||
"item_type_code": "PRODUCT",
|
||||
"item_id": 1,
|
||||
"purchase_price": 50000,
|
||||
"sales_price": 70000,
|
||||
"effective_from": "2025-01-01",
|
||||
"status": "active"
|
||||
},
|
||||
"expect": {
|
||||
"status": [201],
|
||||
"jsonPath": {
|
||||
"$.success": true,
|
||||
"$.data.id": "@isNumber"
|
||||
}
|
||||
},
|
||||
"extract": {
|
||||
"finalize_price_id": "$.data.id"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "finalize_price",
|
||||
"name": "가격 확정 (불변 처리)",
|
||||
"method": "POST",
|
||||
"endpoint": "/pricing/{{create_price_for_finalize.finalize_price_id}}/finalize",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true,
|
||||
"$.data.status": "finalized",
|
||||
"$.data.is_final": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "delete_price",
|
||||
"name": "단가 삭제 (soft delete)",
|
||||
"method": "DELETE",
|
||||
"endpoint": "/pricing/{{create_price.price_id}}",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "verify_deleted",
|
||||
"name": "삭제된 단가 조회 시 404 확인",
|
||||
"method": "GET",
|
||||
"endpoint": "/pricing/{{create_price.price_id}}",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [404],
|
||||
"jsonPath": {
|
||||
"$.success": false
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
201
plans/flow-tests/sales-statement-flow.json
Normal file
201
plans/flow-tests/sales-statement-flow.json
Normal file
@@ -0,0 +1,201 @@
|
||||
{
|
||||
"name": "매출 명세서 플로우",
|
||||
"description": "매출 목록 조회 → 매출 상세 → 명세서 생성 → 확정 → 발송 플로우 테스트",
|
||||
"version": "1.0",
|
||||
"config": {
|
||||
"baseUrl": "",
|
||||
"timeout": 30000,
|
||||
"stopOnFailure": true
|
||||
},
|
||||
"variables": {
|
||||
"user_id": "{{$env.FLOW_TESTER_USER_ID}}",
|
||||
"user_pwd": "{{$env.FLOW_TESTER_USER_PWD}}"
|
||||
},
|
||||
"steps": [
|
||||
{
|
||||
"id": "login",
|
||||
"name": "로그인",
|
||||
"method": "POST",
|
||||
"endpoint": "/api/v1/login",
|
||||
"body": {
|
||||
"user_id": "{{user_id}}",
|
||||
"user_pwd": "{{user_pwd}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.access_token": "@isString"
|
||||
}
|
||||
},
|
||||
"extract": {
|
||||
"token": "$.access_token"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "list_sales",
|
||||
"name": "매출 목록 조회",
|
||||
"method": "GET",
|
||||
"endpoint": "/api/v1/sales",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"queryParams": {
|
||||
"page": 1,
|
||||
"per_page": 10
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true
|
||||
}
|
||||
},
|
||||
"extract": {
|
||||
"first_sale_id": "$.data.data[0].id"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "get_sale_detail",
|
||||
"name": "매출 상세 조회",
|
||||
"method": "GET",
|
||||
"endpoint": "/api/v1/sales/{{list_sales.first_sale_id}}",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true,
|
||||
"$.data.id": "@isNumber"
|
||||
}
|
||||
},
|
||||
"extract": {
|
||||
"sale_data": "$.data"
|
||||
},
|
||||
"continueOnFailure": true
|
||||
},
|
||||
{
|
||||
"id": "create_sale",
|
||||
"name": "매출 등록",
|
||||
"method": "POST",
|
||||
"endpoint": "/api/v1/sales",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"body": {
|
||||
"client_id": 1,
|
||||
"sale_date": "2025-01-15",
|
||||
"due_date": "2025-02-15",
|
||||
"items": [
|
||||
{
|
||||
"product_id": 1,
|
||||
"quantity": 10,
|
||||
"unit_price": 50000,
|
||||
"description": "테스트 상품"
|
||||
}
|
||||
],
|
||||
"memo": "Flow 테스트용 매출"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200, 201],
|
||||
"jsonPath": {
|
||||
"$.success": true
|
||||
}
|
||||
},
|
||||
"extract": {
|
||||
"new_sale_id": "$.data.id"
|
||||
},
|
||||
"continueOnFailure": true
|
||||
},
|
||||
{
|
||||
"id": "get_statement",
|
||||
"name": "매출 명세서 조회",
|
||||
"method": "GET",
|
||||
"endpoint": "/api/v1/sales/{{create_sale.new_sale_id}}/statement",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true
|
||||
}
|
||||
},
|
||||
"continueOnFailure": true
|
||||
},
|
||||
{
|
||||
"id": "confirm_sale",
|
||||
"name": "매출 확정",
|
||||
"method": "POST",
|
||||
"endpoint": "/api/v1/sales/{{create_sale.new_sale_id}}/confirm",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"body": {
|
||||
"confirmed_at": "2025-01-15T10:00:00Z"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true
|
||||
}
|
||||
},
|
||||
"continueOnFailure": true
|
||||
},
|
||||
{
|
||||
"id": "send_statement",
|
||||
"name": "명세서 발송",
|
||||
"method": "POST",
|
||||
"endpoint": "/api/v1/sales/{{create_sale.new_sale_id}}/send",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"body": {
|
||||
"send_type": "email",
|
||||
"recipient_email": "test@example.com",
|
||||
"message": "매출 명세서를 발송합니다."
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true
|
||||
}
|
||||
},
|
||||
"continueOnFailure": true
|
||||
},
|
||||
{
|
||||
"id": "update_sale",
|
||||
"name": "매출 수정",
|
||||
"method": "PUT",
|
||||
"endpoint": "/api/v1/sales/{{create_sale.new_sale_id}}",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"body": {
|
||||
"memo": "Flow 테스트 - 수정됨"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true
|
||||
}
|
||||
},
|
||||
"continueOnFailure": true
|
||||
},
|
||||
{
|
||||
"id": "delete_sale",
|
||||
"name": "매출 삭제",
|
||||
"method": "DELETE",
|
||||
"endpoint": "/api/v1/sales/{{create_sale.new_sale_id}}",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true
|
||||
}
|
||||
},
|
||||
"continueOnFailure": true
|
||||
}
|
||||
]
|
||||
}
|
||||
260
plans/flow-tests/subscription-flow.json
Normal file
260
plans/flow-tests/subscription-flow.json
Normal file
@@ -0,0 +1,260 @@
|
||||
{
|
||||
"name": "구독 관리 플로우",
|
||||
"description": "구독 등록 → 사용량 조회 → 일시 정지 → 재개 → 갱신 → 해지 플로우 테스트",
|
||||
"version": "1.0",
|
||||
"config": {
|
||||
"baseUrl": "",
|
||||
"timeout": 30000,
|
||||
"stopOnFailure": true
|
||||
},
|
||||
"variables": {
|
||||
"user_id": "{{$env.FLOW_TESTER_USER_ID}}",
|
||||
"user_pwd": "{{$env.FLOW_TESTER_USER_PWD}}"
|
||||
},
|
||||
"steps": [
|
||||
{
|
||||
"id": "login",
|
||||
"name": "로그인",
|
||||
"method": "POST",
|
||||
"endpoint": "/api/v1/login",
|
||||
"body": {
|
||||
"user_id": "{{user_id}}",
|
||||
"user_pwd": "{{user_pwd}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.access_token": "@isString"
|
||||
}
|
||||
},
|
||||
"extract": {
|
||||
"token": "$.access_token"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "list_subscriptions",
|
||||
"name": "구독 목록 조회",
|
||||
"method": "GET",
|
||||
"endpoint": "/api/v1/subscriptions",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"queryParams": {
|
||||
"page": 1,
|
||||
"per_page": 10
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "create_subscription",
|
||||
"name": "구독 등록",
|
||||
"method": "POST",
|
||||
"endpoint": "/api/v1/subscriptions",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"body": {
|
||||
"plan_id": 1,
|
||||
"billing_cycle": "monthly",
|
||||
"start_date": "2025-01-01",
|
||||
"payment_method": "card",
|
||||
"auto_renew": true
|
||||
},
|
||||
"expect": {
|
||||
"status": [200, 201],
|
||||
"jsonPath": {
|
||||
"$.success": true
|
||||
}
|
||||
},
|
||||
"extract": {
|
||||
"subscription_id": "$.data.id"
|
||||
},
|
||||
"continueOnFailure": true
|
||||
},
|
||||
{
|
||||
"id": "get_subscription_detail",
|
||||
"name": "구독 상세 조회",
|
||||
"method": "GET",
|
||||
"endpoint": "/api/v1/subscriptions/{{create_subscription.subscription_id}}",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true,
|
||||
"$.data.id": "@isNumber"
|
||||
}
|
||||
},
|
||||
"continueOnFailure": true
|
||||
},
|
||||
{
|
||||
"id": "get_usage",
|
||||
"name": "사용량 조회",
|
||||
"method": "GET",
|
||||
"endpoint": "/api/v1/subscriptions/{{create_subscription.subscription_id}}/usage",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"queryParams": {
|
||||
"period": "current"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true
|
||||
}
|
||||
},
|
||||
"continueOnFailure": true
|
||||
},
|
||||
{
|
||||
"id": "suspend_subscription",
|
||||
"name": "구독 일시 정지",
|
||||
"method": "POST",
|
||||
"endpoint": "/api/v1/subscriptions/{{create_subscription.subscription_id}}/suspend",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"body": {
|
||||
"reason": "테스트용 일시 정지"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true
|
||||
}
|
||||
},
|
||||
"continueOnFailure": true
|
||||
},
|
||||
{
|
||||
"id": "verify_suspended",
|
||||
"name": "정지 상태 확인",
|
||||
"method": "GET",
|
||||
"endpoint": "/api/v1/subscriptions/{{create_subscription.subscription_id}}",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true,
|
||||
"$.data.status": "suspended"
|
||||
}
|
||||
},
|
||||
"continueOnFailure": true
|
||||
},
|
||||
{
|
||||
"id": "resume_subscription",
|
||||
"name": "구독 재개",
|
||||
"method": "POST",
|
||||
"endpoint": "/api/v1/subscriptions/{{create_subscription.subscription_id}}/resume",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true
|
||||
}
|
||||
},
|
||||
"continueOnFailure": true
|
||||
},
|
||||
{
|
||||
"id": "renew_subscription",
|
||||
"name": "구독 갱신",
|
||||
"method": "POST",
|
||||
"endpoint": "/api/v1/subscriptions/{{create_subscription.subscription_id}}/renew",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"body": {
|
||||
"extend_months": 1
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true
|
||||
}
|
||||
},
|
||||
"continueOnFailure": true
|
||||
},
|
||||
{
|
||||
"id": "update_subscription",
|
||||
"name": "구독 수정",
|
||||
"method": "PUT",
|
||||
"endpoint": "/api/v1/subscriptions/{{create_subscription.subscription_id}}",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"body": {
|
||||
"auto_renew": false
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true
|
||||
}
|
||||
},
|
||||
"continueOnFailure": true
|
||||
},
|
||||
{
|
||||
"id": "export_usage",
|
||||
"name": "사용량 내역 내보내기",
|
||||
"method": "GET",
|
||||
"endpoint": "/api/v1/subscriptions/{{create_subscription.subscription_id}}/export",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"queryParams": {
|
||||
"format": "xlsx",
|
||||
"period": "last_month"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200]
|
||||
},
|
||||
"continueOnFailure": true
|
||||
},
|
||||
{
|
||||
"id": "cancel_subscription",
|
||||
"name": "구독 해지",
|
||||
"method": "POST",
|
||||
"endpoint": "/api/v1/subscriptions/{{create_subscription.subscription_id}}/cancel",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"body": {
|
||||
"reason": "테스트 완료",
|
||||
"cancel_immediately": true
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true
|
||||
}
|
||||
},
|
||||
"continueOnFailure": true
|
||||
},
|
||||
{
|
||||
"id": "verify_cancelled",
|
||||
"name": "해지 상태 확인",
|
||||
"method": "GET",
|
||||
"endpoint": "/api/v1/subscriptions/{{create_subscription.subscription_id}}",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true,
|
||||
"$.data.status": "cancelled"
|
||||
}
|
||||
},
|
||||
"continueOnFailure": true
|
||||
}
|
||||
]
|
||||
}
|
||||
125
plans/flow-tests/user-invitation-flow.json
Normal file
125
plans/flow-tests/user-invitation-flow.json
Normal file
@@ -0,0 +1,125 @@
|
||||
{
|
||||
"name": "사용자 초대 CRUD 플로우",
|
||||
"description": "사용자 초대 발송 → 목록 조회 → 재발송 → 취소 전체 플로우 테스트",
|
||||
"version": "1.0",
|
||||
"config": {
|
||||
"baseUrl": "",
|
||||
"timeout": 30000,
|
||||
"stopOnFailure": true
|
||||
},
|
||||
"variables": {
|
||||
"user_id": "{{$env.FLOW_TESTER_USER_ID}}",
|
||||
"user_pwd": "{{$env.FLOW_TESTER_USER_PWD}}",
|
||||
"test_email": "flowtest_invite_{{$timestamp}}@example.com"
|
||||
},
|
||||
"steps": [
|
||||
{
|
||||
"id": "login",
|
||||
"name": "로그인",
|
||||
"method": "POST",
|
||||
"endpoint": "/api/v1/login",
|
||||
"body": {
|
||||
"user_id": "{{user_id}}",
|
||||
"user_pwd": "{{user_pwd}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.access_token": "@isString"
|
||||
}
|
||||
},
|
||||
"extract": {
|
||||
"token": "$.access_token"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "invite_user",
|
||||
"name": "사용자 초대 발송 (role=user)",
|
||||
"method": "POST",
|
||||
"endpoint": "/api/v1/users/invite",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"body": {
|
||||
"email": "{{test_email}}",
|
||||
"role": "user",
|
||||
"message": "SAM 시스템에 합류해 주세요!"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true,
|
||||
"$.data.status": "pending",
|
||||
"$.data.email": "@isString"
|
||||
}
|
||||
},
|
||||
"extract": {
|
||||
"invitation_id": "$.data.id",
|
||||
"invitation_token": "$.data.token",
|
||||
"invited_email": "$.data.email"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "list_pending",
|
||||
"name": "대기 중 초대 목록 조회",
|
||||
"method": "GET",
|
||||
"endpoint": "/api/v1/users/invitations?status=pending&per_page=10",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true,
|
||||
"$.data.data": "@isArray"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "resend_invitation",
|
||||
"name": "초대 재발송",
|
||||
"method": "POST",
|
||||
"endpoint": "/api/v1/users/invitations/{{invite_user.invitation_id}}/resend",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true,
|
||||
"$.data.status": "pending"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "cancel_invitation",
|
||||
"name": "초대 취소",
|
||||
"method": "DELETE",
|
||||
"endpoint": "/api/v1/users/invitations/{{invite_user.invitation_id}}",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "verify_cancelled",
|
||||
"name": "취소된 초대 목록 확인",
|
||||
"method": "GET",
|
||||
"endpoint": "/api/v1/users/invitations?status=cancelled&per_page=10",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": {
|
||||
"$.success": true
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
# MNG 견적수식 관리 개발 계획
|
||||
|
||||
> **작성일**: 2025-12-22
|
||||
> **상태**: 계획 수립
|
||||
> **상태**: ✅ 완료
|
||||
> **대상**: mng.sam.kr/quote-formulas
|
||||
|
||||
---
|
||||
@@ -24,15 +24,9 @@
|
||||
| 시뮬레이터 | ✅ 완료 | 입력값 → 계산 결과 미리보기 |
|
||||
| 변수 참조 | ✅ 완료 | 사용 가능한 변수 목록 표시 |
|
||||
| 수식 검증 | ✅ 완료 | 문법 검증 API |
|
||||
|
||||
#### 미구현/미완성 기능 (mng)
|
||||
|
||||
| 기능 | 상태 | 설명 |
|
||||
|-----|------|-----|
|
||||
| 범위(Range) 관리 UI | ❌ 미구현 | 범위별 결과 설정 화면 없음 |
|
||||
| 매핑(Mapping) 관리 UI | ❌ 미구현 | 매핑 규칙 설정 화면 없음 |
|
||||
| 품목(Item) 관리 UI | ❌ 미구현 | 출력 품목 설정 화면 없음 |
|
||||
| 5130 데이터 연동 | ❌ 미구현 | 레거시 가격 데이터 동기화 |
|
||||
| 범위(Range) 관리 UI | ✅ 완료 | 범위별 결과 설정 화면 (Phase 1) |
|
||||
| 매핑(Mapping) 관리 UI | ✅ 완료 | 매핑 규칙 설정 화면 (Phase 2) |
|
||||
| 품목(Item) 관리 UI | ✅ 완료 | 출력 품목 설정 화면 (Phase 3) |
|
||||
|
||||
### 1.2 API 프로젝트 현재 상태
|
||||
|
||||
@@ -52,7 +46,7 @@ QuoteFormulaCategory (카테고리)
|
||||
|-----|---------|-----|
|
||||
| QuoteFormulaCategorySeeder | 11개 | 카테고리 (오픈사이즈~단가수식) |
|
||||
| QuoteFormulaSeeder | 30개 수식, 18개 범위 | 스크린 계산 수식 |
|
||||
| QuoteFormulaItemSeeder | 25개 | 품목 마스터 (5130 가격 적용) |
|
||||
| QuoteFormulaItemSeeder | 25개 | 품목 마스터 |
|
||||
|
||||
#### 서비스 (api)
|
||||
|
||||
@@ -86,41 +80,26 @@ QuoteFormulaCategory (카테고리)
|
||||
|-----|-----|-----|-----|
|
||||
| 수식 CRUD | ✅ | ✅ | 동일 |
|
||||
| 카테고리 CRUD | ✅ | ✅ | 동일 |
|
||||
| **범위 관리 UI** | ❌ | ✅ (시더) | MNG에 UI 필요 |
|
||||
| **매핑 관리 UI** | ❌ | ✅ (시더) | MNG에 UI 필요 |
|
||||
| **품목 관리 UI** | ❌ | ✅ (시더) | MNG에 UI 필요 |
|
||||
| 범위 관리 UI | ✅ | ✅ (시더) | Phase 1 완료 |
|
||||
| 매핑 관리 UI | ✅ | ✅ (시더) | Phase 2 완료 |
|
||||
| 품목 관리 UI | ✅ | ✅ (시더) | Phase 3 완료 |
|
||||
| 시뮬레이터 | ✅ | ✅ | 동일 |
|
||||
| 자동산출 API | - | ✅ | API 전용 |
|
||||
|
||||
### 2.3 핵심 차이점
|
||||
|
||||
```
|
||||
MNG (관리 UI)
|
||||
├── 수식 기본 정보 관리 ✅
|
||||
├── 카테고리 관리 ✅
|
||||
├── 시뮬레이터 ✅
|
||||
└── 범위/매핑/품목 관리 ❌ ← 개발 필요
|
||||
|
||||
API (자동산출 엔진)
|
||||
├── 시더로 데이터 주입 ✅
|
||||
├── 자동산출 서비스 ✅
|
||||
└── 견적 생성 API ✅
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 개발 계획
|
||||
## 3. 개발 계획 (완료)
|
||||
|
||||
### 3.1 목표
|
||||
|
||||
MNG에서 **범위(Range), 매핑(Mapping), 품목(Item)** 관리 UI를 추가하여:
|
||||
1. 시더 없이도 관리자가 직접 수식 규칙 설정 가능
|
||||
2. 5130 레거시 데이터를 참조하여 가격 설정 가능
|
||||
2. SAM 자체 품목 마스터로 가격 설정
|
||||
3. 실시간 시뮬레이션으로 설정 검증 가능
|
||||
|
||||
### 3.2 개발 범위
|
||||
### 3.2 개발 범위 (완료)
|
||||
|
||||
#### Phase 1: 범위(Range) 관리 UI
|
||||
#### Phase 1: 범위(Range) 관리 UI ✅
|
||||
|
||||
**우선순위**: 높음
|
||||
**이유**: 모터, 가이드레일, 케이스 자동 선택에 필수
|
||||
@@ -160,7 +139,7 @@ DELETE /api/admin/quote-formulas/formulas/{id}/ranges/{rangeId}
|
||||
POST /api/admin/quote-formulas/formulas/{id}/ranges/reorder
|
||||
```
|
||||
|
||||
#### Phase 2: 매핑(Mapping) 관리 UI
|
||||
#### Phase 2: 매핑(Mapping) 관리 UI ✅
|
||||
|
||||
**우선순위**: 중간
|
||||
**이유**: 제어기 유형 등 코드 매핑에 사용
|
||||
@@ -185,7 +164,7 @@ POST /api/admin/quote-formulas/formulas/{id}/ranges/reorder
|
||||
└── [+ 매핑 추가]
|
||||
```
|
||||
|
||||
#### Phase 3: 품목(Item) 관리 UI
|
||||
#### Phase 3: 품목(Item) 관리 UI ✅
|
||||
|
||||
**우선순위**: 중간
|
||||
**이유**: 수식 결과로 생성되는 품목 정의
|
||||
@@ -195,7 +174,7 @@ POST /api/admin/quote-formulas/formulas/{id}/ranges/reorder
|
||||
2. 품목 목록 표시
|
||||
3. 품목 추가/수정/삭제
|
||||
4. 수량/단가 수식 입력
|
||||
5. 5130 품목 검색 및 가격 참조
|
||||
5. SAM 품목 마스터에서 가격 참조
|
||||
|
||||
**화면 설계**:
|
||||
```
|
||||
@@ -207,120 +186,45 @@ POST /api/admin/quote-formulas/formulas/{id}/ranges/reorder
|
||||
│ │ PT-MOTOR-150 │ 개폐전동기 150kg│ 150K(S) │ 1 │ 285000│
|
||||
│ │ PT-GR-3000 │ 가이드레일 3000 │ 3000mm │ 2 │ 42000 │
|
||||
│ └───────────────────────────────────────────────────────────┘
|
||||
├── [+ 품목 추가]
|
||||
└── [5130에서 가져오기] ← 레거시 연동
|
||||
└── [+ 품목 추가]
|
||||
```
|
||||
|
||||
#### Phase 4: 5130 데이터 연동
|
||||
|
||||
**우선순위**: 높음
|
||||
**이유**: 실제 가격 데이터 필요
|
||||
|
||||
**기능 목록**:
|
||||
1. 5130 DB 가격 테이블 조회 API
|
||||
2. 품목 추가 시 5130 검색 모달
|
||||
3. 가격 동기화 기능
|
||||
4. 가격 변경 이력 관리
|
||||
|
||||
**5130 테이블 참조**:
|
||||
```sql
|
||||
-- chandj.price_motor: 모터 가격
|
||||
-- chandj.price_*: 기타 품목 가격
|
||||
```
|
||||
|
||||
### 3.3 파일 수정/추가 목록
|
||||
|
||||
#### Routes (mng/routes/web.php)
|
||||
```php
|
||||
// 기존 라우트에 추가
|
||||
Route::prefix('quote-formulas')->group(function () {
|
||||
// ... 기존 라우트
|
||||
Route::get('/{id}/ranges', [QuoteFormulaController::class, 'ranges']);
|
||||
Route::get('/{id}/mappings', [QuoteFormulaController::class, 'mappings']);
|
||||
Route::get('/{id}/items', [QuoteFormulaController::class, 'items']);
|
||||
});
|
||||
```
|
||||
|
||||
#### API Routes (mng/routes/api.php)
|
||||
```php
|
||||
// 범위/매핑/품목 CRUD API
|
||||
Route::prefix('quote-formulas/formulas/{formulaId}')->group(function () {
|
||||
Route::apiResource('ranges', QuoteFormulaRangeController::class);
|
||||
Route::post('ranges/reorder', [QuoteFormulaRangeController::class, 'reorder']);
|
||||
|
||||
Route::apiResource('mappings', QuoteFormulaMappingController::class);
|
||||
|
||||
Route::apiResource('items', QuoteFormulaItemController::class);
|
||||
});
|
||||
|
||||
// 5130 연동
|
||||
Route::prefix('legacy')->group(function () {
|
||||
Route::get('items/search', [LegacyController::class, 'searchItems']);
|
||||
Route::get('prices/{itemCode}', [LegacyController::class, 'getPrice']);
|
||||
});
|
||||
```
|
||||
### 3.3 파일 구조 (구현 완료)
|
||||
|
||||
#### Controllers
|
||||
```
|
||||
app/Http/Controllers/
|
||||
├── QuoteFormulaController.php (수정: 탭 추가)
|
||||
└── Api/Admin/Quote/
|
||||
├── QuoteFormulaRangeController.php (신규)
|
||||
├── QuoteFormulaMappingController.php (신규)
|
||||
├── QuoteFormulaItemController.php (신규)
|
||||
└── LegacyController.php (신규: 5130 연동)
|
||||
├── QuoteFormulaController.php
|
||||
├── QuoteFormulaRangeController.php ✅
|
||||
├── QuoteFormulaMappingController.php ✅
|
||||
├── QuoteFormulaItemController.php ✅
|
||||
└── QuoteFormulaCategoryController.php
|
||||
```
|
||||
|
||||
#### Services
|
||||
```
|
||||
app/Services/
|
||||
├── Quote/
|
||||
│ ├── QuoteFormulaRangeService.php (신규)
|
||||
│ ├── QuoteFormulaMappingService.php (신규)
|
||||
│ └── QuoteFormulaItemService.php (신규)
|
||||
└── Legacy/
|
||||
└── LegacyPriceService.php (신규: 5130 가격 조회)
|
||||
app/Services/Quote/
|
||||
├── QuoteFormulaService.php
|
||||
├── QuoteFormulaRangeService.php ✅
|
||||
├── QuoteFormulaMappingService.php ✅
|
||||
├── QuoteFormulaItemService.php ✅
|
||||
└── QuoteFormulaCategoryService.php
|
||||
```
|
||||
|
||||
#### Views
|
||||
```
|
||||
resources/views/quote-formulas/
|
||||
├── edit.blade.php (수정: 탭 구조로 변경)
|
||||
├── partials/
|
||||
│ ├── ranges-tab.blade.php (신규)
|
||||
│ ├── mappings-tab.blade.php (신규)
|
||||
│ └── items-tab.blade.php (신규)
|
||||
└── modals/
|
||||
├── range-form.blade.php (신규)
|
||||
├── mapping-form.blade.php (신규)
|
||||
├── item-form.blade.php (신규)
|
||||
└── legacy-item-search.blade.php (신규)
|
||||
```
|
||||
|
||||
### 3.4 개발 순서
|
||||
|
||||
```
|
||||
Phase 1: 범위 관리 (1주)
|
||||
├── Day 1-2: API 엔드포인트 구현
|
||||
├── Day 3-4: UI 컴포넌트 구현
|
||||
└── Day 5: 테스트 및 검증
|
||||
|
||||
Phase 2: 매핑 관리 (0.5주)
|
||||
├── Day 1: API 구현
|
||||
└── Day 2-3: UI 구현
|
||||
|
||||
Phase 3: 품목 관리 (0.5주)
|
||||
├── Day 1: API 구현
|
||||
└── Day 2-3: UI 구현
|
||||
|
||||
Phase 4: 5130 연동 (1주)
|
||||
├── Day 1-2: 레거시 DB 조회 서비스
|
||||
├── Day 3-4: 검색 모달 UI
|
||||
└── Day 5: 가격 동기화 기능
|
||||
|
||||
통합 테스트 (0.5주)
|
||||
├── 시뮬레이터 연동 테스트
|
||||
└── 전체 플로우 검증
|
||||
├── index.blade.php
|
||||
├── create.blade.php
|
||||
├── edit.blade.php (수정: 탭 구조)
|
||||
├── simulator.blade.php
|
||||
└── partials/
|
||||
├── basic-info-tab.blade.php ✅
|
||||
├── ranges-tab.blade.php ✅
|
||||
├── mappings-tab.blade.php ✅
|
||||
└── items-tab.blade.php ✅
|
||||
```
|
||||
|
||||
---
|
||||
@@ -336,30 +240,17 @@ Phase 4: 5130 연동 (1주)
|
||||
### 4.2 Backend (MNG)
|
||||
- **Framework**: Laravel 12
|
||||
- **ORM**: Eloquent
|
||||
- **DB**: MySQL (samdb + chandj)
|
||||
- **DB**: MySQL (samdb)
|
||||
- **Auth**: Session 기반
|
||||
|
||||
### 4.3 API 연동
|
||||
- MNG 내부 API (`/api/admin/quote-formulas/*`)
|
||||
- 5130 DB 직접 조회 (chandj 데이터베이스)
|
||||
|
||||
---
|
||||
|
||||
## 5. 데이터 마이그레이션
|
||||
## 5. 검증 계획
|
||||
|
||||
### 5.1 현재 상태
|
||||
- API 시더로 30개 수식, 18개 범위, 25개 품목 등록됨
|
||||
- 5130 실제 가격 데이터 반영 완료
|
||||
|
||||
### 5.2 마이그레이션 계획
|
||||
1. **Phase 1 완료 후**: 시더 데이터를 MNG UI로 확인 가능
|
||||
2. **Phase 4 완료 후**: 5130 데이터 자동 동기화
|
||||
|
||||
---
|
||||
|
||||
## 6. 검증 계획
|
||||
|
||||
### 6.1 시뮬레이터 테스트
|
||||
### 5.1 시뮬레이터 테스트
|
||||
```
|
||||
입력: W0=3000, H0=2500
|
||||
예상 결과:
|
||||
@@ -368,22 +259,31 @@ Phase 4: 5130 연동 (1주)
|
||||
- MOTOR: PT-MOTOR-150 (K=41.21kg)
|
||||
```
|
||||
|
||||
### 6.2 CRUD 테스트
|
||||
### 5.2 CRUD 테스트
|
||||
- 범위 추가/수정/삭제 후 시뮬레이터 결과 확인
|
||||
- 품목 가격 변경 후 합계 확인
|
||||
|
||||
---
|
||||
|
||||
## 7. 참고 자료
|
||||
## 6. 참고 자료
|
||||
|
||||
### 7.1 기존 파일 위치 (MNG)
|
||||
### 6.1 파일 위치 (MNG)
|
||||
```
|
||||
mng/
|
||||
├── app/Http/Controllers/
|
||||
│ ├── QuoteFormulaController.php
|
||||
│ └── Api/Admin/Quote/QuoteFormulaController.php
|
||||
│ └── Api/Admin/Quote/
|
||||
│ ├── QuoteFormulaController.php
|
||||
│ ├── QuoteFormulaRangeController.php
|
||||
│ ├── QuoteFormulaMappingController.php
|
||||
│ ├── QuoteFormulaItemController.php
|
||||
│ └── QuoteFormulaCategoryController.php
|
||||
├── app/Services/Quote/
|
||||
│ └── QuoteFormulaService.php
|
||||
│ ├── QuoteFormulaService.php
|
||||
│ ├── QuoteFormulaRangeService.php
|
||||
│ ├── QuoteFormulaMappingService.php
|
||||
│ ├── QuoteFormulaItemService.php
|
||||
│ └── QuoteFormulaCategoryService.php
|
||||
├── app/Models/Quote/
|
||||
│ ├── QuoteFormula.php
|
||||
│ ├── QuoteFormulaCategory.php
|
||||
@@ -394,10 +294,15 @@ mng/
|
||||
├── index.blade.php
|
||||
├── create.blade.php
|
||||
├── edit.blade.php
|
||||
└── simulator.blade.php
|
||||
├── simulator.blade.php
|
||||
└── partials/
|
||||
├── basic-info-tab.blade.php
|
||||
├── ranges-tab.blade.php
|
||||
├── mappings-tab.blade.php
|
||||
└── items-tab.blade.php
|
||||
```
|
||||
|
||||
### 7.2 API 시더 위치
|
||||
### 6.2 API 시더 위치
|
||||
```
|
||||
api/database/seeders/
|
||||
├── QuoteFormulaCategorySeeder.php
|
||||
@@ -405,35 +310,11 @@ api/database/seeders/
|
||||
└── QuoteFormulaItemSeeder.php
|
||||
```
|
||||
|
||||
### 7.3 5130 가격 테이블
|
||||
```sql
|
||||
chandj.price_motor -- 모터 가격
|
||||
chandj.price_* -- 기타 품목 가격 (확인 필요)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 리스크 및 대응
|
||||
## 7. 코딩 컨벤션 및 예시 코드
|
||||
|
||||
| 리스크 | 영향 | 대응 |
|
||||
|-------|-----|-----|
|
||||
| 5130 DB 스키마 변경 | 중 | JSON 파싱 로직 유연하게 구현 |
|
||||
| MNG-API 데이터 불일치 | 높 | 동일 DB 사용으로 해결됨 |
|
||||
| 시뮬레이터 성능 저하 | 낮 | 수식 캐싱 적용 |
|
||||
|
||||
---
|
||||
|
||||
## 9. 다음 단계
|
||||
|
||||
1. **승인 요청**: 이 계획에 대한 검토 및 승인
|
||||
2. **Phase 1 착수**: 범위 관리 UI 개발 시작
|
||||
3. **주간 리뷰**: 진행 상황 점검
|
||||
|
||||
---
|
||||
|
||||
## 10. 코딩 컨벤션 및 예시 코드
|
||||
|
||||
### 10.1 API Controller 패턴 (MNG)
|
||||
### 7.1 API Controller 패턴 (MNG)
|
||||
|
||||
```php
|
||||
<?php
|
||||
@@ -541,7 +422,7 @@ class QuoteFormulaRangeController extends Controller
|
||||
}
|
||||
```
|
||||
|
||||
### 10.2 Service 패턴 (MNG)
|
||||
### 7.2 Service 패턴 (MNG)
|
||||
|
||||
```php
|
||||
<?php
|
||||
@@ -611,284 +492,7 @@ class QuoteFormulaRangeService
|
||||
}
|
||||
```
|
||||
|
||||
### 10.3 Model 구조 (참조용)
|
||||
|
||||
```php
|
||||
<?php
|
||||
// 파일: app/Models/Quote/QuoteFormulaRange.php (기존)
|
||||
|
||||
class QuoteFormulaRange extends Model
|
||||
{
|
||||
protected $table = 'quote_formula_ranges';
|
||||
|
||||
protected $fillable = [
|
||||
'formula_id',
|
||||
'min_value',
|
||||
'max_value',
|
||||
'condition_variable',
|
||||
'result_value', // JSON 저장 가능: {"value": "150K", "item_code": "PT-MOTOR-150"}
|
||||
'result_type', // 'fixed' | 'formula'
|
||||
'sort_order',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'min_value' => 'decimal:4',
|
||||
'max_value' => 'decimal:4',
|
||||
'sort_order' => 'integer',
|
||||
];
|
||||
|
||||
public function formula(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(QuoteFormula::class, 'formula_id');
|
||||
}
|
||||
|
||||
public function isInRange($value): bool
|
||||
{
|
||||
// min < value <= max 체크
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 10.4 Blade View 패턴 (HTMX + Alpine.js)
|
||||
|
||||
```blade
|
||||
{{-- 파일: resources/views/quote-formulas/partials/ranges-tab.blade.php --}}
|
||||
|
||||
<div x-data="rangesManager()" x-init="loadRanges()">
|
||||
{{-- 헤더 --}}
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h3 class="text-lg font-medium">범위 설정</h3>
|
||||
<button @click="openAddModal()" class="btn btn-primary btn-sm">
|
||||
+ 범위 추가
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{{-- 조건 변수 표시 --}}
|
||||
<div class="mb-4">
|
||||
<span class="text-sm text-gray-600">조건 변수:</span>
|
||||
<span class="font-mono bg-gray-100 px-2 py-1 rounded" x-text="conditionVariable || '-'"></span>
|
||||
</div>
|
||||
|
||||
{{-- 범위 목록 테이블 --}}
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table table-zebra w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="w-12">#</th>
|
||||
<th>최소값</th>
|
||||
<th>최대값</th>
|
||||
<th>결과값</th>
|
||||
<th>품목코드</th>
|
||||
<th class="w-24">액션</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<template x-for="(range, index) in ranges" :key="range.id">
|
||||
<tr>
|
||||
<td x-text="index + 1"></td>
|
||||
<td x-text="range.min_value ?? '-'"></td>
|
||||
<td x-text="range.max_value ?? '-'"></td>
|
||||
<td>
|
||||
<span class="font-mono" x-text="getResultDisplay(range)"></span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge badge-ghost" x-text="getItemCode(range) || '-'"></span>
|
||||
</td>
|
||||
<td>
|
||||
<button @click="editRange(range)" class="btn btn-ghost btn-xs">수정</button>
|
||||
<button @click="deleteRange(range.id)" class="btn btn-ghost btn-xs text-red-500">삭제</button>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{{-- 빈 상태 --}}
|
||||
<div x-show="ranges.length === 0" class="text-center py-8 text-gray-500">
|
||||
설정된 범위가 없습니다.
|
||||
</div>
|
||||
|
||||
{{-- 범위 추가/수정 모달 --}}
|
||||
<dialog id="rangeModal" class="modal">
|
||||
<div class="modal-box">
|
||||
<h3 class="font-bold text-lg" x-text="editingRange ? '범위 수정' : '범위 추가'"></h3>
|
||||
<form @submit.prevent="saveRange()">
|
||||
<div class="grid grid-cols-2 gap-4 mt-4">
|
||||
<div>
|
||||
<label class="label">최소값</label>
|
||||
<input type="number" step="0.0001" x-model="form.min_value"
|
||||
class="input input-bordered w-full">
|
||||
</div>
|
||||
<div>
|
||||
<label class="label">최대값</label>
|
||||
<input type="number" step="0.0001" x-model="form.max_value"
|
||||
class="input input-bordered w-full">
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<label class="label">결과값 (JSON)</label>
|
||||
<textarea x-model="form.result_value" rows="3"
|
||||
class="textarea textarea-bordered w-full font-mono"
|
||||
placeholder='{"value": "150K", "item_code": "PT-MOTOR-150"}'></textarea>
|
||||
</div>
|
||||
<div class="modal-action">
|
||||
<button type="button" @click="closeModal()" class="btn">취소</button>
|
||||
<button type="submit" class="btn btn-primary">저장</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</dialog>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function rangesManager() {
|
||||
return {
|
||||
ranges: [],
|
||||
conditionVariable: '',
|
||||
editingRange: null,
|
||||
form: {
|
||||
min_value: null,
|
||||
max_value: null,
|
||||
result_value: '',
|
||||
result_type: 'fixed',
|
||||
},
|
||||
|
||||
async loadRanges() {
|
||||
const formulaId = {{ $formula->id }};
|
||||
const res = await fetch(`/api/admin/quote-formulas/formulas/${formulaId}/ranges`);
|
||||
const data = await res.json();
|
||||
if (data.success) {
|
||||
this.ranges = data.data;
|
||||
if (this.ranges.length > 0) {
|
||||
this.conditionVariable = this.ranges[0].condition_variable;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
getResultDisplay(range) {
|
||||
try {
|
||||
const parsed = JSON.parse(range.result_value);
|
||||
return parsed.value || range.result_value;
|
||||
} catch {
|
||||
return range.result_value;
|
||||
}
|
||||
},
|
||||
|
||||
getItemCode(range) {
|
||||
try {
|
||||
const parsed = JSON.parse(range.result_value);
|
||||
return parsed.item_code;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
openAddModal() {
|
||||
this.editingRange = null;
|
||||
this.form = { min_value: null, max_value: null, result_value: '', result_type: 'fixed' };
|
||||
document.getElementById('rangeModal').showModal();
|
||||
},
|
||||
|
||||
editRange(range) {
|
||||
this.editingRange = range;
|
||||
this.form = { ...range };
|
||||
document.getElementById('rangeModal').showModal();
|
||||
},
|
||||
|
||||
closeModal() {
|
||||
document.getElementById('rangeModal').close();
|
||||
},
|
||||
|
||||
async saveRange() {
|
||||
const formulaId = {{ $formula->id }};
|
||||
const url = this.editingRange
|
||||
? `/api/admin/quote-formulas/formulas/${formulaId}/ranges/${this.editingRange.id}`
|
||||
: `/api/admin/quote-formulas/formulas/${formulaId}/ranges`;
|
||||
const method = this.editingRange ? 'PUT' : 'POST';
|
||||
|
||||
const res = await fetch(url, {
|
||||
method,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-TOKEN': '{{ csrf_token() }}',
|
||||
},
|
||||
body: JSON.stringify(this.form),
|
||||
});
|
||||
|
||||
const data = await res.json();
|
||||
if (data.success) {
|
||||
this.closeModal();
|
||||
await this.loadRanges();
|
||||
showToast(data.message, 'success');
|
||||
} else {
|
||||
showToast(data.message, 'error');
|
||||
}
|
||||
},
|
||||
|
||||
async deleteRange(rangeId) {
|
||||
if (!confirm('이 범위를 삭제하시겠습니까?')) return;
|
||||
|
||||
const formulaId = {{ $formula->id }};
|
||||
const res = await fetch(`/api/admin/quote-formulas/formulas/${formulaId}/ranges/${rangeId}`, {
|
||||
method: 'DELETE',
|
||||
headers: { 'X-CSRF-TOKEN': '{{ csrf_token() }}' },
|
||||
});
|
||||
|
||||
const data = await res.json();
|
||||
if (data.success) {
|
||||
await this.loadRanges();
|
||||
showToast(data.message, 'success');
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
### 10.5 DB 스키마 (참조용)
|
||||
|
||||
```sql
|
||||
-- quote_formula_ranges 테이블 (기존)
|
||||
CREATE TABLE `quote_formula_ranges` (
|
||||
`id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
`formula_id` BIGINT UNSIGNED NOT NULL,
|
||||
`min_value` DECIMAL(15,4) NULL COMMENT '최소값 (NULL=제한없음)',
|
||||
`max_value` DECIMAL(15,4) NULL COMMENT '최대값 (NULL=제한없음)',
|
||||
`condition_variable` VARCHAR(50) NOT NULL COMMENT '조건 변수 (K, H1, S 등)',
|
||||
`result_value` TEXT NOT NULL COMMENT '결과값 (JSON 또는 문자열)',
|
||||
`result_type` ENUM('fixed','formula') DEFAULT 'fixed' COMMENT '결과 유형',
|
||||
`sort_order` INT DEFAULT 0,
|
||||
`created_at` TIMESTAMP NULL,
|
||||
`updated_at` TIMESTAMP NULL,
|
||||
|
||||
INDEX `idx_formula_id` (`formula_id`),
|
||||
CONSTRAINT `fk_ranges_formula` FOREIGN KEY (`formula_id`)
|
||||
REFERENCES `quote_formulas`(`id`) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- quote_formula_items 테이블 (기존)
|
||||
CREATE TABLE `quote_formula_items` (
|
||||
`id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
`formula_id` BIGINT UNSIGNED NOT NULL,
|
||||
`item_code` VARCHAR(50) NOT NULL COMMENT '품목 코드',
|
||||
`item_name` VARCHAR(200) NOT NULL COMMENT '품목명',
|
||||
`specification` VARCHAR(200) NULL COMMENT '규격',
|
||||
`unit` VARCHAR(20) NOT NULL COMMENT '단위',
|
||||
`quantity_formula` VARCHAR(500) NOT NULL COMMENT '수량 계산식',
|
||||
`unit_price_formula` VARCHAR(500) NULL COMMENT '단가 계산식 (NULL=마스터 참조)',
|
||||
`sort_order` INT DEFAULT 0,
|
||||
`created_at` TIMESTAMP NULL,
|
||||
`updated_at` TIMESTAMP NULL,
|
||||
|
||||
INDEX `idx_formula_id` (`formula_id`),
|
||||
INDEX `idx_item_code` (`item_code`),
|
||||
CONSTRAINT `fk_items_formula` FOREIGN KEY (`formula_id`)
|
||||
REFERENCES `quote_formulas`(`id`) ON DELETE CASCADE
|
||||
);
|
||||
```
|
||||
|
||||
### 10.6 API 응답 형식
|
||||
### 7.3 API 응답 형식
|
||||
|
||||
```json
|
||||
// 성공 응답
|
||||
@@ -924,29 +528,26 @@ CREATE TABLE `quote_formula_items` (
|
||||
|
||||
---
|
||||
|
||||
## 11. 체크리스트 (새 세션용)
|
||||
## 8. 체크리스트 (완료)
|
||||
|
||||
### 개발 시작 전 확인
|
||||
### 개발 완료 확인
|
||||
|
||||
- [ ] mng 프로젝트 디렉토리 확인: `/Users/hskwon/Works/@KD_SAM/SAM/mng`
|
||||
- [ ] 기존 Controller 패턴 확인: `app/Http/Controllers/Api/Admin/Quote/QuoteFormulaController.php`
|
||||
- [ ] 기존 Model 확인: `app/Models/Quote/QuoteFormulaRange.php`
|
||||
- [ ] 기존 View 확인: `resources/views/quote-formulas/edit.blade.php`
|
||||
- [ ] DB 테이블 확인: `quote_formula_ranges`, `quote_formula_items`
|
||||
|
||||
### Phase 1 (범위 관리) 작업 순서
|
||||
|
||||
1. [ ] `QuoteFormulaRangeController.php` 생성
|
||||
2. [ ] `QuoteFormulaRangeService.php` 생성
|
||||
3. [ ] `routes/api.php`에 라우트 추가
|
||||
4. [ ] `edit.blade.php` 탭 구조로 수정
|
||||
5. [ ] `partials/ranges-tab.blade.php` 생성
|
||||
6. [ ] HTMX/Alpine.js 연동 테스트
|
||||
7. [ ] 시뮬레이터와 연동 확인
|
||||
- [x] mng 프로젝트 디렉토리: `/Users/hskwon/Works/@KD_SAM/SAM/mng`
|
||||
- [x] `QuoteFormulaRangeController.php` 생성
|
||||
- [x] `QuoteFormulaRangeService.php` 생성
|
||||
- [x] `QuoteFormulaMappingController.php` 생성
|
||||
- [x] `QuoteFormulaMappingService.php` 생성
|
||||
- [x] `QuoteFormulaItemController.php` 생성
|
||||
- [x] `QuoteFormulaItemService.php` 생성
|
||||
- [x] `routes/api.php`에 라우트 추가
|
||||
- [x] `edit.blade.php` 탭 구조로 수정
|
||||
- [x] `partials/ranges-tab.blade.php` 생성
|
||||
- [x] `partials/mappings-tab.blade.php` 생성
|
||||
- [x] `partials/items-tab.blade.php` 생성
|
||||
|
||||
---
|
||||
|
||||
*문서 버전*: 1.1
|
||||
*문서 버전*: 2.0
|
||||
*작성자*: Claude Code
|
||||
*검토자*: -
|
||||
*업데이트*: 코딩 컨벤션 및 예시 코드 추가 (2025-12-22)
|
||||
*최종 업데이트*: 2025-12-22 (Phase 1-3 완료, 5130 연동 제거)
|
||||
743
plans/quote-auto-calculation-development-plan.md
Normal file
743
plans/quote-auto-calculation-development-plan.md
Normal file
@@ -0,0 +1,743 @@
|
||||
# 견적 자동산출 개발 계획
|
||||
|
||||
> **작성일**: 2025-12-22
|
||||
> **상태**: ✅ 구현 완료
|
||||
> **목표**: MNG 견적수식 데이터 셋팅 + React 견적관리 자동산출 기능 구현
|
||||
> **완료일**: 2025-12-22
|
||||
> **실제 소요 시간**: 약 2시간
|
||||
|
||||
---
|
||||
|
||||
## 0. 빠른 시작 가이드
|
||||
|
||||
### 폴더 구조 이해 (중요!)
|
||||
|
||||
| 폴더 | 포트 | 역할 | 비고 |
|
||||
|------|------|------|------|
|
||||
| `design/` | localhost:3002 | 디자인 프로토타입 | UI 참고용 |
|
||||
| `react/` | localhost:3000 | **실제 프론트엔드** | 구현 대상 ✅ |
|
||||
| `mng/` | mng.sam.kr | 관리자 패널 | 수식 데이터 관리 |
|
||||
| `api/` | api.sam.kr | REST API | 견적 산출 엔진 |
|
||||
|
||||
### 이 문서만으로 작업을 시작하려면:
|
||||
|
||||
```bash
|
||||
# 1. Docker 서비스 시작
|
||||
cd /Users/hskwon/Works/@KD_SAM/SAM
|
||||
docker-compose up -d
|
||||
|
||||
# 2. MNG 시더 실행 (Phase 1 완료 후)
|
||||
cd mng
|
||||
php artisan quote:seed-formulas --tenant=1
|
||||
|
||||
# 3. React 개발 서버 (실제 구현 대상)
|
||||
cd react
|
||||
npm run dev
|
||||
# http://localhost:3000 접속
|
||||
```
|
||||
|
||||
### 핵심 파일 위치
|
||||
|
||||
| 구분 | 파일 경로 | 역할 |
|
||||
|------|----------|------|
|
||||
| **MNG 시더** | `mng/app/Console/Commands/SeedQuoteFormulasCommand.php` | 🆕 생성 필요 |
|
||||
| **React 자동산출** | `react/src/components/quotes/QuoteRegistration.tsx` | ⚡ 수정 필요 (line 332) |
|
||||
| **API 클라이언트** | `react/src/lib/api/client.ts` | 참조 |
|
||||
| **API 엔드포인트** | `api/app/Http/Controllers/Api/V1/QuoteController.php` | ✅ 구현됨 |
|
||||
| **수식 엔진** | `api/app/Services/Quote/QuoteCalculationService.php` | ✅ 구현됨 |
|
||||
|
||||
---
|
||||
|
||||
## 1. 현황 분석
|
||||
|
||||
### 1.1 시스템 구조
|
||||
|
||||
```
|
||||
┌───────────────────────────────────────────────────────────────────────────────┐
|
||||
│ SAM 시스템 │
|
||||
├───────────────────────────────────────────────────────────────────────────────┤
|
||||
│ MNG (mng.sam.kr) │ React (react/ 폴더) │ Design │
|
||||
│ ├── 기준정보관리 │ ├── 판매관리 │ (참고용) │
|
||||
│ │ └── 견적수식관리 ✅ │ │ └── 견적관리 │ │
|
||||
│ │ - 카테고리 CRUD │ │ └── 자동견적산출 │ design/ │
|
||||
│ │ - 수식 CRUD │ │ UI 있음 ✅ │ :3002 │
|
||||
│ │ - 범위/매핑/품목 탭 │ │ API 연동 ❌ │ │
|
||||
│ │ │ │ │ │
|
||||
│ └── DB: quote_formulas 테이블 │ └── API 호출: │ │
|
||||
│ (데이터 없음! ❌) │ POST /v1/quotes/calculate │ │
|
||||
└───────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
※ design/ 폴더 (localhost:3002)는 UI 프로토타입용이며, 실제 구현은 react/ 폴더에서 진행
|
||||
```
|
||||
|
||||
### 1.2 React 견적등록 컴포넌트 현황
|
||||
|
||||
**파일**: `react/src/components/quotes/QuoteRegistration.tsx`
|
||||
|
||||
```typescript
|
||||
// 현재 상태 (line 332-335)
|
||||
const handleAutoCalculate = () => {
|
||||
toast.info(`자동 견적 산출 (${formData.items.length}개 항목) - API 연동 필요`);
|
||||
};
|
||||
|
||||
// 입력 필드 (이미 구현됨):
|
||||
interface QuoteItem {
|
||||
openWidth: string; // W0 (오픈사이즈 가로)
|
||||
openHeight: string; // H0 (오픈사이즈 세로)
|
||||
productCategory: string; // screen | steel
|
||||
quantity: number;
|
||||
// ... 기타 필드
|
||||
}
|
||||
```
|
||||
|
||||
### 1.3 API 엔드포인트 현황
|
||||
|
||||
**파일**: `api/app/Http/Controllers/Api/V1/QuoteController.php`
|
||||
|
||||
```php
|
||||
// 이미 구현됨 (line 135-145)
|
||||
public function calculate(QuoteCalculateRequest $request)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($request) {
|
||||
$validated = $request->validated();
|
||||
return $this->calculationService->calculate(
|
||||
$validated['inputs'] ?? $validated,
|
||||
$validated['product_category'] ?? null
|
||||
);
|
||||
}, __('message.quote.calculated'));
|
||||
}
|
||||
```
|
||||
|
||||
### 1.4 수식 시더 데이터 (API)
|
||||
|
||||
**파일**: `api/database/seeders/QuoteFormulaSeeder.php`
|
||||
|
||||
| 카테고리 | 수식 수 | 설명 |
|
||||
|---------|--------|------|
|
||||
| OPEN_SIZE | 2 | W0, H0 입력값 |
|
||||
| MAKE_SIZE | 4 | 제작사이즈 계산 |
|
||||
| AREA | 1 | 면적 = W1 * H1 / 1000000 |
|
||||
| WEIGHT | 2 | 중량 계산 (스크린/철재) |
|
||||
| GUIDE_RAIL | 5 | 가이드레일 자동 선택 |
|
||||
| CASE | 3 | 케이스 자동 선택 |
|
||||
| MOTOR | 1 | 모터 자동 선택 (범위 9개) |
|
||||
| CONTROLLER | 2 | 제어기 매핑 |
|
||||
| EDGE_WING | 1 | 마구리 수량 |
|
||||
| INSPECTION | 1 | 검사비 |
|
||||
| PRICE_FORMULA | 8 | 단가 수식 |
|
||||
| **합계** | **30개** | + 범위 18개 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 개발 상세 계획
|
||||
|
||||
### Phase 1: MNG 시더 데이터 생성 (1일)
|
||||
|
||||
#### 2.1 Artisan 명령어 생성
|
||||
|
||||
**생성할 파일**: `mng/app/Console/Commands/SeedQuoteFormulasCommand.php`
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class SeedQuoteFormulasCommand extends Command
|
||||
{
|
||||
protected $signature = 'quote:seed-formulas
|
||||
{--tenant=1 : 테넌트 ID}
|
||||
{--only= : categories|formulas|ranges|mappings|items}
|
||||
{--fresh : 기존 데이터 삭제 후 재생성}';
|
||||
|
||||
protected $description = '견적수식 시드 데이터를 생성합니다';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$tenantId = (int) $this->option('tenant');
|
||||
$only = $this->option('only');
|
||||
$fresh = $this->option('fresh');
|
||||
|
||||
if ($fresh) {
|
||||
$this->warn('기존 데이터를 삭제합니다...');
|
||||
$this->truncateTables($tenantId);
|
||||
}
|
||||
|
||||
if (!$only || $only === 'categories') {
|
||||
$this->seedCategories($tenantId);
|
||||
}
|
||||
|
||||
if (!$only || $only === 'formulas') {
|
||||
$this->seedFormulas($tenantId);
|
||||
}
|
||||
|
||||
if (!$only || $only === 'ranges') {
|
||||
$this->seedRanges($tenantId);
|
||||
}
|
||||
|
||||
$this->info('✅ 견적수식 시드 완료!');
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
private function seedCategories(int $tenantId): void
|
||||
{
|
||||
$categories = [
|
||||
['code' => 'OPEN_SIZE', 'name' => '오픈사이즈', 'sort_order' => 1],
|
||||
['code' => 'MAKE_SIZE', 'name' => '제작사이즈', 'sort_order' => 2],
|
||||
['code' => 'AREA', 'name' => '면적', 'sort_order' => 3],
|
||||
['code' => 'WEIGHT', 'name' => '중량', 'sort_order' => 4],
|
||||
['code' => 'GUIDE_RAIL', 'name' => '가이드레일', 'sort_order' => 5],
|
||||
['code' => 'CASE', 'name' => '케이스', 'sort_order' => 6],
|
||||
['code' => 'MOTOR', 'name' => '모터', 'sort_order' => 7],
|
||||
['code' => 'CONTROLLER', 'name' => '제어기', 'sort_order' => 8],
|
||||
['code' => 'EDGE_WING', 'name' => '마구리', 'sort_order' => 9],
|
||||
['code' => 'INSPECTION', 'name' => '검사', 'sort_order' => 10],
|
||||
['code' => 'PRICE_FORMULA', 'name' => '단가수식', 'sort_order' => 11],
|
||||
];
|
||||
|
||||
foreach ($categories as $cat) {
|
||||
DB::table('quote_formula_categories')->updateOrInsert(
|
||||
['tenant_id' => $tenantId, 'code' => $cat['code']],
|
||||
array_merge($cat, [
|
||||
'tenant_id' => $tenantId,
|
||||
'is_active' => true,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
$this->info("카테고리 " . count($categories) . "개 생성됨");
|
||||
}
|
||||
|
||||
private function seedFormulas(int $tenantId): void
|
||||
{
|
||||
// API 시더와 동일한 데이터 (api/database/seeders/QuoteFormulaSeeder.php 참조)
|
||||
$formulas = $this->getFormulaData();
|
||||
|
||||
$categoryMap = DB::table('quote_formula_categories')
|
||||
->where('tenant_id', $tenantId)
|
||||
->pluck('id', 'code')
|
||||
->toArray();
|
||||
|
||||
$count = 0;
|
||||
foreach ($formulas as $formula) {
|
||||
$categoryId = $categoryMap[$formula['category_code']] ?? null;
|
||||
if (!$categoryId) continue;
|
||||
|
||||
DB::table('quote_formulas')->updateOrInsert(
|
||||
['tenant_id' => $tenantId, 'variable' => $formula['variable']],
|
||||
[
|
||||
'tenant_id' => $tenantId,
|
||||
'category_id' => $categoryId,
|
||||
'variable' => $formula['variable'],
|
||||
'name' => $formula['name'],
|
||||
'type' => $formula['type'],
|
||||
'formula' => $formula['formula'] ?? null,
|
||||
'output_type' => 'variable',
|
||||
'description' => $formula['description'] ?? null,
|
||||
'sort_order' => $formula['sort_order'] ?? 0,
|
||||
'is_active' => $formula['is_active'] ?? true,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]
|
||||
);
|
||||
$count++;
|
||||
}
|
||||
|
||||
$this->info("수식 {$count}개 생성됨");
|
||||
}
|
||||
|
||||
private function getFormulaData(): array
|
||||
{
|
||||
return [
|
||||
// 오픈사이즈
|
||||
['category_code' => 'OPEN_SIZE', 'variable' => 'W0', 'name' => '오픈사이즈 W0 (가로)', 'type' => 'input', 'formula' => null, 'sort_order' => 1],
|
||||
['category_code' => 'OPEN_SIZE', 'variable' => 'H0', 'name' => '오픈사이즈 H0 (세로)', 'type' => 'input', 'formula' => null, 'sort_order' => 2],
|
||||
|
||||
// 제작사이즈
|
||||
['category_code' => 'MAKE_SIZE', 'variable' => 'W1_SCREEN', 'name' => '제작사이즈 W1 (스크린)', 'type' => 'calculation', 'formula' => 'W0 + 140', 'sort_order' => 1],
|
||||
['category_code' => 'MAKE_SIZE', 'variable' => 'H1_SCREEN', 'name' => '제작사이즈 H1 (스크린)', 'type' => 'calculation', 'formula' => 'H0 + 350', 'sort_order' => 2],
|
||||
['category_code' => 'MAKE_SIZE', 'variable' => 'W1_STEEL', 'name' => '제작사이즈 W1 (철재)', 'type' => 'calculation', 'formula' => 'W0 + 110', 'sort_order' => 3],
|
||||
['category_code' => 'MAKE_SIZE', 'variable' => 'H1_STEEL', 'name' => '제작사이즈 H1 (철재)', 'type' => 'calculation', 'formula' => 'H0 + 350', 'sort_order' => 4],
|
||||
|
||||
// 면적
|
||||
['category_code' => 'AREA', 'variable' => 'M', 'name' => '면적 계산', 'type' => 'calculation', 'formula' => 'W1 * H1 / 1000000', 'sort_order' => 1],
|
||||
|
||||
// 중량
|
||||
['category_code' => 'WEIGHT', 'variable' => 'K_SCREEN', 'name' => '중량 계산 (스크린)', 'type' => 'calculation', 'formula' => 'M * 2 + W0 / 1000 * 14.17', 'sort_order' => 1],
|
||||
['category_code' => 'WEIGHT', 'variable' => 'K_STEEL', 'name' => '중량 계산 (철재)', 'type' => 'calculation', 'formula' => 'M * 25', 'sort_order' => 2],
|
||||
|
||||
// 가이드레일
|
||||
['category_code' => 'GUIDE_RAIL', 'variable' => 'G', 'name' => '가이드레일 제작길이', 'type' => 'calculation', 'formula' => 'H0 + 250', 'sort_order' => 1],
|
||||
['category_code' => 'GUIDE_RAIL', 'variable' => 'GR_AUTO_SELECT', 'name' => '가이드레일 자재 자동 선택', 'type' => 'range', 'formula' => null, 'sort_order' => 2],
|
||||
|
||||
// 케이스
|
||||
['category_code' => 'CASE', 'variable' => 'S_SCREEN', 'name' => '케이스 사이즈 (스크린)', 'type' => 'calculation', 'formula' => 'W0 + 220', 'sort_order' => 1],
|
||||
['category_code' => 'CASE', 'variable' => 'S_STEEL', 'name' => '케이스 사이즈 (철재)', 'type' => 'calculation', 'formula' => 'W0 + 240', 'sort_order' => 2],
|
||||
['category_code' => 'CASE', 'variable' => 'CASE_AUTO_SELECT', 'name' => '케이스 자재 자동 선택', 'type' => 'range', 'formula' => null, 'sort_order' => 3],
|
||||
|
||||
// 모터
|
||||
['category_code' => 'MOTOR', 'variable' => 'MOTOR_AUTO_SELECT', 'name' => '모터 자동 선택', 'type' => 'range', 'formula' => null, 'sort_order' => 1],
|
||||
|
||||
// 제어기
|
||||
['category_code' => 'CONTROLLER', 'variable' => 'CONTROLLER_TYPE', 'name' => '제어기 유형', 'type' => 'input', 'formula' => null, 'sort_order' => 0],
|
||||
['category_code' => 'CONTROLLER', 'variable' => 'CTRL_AUTO_SELECT', 'name' => '제어기 자동 선택', 'type' => 'mapping', 'formula' => null, 'sort_order' => 1],
|
||||
|
||||
// 검사
|
||||
['category_code' => 'INSPECTION', 'variable' => 'INSP_FEE', 'name' => '검사비', 'type' => 'calculation', 'formula' => '1', 'sort_order' => 1],
|
||||
];
|
||||
}
|
||||
|
||||
// ... 나머지 메서드 (seedRanges, truncateTables 등)
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.2 작업 순서
|
||||
|
||||
```bash
|
||||
# 1. 명령어 파일 생성
|
||||
# mng/app/Console/Commands/SeedQuoteFormulasCommand.php
|
||||
|
||||
# 2. 실행
|
||||
cd mng
|
||||
php artisan quote:seed-formulas --tenant=1
|
||||
|
||||
# 3. 확인
|
||||
php artisan tinker
|
||||
>>> \App\Models\Quote\QuoteFormula::count()
|
||||
# 예상: 30
|
||||
|
||||
# 4. 시뮬레이터 테스트
|
||||
# mng.sam.kr/quote-formulas/simulator
|
||||
# 입력: W0=3000, H0=2500
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Phase 2: React 자동산출 기능 구현 (2-3일)
|
||||
|
||||
#### 2.1 API 클라이언트 추가
|
||||
|
||||
**수정할 파일**: `react/src/lib/api/quote.ts` (신규)
|
||||
|
||||
```typescript
|
||||
// react/src/lib/api/quote.ts
|
||||
import { ApiClient } from './client';
|
||||
import { AUTH_CONFIG } from './auth/auth-config';
|
||||
|
||||
// API 응답 타입
|
||||
interface CalculationResult {
|
||||
inputs: Record<string, number | string>;
|
||||
outputs: Record<string, {
|
||||
name: string;
|
||||
value: number;
|
||||
category: string;
|
||||
type: string;
|
||||
}>;
|
||||
items: Array<{
|
||||
item_code: string;
|
||||
item_name: string;
|
||||
specification?: string;
|
||||
unit?: string;
|
||||
quantity: number;
|
||||
unit_price: number;
|
||||
total_price: number;
|
||||
formula_variable: string;
|
||||
}>;
|
||||
costs: {
|
||||
material_cost: number;
|
||||
labor_cost: number;
|
||||
install_cost: number;
|
||||
subtotal: number;
|
||||
};
|
||||
errors: string[];
|
||||
}
|
||||
|
||||
interface CalculateRequest {
|
||||
inputs: {
|
||||
W0: number;
|
||||
H0: number;
|
||||
QTY?: number;
|
||||
INSTALL_TYPE?: string;
|
||||
CONTROL_TYPE?: string;
|
||||
};
|
||||
product_category: 'screen' | 'steel';
|
||||
}
|
||||
|
||||
// Quote API 클라이언트
|
||||
class QuoteApiClient extends ApiClient {
|
||||
constructor() {
|
||||
super({
|
||||
mode: 'bearer',
|
||||
apiKey: AUTH_CONFIG.apiKey,
|
||||
getToken: () => {
|
||||
if (typeof window !== 'undefined') {
|
||||
return localStorage.getItem('auth_token');
|
||||
}
|
||||
return null;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 자동 견적 산출
|
||||
*/
|
||||
async calculate(request: CalculateRequest): Promise<{ success: boolean; data: CalculationResult; message: string }> {
|
||||
return this.post('/api/v1/quotes/calculate', request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 입력 스키마 조회
|
||||
*/
|
||||
async getCalculationSchema(productCategory?: string): Promise<{ success: boolean; data: Record<string, unknown> }> {
|
||||
const query = productCategory ? `?product_category=${productCategory}` : '';
|
||||
return this.get(`/api/v1/quotes/calculation-schema${query}`);
|
||||
}
|
||||
}
|
||||
|
||||
export const quoteApi = new QuoteApiClient();
|
||||
```
|
||||
|
||||
#### 2.2 QuoteRegistration.tsx 수정
|
||||
|
||||
**수정할 파일**: `react/src/components/quotes/QuoteRegistration.tsx`
|
||||
|
||||
```typescript
|
||||
// 추가할 import
|
||||
import { quoteApi } from '@/lib/api/quote';
|
||||
import { useState } from 'react';
|
||||
|
||||
// 상태 추가 (컴포넌트 내부)
|
||||
const [calculationResult, setCalculationResult] = useState<CalculationResult | null>(null);
|
||||
const [isCalculating, setIsCalculating] = useState(false);
|
||||
|
||||
// handleAutoCalculate 수정 (line 332-335)
|
||||
const handleAutoCalculate = async () => {
|
||||
const item = formData.items[activeItemIndex];
|
||||
|
||||
if (!item.openWidth || !item.openHeight) {
|
||||
toast.error('오픈사이즈(W0, H0)를 입력해주세요.');
|
||||
return;
|
||||
}
|
||||
|
||||
setIsCalculating(true);
|
||||
try {
|
||||
const response = await quoteApi.calculate({
|
||||
inputs: {
|
||||
W0: parseFloat(item.openWidth),
|
||||
H0: parseFloat(item.openHeight),
|
||||
QTY: item.quantity,
|
||||
INSTALL_TYPE: item.guideRailType,
|
||||
CONTROL_TYPE: item.controller,
|
||||
},
|
||||
product_category: item.productCategory as 'screen' | 'steel' || 'screen',
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
setCalculationResult(response.data);
|
||||
toast.success('자동 산출이 완료되었습니다.');
|
||||
} else {
|
||||
toast.error(response.message || '산출 중 오류가 발생했습니다.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('자동 산출 오류:', error);
|
||||
toast.error('서버 연결에 실패했습니다.');
|
||||
} finally {
|
||||
setIsCalculating(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 산출 결과 반영 함수 추가
|
||||
const handleApplyCalculation = () => {
|
||||
if (!calculationResult) return;
|
||||
|
||||
// 산출된 품목을 견적 항목에 반영
|
||||
const newItems = calculationResult.items.map((item, index) => ({
|
||||
id: `calc-${Date.now()}-${index}`,
|
||||
floor: formData.items[activeItemIndex].floor,
|
||||
code: item.item_code,
|
||||
productCategory: formData.items[activeItemIndex].productCategory,
|
||||
productName: item.item_name,
|
||||
openWidth: formData.items[activeItemIndex].openWidth,
|
||||
openHeight: formData.items[activeItemIndex].openHeight,
|
||||
guideRailType: formData.items[activeItemIndex].guideRailType,
|
||||
motorPower: formData.items[activeItemIndex].motorPower,
|
||||
controller: formData.items[activeItemIndex].controller,
|
||||
quantity: item.quantity,
|
||||
wingSize: formData.items[activeItemIndex].wingSize,
|
||||
inspectionFee: item.unit_price,
|
||||
unitPrice: item.unit_price,
|
||||
totalAmount: item.total_price,
|
||||
}));
|
||||
|
||||
setFormData({
|
||||
...formData,
|
||||
items: [...formData.items.slice(0, activeItemIndex), ...newItems, ...formData.items.slice(activeItemIndex + 1)],
|
||||
});
|
||||
|
||||
setCalculationResult(null);
|
||||
toast.success(`${newItems.length}개 품목이 반영되었습니다.`);
|
||||
};
|
||||
```
|
||||
|
||||
#### 2.3 산출 결과 표시 UI 추가
|
||||
|
||||
```tsx
|
||||
{/* 자동 견적 산출 버튼 아래에 추가 */}
|
||||
{calculationResult && (
|
||||
<Card className="border-green-200 bg-green-50/50">
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-sm font-medium flex items-center gap-2">
|
||||
<Calculator className="h-4 w-4" />
|
||||
산출 결과
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
{/* 계산 변수 */}
|
||||
<div className="grid grid-cols-4 gap-2 text-sm">
|
||||
{Object.entries(calculationResult.outputs).map(([key, val]) => (
|
||||
<div key={key} className="bg-white p-2 rounded border">
|
||||
<div className="text-gray-500 text-xs">{val.name}</div>
|
||||
<div className="font-medium">{typeof val.value === 'number' ? val.value.toFixed(2) : val.value}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* 산출 품목 */}
|
||||
<table className="w-full text-sm border-collapse">
|
||||
<thead>
|
||||
<tr className="bg-gray-100">
|
||||
<th className="p-2 text-left">품목코드</th>
|
||||
<th className="p-2 text-left">품목명</th>
|
||||
<th className="p-2 text-right">수량</th>
|
||||
<th className="p-2 text-right">단가</th>
|
||||
<th className="p-2 text-right">금액</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{calculationResult.items.map((item, i) => (
|
||||
<tr key={i} className="border-b">
|
||||
<td className="p-2 font-mono text-xs">{item.item_code}</td>
|
||||
<td className="p-2">{item.item_name}</td>
|
||||
<td className="p-2 text-right">{item.quantity}</td>
|
||||
<td className="p-2 text-right">{item.unit_price.toLocaleString()}</td>
|
||||
<td className="p-2 text-right font-medium">{item.total_price.toLocaleString()}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr className="bg-gray-50 font-medium">
|
||||
<td colSpan={4} className="p-2 text-right">합계</td>
|
||||
<td className="p-2 text-right">{calculationResult.costs.subtotal.toLocaleString()}원</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
|
||||
{/* 반영 버튼 */}
|
||||
<Button onClick={handleApplyCalculation} className="w-full bg-green-600 hover:bg-green-700">
|
||||
<Check className="h-4 w-4 mr-2" />
|
||||
품목에 반영하기
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Phase 3: 통합 테스트 (1일)
|
||||
|
||||
#### 3.1 테스트 시나리오
|
||||
|
||||
| 번호 | 테스트 케이스 | 입력값 | 예상 결과 |
|
||||
|-----|-------------|-------|----------|
|
||||
| 1 | 기본 스크린 산출 | W0=3000, H0=2500 | 가이드레일 PT-GR-3000, 모터 PT-MOTOR-150 |
|
||||
| 2 | 대형 스크린 산출 | W0=5000, H0=4000 | 모터 규격 상향 (300K 이상) |
|
||||
| 3 | 철재 산출 | W0=2000, H0=2000, steel | 중량 M*25 적용 |
|
||||
| 4 | 품목 반영 | 산출 후 반영 클릭 | 견적 항목에 추가됨 |
|
||||
| 5 | 에러 처리 | W0/H0 미입력 | "오픈사이즈를 입력해주세요" |
|
||||
|
||||
#### 3.2 검증 체크리스트
|
||||
|
||||
```
|
||||
□ MNG 시뮬레이터에서 수식 계산 정확도 확인
|
||||
□ React 자동산출 버튼 클릭 → API 호출 확인
|
||||
□ 산출 결과 테이블 정상 표시
|
||||
□ "품목에 반영하기" 클릭 → 견적 항목 추가 확인
|
||||
□ 견적 저장 시 calculation_inputs 필드 저장 확인
|
||||
□ 에러 시 적절한 메시지 표시
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. SAM 개발 규칙 요약
|
||||
|
||||
### 3.1 API 개발 규칙 (CLAUDE.md 참조)
|
||||
|
||||
```php
|
||||
// Controller: FormRequest + ApiResponse 패턴
|
||||
public function calculate(QuoteCalculateRequest $request)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($request) {
|
||||
return $this->calculationService->calculate($request->validated());
|
||||
}, __('message.quote.calculated'));
|
||||
}
|
||||
|
||||
// Service: 비즈니스 로직 분리
|
||||
class QuoteCalculationService extends Service
|
||||
{
|
||||
public function calculate(array $inputs, ?string $productCategory = null): array
|
||||
{
|
||||
$tenantId = $this->tenantId(); // 필수
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
// 응답 형식
|
||||
{
|
||||
"success": true,
|
||||
"message": "견적이 산출되었습니다.",
|
||||
"data": { ... }
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 React 개발 패턴
|
||||
|
||||
```typescript
|
||||
// API 클라이언트 패턴 (react/src/lib/api/client.ts)
|
||||
class ApiClient {
|
||||
async post<T>(endpoint: string, data?: unknown): Promise<T>
|
||||
async get<T>(endpoint: string): Promise<T>
|
||||
}
|
||||
|
||||
// 컴포넌트 패턴
|
||||
// - shadcn/ui 컴포넌트 사용
|
||||
// - toast (sonner) 알림
|
||||
// - FormField, Card, Button 등
|
||||
```
|
||||
|
||||
### 3.3 MNG 개발 패턴
|
||||
|
||||
```php
|
||||
// Artisan 명령어 패턴
|
||||
protected $signature = 'quote:seed-formulas {--tenant=1}';
|
||||
|
||||
// 모델 사용
|
||||
use App\Models\Quote\QuoteFormula;
|
||||
use App\Models\Quote\QuoteFormulaCategory;
|
||||
|
||||
// 서비스 패턴
|
||||
class QuoteFormulaService {
|
||||
public function __construct(
|
||||
private FormulaEvaluatorService $evaluator
|
||||
) {}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 파일 구조
|
||||
|
||||
```
|
||||
SAM/
|
||||
├── mng/
|
||||
│ ├── app/Console/Commands/
|
||||
│ │ └── SeedQuoteFormulasCommand.php # 🆕 Phase 1
|
||||
│ ├── app/Models/Quote/
|
||||
│ │ ├── QuoteFormula.php # ✅ 있음
|
||||
│ │ ├── QuoteFormulaCategory.php # ✅ 있음
|
||||
│ │ └── QuoteFormulaRange.php # ✅ 있음
|
||||
│ └── app/Services/Quote/
|
||||
│ └── FormulaEvaluatorService.php # ✅ 있음
|
||||
│
|
||||
├── api/
|
||||
│ ├── app/Http/Controllers/Api/V1/
|
||||
│ │ └── QuoteController.php # ✅ calculate() 있음
|
||||
│ ├── app/Services/Quote/
|
||||
│ │ ├── QuoteCalculationService.php # ✅ 있음
|
||||
│ │ └── FormulaEvaluatorService.php # ✅ 있음
|
||||
│ └── database/seeders/
|
||||
│ └── QuoteFormulaSeeder.php # 참조용 데이터
|
||||
│
|
||||
├── react/
|
||||
│ ├── src/lib/api/
|
||||
│ │ ├── client.ts # ✅ ApiClient 클래스
|
||||
│ │ └── quote.ts # 🆕 Phase 2
|
||||
│ └── src/components/quotes/
|
||||
│ └── QuoteRegistration.tsx # ⚡ Phase 2 수정
|
||||
│
|
||||
└── docs/plans/
|
||||
└── quote-auto-calculation-development-plan.md # 이 문서
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 수식 계산 예시
|
||||
|
||||
```
|
||||
입력: W0=3000mm, H0=2500mm, product_category=screen
|
||||
|
||||
계산 순서:
|
||||
1. W1 = W0 + 140 = 3140mm (스크린 제작 가로)
|
||||
2. H1 = H0 + 350 = 2850mm (스크린 제작 세로)
|
||||
3. M = W1 * H1 / 1000000 = 8.949㎡ (면적)
|
||||
4. K = M * 2 + W0 / 1000 * 14.17 = 60.41kg (중량)
|
||||
5. G = H0 + 250 = 2750mm (가이드레일 길이)
|
||||
6. S = W0 + 220 = 3220mm (케이스 사이즈)
|
||||
|
||||
범위 자동 선택:
|
||||
- 가이드레일: G=2750 → 2438 < G ≤ 3000 → PT-GR-3000 × 2개
|
||||
- 케이스: S=3220 → 3000 < S ≤ 3600 → PT-CASE-3600 × 1개
|
||||
- 모터: K=60.41 → 0 < K ≤ 150 → PT-MOTOR-150 × 1개
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 일정 요약
|
||||
|
||||
| Phase | 작업 | 예상 기간 | 상태 |
|
||||
|-------|------|----------|------|
|
||||
| 1 | MNG 시더 명령어 생성 | 1일 | ✅ 완료 |
|
||||
| 2 | React Quote API 클라이언트 생성 | 0.5일 | ✅ 완료 |
|
||||
| 3 | React handleAutoCalculate API 연동 | 0.5일 | ✅ 완료 |
|
||||
| 4 | 산출 결과 UI 추가 | 0.5일 | ✅ 완료 |
|
||||
| 5 | 문서 업데이트 | 0.5시간 | ✅ 완료 |
|
||||
| **합계** | | **약 2시간** | ✅ |
|
||||
|
||||
---
|
||||
|
||||
## 7. 완료된 구현 내역
|
||||
|
||||
### 생성된 파일
|
||||
| 파일 경로 | 역할 |
|
||||
|----------|------|
|
||||
| `mng/app/Console/Commands/SeedQuoteFormulasCommand.php` | MNG 견적수식 시더 명령어 |
|
||||
| `react/src/lib/api/quote.ts` | React Quote API 클라이언트 |
|
||||
|
||||
### 수정된 파일
|
||||
| 파일 경로 | 변경 내용 |
|
||||
|----------|----------|
|
||||
| `react/src/components/quotes/QuoteRegistration.tsx` | handleAutoCalculate API 연동, 산출 결과 UI 추가 |
|
||||
|
||||
### MNG 시더 실행 결과
|
||||
```
|
||||
✅ 견적수식 시드 완료!
|
||||
카테고리: 11개
|
||||
수식: 18개
|
||||
범위: 18개
|
||||
```
|
||||
|
||||
### React 기능 구현
|
||||
- `handleAutoCalculate`: API 호출 및 로딩 상태 관리
|
||||
- `handleApplyCalculation`: 산출 결과를 견적 항목에 반영
|
||||
- 산출 결과 UI: 변수, 품목 테이블, 비용 합계 표시
|
||||
- 에러 처리: 입력값 검증, API 에러 토스트
|
||||
|
||||
---
|
||||
|
||||
*문서 버전*: 3.0 (구현 완료)
|
||||
*작성자*: Claude Code
|
||||
*최종 업데이트*: 2025-12-22
|
||||
@@ -47,19 +47,19 @@
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ - 도구: https://mng.sam.kr/dev-tools/flow-tester │
|
||||
│ - 목적: 각 기능의 모든 경우의 수(TC)를 자동화 테스트 │
|
||||
│ - 저장: api/docs/flow-tests/{feature}-flow.json │
|
||||
│ - 저장: docs/plans/flow-tests/{feature}-flow.json │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Flow Test JSON 형식:**
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "기능명 플로우 테스트",
|
||||
"description": "테스트 설명",
|
||||
"name": "플로우 이름 (50자 이내)",
|
||||
"description": "플로우 상세 설명",
|
||||
"version": "1.0",
|
||||
"config": {
|
||||
"baseUrl": "https://api.sam.kr/api/v1",
|
||||
"apiKey": "{{$env.FLOW_TESTER_API_KEY}}",
|
||||
"baseUrl": "",
|
||||
"timeout": 30000,
|
||||
"stopOnFailure": true
|
||||
},
|
||||
@@ -69,22 +69,69 @@
|
||||
},
|
||||
"steps": [
|
||||
{
|
||||
"id": "step_id",
|
||||
"name": "단계명",
|
||||
"method": "GET|POST|PUT|PATCH|DELETE",
|
||||
"endpoint": "/endpoint",
|
||||
"headers": { "Authorization": "Bearer {{login.token}}" },
|
||||
"body": { },
|
||||
"id": "login",
|
||||
"name": "로그인",
|
||||
"method": "POST",
|
||||
"endpoint": "/api/v1/login",
|
||||
"body": {
|
||||
"user_id": "{{user_id}}",
|
||||
"user_pwd": "{{user_pwd}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": { "$.access_token": "@isString" }
|
||||
},
|
||||
"extract": { "token": "$.access_token" }
|
||||
},
|
||||
{
|
||||
"id": "step2",
|
||||
"name": "다음 단계",
|
||||
"method": "GET",
|
||||
"endpoint": "/api/v1/path",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{login.token}}"
|
||||
},
|
||||
"expect": {
|
||||
"status": [200],
|
||||
"jsonPath": { "$.success": true }
|
||||
},
|
||||
"extract": { "key": "$.data.value" }
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### 🔴 Flow Test JSON 작성 규칙 (필수 준수)
|
||||
|
||||
| # | 규칙 | 설명 |
|
||||
|---|------|------|
|
||||
| 1 | **config.baseUrl** | 빈 문자열 `""` 로 설정 (서버에서 .env 기본값 사용) |
|
||||
| 2 | **config.apiKey** | ❌ JSON에 포함하지 않음 (서버에서 자동 주입) |
|
||||
| 3 | **variables** | `{{$env.XXX}}` 형식으로 환경변수 참조 |
|
||||
| 4 | **steps[].body** | `{{변수명}}` 형식으로 variables 값 참조 |
|
||||
| 5 | **headers.Authorization** | `Bearer {{login.token}}` 형식으로 추출한 토큰 사용 |
|
||||
| 6 | **extract** | 다음 스텝에서 `{{stepId.변수명}}`으로 참조됨 |
|
||||
| 7 | **endpoint** | `/api/v1`을 포함한 전체 경로 필수 |
|
||||
| 8 | **flows 배열** | ❌ 사용 금지 - 최상위에 바로 `steps` 배열 사용 |
|
||||
| 9 | **dependsOn** | ❌ 사용 금지 - steps 순서대로 실행됨 |
|
||||
|
||||
#### 변수 참조 규칙
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ ✅ 올바른 참조 │
|
||||
├─────────────────────────────────────────────────────────────────────────┤
|
||||
│ variables 참조: "user_id": "{{user_id}}" │
|
||||
│ 환경변수 참조: "user_id": "{{$env.FLOW_TESTER_USER_ID}}" │
|
||||
│ 추출값 참조: "Authorization": "Bearer {{login.token}}" │
|
||||
│ 동적 값: "email": "test_{{$timestamp}}@example.com" │
|
||||
├─────────────────────────────────────────────────────────────────────────┤
|
||||
│ ❌ 잘못된 참조 │
|
||||
├─────────────────────────────────────────────────────────────────────────┤
|
||||
│ ❌ "user_id": "{{variables.user_id}}" (variables. 접두사 사용 금지) │
|
||||
│ ❌ "Authorization": "Bearer {{login.accessToken}}" (추출 키 불일치) │
|
||||
└─────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 대상 페이지 목록
|
||||
@@ -93,13 +140,13 @@
|
||||
|
||||
| # | React URL (예상) | 기능 | 관련 API | 연동 | Flow Test |
|
||||
|---|-----------------|------|----------|:----:|:---------:|
|
||||
| 5.1-1 | `/hr/employees/invite` | 사용자 초대 | `POST /v1/users/invite` | ✅ | ⏳ |
|
||||
| 5.1-1 | `/hr/employees/invite` | 사용자 초대 | `POST /v1/users/invite` | ✅ | ✅ |
|
||||
| 5.1-2 | `/hr/employees/invitations` | 초대 목록 | `GET /v1/users/invitations` | ⏭️ | ⏭️ |
|
||||
| 5.2-1 | `/settings/notifications` | 알림 설정 | `GET /v1/settings/notifications` | ✅ | ⏳ |
|
||||
| 5.2-2 | `/settings/notifications` | 알림 설정 수정 | `PUT /v1/settings/notifications` | ✅ | ⏳ |
|
||||
| 5.3-1 | `/account` | 회원 탈퇴 | `POST /v1/account/withdraw` | ✅ | ⏳ |
|
||||
| 5.3-2 | `/account` | 사용 중지 | `POST /v1/account/suspend` | ✅ | ⏳ |
|
||||
| 5.4-1 | `/accounting/sales/{id}` | 거래명세서 | `GET /v1/sales/{id}/statement` | ✅ | ⏳ |
|
||||
| 5.2-1 | `/settings/notifications` | 알림 설정 | `GET /v1/settings/notifications` | ✅ | ✅ |
|
||||
| 5.2-2 | `/settings/notifications` | 알림 설정 수정 | `PUT /v1/settings/notifications` | ✅ | ✅ |
|
||||
| 5.3-1 | `/account` | 회원 탈퇴 | `POST /v1/account/withdraw` | ✅ | ✅ |
|
||||
| 5.3-2 | `/account` | 사용 중지 | `POST /v1/account/suspend` | ✅ | ✅ |
|
||||
| 5.4-1 | `/accounting/sales/{id}` | 거래명세서 | `GET /v1/sales/{id}/statement` | ✅ | ✅ |
|
||||
|
||||
**Phase 5 Flow Test 파일:**
|
||||
- `user-invitation-flow.json` - 사용자 초대 전체 플로우
|
||||
@@ -111,13 +158,13 @@
|
||||
|
||||
| # | React URL (예상) | 기능 | 관련 API | 연동 | Flow Test |
|
||||
|---|-----------------|------|----------|:----:|:---------:|
|
||||
| 6.1-1 | `/accounting/bad-debts` | 악성채권 목록 | `GET /v1/bad-debts` | ✅ | ⏳ |
|
||||
| 6.1-2 | `/accounting/bad-debts` | 악성채권 요약 | `GET /v1/bad-debts/summary` | ✅ | ⏳ |
|
||||
| 6.1-3 | `/accounting/bad-debts/{id}` | 악성채권 상세 | `GET /v1/bad-debts/{id}` | ✅ | ⏳ |
|
||||
| 6.1-4 | `/accounting/bad-debts/new` | 악성채권 등록 | `POST /v1/bad-debts` | ✅ | ⏳ |
|
||||
| 6.2-1 | `/settings/popups` | 팝업 목록 | `GET /v1/popups` | ✅ | ⏳ |
|
||||
| 6.2-2 | `/settings/popups` | 활성 팝업 | `GET /v1/popups/active` | ✅ | ⏳ |
|
||||
| 6.2-3 | `/settings/popups/{id}` | 팝업 상세 | `GET /v1/popups/{id}` | ✅ | ⏳ |
|
||||
| 6.1-1 | `/accounting/bad-debts` | 악성채권 목록 | `GET /v1/bad-debts` | ✅ | ✅ |
|
||||
| 6.1-2 | `/accounting/bad-debts` | 악성채권 요약 | `GET /v1/bad-debts/summary` | ✅ | ✅ |
|
||||
| 6.1-3 | `/accounting/bad-debts/{id}` | 악성채권 상세 | `GET /v1/bad-debts/{id}` | ✅ | ✅ |
|
||||
| 6.1-4 | `/accounting/bad-debts/new` | 악성채권 등록 | `POST /v1/bad-debts` | ✅ | ✅ |
|
||||
| 6.2-1 | `/settings/popups` | 팝업 목록 | `GET /v1/popups` | ✅ | ✅ |
|
||||
| 6.2-2 | `/settings/popups` | 활성 팝업 | `GET /v1/popups/active` | ✅ | ✅ |
|
||||
| 6.2-3 | `/settings/popups/{id}` | 팝업 상세 | `GET /v1/popups/{id}` | ✅ | ✅ |
|
||||
|
||||
**Phase 6 Flow Test 파일:**
|
||||
- `bad-debt-flow.json` - 악성채권 추심관리 CRUD 플로우
|
||||
@@ -143,14 +190,14 @@
|
||||
|
||||
| # | React URL (예상) | 기능 | 관련 API | 연동 | Flow Test |
|
||||
|---|-----------------|------|----------|:----:|:---------:|
|
||||
| 8.1-1 | `/subscription` | 현재 구독 정보 | `GET /v1/subscriptions/current` | ✅ | ⏳ |
|
||||
| 8.1-2 | `/subscription` | 사용량 조회 | `GET /v1/subscriptions/usage` | ✅ | ⏳ |
|
||||
| 8.1-3 | `/subscription/export` | 자료 내보내기 | `POST /v1/subscriptions/export` | ✅ | ⏳ |
|
||||
| 8.1-4 | `/subscription` | 서비스 해지 | `POST /v1/subscriptions/{id}/cancel` | ✅ | ⏳ |
|
||||
| 8.2-1 | `/payments` | 결제 내역 | `GET /v1/payments` | ✅ | ⏳ |
|
||||
| 8.2-2 | `/payments/{id}` | 결제 상세 | `GET /v1/payments/{id}` | ✅ | ⏳ |
|
||||
| 8.2-3 | `/payments/{id}/statement` | 거래명세서 | `GET /v1/payments/{id}/statement` | ✅ | ⏳ |
|
||||
| 8.3-1 | `/companies/add` | 회사 추가 | `POST /v1/companies/request` | ✅ | ⏳ |
|
||||
| 8.1-1 | `/subscription` | 현재 구독 정보 | `GET /v1/subscriptions/current` | ✅ | ✅ |
|
||||
| 8.1-2 | `/subscription` | 사용량 조회 | `GET /v1/subscriptions/usage` | ✅ | ✅ |
|
||||
| 8.1-3 | `/subscription/export` | 자료 내보내기 | `POST /v1/subscriptions/export` | ✅ | ✅ |
|
||||
| 8.1-4 | `/subscription` | 서비스 해지 | `POST /v1/subscriptions/{id}/cancel` | ✅ | ✅ |
|
||||
| 8.2-1 | `/payments` | 결제 내역 | `GET /v1/payments` | ✅ | ✅ |
|
||||
| 8.2-2 | `/payments/{id}` | 결제 상세 | `GET /v1/payments/{id}` | ✅ | ✅ |
|
||||
| 8.2-3 | `/payments/{id}/statement` | 거래명세서 | `GET /v1/payments/{id}/statement` | ✅ | ✅ |
|
||||
| 8.3-1 | `/companies/add` | 회사 추가 | `POST /v1/companies/request` | ✅ | ✅ |
|
||||
|
||||
**Phase 8 Flow Test 파일:**
|
||||
- `subscription-flow.json` - 구독관리 플로우
|
||||
@@ -193,7 +240,7 @@ Step 4: 수정 사항 정리
|
||||
Step 5: Flow Test 작성
|
||||
├── 해당 기능의 모든 TC(Test Case) 도출
|
||||
├── Flow Test JSON 작성
|
||||
├── api/docs/flow-tests/{feature}-flow.json 저장
|
||||
├── docs/plans/flow-tests/{feature}-flow.json 저장
|
||||
└── https://mng.sam.kr/dev-tools/flow-tester 에서 실행
|
||||
```
|
||||
|
||||
@@ -326,16 +373,16 @@ Step 5: Flow Test 작성
|
||||
|
||||
**Flow Test 저장 위치:**
|
||||
```
|
||||
api/docs/flow-tests/
|
||||
├── user-invitation-flow.json
|
||||
├── notification-settings-flow.json
|
||||
├── bad-debt-flow.json
|
||||
├── popup-flow.json
|
||||
├── board-management-flow.json
|
||||
├── post-crud-flow.json
|
||||
├── subscription-flow.json
|
||||
├── payment-flow.json
|
||||
└── company-request-flow.json
|
||||
docs/plans/flow-tests/
|
||||
├── user-invitation-flow.json ✅ 완료 (6 steps)
|
||||
├── notification-settings-flow.json ✅ 완료 (10 steps)
|
||||
├── account-management-flow.json ✅ 완료 (6 steps)
|
||||
├── sales-statement-flow.json ✅ 완료 (9 steps)
|
||||
├── bad-debt-flow.json ✅ 완료 (11 steps)
|
||||
├── popup-flow.json ✅ 완료 (9 steps)
|
||||
├── subscription-flow.json ✅ 완료 (13 steps)
|
||||
├── payment-flow.json ✅ 완료 (12 steps)
|
||||
└── company-request-flow.json ✅ 완료 (9 steps)
|
||||
```
|
||||
|
||||
### 3.3 분석 템플릿
|
||||
@@ -726,6 +773,18 @@ if (checkResult.valid) {
|
||||
|
||||
| 날짜 | 페이지 | 변경 내용 | 파일 | 승인 |
|
||||
|------|--------|----------|------|------|
|
||||
| 2025-12-22 | 8.3 | 회사 가입 신청 Flow Test 작성 (9 steps) | `company-request-flow.json` | ✅ |
|
||||
| 2025-12-22 | 8.2 | 결제 관리 Flow Test 작성 (12 steps) | `payment-flow.json` | ✅ |
|
||||
| 2025-12-22 | 8.1 | 구독 관리 Flow Test 작성 (13 steps) | `subscription-flow.json` | ✅ |
|
||||
| 2025-12-22 | 6.2 | 팝업 관리 Flow Test 작성 (9 steps) | `popup-flow.json` | ✅ |
|
||||
| 2025-12-22 | 6.1 | 부실채권 관리 Flow Test 작성 (11 steps) | `bad-debt-flow.json` | ✅ |
|
||||
| 2025-12-22 | 5.4 | 매출 명세서 Flow Test 작성 (9 steps) | `sales-statement-flow.json` | ✅ |
|
||||
| 2025-12-22 | 5.3 | 계정 관리 Flow Test 작성 (6 steps) | `account-management-flow.json` | ✅ |
|
||||
| 2025-12-22 | 5.2 | 알림 설정 Flow Test 작성 (10 steps) | `notification-settings-flow.json` | ✅ |
|
||||
| 2025-12-22 | - | Flow Test JSON 작성 규칙 정리 (9개 필수 규칙 + 변수 참조 규칙) | 문서 | ✅ |
|
||||
| 2025-12-22 | - | endpoint 정책 추가 (/api/v1 전체 경로 필수) | 문서 + user-invitation-flow.json | ✅ |
|
||||
| 2025-12-22 | - | Flow Test 파일 통합 (docs/plans/flow-tests/로 이동) | 9개 파일 이동 | ✅ |
|
||||
| 2025-12-22 | 5.1-1 | 사용자 초대 Flow Test 작성 (14 flows, P1~P3) | `user-invitation-flow.json` | ✅ |
|
||||
| 2025-12-22 | 8.1 | 구독관리 API 분석 - 완전 구현됨, 필드명 매핑 필요 | - | ✅ |
|
||||
| 2025-12-22 | 8.2 | 결제내역 API 분석 - 완전 구현됨, 필드명 매핑 필요 | - | ✅ |
|
||||
| 2025-12-22 | 8.3 | 회사 추가 API 분석 - 완전 구현됨, React 연동만 필요 | - | ✅ |
|
||||
@@ -750,4 +809,4 @@ if (checkResult.valid) {
|
||||
- **API Swagger UI**: http://sam.kr/api-docs/index.html
|
||||
- **React 개발 서버**: http://dev.sam.kr
|
||||
- **Flow Tester**: https://mng.sam.kr/dev-tools/flow-tester
|
||||
- **Flow Test 예제**: `api/claudedocs/flow-tester-*.json`
|
||||
- **Flow Test 파일**: `docs/plans/flow-tests/*.json`
|
||||
|
||||
@@ -72,22 +72,22 @@ ListClient.tsx (클라이언트)
|
||||
| # | 페이지 | React 경로 | API 엔드포인트 | 상태 |
|
||||
|---|--------|-----------|---------------|------|
|
||||
| A-1 | 악성채권 관리 | `/accounting/bad-debt-collection` | `GET/POST /v1/bad-debts` | ✅ 완료 |
|
||||
| A-2 | 팝업 관리 | `/settings/popup-management` | `GET/POST /v1/popups` | ⏳ 대기 |
|
||||
| A-3 | 결제 내역 | `/settings/payment-history` | `GET /v1/payments` | ⏳ 대기 |
|
||||
| A-4 | 구독 관리 | `/settings/subscription` | `GET /v1/subscriptions` | ⏳ 대기 |
|
||||
| A-5 | 알림 설정 | `/settings/notifications` | `GET/PUT /v1/settings/notifications` | ⏳ 대기 |
|
||||
| A-6 | 거래처 원장 | `/accounting/vendor-ledger` | `GET /v1/vendor-ledger` | ⏳ 대기 |
|
||||
| A-2 | 팝업 관리 | `/settings/popup-management` | `GET/POST /v1/popups` | ✅ 완료 |
|
||||
| A-3 | 결제 내역 | `/settings/payment-history` | `GET /v1/payments` | ✅ 완료 |
|
||||
| A-4 | 구독 관리 | `/settings/subscription` | `GET /v1/subscriptions` | ✅ 완료 |
|
||||
| A-5 | 알림 설정 | `/settings/notifications` | `GET/PUT /v1/settings/notifications` | ✅ 완료 |
|
||||
| A-6 | 거래처 원장 | `/accounting/vendor-ledger` | `GET /v1/vendor-ledger` | ⏭️ API 미존재 |
|
||||
|
||||
### 2.2 Phase B: 핵심 업무 기능
|
||||
|
||||
| # | 페이지 | React 경로 | API 엔드포인트 | 상태 |
|
||||
|---|--------|-----------|---------------|------|
|
||||
| B-1 | 매출 관리 | `/accounting/sales` | `GET/POST /v1/sales` | ⏳ 대기 |
|
||||
| B-2 | 매입 관리 | `/accounting/purchase` | `GET/POST /v1/purchases` | ⏳ 대기 |
|
||||
| B-3 | 입금 관리 | `/accounting/deposit` | `GET/POST /v1/deposits` | ⏳ 대기 |
|
||||
| B-4 | 출금 관리 | `/accounting/withdrawal` | `GET/POST /v1/withdrawals` | ⏳ 대기 |
|
||||
| B-5 | 거래처 관리 | `/accounting/vendor` | `GET/POST /v1/clients` | ⏳ 대기 |
|
||||
| B-6 | 어음 관리 | `/accounting/bill` | `GET/POST /v1/bills` | ⏳ 대기 |
|
||||
| B-1 | 매출 관리 | `/accounting/sales` | `GET/POST /v1/sales` | ✅ 완료 (기존 연동) |
|
||||
| B-2 | 매입 관리 | `/accounting/purchase` | `GET/POST /v1/purchases` | ✅ 완료 (기존 연동) |
|
||||
| B-3 | 입금 관리 | `/accounting/deposit` | `GET/POST /v1/deposits` | ✅ 완료 |
|
||||
| B-4 | 출금 관리 | `/accounting/withdrawal` | `GET/POST /v1/withdrawals` | ✅ 완료 |
|
||||
| B-5 | 거래처 관리 | `/accounting/vendor` | `GET/POST /v1/clients` | ✅ 완료 |
|
||||
| B-6 | 어음 관리 | `/accounting/bills` | `GET/POST /v1/bills` | ✅ 완료 |
|
||||
|
||||
### 2.3 Phase C: 인사/근태
|
||||
|
||||
@@ -776,10 +776,11 @@ export function transformFrontendToApi(data: Record<string, any>): Record<string
|
||||
| 일차 | 작업 내용 | 상태 |
|
||||
|------|----------|------|
|
||||
| Day 1 | A-1 악성채권 관리 연동 | ✅ 완료 (2025-12-23) |
|
||||
| Day 2 | A-2 팝업 관리 연동 | ⏳ |
|
||||
| Day 3 | A-3 결제 내역, A-4 구독 관리 연동 | ⏳ |
|
||||
| Day 4 | A-5 알림 설정 연동 | ⏳ |
|
||||
| Day 5 | A-6 거래처 원장 연동 + 테스트 | ⏳ |
|
||||
| Day 2 | A-2 팝업 관리 연동 | ✅ 완료 (2025-12-23) |
|
||||
| Day 3 | A-3 결제 내역 연동 | ✅ 완료 (2025-12-23) |
|
||||
| Day 4 | A-4 구독 관리 연동 | ✅ 완료 (2025-12-23) |
|
||||
| Day 5 | A-5 알림 설정 연동 | ✅ 완료 (2025-12-23) |
|
||||
| Day 6 | A-6 거래처 원장 - API 미존재로 건너뜀 | ⏭️ 건너뜀 |
|
||||
|
||||
### Phase B: 핵심 업무 기능 (2주)
|
||||
|
||||
@@ -800,6 +801,16 @@ export function transformFrontendToApi(data: Record<string, any>): Record<string
|
||||
| 2025-12-23 | 문서 초안 작성 | Claude |
|
||||
| 2025-12-23 | Phase B 상세 필드 매핑 추가 (6.1~6.6) | Claude |
|
||||
| 2025-12-23 | A-1 악성채권 관리 API 연동 완료 (`actions.ts`, `page.tsx`, `index.tsx`) | Claude |
|
||||
| 2025-12-23 | A-2 팝업 관리 API 연동 완료 (`actions.ts`, `page.tsx`, `PopupList.tsx`, `[id]/page.tsx`, `[id]/edit/page.tsx`) | Claude |
|
||||
| 2025-12-23 | A-3 결제 내역 API 연동 완료 (`types.ts`, `actions.ts`, `page.tsx`, `PaymentHistoryClient.tsx`, `index.ts`) | Claude |
|
||||
| 2025-12-23 | A-4 구독 관리 API 연동 완료 (`types.ts`, `utils.ts`, `actions.ts`, `page.tsx`, `SubscriptionClient.tsx`, `index.ts`) | Claude |
|
||||
| 2025-12-23 | A-5 알림 설정 API 연동 완료 (`types.ts`, `actions.ts`, `page.tsx`, `NotificationSettingsClient.tsx`, `index.ts`) | Claude |
|
||||
| 2025-12-23 | A-6 거래처 원장 - API 미존재 확인, Phase A 완료 | Claude |
|
||||
| 2025-12-23 | B-1 매출 관리 - 기존 API 연동 확인 (`/api/proxy/sales` 사용) | Claude |
|
||||
| 2025-12-23 | B-2 매입 관리 - 기존 API 연동 확인 (`/api/proxy/purchases` 사용) | Claude |
|
||||
| 2025-12-23 | B-3 입금 관리 API 연동 완료 (`types.ts`: API 타입 추가, `index.tsx`: Mock → API 호출 전환) | Claude |
|
||||
| 2025-12-23 | B-4 출금 관리 API 연동 완료 (`types.ts`: API 타입 추가, `index.tsx`: Mock → API 호출 전환) | Claude |
|
||||
| 2025-12-23 | B-5 거래처 관리 API 연동 완료 (`types.ts`: API 타입 추가, `actions.ts`: Server Actions, `page.tsx`: 서버 컴포넌트, `VendorManagementClient.tsx`: 클라이언트 컴포넌트) | Claude |
|
||||
|
||||
---
|
||||
|
||||
|
||||
223
projects/index_projects.md
Normal file
223
projects/index_projects.md
Normal file
@@ -0,0 +1,223 @@
|
||||
# 프로젝트 문서 인덱스
|
||||
|
||||
> SAM 시스템 개발 프로젝트별 문서 모음
|
||||
> **최종 업데이트**: 2025-12-23
|
||||
|
||||
---
|
||||
|
||||
## 프로젝트 현황 요약
|
||||
|
||||
| 프로젝트 | 상태 | 설명 |
|
||||
|---------|------|------|
|
||||
| [mes](#mes---meserp-프로젝트) | 🟡 진행중 | 차세대 MES/ERP 기능 개발 |
|
||||
| [quotation](#quotation---견적-기능) | 🟢 Phase 3 완료 | 5130 견적 → SAM 이관 |
|
||||
| [api-integration](#api-integration---react--api-연동) | 🟡 진행중 | React ↔ API 연동 |
|
||||
| [5130-migration](#5130-migration---품목-마이그레이션) | 🟡 Phase 1 진행중 | 5130 품목 데이터 마이그레이션 |
|
||||
| [legacy-5130](#legacy-5130---레거시-분석) | 📚 참조용 | 5130 레거시 모듈 분석 |
|
||||
| [mng-mobile-responsive](#mng-mobile-responsive---모바일-반응형) | 🟡 진행중 | mng 모바일 반응형 개선 |
|
||||
| [auto-login](#auto-login---자동-로그인) | ⚪ 대기 | 자동 로그인 기능 |
|
||||
| [migration-5130-mng](#migration-5130-mng---5130--mng-마이그레이션) | 🟡 진행중 | 5130 → mng 통합 마이그레이션 |
|
||||
|
||||
---
|
||||
|
||||
## 프로젝트 상세
|
||||
|
||||
### mes - MES/ERP 프로젝트
|
||||
|
||||
**경로**: `docs/projects/mes/`
|
||||
**상태**: 🟡 Phase 0 (베이스라인 분석) 30% 완료
|
||||
**목표**: SAM 시스템의 차세대 MES/ERP 기능 개발
|
||||
|
||||
**핵심 문서**:
|
||||
- [README.md](./mes/README.md) - 프로젝트 개요 및 문서 안내
|
||||
- [MES_PROGRESS_TRACKER.md](./mes/MES_PROGRESS_TRACKER.md) - 진행 상황 추적
|
||||
- [MES_PROJECT_ROADMAP.md](./mes/MES_PROJECT_ROADMAP.md) - 전체 로드맵
|
||||
|
||||
**분석 결과**:
|
||||
- `00_baseline/` - Phase 0 분석 결과
|
||||
- [PHASE_0_FINAL_REPORT.md](./mes/00_baseline/PHASE_0_FINAL_REPORT.md)
|
||||
- [BACKEND_DEVELOPMENT_ROADMAP_V2.md](./mes/00_baseline/BACKEND_DEVELOPMENT_ROADMAP_V2.md)
|
||||
- `docs_breakdown/` - 문서 분석 (7개)
|
||||
|
||||
**v2 분석**:
|
||||
- `v2-analysis/` - MES v2 화면 분석
|
||||
- `quote-analysis/` - 견적 분석
|
||||
- `order-analysis/` - 주문 분석
|
||||
- `production-analysis/` - 생산 분석
|
||||
- `customer-analysis/` - 거래처 분석
|
||||
- `site-analysis/` - 현장 분석
|
||||
- `price-analysis/` - 단가 분석
|
||||
- `master-data-analysis/` - 기준정보 분석
|
||||
- `production-userflow/` - 생산 유저플로우
|
||||
|
||||
---
|
||||
|
||||
### quotation - 견적 기능
|
||||
|
||||
**경로**: `docs/projects/quotation/`
|
||||
**상태**: 🟢 Phase 3 완료 (2025-12-19)
|
||||
**목표**: 5130 레거시 견적 기능을 SAM 시스템으로 이관
|
||||
|
||||
**핵심 문서**:
|
||||
- [MASTER_PLAN.md](./quotation/MASTER_PLAN.md) - 마스터 플랜
|
||||
- [PROGRESS.md](./quotation/PROGRESS.md) - 진행 현황
|
||||
|
||||
**Phase 문서**:
|
||||
| Phase | 상태 | 경로 |
|
||||
|-------|------|------|
|
||||
| 1. 5130 분석 | ✅ 완료 | `phase-1-5130-analysis/` |
|
||||
| 2. mng 분석 | ✅ 완료 | `phase-2-mng-analysis/` |
|
||||
| 3. 구현 | ✅ 완료 | `phase-3-implementation/` |
|
||||
| 4. API 개발 | ⚪ 대기 | `phase-4-api/` |
|
||||
|
||||
**참조 자료**:
|
||||
- `screenshots/` - MES 프로토타입 화면 캡쳐 (7개)
|
||||
|
||||
---
|
||||
|
||||
### api-integration - React ↔ API 연동
|
||||
|
||||
**경로**: `docs/projects/api-integration/`
|
||||
**상태**: 🟡 Phase 4 진행중
|
||||
**목표**: React(dev.sam.kr)와 API(api.sam.kr) 완벽 연동
|
||||
|
||||
**핵심 문서**:
|
||||
- [MASTER_PLAN.md](./api-integration/MASTER_PLAN.md) - 마스터 플랜
|
||||
- [PROGRESS.md](./api-integration/PROGRESS.md) - 진행 현황
|
||||
- [WORKFLOW.md](./api-integration/WORKFLOW.md) - 작업 프로세스
|
||||
|
||||
**Phase 문서**:
|
||||
| Phase | 상태 | 경로 |
|
||||
|-------|------|------|
|
||||
| 1. 테이블 통합 | 🟢 완료(스킵) | `phase-1-table-migration/` |
|
||||
| 2. 메뉴 추출 | 🟡 진행중 | `phase-2-menu-extraction/` |
|
||||
| 3. API 매핑 | 🟡 진행중 | `phase-3-api-mapping/` |
|
||||
| 4. 연동+검증 | 🟡 진행중 | `phase-4-integration/` |
|
||||
|
||||
**TC 파일**: `phase-4-integration/tc/` - 기능별 테스트 케이스 JSON (17개)
|
||||
|
||||
---
|
||||
|
||||
### 5130-migration - 품목 마이그레이션
|
||||
|
||||
**경로**: `docs/projects/5130-migration/`
|
||||
**상태**: 🟡 Phase 1 진행중
|
||||
**목표**: 5130 품목(부품, 자재, BOM) 데이터를 SAM DB로 이전
|
||||
|
||||
**핵심 문서**:
|
||||
- [MASTER_PLAN.md](./5130-migration/MASTER_PLAN.md) - 마스터 플랜
|
||||
- [PROGRESS.md](./5130-migration/PROGRESS.md) - 진행 현황
|
||||
|
||||
**Phase 문서**:
|
||||
| Phase | 상태 | 경로 |
|
||||
|-------|------|------|
|
||||
| 1. 소스 분석 | 🟡 진행중 | `phase-1-source-analysis/` |
|
||||
| 2. 타겟 분석 | ⚪ 대기 | `phase-2-target-analysis/` |
|
||||
| 3. 매핑 설계 | ⚪ 대기 | `phase-3-mapping/` |
|
||||
|
||||
---
|
||||
|
||||
### legacy-5130 - 레거시 분석
|
||||
|
||||
**경로**: `docs/projects/legacy-5130/`
|
||||
**상태**: 📚 참조용 문서
|
||||
**용도**: 5130 레거시 시스템 모듈별 분석
|
||||
|
||||
**모듈별 분석 문서**:
|
||||
| 문서 | 내용 |
|
||||
|------|------|
|
||||
| [00_OVERVIEW.md](./legacy-5130/00_OVERVIEW.md) | 시스템 개요 |
|
||||
| [01_MATERIAL.md](./legacy-5130/01_MATERIAL.md) | 자재 관리 |
|
||||
| [02_PRODUCT.md](./legacy-5130/02_PRODUCT.md) | 제품 관리 |
|
||||
| [03_ESTIMATE.md](./legacy-5130/03_ESTIMATE.md) | 견적 관리 |
|
||||
| [04_PRODUCTION.md](./legacy-5130/04_PRODUCTION.md) | 생산 관리 |
|
||||
| [05_SHIPPING.md](./legacy-5130/05_SHIPPING.md) | 출하 관리 |
|
||||
| [06_QUALITY.md](./legacy-5130/06_QUALITY.md) | 품질 관리 |
|
||||
| [07_ACCOUNTING.md](./legacy-5130/07_ACCOUNTING.md) | 회계 관리 |
|
||||
| [08_SAM_COMPARISON.md](./legacy-5130/08_SAM_COMPARISON.md) | SAM 비교 분석 |
|
||||
| [draw-module.md](./legacy-5130/draw-module.md) | 도면 모듈 |
|
||||
|
||||
---
|
||||
|
||||
### mng-mobile-responsive - 모바일 반응형
|
||||
|
||||
**경로**: `docs/projects/mng-mobile-responsive/`
|
||||
**상태**: 🟡 진행중
|
||||
**목표**: mng 관리자 패널 모바일 반응형 개선
|
||||
|
||||
**문서**:
|
||||
- [01-analysis.md](./mng-mobile-responsive/01-analysis.md) - 분석
|
||||
- [02-implementation-plan.md](./mng-mobile-responsive/02-implementation-plan.md) - 구현 계획
|
||||
- [06-excluded-menus.md](./mng-mobile-responsive/06-excluded-menus.md) - 제외 메뉴
|
||||
- [PROGRESS.md](./mng-mobile-responsive/PROGRESS.md) - 진행 현황
|
||||
|
||||
---
|
||||
|
||||
### auto-login - 자동 로그인
|
||||
|
||||
**경로**: `docs/projects/auto-login/`
|
||||
**상태**: ⚪ 대기
|
||||
**목표**: 자동 로그인 기능 구현
|
||||
|
||||
**문서**:
|
||||
- [PROGRESS.md](./auto-login/PROGRESS.md) - 진행 현황
|
||||
|
||||
---
|
||||
|
||||
### migration-5130-mng - 5130 → mng 마이그레이션
|
||||
|
||||
**경로**: `docs/projects/migration-5130-mng/`
|
||||
**상태**: 🟡 진행중
|
||||
**목표**: 5130 기능을 mng로 통합 마이그레이션
|
||||
|
||||
**문서**:
|
||||
- [MIGRATION_TRACKER.md](./migration-5130-mng/MIGRATION_TRACKER.md) - 마이그레이션 추적
|
||||
|
||||
---
|
||||
|
||||
## 디렉토리 구조
|
||||
|
||||
```
|
||||
docs/projects/
|
||||
├── INDEX.md # 이 파일
|
||||
├── mes/ # MES/ERP 프로젝트 (핵심)
|
||||
│ ├── 00_baseline/ # Phase 0 분석
|
||||
│ ├── v1-analysis/ # v1 분석
|
||||
│ ├── v2-analysis/ # v2 화면 분석
|
||||
│ └── phases/ # Phase별 진행
|
||||
├── quotation/ # 견적 기능
|
||||
│ ├── phase-1-5130-analysis/
|
||||
│ ├── phase-2-mng-analysis/
|
||||
│ ├── phase-3-implementation/
|
||||
│ └── screenshots/
|
||||
├── api-integration/ # React ↔ API 연동
|
||||
│ ├── phase-1-table-migration/
|
||||
│ ├── phase-2-menu-extraction/
|
||||
│ ├── phase-3-api-mapping/
|
||||
│ └── phase-4-integration/tc/
|
||||
├── 5130-migration/ # 품목 마이그레이션
|
||||
│ ├── phase-1-source-analysis/
|
||||
│ ├── phase-2-target-analysis/
|
||||
│ └── phase-3-mapping/
|
||||
├── legacy-5130/ # 레거시 분석 (참조용)
|
||||
├── mng-mobile-responsive/ # 모바일 반응형
|
||||
├── auto-login/ # 자동 로그인
|
||||
└── migration-5130-mng/ # 5130→mng 마이그레이션
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 관련 문서
|
||||
|
||||
- [docs/INDEX.md](../INDEX.md) - 전체 문서 인덱스
|
||||
- [docs/plans/index_plans.md](../plans/index_plans.md) - 기획 문서 인덱스
|
||||
- [docs/guides/PROJECT_DEVELOPMENT_POLICY.md](../guides/PROJECT_DEVELOPMENT_POLICY.md) - 공통 개발 정책
|
||||
- [CURRENT_WORKS.md](../../CURRENT_WORKS.md) - 현재 작업
|
||||
|
||||
---
|
||||
|
||||
**범례**:
|
||||
- 🟢 완료
|
||||
- 🟡 진행중
|
||||
- ⚪ 대기
|
||||
- 📚 참조용
|
||||
287
requests/prompt-workflow-sharing.md
Normal file
287
requests/prompt-workflow-sharing.md
Normal file
@@ -0,0 +1,287 @@
|
||||
# Claude Code 프롬프트 워크플로우 공유
|
||||
|
||||
> **목적:** 팀 내 Claude Code 사용 방식 공유 및 개선 논의
|
||||
> **작성일:** 2025-12-23
|
||||
> **작성자:** hskwon
|
||||
|
||||
---
|
||||
|
||||
## 📋 현재 사용 중인 워크플로우
|
||||
|
||||
### 1. Code-Workflow 스킬 (5단계 강제 프로세스)
|
||||
|
||||
코드 수정 시 체계적인 프로세스를 **강제**하는 커스텀 스킬입니다.
|
||||
|
||||
```
|
||||
분석 → 수정 → 검증 → 정리 → 커밋
|
||||
```
|
||||
|
||||
#### 왜 사용하는가?
|
||||
|
||||
| 문제 | 해결책 |
|
||||
|------|--------|
|
||||
| Claude가 분석 없이 바로 수정 시작 | 1단계에서 **분석 후 승인** 강제 |
|
||||
| 여러 파일 동시 수정 → 롤백 어려움 | 한 번에 하나씩 순차 수정 |
|
||||
| 수정 후 검증 생략 | 3단계 검증 필수 |
|
||||
| 커밋 메시지 대충 작성 | 4단계에서 변경 내용 정리 문서화 |
|
||||
| 자동 커밋으로 실수 | 5단계에서 **커밋 전 승인** 강제 |
|
||||
|
||||
#### 핵심 규칙
|
||||
|
||||
```markdown
|
||||
🔴 필수 규칙 (반드시 준수)
|
||||
|
||||
1. 분석 후 승인 필수
|
||||
- 분석 결과 + 일정 산정 제시
|
||||
- AskUserQuestion으로 승인 요청
|
||||
- 승인 없이 수정 진행 금지
|
||||
|
||||
2. 커밋 전 승인 필수
|
||||
- 변경 내용 요약 제시
|
||||
- 승인 없이 커밋 금지
|
||||
- 승인 후 → 요약 MD 작성 → 커밋
|
||||
```
|
||||
|
||||
#### 스킬 호출 방법
|
||||
|
||||
```bash
|
||||
/code-workflow # 또는 Claude가 코드 수정 요청 시 자동 활성화
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. 프로젝트 문서 기반 진행 관리
|
||||
|
||||
#### 문서 구조
|
||||
|
||||
```
|
||||
docs/projects/{프로젝트명}/
|
||||
├── MASTER_PLAN.md # 마스터 플랜 (전체 로드맵)
|
||||
├── PROGRESS.md # 진행 현황 (실시간 업데이트)
|
||||
├── screenshots/ # 참조 이미지
|
||||
├── phase-1-xxx/ # Phase별 상세 문서
|
||||
│ ├── README.md # Phase 체크리스트 및 요약
|
||||
│ └── *.md # 분석/구현 문서
|
||||
├── phase-2-xxx/
|
||||
└── phase-3-xxx/
|
||||
```
|
||||
|
||||
#### 핵심 문서 역할
|
||||
|
||||
| 문서 | 역할 | 업데이트 주기 |
|
||||
|------|------|---------------|
|
||||
| `MASTER_PLAN.md` | 전체 방향, Phase 정의, 핵심 원칙 | 큰 변경 시 |
|
||||
| `PROGRESS.md` | 현재 진행률, 완료 작업, 다음 작업 | **매 작업 완료 시** |
|
||||
| `phase-*/README.md` | 각 Phase 체크리스트 | Phase 진행 중 |
|
||||
|
||||
#### 실제 사용 예시 (견적 프로젝트)
|
||||
|
||||
```markdown
|
||||
# PROGRESS.md 예시
|
||||
|
||||
## 전체 진행률
|
||||
|
||||
| Phase | 상태 | 진행률 | 시작일 | 완료일 |
|
||||
|-------|------|--------|--------|--------|
|
||||
| Phase 1: 5130 분석 | ✅ 완료 | 100% | 2025-12-19 | 2025-12-19 |
|
||||
| Phase 2: mng 분석 | ✅ 완료 | 100% | 2025-12-19 | 2025-12-19 |
|
||||
| Phase 3: 구현 | ✅ 완료 | 100% | 2025-12-19 | 2025-12-19 |
|
||||
| Phase 4: API 개발 | 🔄 진행 | 60% | 2025-12-19 | - |
|
||||
|
||||
## 🔄 현재 작업
|
||||
|
||||
**현재 Phase:** Phase 4 진행 중
|
||||
**완료된 작업:** mng 패턴 적용 - DB 기반 견적 산출 서비스 재작성
|
||||
**다음 작업:** 견적 API 통합 테스트 및 Swagger 문서화
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. WORKFLOW_STATE.md (작업 중 상태 추적)
|
||||
|
||||
코드 수정 중 **중단 후 재개**를 위한 임시 상태 파일입니다.
|
||||
|
||||
#### 생성 시점
|
||||
- code-workflow 2단계(순차 수정) 시작 시 생성
|
||||
- 5단계(커밋) 완료 후 삭제
|
||||
|
||||
#### 내용
|
||||
|
||||
```markdown
|
||||
# Workflow 진행 상태
|
||||
|
||||
## 현재 단계: 2단계 (순차 수정)
|
||||
|
||||
## 전체 작업 목록
|
||||
- [x] 1단계: 분석 완료
|
||||
- [ ] 2단계: 순차 수정 (3/7 완료)
|
||||
- [ ] 3단계: 검증 대기
|
||||
- [ ] 4단계: 문서 정리 대기
|
||||
- [ ] 5단계: 커밋 대기
|
||||
|
||||
## 순차 수정 상세 (2단계)
|
||||
- [x] 수정 1/7: tenants 마이그레이션 생성
|
||||
- [x] 수정 2/7: folders 마이그레이션 생성
|
||||
- [x] 수정 3/7: files 마이그레이션 확장
|
||||
- [ ] 수정 4/7: FileStorageService 구현 ← 현재 진행 중
|
||||
- [ ] 수정 5/7: Tenant 모델 확장
|
||||
- [ ] 수정 6/7: File 모델 확장
|
||||
- [ ] 수정 7/7: Folder 모델 생성
|
||||
```
|
||||
|
||||
#### 재개 시
|
||||
|
||||
```
|
||||
사용자: "어디까지 진행했지?"
|
||||
|
||||
Claude: [WORKFLOW_STATE.md 확인]
|
||||
|
||||
📋 작업 진행 현황
|
||||
- 현재 단계: 2단계 (순차 수정)
|
||||
- 전체 진행률: 43% (3/7 완료)
|
||||
- 마지막 완료: files 마이그레이션 확장
|
||||
|
||||
계속 진행할까요? (y/n)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4. 도구 사용 기록
|
||||
|
||||
모든 단계에서 **사용된 도구를 명시적으로 기록**합니다.
|
||||
|
||||
#### Footer 형식
|
||||
|
||||
```
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
🔧 사용된 도구
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
📦 MCP 서버
|
||||
- Sequential Thinking: 복잡한 의존성 분석
|
||||
- Context7: Laravel Storage 공식 문서 조회
|
||||
|
||||
🧠 SuperClaude 페르소나
|
||||
- backend-architect: 파일 저장 시스템 아키텍처 설계
|
||||
- security-engineer: 파일 업로드 보안 검토
|
||||
|
||||
🛠️ 네이티브 도구
|
||||
- Read: 5회 (File.php, Tenant.php 등)
|
||||
- Glob: 3회
|
||||
- Grep: 2회
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 💡 이 방식의 장점
|
||||
|
||||
### 1. 세션 중단에 강함
|
||||
- `PROGRESS.md` + `WORKFLOW_STATE.md`로 언제든 이어서 작업 가능
|
||||
- Claude 컨텍스트가 끊겨도 문서만 읽으면 상태 파악
|
||||
|
||||
### 2. 실수 방지
|
||||
- 분석 → 승인 → 수정 순서 강제
|
||||
- 커밋 전 승인 강제
|
||||
- 여러 파일 동시 수정 금지
|
||||
|
||||
### 3. 문서화 자동화
|
||||
- 변경 내용이 자동으로 `docs/changes/` 에 기록
|
||||
- 나중에 "왜 이렇게 바꿨지?" 추적 가능
|
||||
|
||||
### 4. 복잡한 프로젝트 관리
|
||||
- Phase별 분리로 큰 프로젝트도 관리 가능
|
||||
- 체크리스트 기반으로 누락 방지
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 이 방식의 단점
|
||||
|
||||
### 1. 초기 설정 비용
|
||||
- 스킬 파일, CLAUDE.md 설정 필요
|
||||
- 프로젝트별 문서 구조 생성 필요
|
||||
|
||||
### 2. 간단한 작업에는 과함
|
||||
- 오타 수정 같은 건 5단계가 불필요
|
||||
- 예외 처리 규칙 필요 (hotfix 모드 등)
|
||||
|
||||
### 3. Claude 의존
|
||||
- Claude가 규칙을 "잊어버리면" 무용지물
|
||||
- 가끔 리마인드 필요
|
||||
|
||||
---
|
||||
|
||||
## 🤔 논의 포인트
|
||||
|
||||
### 1. 다른 분들은 어떻게 사용하고 계신가요?
|
||||
|
||||
- 프로젝트 문서 구조는?
|
||||
- 코드 수정 시 어떤 프로세스?
|
||||
- 세션 중단 후 재개 방법?
|
||||
- 커밋 관리 방식?
|
||||
|
||||
### 2. 개선 아이디어
|
||||
|
||||
- 현재 방식에서 불편한 점?
|
||||
- 더 좋은 문서 구조?
|
||||
- 더 나은 진행 상태 추적 방법?
|
||||
|
||||
### 3. 공통화 가능한 부분
|
||||
|
||||
- 팀 공통 CLAUDE.md?
|
||||
- 공통 스킬 라이브러리?
|
||||
- 프로젝트 템플릿?
|
||||
|
||||
---
|
||||
|
||||
## 📁 관련 파일
|
||||
|
||||
| 파일 | 설명 |
|
||||
|------|------|
|
||||
| `.claude/skills/code-workflow/SKILL.md` | code-workflow 스킬 정의 |
|
||||
| `CLAUDE.md` | 프로젝트 지시사항 |
|
||||
| `docs/projects/*/PROGRESS.md` | 프로젝트별 진행 현황 |
|
||||
| `docs/projects/*/MASTER_PLAN.md` | 프로젝트별 마스터 플랜 |
|
||||
|
||||
---
|
||||
|
||||
## 📝 스킬 전문 (참고용)
|
||||
|
||||
<details>
|
||||
<summary>code-workflow 스킬 전체 내용 (클릭해서 펼치기)</summary>
|
||||
|
||||
```markdown
|
||||
# 5단계 워크플로우 요약
|
||||
|
||||
## 1단계: 분석 (ANALYZE)
|
||||
- SAM 프로젝트 컨텍스트 로드
|
||||
- MCP로 관련 파일 검색 및 의존성 분석
|
||||
- 영향받는 파일 식별
|
||||
- 예상 부작용/리스크 파악
|
||||
- **사용자 확인 후 다음 단계로**
|
||||
|
||||
## 2단계: 순차 수정 (MODIFY)
|
||||
- WORKFLOW_STATE.md 생성
|
||||
- 한 번에 하나씩 수정
|
||||
- 각 수정 완료 후 상태 업데이트
|
||||
- 이전 코드 주석 처리 (삭제 금지)
|
||||
|
||||
## 3단계: 검증 (VERIFY)
|
||||
- 문법 오류 확인 (Pint)
|
||||
- 로직 검증
|
||||
- 기존 기능 유지 확인
|
||||
- 보안/성능 체크
|
||||
|
||||
## 4단계: 변경 내용 정리 (SUMMARY)
|
||||
- docs/changes/YYYYMMDD_HHMM_feature_name.md 생성
|
||||
- CURRENT_WORKS.md 업데이트
|
||||
|
||||
## 5단계: Git 커밋 (COMMIT)
|
||||
- 변경 파일 확인
|
||||
- 커밋 메시지 제안
|
||||
- **사용자 확인 후 커밋**
|
||||
- WORKFLOW_STATE.md 삭제
|
||||
```
|
||||
|
||||
</details>
|
||||
Reference in New Issue
Block a user