diff --git a/CURRENT_WORKS.md b/CURRENT_WORKS.md index f229361..349cc09 100644 --- a/CURRENT_WORKS.md +++ b/CURRENT_WORKS.md @@ -1,5 +1,388 @@ # SAM API 작업 현황 +## 2025-12-09 (월) - HR API 개발 완료 (Employee, Attendance, Department Tree) + +### 작업 목표 +- `docs/features/HR_API_ANALYSIS.md` 기반 HR API 구현 +- Employee 관리 API (tenant_user_profiles 활용) +- Attendance 근태 관리 API (attendances 테이블) +- Department 트리 조회 API + +### Phase 1: 마이그레이션 ✅ + +**추가된 마이그레이션:** +- `2025_12_09_084138_add_employee_status_to_tenant_user_profiles_table.php` + - `employee_status` ENUM('active', 'leave', 'resigned') DEFAULT 'active' + - `json_extra` JSON nullable (유연한 사원 정보) + +- `2025_12_09_084231_create_attendances_table.php` + - `user_id`, `base_date`, `status`, `json_details`, `remarks` + - `json_details`: check_in, check_out, gps_data, work_minutes 등 + +### Phase 2: Employee API ✅ + +**수정된 파일:** +- `app/Models/Tenants/TenantUserProfile.php` - employee_status, json_extra 헬퍼 추가 + +**추가된 파일:** +- `app/Services/EmployeeService.php` - 사원 CRUD, 통계, 계정 생성 +- `app/Http/Controllers/Api/V1/EmployeeController.php` +- `app/Http/Requests/Employee/` (5개 FormRequest) + - IndexRequest, StoreRequest, UpdateRequest, BulkDeleteRequest, CreateAccountRequest +- `app/Swagger/v1/EmployeeApi.php` - 8개 엔드포인트 문서 + +**API 엔드포인트 (8개):** +| Method | Endpoint | 설명 | +|--------|----------|------| +| GET | `/v1/employees` | 사원 목록 | +| POST | `/v1/employees` | 사원 등록 | +| GET | `/v1/employees/stats` | 사원 통계 | +| GET | `/v1/employees/{id}` | 사원 상세 | +| PATCH | `/v1/employees/{id}` | 사원 수정 | +| DELETE | `/v1/employees/{id}` | 사원 삭제 | +| POST | `/v1/employees/bulk-delete` | 일괄 삭제 | +| POST | `/v1/employees/{id}/create-account` | 계정 생성 | + +### Phase 3: Department Tree API ✅ + +**수정된 파일:** +- `app/Services/DepartmentService.php` - getTree(), buildTreeNode() 추가 +- `app/Http/Controllers/Api/V1/DepartmentController.php` - tree() 액션 추가 +- `routes/api.php` - `/v1/departments/tree` 라우트 추가 + +### Phase 4: Attendance API ✅ + +**추가된 파일:** +- `app/Models/Tenants/Attendance.php` - 근태 모델 + - BelongsToTenant, SoftDeletes + - json_details 헬퍼 (check_in, check_out, gps_data, work_minutes 등) + - 스코프: onDate, betweenDates, forUser, withStatus + +- `app/Services/AttendanceService.php` - 근태 CRUD, 출퇴근, 월간 통계 +- `app/Http/Controllers/Api/V1/AttendanceController.php` +- `app/Http/Requests/Attendance/` (6개 FormRequest) + - IndexRequest, StoreRequest, UpdateRequest, CheckInRequest, CheckOutRequest, MonthlyStatsRequest +- `app/Swagger/v1/AttendanceApi.php` - 9개 엔드포인트 문서 + +**API 엔드포인트 (9개):** +| Method | Endpoint | 설명 | +|--------|----------|------| +| GET | `/v1/attendances` | 근태 목록 | +| POST | `/v1/attendances` | 근태 등록 | +| GET | `/v1/attendances/monthly-stats` | 월간 통계 | +| POST | `/v1/attendances/check-in` | 출근 기록 | +| POST | `/v1/attendances/check-out` | 퇴근 기록 | +| GET | `/v1/attendances/{id}` | 근태 상세 | +| PATCH | `/v1/attendances/{id}` | 근태 수정 | +| DELETE | `/v1/attendances/{id}` | 근태 삭제 | +| POST | `/v1/attendances/bulk-delete` | 일괄 삭제 | + +### 검증 결과 +- ✅ Pint 코드 포맷팅 완료 (13개 파일 수정) +- ✅ 마이그레이션 실행 완료 (Batch 48) +- ✅ 라우트 등록 확인 (Employee 8개, Attendance 9개, Department Tree 1개) +- ✅ Swagger 문서 생성 완료 + +### 수정된 파일 목록 + +**routes/api.php:** +- EmployeeController, AttendanceController import 추가 +- Employee API 라우트 그룹 (8개) +- Attendance API 라우트 그룹 (9개) +- Department /tree 라우트 추가 + +**버그 수정:** +- `app/Models/Tenants/Attendance.php` - BelongsToTenant 경로 수정 + - `App\Models\Scopes\BelongsToTenant` → `App\Traits\BelongsToTenant` + +### 다음 작업 +- [ ] React 프론트엔드 연동 +- [ ] 휴가 관리 API 구현 (향후) + +--- + +## 2025-12-08 (일) - Flow Tester Error #61 해결 (GET query 파라미터 처리) + +### 문제 +- `GET /pricing/cost` 요청 시 422 에러 발생 +- 에러: `item_id`와 `item_type_code`가 필수 항목인데 누락 + +### 원인 +- Flow 정의에 `query` 필드로 파라미터 정의되어 있음 +- **FlowExecutor**가 `query` 필드를 처리하지 않고 `body`만 처리 +- GET 요청은 query string으로 파라미터를 전달해야 하는데 누락됨 + +### 해결 +**수정 파일**: `mng/app/Services/FlowTester/FlowExecutor.php` + +```php +// Line 226: query 변수 바인딩 추가 +$query = $this->binder->bind($step['query'] ?? []); + +// Line 230-234: HTTP 요청에 query 옵션 전달 +$response = $this->httpClient->request($method, $endpoint, [ + 'headers' => $headers, + 'body' => $body, + 'query' => $query, // 추가 +]); + +// 결과 로그에도 query 정보 포함 +'request' => [ + 'method' => $method, + 'endpoint' => $endpoint, + 'headers' => $headers, + 'body' => $body, + 'query' => $query, // 추가 +], +``` + +### 검증 +- PHP 문법 검사: ✅ 통과 + +--- + +## 2025-12-08 (일) - Flow Tester Error #60 해결 (중복 테스트 데이터 삭제) + +### 문제 +- Flow Tester 재실행 시 `error.duplicate_key` 오류 발생 +- 이전 테스트 실행(Error #59)이 중간에 실패하면서 테스트 데이터가 남아있음 +- 동적 날짜(`{{$date}}`)가 정상 작동 중이지만, 같은 날 재실행 시 중복 발생 + +### 원인 +- Error #59 실행 시 create_price 단계까지 진행 후 실패 +- cleanup 단계(delete_price)에 도달하지 못해 테스트 데이터 잔류 +- checkDuplicate() 메서드가 기존 데이터 발견 + +### 해결 +```bash +# 잔류 테스트 데이터 삭제 +docker exec -i sam-api-1 php artisan tinker --execute=" + use App\Models\Products\Price; + Price::where('effective_from', '2025-12-08')->delete(); +" +# 결과: 1건 삭제 완료 +``` + +### 추가 권장사항 +- Flow Tester 재실행 전 cleanup 또는 setup 스크립트 추가 고려 +- 또는 effective_from에 `{{$uuid}}`나 `{{$timestamp}}`를 조합하여 고유성 보장 + +--- + +## 2025-12-08 (일) - User 모델 경로 오류 수정 (500 에러 해결) + +### 문제 +- Flow Tester #59 실행 시 `GET /pricing/{id}/revisions` 에서 500 에러 +- 에러: `Class "App\Models\User" not found` + +### 원인 +- `PriceRevision.php`, `Board.php`에서 잘못된 User 모델 경로 참조 +- 실제 경로: `App\Models\Members\User` + +### 수정된 파일 +- `app/Models/Products/PriceRevision.php` - `\App\Models\User` → `\App\Models\Members\User` +- `app/Models/Boards/Board.php` - `use App\Models\User` → `use App\Models\Members\User` + +--- + +## 2025-12-08 (일) - Flow Tester 동적 날짜 변수 적용 (duplicate key 해결) + +### 문제 +- Flow Tester #58 실행 시 `error.duplicate_key` 에러 발생 +- 원인: 하드코딩된 `effective_from: "2025-01-01"`로 인해 이전 테스트 데이터와 중복 + +### 해결 +`{{$date}}` 동적 변수를 사용하여 매번 테스트 시 오늘 날짜 사용 + +### 수정 내용 (Flow ID: 8 - 단가 관리 CRUD 테스트) + +| 스텝 | 필드 | 변경 전 | 변경 후 | +|------|------|---------|---------| +| create_price | effective_from | "2025-01-01" | "{{$date}}" | +| create_price | effective_to | "2025-12-31" | "2099-12-31" | +| create_price_for_finalize | effective_from | "2025-01-01" | "{{$date}}" | +| get_cost | date | "2025-06-15" | "{{$date}}" | +| by_items | date | "2025-06-15" | "{{$date}}" | + +### VariableBinder 지원 변수 + +| 변수 | 설명 | 예시 | +|------|------|------| +| `{{$date}}` | 현재 날짜 (Y-m-d) | 2025-12-08 | +| `{{$datetime}}` | 현재 날짜시간 (Y-m-d H:i:s) | 2025-12-08 14:30:00 | +| `{{$timestamp}}` | Unix 타임스탬프 | 1733637000 | +| `{{$uuid}}` | 랜덤 UUID | 550e8400-e29b-41d4-a716-... | +| `{{$random:N}}` | N자리 랜덤 숫자 | 123456 | +| `{{$faker.xxx}}` | Faker 랜덤 데이터 | 회사명, 이름 등 | + +### 해결 원리 +- 매번 테스트 시 오늘 날짜가 사용되어 다른 날의 중복 데이터와 충돌 없음 +- 플로우 마지막에 `delete_price`와 `cleanup_finalized` 스텝이 테스트 데이터 정리 +- 별도의 cleanup 스텝 없이도 반복 실행 가능 + +--- + +## 2025-12-08 (일) - Flow Tester HTTP 상태 코드 수정 + +### 문제 +- Flow Tester `POST /pricing` 요청에서 예상 상태 코드 201, 실제 200 반환 +- HTTP 표준: POST 리소스 생성 시 201 Created 반환 필요 + +### 수정 내용 + +**ApiResponse 클래스 개선:** +- `ApiResponse::success()`: `$statusCode` 파라미터 추가 (기본값 200) +- `ApiResponse::handle()`: 콜백에서 `statusCode` 키로 상태 코드 지정 가능 + +**PricingController 수정:** +- `store()` 메서드: `'statusCode' => 201` 반환 + +### 수정된 파일 +- `app/Helpers/ApiResponse.php` - 상태 코드 파라미터 추가 +- `app/Http/Controllers/Api/V1/PricingController.php` - store 201 반환 + +### 사용 예시 +```php +// Controller에서 201 반환 +return ApiResponse::handle(function () use ($request) { + $data = $this->service->store($request->validated()); + return ['data' => $data, 'message' => __('message.created'), 'statusCode' => 201]; +}); +``` + +### 검증 필요 +- [x] Flow Tester 재실행하여 201 응답 확인 ✅ +- [x] duplicate key 에러 해결 (동적 날짜 변수 적용) ✅ + +### 남은 작업 (TODO) +다른 Controller store 메서드 일괄 수정 (27개): +- AdminController, BoardController, CategoryController, CategoryFieldController +- CategoryTemplateController, ClassificationController, ClientGroupController +- CommonController, DesignModelController, EstimateController, FolderController +- ItemsController, MaterialController, MenuController, ModelSetController +- PostController, QuoteController, RoleController, TenantController +- TenantOptionGroupController, TenantOptionValueController, TenantStatFieldController +- ItemMaster 하위: CustomTabController, ItemBomItemController, ItemFieldController +- ItemMaster 하위: ItemSectionController, UnitOptionController + +--- + +## 2025-12-08 (일) - 단가 관리 API 전면 재설계 (prices + price_revisions) + +### 작업 목표 +- `price_histories` → `prices` + `price_revisions` 구조로 전면 재설계 +- 원가 조회 시 수입검사 입고단가 우선, 표준원가 폴백 로직 구현 +- 가격 확정(finalize) 기능으로 불변성 보장 +- 리비전 관리로 변경 이력 추적 + +### 테이블 구조 변경 + +**prices (단가 마스터):** +| 컬럼 | 설명 | +|------|------| +| item_type_code | 품목유형 (PRODUCT/MATERIAL) | +| item_id | 품목 ID | +| client_group_id | 고객그룹 ID (NULL=기본가) | +| purchase_price | 매입단가 (표준원가) | +| processing_cost | 가공비 | +| loss_rate | LOSS율 (%) | +| margin_rate | 마진율 (%) | +| sales_price | 판매단가 | +| rounding_rule | 반올림 규칙 (round/ceil/floor) | +| rounding_unit | 반올림 단위 (1,10,100,1000) | +| effective_from/to | 적용 기간 | +| status | 상태 (draft/active/inactive/finalized) | +| is_final | 최종 확정 여부 | + +**price_revisions (변경 이력):** +| 컬럼 | 설명 | +|------|------| +| price_id | 단가 FK | +| revision_number | 리비전 번호 | +| changed_at | 변경 일시 | +| changed_by | 변경자 ID | +| change_reason | 변경 사유 | +| before_snapshot | 변경 전 JSON | +| after_snapshot | 변경 후 JSON | + +### 생성된 파일 + +**마이그레이션 (4개):** +- `2025_12_08_154633_create_prices_table.php` +- `2025_12_08_154634_create_price_revisions_table.php` +- `2025_12_08_154635_migrate_price_histories_to_prices.php` +- `2025_12_08_154636_drop_price_histories_table.php` + +**모델 (2개):** +- `app/Models/Price.php` - BelongsToTenant, SoftDeletes +- `app/Models/PriceRevision.php` + +**서비스 (1개):** +- `app/Services/PricingService.php` + - index, show, store, update, destroy (CRUD) + - byItems: 다중 품목 단가 조회 + - revisions: 변경 이력 조회 + - finalize: 가격 확정 (불변 처리) + - getCost: 원가 조회 (receipt > standard 폴백) + +**FormRequest (5개):** +- `app/Http/Requests/Pricing/PriceIndexRequest.php` +- `app/Http/Requests/Pricing/PriceStoreRequest.php` +- `app/Http/Requests/Pricing/PriceUpdateRequest.php` +- `app/Http/Requests/Pricing/PriceByItemsRequest.php` +- `app/Http/Requests/Pricing/PriceCostRequest.php` + +**Swagger (1개):** +- `app/Swagger/v1/PricingApi.php` - 전면 재작성 + +### 삭제된 파일 +- `app/Models/PriceHistory.php` + +### API 엔드포인트 (9개) + +| Method | Endpoint | 설명 | +|--------|----------|------| +| GET | `/api/v1/pricing` | 단가 목록 (페이지네이션) | +| POST | `/api/v1/pricing` | 단가 생성 | +| GET | `/api/v1/pricing/cost` | 원가 조회 (receipt > standard) | +| POST | `/api/v1/pricing/by-items` | 다중 품목 단가 조회 | +| GET | `/api/v1/pricing/{id}` | 단가 상세 | +| PUT | `/api/v1/pricing/{id}` | 단가 수정 | +| DELETE | `/api/v1/pricing/{id}` | 단가 삭제 | +| POST | `/api/v1/pricing/{id}/finalize` | 가격 확정 | +| GET | `/api/v1/pricing/{id}/revisions` | 변경 이력 조회 | + +### 원가 조회 로직 (getCost) + +``` +1순위: 자재인 경우 → material_receipts.purchase_price_excl_vat + (수입검사 완료된 최신 입고단가) + +2순위: prices.purchase_price (표준원가) + +총원가 계산: + total_cost = (purchase_price + processing_cost) × (1 + loss_rate/100) + +판매가 계산: + sales_price = round(total_cost × (1 + margin_rate/100), rounding_unit, rounding_rule) +``` + +### 검증 결과 +- PHP 문법 검사: ✅ 모든 파일 통과 +- Pint 코드 포맷팅: ✅ 14개 파일 수정 완료 +- Swagger 문서 생성: ✅ 완료 + +### 다음 작업 +- [ ] 마이그레이션 실행 (php artisan migrate) +- [ ] API 테스트 (Swagger UI) +- [ ] React 프론트엔드 연동 + +### 참조 문서 +- `docs/front/[API-2025-12-08] pricing-api-enhancement-request.md` +- `docs/rules/pricing-policy.md` + +--- + ## 2025-12-04 (수) - 견적 API Phase 3: Controller + FormRequest + Routes + Swagger 완료 ### 작업 목표 diff --git a/LOGICAL_RELATIONSHIPS.md b/LOGICAL_RELATIONSHIPS.md index 309b2e0..4256f30 100644 --- a/LOGICAL_RELATIONSHIPS.md +++ b/LOGICAL_RELATIONSHIPS.md @@ -1,6 +1,6 @@ # 논리적 데이터베이스 관계 문서 -> **자동 생성**: 2025-12-04 20:56:47 +> **자동 생성**: 2025-12-08 20:14:33 > **소스**: Eloquent 모델 관계 분석 ## 📊 모델별 관계 현황 @@ -8,6 +8,8 @@ ## 📊 모델별 관계 현황 ### boards **모델**: `App\Models\Boards\Board` +- **creator()**: belongsTo → `users` +- **updater()**: belongsTo → `users` - **customFields()**: hasMany → `board_settings` - **posts()**: hasMany → `posts` @@ -299,10 +301,19 @@ ### parts - **category()**: belongsTo → `common_codes` - **partType()**: belongsTo → `common_codes` -### price_historys -**모델**: `App\Models\Products\PriceHistory` +### prices +**모델**: `App\Models\Products\Price` - **clientGroup()**: belongsTo → `client_groups` +- **product()**: belongsTo → `products` +- **material()**: belongsTo → `materials` +- **revisions()**: hasMany → `price_revisions` + +### price_revisions +**모델**: `App\Models\Products\PriceRevision` + +- **price()**: belongsTo → `prices` +- **changedByUser()**: belongsTo → `users` ### products **모델**: `App\Models\Products\Product`