Files
sam-api/CURRENT_WORKS.md
권혁성 d186a0c111 오늘 이슈(TodayIssue) 기능 구현
- TodayIssue 모델 및 마이그레이션 추가
- TodayIssueController, TodayIssueService 구현
- TodayIssueObserverService 및 Observer 패턴 적용
- DailyReportService 연동
- Swagger API 문서 업데이트
- 라우트 추가
2026-01-22 09:47:29 +09:00

2509 lines
90 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## 2026-01-21 (화) - TodayIssue 헤더 알림 API (Phase 3 완료)
### 작업 목표
- TodayIssue + 알림 시스템 통합 Phase 3: 헤더 알림 API 구현
- 읽지 않은 이슈 목록/개수 조회, 읽음 처리 API 구현
### 수정된 파일
| 파일명 | 설명 |
|--------|------|
| `app/Services/TodayIssueService.php` | getUnreadList(), getUnreadCount(), markAllAsRead() 추가 |
| `app/Http/Controllers/Api/V1/TodayIssueController.php` | unread(), unreadCount(), markAsRead(), markAllAsRead() 추가 |
| `routes/api.php` | 4개 엔드포인트 추가 |
| `lang/ko/message.php` | today_issue.marked_as_read, all_marked_as_read 메시지 추가 |
| `app/Swagger/v1/TodayIssueApi.php` | 4개 엔드포인트 + 스키마 문서화 |
| `app/Swagger/v1/ComprehensiveAnalysisApi.php` | 스키마 이름 충돌 해결 (TodayIssueItem → ComprehensiveTodayIssueItem) |
### API 엔드포인트
| Method | Endpoint | 설명 |
|--------|----------|------|
| GET | `/api/v1/today-issues/unread` | 읽지 않은 이슈 목록 (헤더 알림 드롭다운용) |
| GET | `/api/v1/today-issues/unread/count` | 읽지 않은 이슈 개수 (헤더 뱃지용) |
| POST | `/api/v1/today-issues/{id}/read` | 단일 이슈 읽음 처리 |
| POST | `/api/v1/today-issues/read-all` | 모든 이슈 읽음 처리 |
### 검증 완료
- [x] Pint 코드 스타일 통과
- [x] Swagger 문서 생성 완료
- [x] PHP 문법 검증 통과
- [x] Service-First 아키텍처 준수
- [x] Multi-tenancy (tenant_id 필터링) 적용
- [x] i18n 메시지 키 사용
### 계획 문서
- `docs/plans/today-issue-notification-integration-plan.md`
- 백엔드 작업 완료 (Phase 1-3: 100%)
- Phase 4 (React 헤더 연동)는 프론트엔드 담당
---
## 2026-01-11 (토) - Labor(노임관리) API 구현
### 작업 목표
- 시공관리 > 노임관리 API 백엔드 구현
- Frontend actions.ts API 연동
### 생성된 파일
| 파일명 | 설명 |
|--------|------|
| `app/Models/Labor.php` | 노임 모델 (BelongsToTenant, SoftDeletes) |
| `app/Http/Controllers/Api/V1/LaborController.php` | 노임 컨트롤러 (7개 메서드) |
| `app/Services/LaborService.php` | 노임 서비스 (비즈니스 로직) |
| `app/Http/Requests/Labor/LaborIndexRequest.php` | 목록 조회 검증 |
| `app/Http/Requests/Labor/LaborStoreRequest.php` | 등록 요청 검증 |
| `app/Http/Requests/Labor/LaborUpdateRequest.php` | 수정 요청 검증 |
| `app/Http/Requests/Labor/LaborBulkDeleteRequest.php` | 일괄 삭제 검증 |
| `database/migrations/2026_01_11_000000_create_labors_table.php` | 마이그레이션 |
### 수정된 파일
| 파일명 | 설명 |
|--------|------|
| `routes/api.php` | Labor 라우트 7개 추가 (line 1005-1014) |
### API 엔드포인트
| Method | Endpoint | 설명 |
|--------|----------|------|
| GET | `/api/v1/labor` | 목록 조회 |
| GET | `/api/v1/labor/stats` | 통계 조회 |
| POST | `/api/v1/labor` | 등록 |
| GET | `/api/v1/labor/{id}` | 상세 조회 |
| PUT | `/api/v1/labor/{id}` | 수정 |
| DELETE | `/api/v1/labor/{id}` | 삭제 |
| DELETE | `/api/v1/labor/bulk` | 일괄 삭제 |
### 검증 완료
- [x] 마이그레이션 실행 완료
- [x] Pint 코드 스타일 통과
- [x] Service-First 아키텍처 준수
- [x] FormRequest 검증 사용
- [x] Multi-tenancy (BelongsToTenant) 적용
---
# SAM API 작업 현황
## 2025-01-09 (목) - 작업지시 코드 리뷰 기반 전면 개선
### 작업 목표
- 작업지시(Work Orders) 기능 코드 리뷰 결과 기반 전면 개선
- Critical, High, Medium 우선순위 항목 전체 수정
### 수정된 파일
| 파일명 | 설명 |
|--------|------|
| `app/Models/Production/WorkOrderItem.php` | BelongsToTenant 트레이트 적용 |
| `app/Models/Production/WorkOrderBendingDetail.php` | BelongsToTenant 트레이트 적용 |
| `app/Models/Production/WorkOrderIssue.php` | BelongsToTenant 트레이트 적용 |
| `app/Models/Production/WorkOrder.php` | 상태 전이 규칙 (STATUS_TRANSITIONS, canTransitionTo, transitionTo) |
| `app/Services/WorkOrderService.php` | 감사 로그, 다중 담당자, 부분 수정 지원 |
### 생성된 파일
| 파일명 | 설명 |
|--------|------|
| `app/Models/Production/WorkOrderAssignee.php` | 다중 담당자 피벗 모델 |
| `database/migrations/*_create_work_order_assignees_table.php` | 다중 담당자 테이블 마이그레이션 |
### 주요 변경 내용
1. **Multi-tenancy 적용**: 하위 모델 4개에 BelongsToTenant 트레이트 적용
2. **감사 로그 적용**: 품목 삭제, 상태 변경, 이슈 등록/해결, 담당자 배정 시 기록
3. **상태 전이 규칙**: STATUS_TRANSITIONS 상수 + canTransitionTo(), transitionTo() 메서드
4. **다중 담당자 지원**:
- WorkOrderAssignee 피벗 모델 생성
- assign() 메서드에서 배열 담당자 지원
- is_primary 플래그로 주 담당자 구분
5. **부분 수정 지원**: 품목 업데이트 시 ID 기반 upsert/delete (기존 삭제 후 재생성 → ID 기반 부분 수정)
### 검증 완료
- [x] Pint 코드 스타일 (3개 파일)
- [x] Service-First 아키텍처 준수
- [x] Eager loading에 assignees.user:id,name 추가
### Git 커밋
- `349917f refactor(work-orders): 코드 리뷰 기반 전면 개선`
### 관련 문서
- 계획: `~/.claude/plans/purring-sparking-pinwheel.md`
---
## 2026-01-08 (수) - Order Management API Phase 1.1 구현
### 작업 목표
- 수주관리(Order Management) API 기본 CRUD 및 상태 관리 기능 구현
- WorkOrderService/Controller 패턴을 참고하여 SAM API 규칙 준수
### 생성된 파일
| 파일명 | 설명 |
|--------|------|
| `app/Services/OrderService.php` | 수주 비즈니스 로직 서비스 |
| `app/Http/Controllers/Api/V1/OrderController.php` | 수주 API 컨트롤러 |
| `app/Http/Requests/Order/StoreOrderRequest.php` | 생성 요청 검증 |
| `app/Http/Requests/Order/UpdateOrderRequest.php` | 수정 요청 검증 |
| `app/Http/Requests/Order/UpdateOrderStatusRequest.php` | 상태 변경 요청 검증 |
| `app/Swagger/v1/OrderApi.php` | Swagger API 문서 |
### 수정된 파일
| 파일명 | 설명 |
|--------|------|
| `routes/api.php` | OrderController import 및 7개 라우트 추가 |
| `lang/ko/message.php` | 수주 관련 메시지 키 추가 |
| `lang/en/message.php` | 수주 관련 메시지 키 추가 |
| `lang/ko/error.php` | 수주 에러 메시지 키 추가 |
| `lang/en/error.php` | 수주 에러 메시지 키 추가 |
### 주요 구현 내용
1. **OrderService 메서드**: index, stats, show, store, update, destroy, updateStatus
2. **상태 전환 규칙**: DRAFT → CONFIRMED → IN_PROGRESS → COMPLETED/CANCELLED
3. **수주번호 자동생성**: ORD{YYYYMMDD}{0001} 형식
4. **품목 금액 계산**: 공급가, 세액, 합계 자동 계산
5. **Swagger 스키마**: Order, OrderItem, OrderPagination, OrderStats 등
### 검증 완료
- [x] Pint 코드 스타일 (6개 파일 자동 수정)
- [x] Swagger 문서 생성
- [x] Service-First 아키텍처 준수
- [x] i18n 메시지 키 사용
### 관련 문서
- 계획: `docs/plans/order-management-plan.md`
- 변경 요약: `docs/changes/20250108_order_management_phase1.md`
---
## 2026-01-02 (목) - 채권현황 동적월 지원 및 버그 수정
### 작업 목표
- "최근 1년" 필터 선택 시 동적 월 기간(최근 12개월) 지원
- year=0 파라미터 처리 버그 수정
- 거래처별 연체 상태 및 메모 관리 기능 추가
### 수정된 파일
| 파일명 | 설명 |
|--------|------|
| `app/Http/Controllers/Api/V1/ReceivablesController.php` | boolean 유효성 검사 수정, 디버깅 로그 추가 |
| `app/Services/ReceivablesService.php` | 동적 월 기간 지원, 이월잔액 계산 추가 |
| `app/Models/Orders/Client.php` | is_overdue, memo 필드 추가 |
| `routes/api.php` | 채권현황 라우트 추가 |
### 생성된 파일
| 파일명 | 설명 |
|--------|------|
| `database/migrations/2026_01_02_113722_add_is_overdue_to_clients_table.php` | clients 테이블 is_overdue 컬럼 추가 |
### 주요 변경 내용
1. **Boolean 유효성 검사 수정**: `'nullable|boolean'``'nullable|string|in:true,false,1,0'`
- 쿼리 문자열의 `"true"` 문자열을 올바르게 처리
2. **동적 월 기간 지원**: `recent_year=true` 시 최근 12개월 동적 계산
3. **월별 레이블 동적 생성**: 예: `['25.02', '25.03', ...]`
4. **이월잔액(carry_forward_balance) 계산**: 선택 기간 이전의 누적 미수금
### Git 커밋
- `4fa38e3` feat(API): 채권현황 동적월 지원 및 year=0 파라미터 버그 수정
### 남은 작업
- [ ] 디버깅 로그 제거 (테스트 완료 후)
- [ ] 추가 UI 개선사항 확인
---
## 2026-01-02 (목) - 견적 BOM 산출 작업 현황 및 Item 모델 주석 추가
### 작업 목표
- 견적 BOM 산출 관련 작업 진행 상황 문서화
- Item 모델 필드 주석 추가
### 수정된 파일
| 파일명 | 설명 |
|--------|------|
| `app/Models/Items/Item.php` | item_category 필드 주석 추가 |
### 주요 변경 내용
1. **Item 모델 필드 주석**:
- `item_category` 필드에 설명 주석 추가
- React 프론트엔드에서 필드 매핑 시 참조용
### Git 커밋
- `02e268e` docs(API): 견적 BOM 산출 작업 현황 및 Item 모델 주석 추가
---
## 2026-01-02 (목) - Phase 1.2 다건 BOM 기반 자동산출 API 구현
### 작업 목표
- React 견적등록 화면에서 여러 품목의 자동산출을 일괄 요청할 수 있는 API 구현
- React QuoteFormItem 인터페이스 필드명(camelCase)과 API 변수명(약어) 모두 지원
### 생성된 파일
| 파일명 | 설명 |
|--------|------|
| `app/Http/Requests/Quote/QuoteBomBulkCalculateRequest.php` | 다건 BOM 산출 FormRequest (필드 변환 포함) |
| `docs/changes/20260102_1300_quote_bom_bulk_calculation.md` | 변경 내용 문서 |
### 수정된 파일
| 파일명 | 설명 |
|--------|------|
| `app/Services/Quote/QuoteCalculationService.php` | calculateBomBulk() 메서드 추가 |
| `app/Http/Controllers/Api/V1/QuoteController.php` | calculateBomBulk 액션 추가 |
| `routes/api.php` | /calculate/bom/bulk 라우트 추가 |
| `app/Swagger/v1/QuoteApi.php` | 스키마 3개 + 엔드포인트 추가 |
### 주요 변경 내용
1. **다건 BOM 기반 자동산출 API**: `POST /api/v1/quotes/calculate/bom/bulk`
2. **필드 매핑 지원**: React camelCase (openWidth, openHeight) ↔ API 약어 (W0, H0)
3. **일괄 처리**: 여러 품목 동시 계산, 성공/실패 요약 제공
4. **Swagger 문서화**: QuoteBomBulkCalculateRequest, QuoteBomBulkItemInput, QuoteBomBulkCalculationResult
### 필드 매핑 테이블
| React 필드 | API 변수 | 설명 |
|-----------|---------|------|
| openWidth | W0 | 개구부 폭 |
| openHeight | H0 | 개구부 높이 |
| quantity | QTY | 수량 |
| productCategory | PC | 제품 카테고리 |
| guideRailType | GT | 가이드레일 타입 |
| motorPower | MP | 모터 출력 |
| controller | CT | 제어반 |
| wingSize | WS | 날개 크기 |
| inspectionFee | INSP | 검사비 |
### Git 커밋
- `4e59bbf` feat: Phase 1.2 - 다건 BOM 기반 자동산출 API 구현
### 관련 문서
- 계획 문서: `docs/plans/quote-calculation-api-plan.md`
- Phase 1.1: `docs/changes/20260102_quote_bom_calculation_api.md`
---
## 2026-01-02 (목) - Phase 1.1 견적 산출 API 엔드포인트 구현
### 작업 목표
- React 프론트엔드에서 BOM 기반 견적 계산 API 호출 가능하도록 구현
- MNG FormulaEvaluatorService.calculateBomWithDebug() 연결
### 생성된 파일
| 파일명 | 설명 |
|--------|------|
| `app/Http/Requests/Quote/QuoteBomCalculateRequest.php` | BOM 계산용 FormRequest |
| `docs/changes/20260102_quote_bom_calculation_api.md` | 변경 내용 문서 |
### 수정된 파일
| 파일명 | 설명 |
|--------|------|
| `app/Services/Quote/QuoteCalculationService.php` | calculateBom 메서드 추가 |
| `app/Http/Controllers/Api/V1/QuoteController.php` | calculateBom 액션 추가 |
| `routes/api.php` | /calculate/bom 라우트 추가 |
| `app/Swagger/v1/QuoteApi.php` | 스키마 및 엔드포인트 문서 추가 |
### 주요 변경 내용
1. **BOM 기반 견적 계산 API**: `POST /api/v1/quotes/calculate/bom`
2. **입력 변수**: finished_goods_code, W0, H0, QTY, PC, GT, MP, CT, WS, INSP
3. **10단계 디버깅**: debug=true 옵션으로 계산 과정 확인 가능
4. **Swagger 문서화**: QuoteBomCalculateRequest, QuoteBomCalculationResult 스키마
### 관련 문서
- 계획 문서: `docs/plans/quote-calculation-api-plan.md`
- FormulaEvaluatorService: Phase 1.1에서 구현 완료
---
## 2025-12-30 (월) - Phase 1.1 견적 계산 MNG 로직 재구현
### 작업 목표
- MNG FormulaEvaluatorService 10단계 BOM 계산 로직을 API로 이식
- React 프론트엔드에서 MNG와 동일한 견적 계산 기능 사용 가능하도록 구현
### 생성된 파일
| 파일명 | 설명 |
|--------|------|
| `app/Models/CategoryGroup.php` | 카테고리별 단가 계산 방식 모델 (신규) |
| `docs/changes/20251230_2339_quote_calculation_mng_logic.md` | 변경 내용 문서 |
### 수정된 파일
| 파일명 | 설명 |
|--------|------|
| `app/Services/Quote/FormulaEvaluatorService.php` | MNG 10단계 BOM 계산 로직 추가 (537줄→1176줄) |
### 주요 변경 내용
1. **CategoryGroup 모델**: 면적/중량/수량 기반 단가 계산 방식 관리
2. **calculateBomWithDebug()**: 10단계 BOM 계산 (디버그 모드)
3. **calculateCategoryPrice()**: 카테고리 기반 단가 계산
4. **groupItemsByProcess()**: 공정별 품목 그룹화
5. **getItemDetails()**: 품목 상세 정보 및 BOM 트리
### 관련 문서
- `docs/plans/quote-calculation-api-plan.md`
---
## 2025-12-30 (월) - Phase L 설정 및 기준정보 API 개발
### 작업 목표
- L-2 권한관리 API 개발
- L-3 직급관리 + L-4 직책관리 API 개발 (통합 positions 테이블)
### 생성된 파일
#### L-2 권한관리
| 파일명 | 설명 |
|--------|------|
| `database/migrations/2025_12_30_160802_add_is_hidden_to_roles_table.php` | roles 테이블 is_hidden 컬럼 추가 |
| `app/Http/Controllers/Api/V1/RoleController.php` | Role CRUD API |
| `app/Http/Controllers/Api/V1/RolePermissionController.php` | 권한 매트릭스 API |
| `app/Services/RoleService.php` | Role 비즈니스 로직 |
| `app/Swagger/v1/RoleApi.php` | Swagger 문서 |
| `app/Swagger/v1/RolePermissionApi.php` | Swagger 문서 |
#### L-3/L-4 직급/직책 관리
| 파일명 | 설명 |
|--------|------|
| `database/migrations/2025_12_30_091821_create_positions_table.php` | positions 테이블 생성 |
| `database/migrations/2025_12_30_091822_add_position_type_to_common_codes.php` | position_type 코드 추가 |
| `app/Models/Tenants/Position.php` | Position 모델 |
| `app/Services/PositionService.php` | Position 비즈니스 로직 |
| `app/Http/Controllers/Api/V1/PositionController.php` | Position CRUD API |
| `app/Http/Requests/PositionRequest.php` | 생성/수정 요청 검증 |
| `app/Http/Requests/PositionReorderRequest.php` | 순서 변경 요청 검증 |
| `app/Swagger/v1/PositionApi.php` | Swagger 문서 |
### API 엔드포인트
#### Role API (9개)
```
GET /api/v1/roles # 역할 목록
POST /api/v1/roles # 역할 생성
GET /api/v1/roles/{id} # 역할 상세
PATCH /api/v1/roles/{id} # 역할 수정
DELETE /api/v1/roles/{id} # 역할 삭제
GET /api/v1/roles/{id}/permissions # 역할 권한 조회
POST /api/v1/roles/{id}/permissions # 권한 추가
DELETE /api/v1/roles/{id}/permissions # 권한 제거
PUT /api/v1/roles/{id}/permissions/sync # 권한 동기화
```
#### Position API (6개)
```
GET /api/v1/positions?type=rank # 직급 목록
GET /api/v1/positions?type=title # 직책 목록
POST /api/v1/positions # 생성 (type 필수)
PUT /api/v1/positions/{id} # 수정
DELETE /api/v1/positions/{id} # 삭제
POST /api/v1/positions/reorder # 순서 변경 (bulk)
```
### 설계 결정사항
- **통합 테이블**: 직급(rank)과 직책(title)을 `positions` 테이블로 통합
- **구분 컬럼**: `type` 컬럼으로 rank/title 구분
- **정렬 지원**: `sort_order` 컬럼 + reorder API로 드래그 앤 드롭 지원
### 참고
- 계획 문서: `docs/plans/l2-permission-management-plan.md`
- 세레나 메모리: `position-api-development`, `l2-permission-state`
---
## 2025-12-28 (토) - 시스템 게시판 tenant_id 및 custom_fields 수정
### 작업 목표
- POST `/api/v1/system-boards/qna/posts` 500 에러 해결
- 시스템 게시판 tenant_id 처리 로직 개선
- custom_fields field_key → field_id 매핑 지원
- 댓글 생성 시 tenant_id 누락 수정
### 문제 원인
1. `posts.tenant_id` NOT NULL 제약조건 위반 (시스템 게시판에서 null 설정 시도)
2. `saveCustomFields()`에서 field_key(string)를 field_id(integer)로 사용
3. `createComment()`에서 tenant_id 미설정
### 수정된 파일 (1개)
| 파일명 | 변경 내용 |
|--------|----------|
| `app/Services/Boards/PostService.php` | tenant_id 항상 설정, custom_fields 매핑 개선, 댓글 tenant_id 추가 |
### 상세 변경사항
#### 1. HQ_TENANT_ID 상수 추가
```php
private const HQ_TENANT_ID = 1; // 본사 테넌트 ID
```
#### 2. applySystemBoardScope() 헬퍼 메서드 추가
```php
private function applySystemBoardScope($query): void
{
$query->where(function ($q) {
$q->where('tenant_id', self::HQ_TENANT_ID)
->orWhere('tenant_id', $this->tenantId());
});
}
```
#### 3. createPost() 수정
- 변경 전: `$data['tenant_id'] = $isSystemBoard ? null : $this->tenantId();`
- 변경 후: `$data['tenant_id'] = $this->tenantId();` (항상 설정)
#### 4. saveCustomFields() 개선
- boardId 파라미터 추가
- field_key → field_id 매핑 로직 추가 (BoardSetting 조회)
- 모든 호출부 업데이트 (createPost, updatePost 등)
#### 5. createComment() 수정
- `$data['tenant_id'] = $this->tenantId();` 추가
### 시스템 게시판 조회 조건
```
(tenant_id = 1) OR (tenant_id = 현재 테넌트)
```
- 본사(tenant_id=1)의 글: 모든 테넌트에서 조회 가능
- 각 테넌트 글: 해당 테넌트만 조회 가능
### 테스트 결과
- ✅ 게시글 생성 (id=7, id=8 with custom_fields)
- ✅ custom_fields 저장 (inquiry_type → field_id=2 변환)
- ✅ 댓글 생성 (id=1, tenant_id=1)
### Git 커밋
```
4a2c185 fix: 게시판 시스템 tenant_id 및 custom_fields 처리 개선
```
---
## 2025-12-27 (금) - 결재 API 프론트엔드 호환성 개선
### 작업 목표
- 프론트엔드에서 `form_code`, `step_type`, `approver_id` 필드명 사용 지원
- 기존 `form_id`, `type`, `user_id` 필드명과 호환성 유지
### 수정된 파일 (4개)
| 파일명 | 변경 내용 |
|--------|----------|
| `app/Http/Requests/Approval/StoreRequest.php` | form_code, step_type, approver_id 필드 추가 |
| `app/Http/Requests/Approval/UpdateRequest.php` | form_code, steps 필드 지원 추가 |
| `app/Services/ApprovalService.php` | store()/update() - form_code→form_id 변환, createApprovalSteps() - step_type/approver_id 지원 |
| `lang/ko/error.php` | `approval.form_required` 에러 메시지 추가 |
### 필드 호환성 매핑
| 프론트엔드 | API (이전) | 설명 |
|-----------|-----------|------|
| `form_code` | `form_id` | 양식 코드로 form_id 자동 조회 |
| `step_type` | `type` | 결재/참조 구분 |
| `approver_id` | `user_id` | 결재자 사용자 ID |
| `step_order` | - | 결재 순서 (자동 증가) |
---
## 2025-12-26 (목) - 휴가관리 휴직 직원 표시 수정
### 작업 목표
- 휴가 사용현황에 휴직(leave) 상태 직원도 표시되도록 수정
### 수정된 파일 (1개)
| 파일명 | 변경 내용 |
|--------|----------|
| `app/Services/LeaveService.php` | `getAllBalances()`에서 employee_status 필터를 `'active'`에서 `['active', 'leave']`로 변경 |
### 영향받는 직원
- 최준호(46), 한지민(50), 오태양(51) - 휴직 상태
### Git 커밋
```
defe971 fix(leave): 휴직 직원도 휴가 사용현황에 표시되도록 수정
```
---
## 2025-12-24 (화) - 매입 세금계산서 수취 토글 기능 추가
### 작업 목표
- 매입(Purchase) 테이블에 세금계산서 수취 여부(tax_invoice_received) 필드 추가
- React 프론트엔드에서 토글 API 호출 가능하도록 지원
### 생성된 마이그레이션 (1개)
| 파일명 | 설명 |
|--------|------|
| `2025_12_24_160000_add_tax_invoice_received_to_purchases_table.php` | purchases 테이블에 tax_invoice_received 컬럼 추가 |
### 수정된 파일 (3개)
| 파일명 | 변경 내용 |
|--------|----------|
| `app/Models/Tenants/Purchase.php` | fillable에 tax_invoice_received 추가, casts에 boolean 타입 추가 |
| `app/Services/PurchaseService.php` | toggleTaxInvoice() 메서드 추가 |
| `app/Http/Requests/V1/Purchase/UpdatePurchaseRequest.php` | tax_invoice_received 필드 검증 규칙 추가 |
### 마이그레이션 실행
```bash
php artisan migrate
# 2025_12_24_160000_add_tax_invoice_received_to_purchases_table ... DONE
```
### 테스트 결과
- 세금계산서 수취 토글 API 정상 동작
- React 매입 관리 페이지에서 토글 기능 정상 작동
---
## 2025-12-22 (일) - 견적수식 시더 업데이트 (5130 연동)
### 작업 목표
- 5130 레거시 데이터 기반 견적수식 시더 업데이트
- 케이스(셔터박스) 3600mm, 6000mm 품목 및 범위 추가
### 수정된 파일 (2개)
| 파일명 | 변경 내용 |
|--------|----------|
| `database/seeders/QuoteFormulaSeeder.php` | CASE_AUTO_SELECT 범위에 3600, 6000 구간 추가 |
| `database/seeders/QuoteFormulaItemSeeder.php` | PT-CASE-3600, PT-CASE-6000 품목 추가 |
### 테스트 결과
- W0=3000, H0=2500 입력 시:
- S=3270 → PT-CASE-3600 정상 선택
- H1=2770 → PT-GR-3000 정상 선택
- K=41.21kg → PT-MOTOR-150 정상 선택
### Git 커밋
- `eeca8d3` feat: 견적수식 케이스 3600/6000 품목 및 범위 추가
---
## 2025-12-19 (목) - Phase 7.2 보완 - 나의 게시글 API 추가
### 작업 목표
- Phase 7 게시판 연동 분석 결과, 7.1/7.2 대부분 구현 완료 확인
- 누락된 `/posts/my` (나의 게시글) API 추가
### 수정된 파일 (4개)
| 파일명 | 변경 내용 |
|--------|----------|
| `app/Services/Boards/PostService.php` | `getMyPosts()` 메서드 추가 |
| `app/Http/Controllers/Api/V1/PostController.php` | `myPosts()` 액션 추가 |
| `routes/api.php` | `GET /v1/posts/my` 라우트 추가 |
| `app/Swagger/v1/PostApi.php` | `/posts/my` Swagger 문서 추가 |
### API 라우트 (1개)
| Method | Endpoint | 설명 |
|--------|----------|------|
| GET | /v1/posts/my | 나의 게시글 목록 (게시판 코드/검색/상태 필터 지원) |
### Git 커밋
- `c15a245` feat: Phase 7.2 보완 - 나의 게시글 API 추가
---
## 2025-12-19 (목) - Phase 6.1 악성채권 추심관리 API 개발
### 작업 목표
- `docs/plans/erp-api-development-plan-d1.0-changes.md` Phase 6.1 악성채권 추심관리
- 악성채권 CRUD, 서류 첨부, 메모 관리 API 구현
### 생성된 마이그레이션 (3개)
| 파일명 | 설명 |
|--------|------|
| `2025_12_19_160001_create_bad_debts_table.php` | 악성채권 테이블 |
| `2025_12_19_160002_create_bad_debt_documents_table.php` | 악성채권 서류 테이블 |
| `2025_12_19_160003_create_bad_debt_memos_table.php` | 악성채권 메모 테이블 |
### 생성된 모델 (3개)
**app/Models/BadDebts/BadDebt.php:**
- 악성채권 모델 (BelongsToTenant, SoftDeletes)
- 상태: collecting(추심중), legal_action(법적조치), recovered(회수완료), bad_debt(대손처리)
- Relations: client(), assignedUser(), creator(), documents(), memos()
**app/Models/BadDebts/BadDebtDocument.php:**
- 서류 모델 (document_type: business_license, tax_invoice, additional)
- Relations: badDebt(), file()
**app/Models/BadDebts/BadDebtMemo.php:**
- 메모 모델
- Relations: badDebt(), creator()
### 생성된 서비스 (1개)
**app/Services/BadDebtService.php:**
- CRUD: index, show, store, update, destroy
- 토글: toggle (is_active)
- 요약: summary (상태별 통계)
- 서류: addDocument, removeDocument
- 메모: addMemo, removeMemo
### 생성된 FormRequest (4개)
| 파일명 | 설명 |
|--------|------|
| `StoreBadDebtRequest.php` | 등록 (client_id, amount, status, assigned_user_id 등) |
| `UpdateBadDebtRequest.php` | 수정 (선택적 필드) |
| `StoreBadDebtDocumentRequest.php` | 서류 첨부 (document_type, file_id) |
| `StoreBadDebtMemoRequest.php` | 메모 추가 (content) |
### 생성된 컨트롤러 (1개)
**app/Http/Controllers/Api/V1/BadDebtController.php:**
- index, summary, store, show, update, destroy, toggle
- addDocument, removeDocument, addMemo, removeMemo
### API 라우트 (11개)
| Method | Endpoint | 설명 |
|--------|----------|------|
| GET | /v1/bad-debts | 목록 |
| POST | /v1/bad-debts | 등록 |
| GET | /v1/bad-debts/summary | 요약 통계 |
| GET | /v1/bad-debts/{id} | 상세 |
| PUT | /v1/bad-debts/{id} | 수정 |
| DELETE | /v1/bad-debts/{id} | 삭제 |
| PATCH | /v1/bad-debts/{id}/toggle | 활성화 토글 |
| POST | /v1/bad-debts/{id}/documents | 서류 첨부 |
| DELETE | /v1/bad-debts/{id}/documents/{documentId} | 서류 삭제 |
| POST | /v1/bad-debts/{id}/memos | 메모 추가 |
| DELETE | /v1/bad-debts/{id}/memos/{memoId} | 메모 삭제 |
### Swagger 문서
**app/Swagger/v1/BadDebtApi.php:**
- BadDebt, BadDebtDocument, BadDebtMemo 스키마
- 모든 엔드포인트 문서화 완료
### Git 커밋
- `c0af888` feat: Phase 6.1 악성채권 추심관리 API 구현
---
## 2025-12-18 (수) - 가지급금 관리 API 개발
### 작업 목표
- `docs/plans/erp-api-development-plan.md` Phase 3의 3.5 가지급금 관리
- 가지급금 CRUD, 정산 처리, 인정이자 계산/리포트 API 구현
### 생성된 마이그레이션 (1개)
| 파일명 | 설명 |
|--------|------|
| `2025_12_18_120001_create_loans_table.php` | 가지급금 테이블 (tenant_id, user_id, loan_date, amount, purpose, settlement_date, settlement_amount, status, withdrawal_id) |
### 생성된 모델 (1개)
**app/Models/Tenants/Loan.php:**
- 가지급금 모델 (BelongsToTenant, SoftDeletes)
- 상태: outstanding(미정산), settled(정산완료), partial(부분정산)
- 인정이자율: 연 4.6% (2024/2025), DEFAULT_INTEREST_RATE
- 세금: 법인세 19%, 소득세 35%, 주민세 10%
- Relations: user(), withdrawal(), creator(), updater()
- Methods: calculateRecognizedInterest(), calculateTaxes(), isEditable(), isSettleable()
### 생성된 서비스 (1개)
**app/Services/LoanService.php:**
- CRUD: index, show, store, update, destroy
- 요약: summary (미정산 건수/금액, 정산완료 금액)
- 정산: settle (전액/부분 정산)
- 인정이자: calculateInterest, interestReport
### 생성된 FormRequest (5개)
| 파일명 | 설명 |
|--------|------|
| `LoanIndexRequest.php` | 목록 조회 (user_id, status, date_from, date_to, per_page) |
| `LoanStoreRequest.php` | 등록 (user_id, loan_date, amount, purpose, withdrawal_id) |
| `LoanUpdateRequest.php` | 수정 (선택적 필드) |
| `LoanSettleRequest.php` | 정산 (settlement_date, settlement_amount) |
| `LoanCalculateInterestRequest.php` | 인정이자 계산 (year, user_id) |
### 생성된 컨트롤러 (1개)
**app/Http/Controllers/Api/V1/LoanController.php:**
- index, summary, store, show, update, destroy, settle, calculateInterest, interestReport
### API 라우트 (9개)
| Method | Endpoint | 설명 |
|--------|----------|------|
| GET | /v1/loans | 목록 조회 |
| POST | /v1/loans | 등록 |
| GET | /v1/loans/summary | 요약 조회 |
| POST | /v1/loans/calculate-interest | 인정이자 계산 |
| GET | /v1/loans/interest-report/{year} | 인정이자 리포트 |
| GET | /v1/loans/{id} | 상세 조회 |
| PUT | /v1/loans/{id} | 수정 |
| DELETE | /v1/loans/{id} | 삭제 |
| POST | /v1/loans/{id}/settle | 정산 |
### i18n 키 추가
**lang/ko/message.php:**
- loan.fetched, loan.created, loan.updated, loan.deleted
- loan.settled, loan.summary_fetched
- loan.interest_calculated, loan.interest_report_fetched
**lang/ko/error.php:**
- loan.not_found, loan.not_editable, loan.not_deletable
- loan.not_settleable, loan.settlement_exceeds
- loan.invalid_withdrawal, loan.user_not_found
**lang/ko/validation.php (attributes):**
- loan_date, amount, purpose, settlement_date, settlement_amount, year
### Git 커밋
- `af83319` - feat: 가지급금 관리 API 구현
---
## 2025-12-17 (화) - 전자결재 모듈 API 개발
### 작업 목표
- `docs/plans/erp-api-development-plan.md` Phase 2의 3.1 전자결재 모듈
- 결재 양식 (approval_forms), 결재선 템플릿 (approval_lines), 결재 문서 (approvals) CRUD API 구현
- 기안함, 결재함, 참조함 조회 및 결재 액션 (상신, 승인, 반려, 회수) 기능
### 생성된 마이그레이션 (4개)
| 파일명 | 설명 |
|--------|------|
| `2025_12_17_200001_create_approval_forms_table.php` | 결재 양식 테이블 (name, code, category, template JSON) |
| `2025_12_17_200002_create_approval_lines_table.php` | 결재선 템플릿 테이블 (name, steps JSON, is_default) |
| `2025_12_17_200003_create_approvals_table.php` | 결재 문서 테이블 (form_id, drafter_id, title, content JSON, status) |
| `2025_12_17_200004_create_approval_steps_table.php` | 결재 단계 테이블 (approval_id, step_order, type, user_id, status) |
### 생성된 모델 (4개)
**app/Models/Tenants/ApprovalForm.php:**
- 결재 양식 모델 (BelongsToTenant, SoftDeletes)
- Relations: creator(), approvals()
- Scopes: active()
**app/Models/Tenants/ApprovalLine.php:**
- 결재선 템플릿 모델 (BelongsToTenant, SoftDeletes)
- Relations: creator()
- Methods: getStepCountAttribute()
**app/Models/Tenants/Approval.php:**
- 결재 문서 모델 (BelongsToTenant, SoftDeletes)
- 상태: draft → pending → approved/rejected/cancelled
- Relations: form(), drafter(), steps(), currentStepApprover()
- Methods: canEdit(), canDelete(), canSubmit(), canAction(), canCancel()
**app/Models/Tenants/ApprovalStep.php:**
- 결재 단계 모델 (SoftDeletes)
- 단계 유형: approval, agreement, reference
- Relations: approval(), user()
- Methods: isPending(), isApproved(), isRejected()
### 생성된 서비스 (1개)
**app/Services/ApprovalService.php:**
- Form CRUD: formIndex, formShow, formStore, formUpdate, formDestroy, formActive
- Line CRUD: lineIndex, lineShow, lineStore, lineUpdate, lineDestroy
- Approval CRUD: show, store, update, destroy
- 기안함: drafts, draftsSummary
- 결재함: inbox, inboxSummary
- 참조함: referenceList
- 액션: submit, approve, reject, cancel, markRead, markUnread
### 생성된 FormRequest (13개)
| 파일 | 설명 |
|------|------|
| `FormIndexRequest.php` | 양식 목록 조회 파라미터 |
| `FormStoreRequest.php` | 양식 생성 검증 (name, code, template) |
| `FormUpdateRequest.php` | 양식 수정 검증 |
| `LineIndexRequest.php` | 결재선 목록 조회 파라미터 |
| `LineStoreRequest.php` | 결재선 생성 검증 (name, steps) |
| `LineUpdateRequest.php` | 결재선 수정 검증 |
| `IndexRequest.php` | 기안함 조회 파라미터 |
| `InboxIndexRequest.php` | 결재함 조회 파라미터 |
| `ReferenceIndexRequest.php` | 참조함 조회 파라미터 |
| `StoreRequest.php` | 문서 생성 검증 (form_id, title) |
| `UpdateRequest.php` | 문서 수정 검증 |
| `SubmitRequest.php` | 상신 검증 (steps 필수) |
| `RejectRequest.php` | 반려 검증 (comment 필수) |
### 생성된 컨트롤러 (3개)
| 파일 | 엔드포인트 |
|------|-----------|
| `ApprovalFormController.php` | index, active, show, store, update, destroy |
| `ApprovalLineController.php` | index, show, store, update, destroy |
| `ApprovalController.php` | drafts, draftsSummary, inbox, inboxSummary, reference, show, store, update, destroy, submit, approve, reject, cancel, markRead, markUnread |
### 수정된 파일
**routes/api.php:**
- Approval Forms 라우트 그룹 추가 (6개 라우트)
- Approval Lines 라우트 그룹 추가 (5개 라우트)
- Approvals 라우트 그룹 추가 (15개 라우트)
**lang/ko/message.php:**
- approval 섹션 추가 (16개 키)
**lang/ko/error.php:**
- approval 섹션 추가 (15개 키)
### 생성된 Swagger 문서 (3개)
| 파일 | 설명 |
|------|------|
| `app/Swagger/v1/ApprovalFormApi.php` | 결재 양식 API 문서 (6개 엔드포인트) |
| `app/Swagger/v1/ApprovalLineApi.php` | 결재선 API 문서 (5개 엔드포인트) |
| `app/Swagger/v1/ApprovalApi.php` | 전자결재 API 문서 (15개 엔드포인트) |
### API 엔드포인트
**결재 양식 API (Approval Forms):**
- `GET /api/v1/approval-forms` - 목록 조회
- `POST /api/v1/approval-forms` - 생성
- `GET /api/v1/approval-forms/active` - 활성 양식 (셀렉트박스용)
- `GET /api/v1/approval-forms/{id}` - 상세 조회
- `PATCH /api/v1/approval-forms/{id}` - 수정
- `DELETE /api/v1/approval-forms/{id}` - 삭제
**결재선 API (Approval Lines):**
- `GET /api/v1/approval-lines` - 목록 조회
- `POST /api/v1/approval-lines` - 생성
- `GET /api/v1/approval-lines/{id}` - 상세 조회
- `PATCH /api/v1/approval-lines/{id}` - 수정
- `DELETE /api/v1/approval-lines/{id}` - 삭제
**전자결재 API (Approvals):**
- `GET /api/v1/approvals/drafts` - 기안함
- `GET /api/v1/approvals/drafts/summary` - 기안함 현황
- `GET /api/v1/approvals/inbox` - 결재함
- `GET /api/v1/approvals/inbox/summary` - 결재함 현황
- `GET /api/v1/approvals/reference` - 참조함
- `POST /api/v1/approvals` - 문서 생성
- `GET /api/v1/approvals/{id}` - 상세 조회
- `PATCH /api/v1/approvals/{id}` - 수정
- `DELETE /api/v1/approvals/{id}` - 삭제
- `POST /api/v1/approvals/{id}/submit` - 상신
- `POST /api/v1/approvals/{id}/approve` - 승인
- `POST /api/v1/approvals/{id}/reject` - 반려
- `POST /api/v1/approvals/{id}/cancel` - 회수
- `POST /api/v1/approvals/{id}/read` - 열람
- `POST /api/v1/approvals/{id}/unread` - 미열람
### 검증 완료
- ✅ Pint 스타일 검사 통과 (19개 파일)
- ✅ 라우트 등록 확인 (26개)
- ✅ Swagger 문서 생성 완료
---
## 2025-12-17 (화) - 매출/매입 관리 API 개발
### 작업 목표
- `docs/plans/erp-api-development-plan.md` Phase 1의 2.5 매출/매입 관리
- 매출(Sale) 및 매입(Purchase) CRUD API 구현
- 확정(confirm) 기능 및 요약(summary) 조회 기능 포함
### 생성된 마이그레이션 (2개)
| 파일명 | 설명 |
|--------|------|
| `2025_12_17_100001_create_sales_table.php` | 매출 테이블 (sale_number, sale_date, client_id, 금액, 상태) |
| `2025_12_17_100002_create_purchases_table.php` | 매입 테이블 (purchase_number, purchase_date, client_id, 금액, 상태) |
### 생성된 모델 (2개)
**app/Models/Tenants/Sale.php:**
- 매출 모델 (BelongsToTenant, SoftDeletes)
- 상태: draft → confirmed → invoiced
- Relations: client(), deposit(), creator()
- Methods: canConfirm(), canEdit(), canDelete()
**app/Models/Tenants/Purchase.php:**
- 매입 모델 (BelongsToTenant, SoftDeletes)
- 상태: draft → confirmed
- Relations: client(), withdrawal(), creator()
- Methods: canConfirm(), canEdit(), canDelete()
### 생성된 서비스 (2개)
**app/Services/SaleService.php:**
- CRUD, confirm(), summary()
- 문서번호 자동 생성: SL{YYYYMMDD}{0001}
- 상태 검증 (수정/삭제는 draft만 가능)
**app/Services/PurchaseService.php:**
- CRUD, confirm(), summary()
- 문서번호 자동 생성: PU{YYYYMMDD}{0001}
- 상태 검증 (수정/삭제는 draft만 가능)
### 생성된 FormRequest (4개)
| 파일 | 설명 |
|------|------|
| `app/Http/Requests/V1/Sale/StoreSaleRequest.php` | 매출 등록 검증 |
| `app/Http/Requests/V1/Sale/UpdateSaleRequest.php` | 매출 수정 검증 |
| `app/Http/Requests/V1/Purchase/StorePurchaseRequest.php` | 매입 등록 검증 |
| `app/Http/Requests/V1/Purchase/UpdatePurchaseRequest.php` | 매입 수정 검증 |
### 생성된 컨트롤러 (2개)
| 파일 | 엔드포인트 |
|------|-----------|
| `SaleController.php` | index, store, show, update, destroy, confirm, summary |
| `PurchaseController.php` | index, store, show, update, destroy, confirm, summary |
### 수정된 파일
**routes/api.php:**
- Sales 라우트 그룹 추가 (7개 라우트)
- Purchases 라우트 그룹 추가 (7개 라우트)
### 생성된 Swagger 문서 (2개)
| 파일 | 설명 |
|------|------|
| `app/Swagger/v1/SaleApi.php` | 매출 API 문서 (전체 엔드포인트) |
| `app/Swagger/v1/PurchaseApi.php` | 매입 API 문서 (전체 엔드포인트) |
### API 엔드포인트
**매출 API (Sales):**
- `GET /api/v1/sales` - 목록 조회
- `POST /api/v1/sales` - 등록
- `GET /api/v1/sales/{id}` - 상세 조회
- `PUT /api/v1/sales/{id}` - 수정
- `DELETE /api/v1/sales/{id}` - 삭제
- `POST /api/v1/sales/{id}/confirm` - 확정
- `GET /api/v1/sales/summary` - 요약
**매입 API (Purchases):**
- `GET /api/v1/purchases` - 목록 조회
- `POST /api/v1/purchases` - 등록
- `GET /api/v1/purchases/{id}` - 상세 조회
- `PUT /api/v1/purchases/{id}` - 수정
- `DELETE /api/v1/purchases/{id}` - 삭제
- `POST /api/v1/purchases/{id}/confirm` - 확정
- `GET /api/v1/purchases/summary` - 요약
### 검증 완료
- ✅ Pint 스타일 검사 통과
- ✅ 라우트 등록 확인 (14개)
- ✅ 마이그레이션 실행 성공
- ✅ Swagger 문서 생성 완료
---
## 2025-12-13 (금) - Items 테이블 통합 마이그레이션 작성
### 작업 목표
- `docs/plans/items-table-unification-plan.md` 기반 작업
- products + materials 테이블을 items 단일 테이블로 통합
- BOM 관리 단순화 (child_item_type + child_item_id → child_item_id만)
### 생성된 마이그레이션 파일 (6개)
| 순서 | 파일명 | Phase | 설명 |
|------|--------|-------|------|
| 1 | `2025_12_13_152423_normalize_item_types_before_unification.php` | 0 | 비표준 item_type 삭제 (PRODUCT, SUBASSEMBLY, PART 등) |
| 2 | `2025_12_13_152507_create_items_table.php` | 1.1 | items 테이블 생성 |
| 3 | `2025_12_13_152553_create_item_details_table.php` | 1.2 | item_details 테이블 생성 (1:1 확장 필드) |
| 4 | `2025_12_13_152631_migrate_products_materials_to_items.php` | 1.3 | 데이터 이관 + item_id_mappings 매핑 테이블 |
| 5 | `2025_12_13_153116_update_item_pages_source_table_to_items.php` | 3 | item_pages.source_table 업데이트 |
| 6 | `2025_12_13_153544_update_reference_tables_to_items.php` | 5 | 참조 테이블 item_id 컬럼 추가 및 매핑 |
### 생성된 모델 (2개)
**app/Models/Items/Item.php:**
- 통합 품목 모델 (FG, PT, SM, RM, CS)
- BelongsToTenant, SoftDeletes
- 스코프: products(), materials(), type(), active()
- BOM 헬퍼: getBomChildIds(), loadBomChildren()
**app/Models/Items/ItemDetail.php:**
- 품목 상세 정보 (1:1 관계)
- Products 전용: is_sellable, is_purchasable, is_producible, 파일 필드
- Materials 전용: is_inspection, specification
### 생성된 서비스
**app/Services/ItemService.php:**
- items 단일 테이블 CRUD
- 동적 필드 → options JSON 병합
- 카테고리 트리 조회
- 활성/비활성 토글
### 수정된 파일
**app/Models/ItemMaster/ItemPage.php:**
- `getTargetModelClass()`: items 테이블 지원 추가
- `isItemPage()`, `isProductType()`, `isMaterialType()` 헬퍼 추가
### ID 매핑 전략
**item_id_mappings 테이블:**
- 기존 products/materials ID → 새 items ID 매핑
- Phase 5 참조 테이블 마이그레이션에서 활용
### 참조 테이블 업데이트 대상 (Phase 5)
| 테이블 | 기존 | 추가 컬럼 |
|--------|------|----------|
| product_components | ref_type + ref_id | item_id, parent_item_id |
| bom_template_items | ref_type + ref_id | item_id |
| orders | product_id | item_id |
| order_items | product_id | item_id |
| material_receipts | material_id | item_id |
| lots | material_id | item_id |
| price_histories | item_type + item_id | new_item_id |
| item_fields | source_table | → 'items' |
### 실행 명령어
```bash
# 마이그레이션 실행 (순서대로)
php artisan migrate
# 롤백 (전체)
php artisan migrate:rollback --step=6
```
### Phase 6: 마이그레이션 실행 완료 ✅
**실행 결과:**
```
items: 362개 (FG:4, PT:4, RM:133, SM:217, CS:4)
item_details: 362개 (1:1 관계)
item_id_mappings: 362개 (ID 매핑 완료)
item_pages: 47개 → source_table='items'로 통합
product_components: 4개 중 2개 item_id 매핑 완료
```
**수정 사항:**
- `migrate_products_materials_to_items.php`: null material_code 자동 생성 로직 추가
### 다음 작업 (Phase 7 이후)
- [ ] ItemsController → ItemService 교체
- [ ] CRUD 테스트 (전체 item_type)
- [ ] BOM 계산 테스트
- [ ] Item-Master 연동 테스트
- [ ] 기존 products/materials 테이블 삭제 (확인 후)
### 참조 문서
- `docs/plans/items-table-unification-plan.md`
- `docs/INDEX.md`
---
## 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 완료
### 작업 목표
- 견적 API Phase 3 Controller Layer 구현
- 16개 API 엔드포인트 구현 완료
### 생성된 파일
**Controller (1개):**
- `app/Http/Controllers/Api/V1/QuoteController.php`
- 16개 메서드: index, show, store, update, destroy, bulkDestroy, finalize, cancelFinalize, convertToOrder, previewNumber, calculate, calculationSchema, generatePdf, sendEmail, sendKakao, sendHistory
- 4개 Service DI: QuoteService, QuoteNumberService, QuoteCalculationService, QuoteDocumentService
- ApiResponse::handle() 패턴 적용
**FormRequest (7개):**
| 파일 | 설명 |
|------|------|
| `QuoteIndexRequest.php` | 목록 조회 파라미터 검증 |
| `QuoteStoreRequest.php` | 견적 생성 검증 (items 배열 포함) |
| `QuoteUpdateRequest.php` | 견적 수정 검증 |
| `QuoteBulkDeleteRequest.php` | 일괄 삭제 IDs 검증 |
| `QuoteCalculateRequest.php` | 자동산출 입력값 검증 |
| `QuoteSendEmailRequest.php` | 이메일 발송 검증 |
| `QuoteSendKakaoRequest.php` | 카카오 발송 검증 |
**Swagger (1개):**
- `app/Swagger/v1/QuoteApi.php`
- 12개 스키마: Quote, QuoteItem, QuotePagination, QuoteCreateRequest, QuoteUpdateRequest 등
- 16개 엔드포인트 문서화
### API 엔드포인트 (16개)
| Method | Endpoint | 설명 |
|--------|----------|------|
| GET | `/api/v1/quotes` | 견적 목록 (페이지네이션) |
| POST | `/api/v1/quotes` | 견적 생성 |
| GET | `/api/v1/quotes/number/preview` | 견적번호 미리보기 |
| POST | `/api/v1/quotes/calculate` | 자동산출 |
| GET | `/api/v1/quotes/calculate/schema` | 산출 스키마 조회 |
| DELETE | `/api/v1/quotes/bulk` | 일괄 삭제 |
| GET | `/api/v1/quotes/{id}` | 견적 상세 |
| PUT | `/api/v1/quotes/{id}` | 견적 수정 |
| DELETE | `/api/v1/quotes/{id}` | 견적 삭제 |
| POST | `/api/v1/quotes/{id}/finalize` | 확정 |
| POST | `/api/v1/quotes/{id}/cancel-finalize` | 확정 취소 |
| POST | `/api/v1/quotes/{id}/convert` | 주문 전환 |
| GET | `/api/v1/quotes/{id}/pdf` | PDF 생성 |
| POST | `/api/v1/quotes/{id}/send/email` | 이메일 발송 |
| POST | `/api/v1/quotes/{id}/send/kakao` | 카카오 발송 |
| GET | `/api/v1/quotes/{id}/send/history` | 발송 이력 |
### 검증 결과
- PHP 문법 검사: ✅ 9개 파일 통과
- Pint 코드 포맷팅: ✅ 완료
- Swagger 문서 생성: ✅ 완료
- 라우트 등록: ✅ 16개 라우트 확인
### 다음 작업 (Phase 4)
- [ ] 단위 테스트 작성
- [ ] 통합 테스트 작성
- [ ] 마이그레이션 실행 및 실제 데이터 검증
---
## 2025-12-04 (수) - 견적 API Phase 2: Service Layer 구현 완료
### 작업 목표
- 견적 API Phase 2 Service Layer 구현
- 5개 Service 파일 생성 완료
### 생성된 파일
**Service Layer (5개):**
| 파일 | 설명 | 주요 기능 |
|------|------|----------|
| `QuoteService.php` | 견적 CRUD + 상태관리 | index, show, store, update, destroy, bulkDestroy, finalize, cancelFinalize, convertToOrder |
| `QuoteNumberService.php` | 견적번호 채번 | generate, preview, validate, parse, isUnique |
| `FormulaEvaluatorService.php` | 수식 평가 엔진 | validateFormula, evaluate, evaluateMultiple, evaluateRange, evaluateMapping |
| `QuoteCalculationService.php` | 견적 자동산출 | calculate, preview, recalculate, getInputSchema |
| `QuoteDocumentService.php` | 문서 생성/발송 | generatePdf, sendEmail, sendKakao, getSendHistory |
### 견적번호 형식
```
KD-{PREFIX}-{YYMMDD}-{SEQ}
예: KD-SC-251204-01 (스크린), KD-ST-251204-01 (철재)
```
### FormulaEvaluatorService 지원 함수
- 수학: `SUM`, `ROUND`, `CEIL`, `FLOOR`, `ABS`, `MIN`, `MAX`
- 논리: `IF`, `AND`, `OR`, `NOT`
### QuoteCalculationService 입력 스키마
**공통 입력:**
- `W0`: 개구부 폭 (mm)
- `H0`: 개구부 높이 (mm)
- `QTY`: 수량
**스크린 제품 추가:**
- `INSTALL_TYPE`: 설치 유형 (wall/ceiling/floor)
- `MOTOR_TYPE`: 모터 유형 (standard/heavy)
- `CONTROL_TYPE`: 제어 방식 (switch/remote/smart)
- `CHAIN_SIDE`: 체인 위치 (left/right)
**철재 제품 추가:**
- `MATERIAL`: 재질 (ss304/ss316/galvanized)
- `THICKNESS`: 두께 (mm)
- `FINISH`: 표면처리 (hairline/mirror/matte)
- `WELDING`: 용접 방식 (tig/mig/spot)
### i18n 키 추가
**에러 메시지 (error.php):**
- `quote_not_found`, `quote_not_editable`, `quote_not_deletable`
- `quote_not_finalizable`, `quote_not_finalized`, `quote_already_converted`
- `quote_not_convertible`, `quote_email_not_found`, `quote_phone_not_found`
- `formula_empty`, `formula_parentheses_mismatch`, `formula_unsupported_function`, `formula_calculation_error`
**성공 메시지 (message.php):**
- `quote.fetched`, `quote.created`, `quote.updated`, `quote.deleted`
- `quote.bulk_deleted`, `quote.finalized`, `quote.finalize_cancelled`
- `quote.converted`, `quote.calculated`, `quote.pdf_generated`
- `quote_email_sent`, `quote_kakao_sent`
### 검증 결과
- PHP 문법 검사: ✅ 5개 파일 통과
- Pint 코드 포맷팅: ✅ 완료
### 다음 작업 (Phase 3)
- [ ] QuoteController.php 생성
- [ ] FormRequest 생성 (QuoteStoreRequest, QuoteUpdateRequest 등)
- [ ] Swagger 문서 작성 (QuoteApi.php)
- [ ] 라우트 등록
---
## 2025-12-04 (수) - 거래처 API 2차 필드 추가 및 견적 API 계획 업데이트
### 작업 목표
- 거래처 API에 2차 필드 추가 (17개 신규 필드)
- 견적 API 변경사항 분석 및 계획 문서 업데이트
### 거래처 API 2차 필드 추가
**추가된 필드 (7개 섹션, 20개 필드):**
| 섹션 | 필드 | 설명 |
|------|------|------|
| 거래처 유형 | `client_type` | 매입/매출/매입매출 |
| 연락처 | `mobile`, `fax` | 모바일, 팩스 |
| 담당자 | `manager_name`, `manager_tel`, `system_manager` | 담당자 정보 |
| 발주처 설정 | `account_id`, `account_password`, `purchase_payment_day`, `sales_payment_day` | 계정 및 결제일 |
| 약정 세금 | `tax_agreement`, `tax_amount`, `tax_start_date`, `tax_end_date` | 세금 약정 정보 |
| 악성채권 | `bad_debt`, `bad_debt_amount`, `bad_debt_receive_date`, `bad_debt_end_date`, `bad_debt_progress` | 채권 정보 |
| 기타 | `memo` | 메모 |
**수정된 파일:**
- `database/migrations/2025_12_04_205603_add_extended_fields_to_clients_table.php` (NEW)
- `app/Models/Orders/Client.php` - fillable, casts, hidden 업데이트
- `app/Http/Requests/Client/ClientStoreRequest.php` - 검증 규칙 추가
- `app/Http/Requests/Client/ClientUpdateRequest.php` - 검증 규칙 추가
- `app/Services/ClientService.php` - store/update 검증 추가
- `app/Swagger/v1/ClientApi.php` - 3개 스키마 업데이트
### 견적 API 계획 업데이트
**신규 요청 - 문서 발송 API (Section 3.5):**
| Method | Endpoint | 설명 |
|--------|----------|------|
| POST | `/api/v1/quotes/{id}/send/email` | 이메일 발송 |
| POST | `/api/v1/quotes/{id}/send/fax` | 팩스 발송 |
| POST | `/api/v1/quotes/{id}/send/kakao` | 카카오톡 발송 |
**계획 문서 업데이트 내용:**
- Phase 2: `QuoteDocumentService` 추가
- Phase 3: `QuoteSendEmailRequest`, `QuoteSendFaxRequest`, `QuoteSendKakaoRequest` 추가
- Service 5개, FormRequest 8개로 조정
### Git 커밋
```
commit d164bb4
feat: [client] 거래처 API 2차 필드 추가 및 견적 계획 업데이트
```
### 다음 작업
- 견적 API Phase 2: Service Layer 구현
---
## 2025-12-04 (수) - 견적수식 시드 데이터 구현
### 작업 목표
- design/src/components/utils/formulaSampleData.ts의 데이터를 MNG에서 관리할 수 있도록 시드 데이터 구현
- 26개 수식 규칙, 11개 카테고리를 DB에 입력
### 추가된 파일
**Seeder (2개):**
- `database/seeders/QuoteFormulaCategorySeeder.php`
- 11개 카테고리 시드 (OPEN_SIZE, MAKE_SIZE, AREA, WEIGHT, GUIDE_RAIL, CASE, MOTOR, CONTROLLER, EDGE_WING, INSPECTION, PRICE_FORMULA)
- updateOrInsert 패턴으로 멱등성 보장
- `database/seeders/QuoteFormulaSeeder.php`
- 29개 수식 시드 (input 2개, calculation 18개, range 3개, mapping 1개, 단가수식 8개)
- 8개 범위 데이터 (quote_formula_ranges)
- 카테고리 코드 → ID 매핑으로 FK 참조
### 시드 데이터 상세
| 카테고리 | 코드 | 수식 수 | 설명 |
|----------|------|---------|------|
| 오픈사이즈 | OPEN_SIZE | 2 | W0, H0 입력 |
| 제작사이즈 | MAKE_SIZE | 4 | W1/H1 (스크린/철재) |
| 면적 | AREA | 1 | W1 × H1 / 1000000 |
| 중량 | WEIGHT | 2 | 스크린/철재 중량 계산 |
| 가이드레일 | GUIDE_RAIL | 5 | 길이, 자동선택, 설치유형별 수량 |
| 케이스 | CASE | 3 | 사이즈, 자재 자동선택 |
| 모터 | MOTOR | 1 | 중량 기반 자동선택 |
| 제어기 | CONTROLLER | 1 | 유형별 자동선택 |
| 마구리 | EDGE_WING | 1 | 날개 수량 계산 |
| 검사 | INSPECTION | 1 | 검사비 고정 |
| 단가수식 | PRICE_FORMULA | 8 | 품목별 단가 계산 |
### 실행 명령어
```bash
# 순서대로 실행
php artisan db:seed --class=QuoteFormulaCategorySeeder
php artisan db:seed --class=QuoteFormulaSeeder
```
### 검증 결과
- 카테고리: 11개 생성 완료 ✅
- 수식: 29개 생성 완료 ✅
- 범위 데이터: 8개 생성 완료 ✅
### 참조 문서
- `mng/docs/QUOTE_FORMULA_SEED_PLAN.md` - 구현 계획서
- `design/src/components/utils/formulaSampleData.ts` - 소스 데이터
---
## 2025-12-02 (월) - 메뉴 통합관리 시스템 구현 (Phase 1-2)
### 작업 목표
- 글로벌 메뉴-테넌트 메뉴 연결 시스템 구현
- Phase 1: DB 스키마 변경 및 모델 수정
- Phase 2: 서비스 및 API 엔드포인트 개발
### Phase 1 완료 (DB 스키마 및 모델)
**추가된 파일:**
- `database/migrations/2025_12_02_100000_add_global_menu_link_columns_to_menus_table.php`
- `global_menu_id` 컬럼: 원본 글로벌 메뉴 ID
- `is_customized` 컬럼: 테넌트 커스터마이징 여부
- 인덱스 추가
**수정된 파일:**
- `app/Models/Commons/Menu.php`
- fillable: `global_menu_id`, `is_customized` 추가
- casts: `is_customized => boolean` 등 추가
- 관계 메서드: `globalMenu()`, `tenantMenus()`
- 헬퍼 메서드: `isGlobal()`, `isClonedFromGlobal()`, `isCustomized()`
- 스코프: `scopeGlobal()`, `scopeActive()`, `scopeVisible()`, `scopeRoots()`
- `getSyncFields()`: 동기화 비교 대상 필드 목록
- `app/Services/MenuBootstrapService.php`
- `cloneGlobalMenusForTenant()`: global_menu_id 저장 추가
- 활성 메뉴만 복제 (is_active=true)
### Phase 2 완료 (서비스 및 API)
**추가된 파일:**
- `app/Services/GlobalMenuService.php` (신규)
- 글로벌 메뉴 CRUD (tenant_id = NULL)
- `syncToAllTenants()`: 특정 메뉴를 모든 테넌트에 동기화
- `stats()`: 글로벌 메뉴 통계
- `app/Services/MenuSyncService.php` (신규)
- 동기화 상태 상수: NEW, UP_TO_DATE, UPDATABLE, CUSTOMIZED, DELETED
- `getSyncStatus()`: 동기화 상태 목록 조회
- `syncMenus()`: 선택 동기화 (신규/업데이트)
- `importNewMenus()`: 신규 글로벌 메뉴 일괄 가져오기
- `syncUpdates()`: 변경된 메뉴 일괄 업데이트 (커스텀 제외)
- `getAvailableGlobalMenus()`: 복제 가능한 글로벌 메뉴 목록
- `app/Http/Controllers/Api/Admin/GlobalMenuController.php` (신규)
- 시스템 관리자용 글로벌 메뉴 관리
- index, tree, show, store, update, destroy, reorder
- syncToTenants, stats
**수정된 파일:**
- `app/Services/MenuService.php`
- `update()`: 글로벌 복제 메뉴 수정 시 is_customized=true 자동 설정
- `restore()`: 삭제된 메뉴 복원 추가
- `trashedList()`: 삭제된 메뉴 목록 조회 추가
- `app/Http/Controllers/Api/V1/MenuController.php`
- MenuSyncService DI 추가
- restore, trashed, availableGlobal, syncStatus, sync, syncNew, syncUpdates 메서드 추가
- `routes/api.php`
- GlobalMenuController use 문 추가
- 테넌트 메뉴 동기화 라우트 6개 추가 (trashed, available-global, sync-status, sync, sync-new, sync-updates, restore)
- 글로벌 메뉴 관리 라우트 9개 추가 (admin/global-menus/*)
### API 엔드포인트
**테넌트 메뉴 동기화 (V1):**
| Method | Path | 설명 |
|--------|------|------|
| GET | /v1/menus/trashed | 삭제된 메뉴 목록 |
| GET | /v1/menus/available-global | 복제 가능한 글로벌 메뉴 |
| GET | /v1/menus/sync-status | 동기화 상태 조회 |
| POST | /v1/menus/sync | 선택 동기화 |
| POST | /v1/menus/sync-new | 신규 메뉴 일괄 가져오기 |
| POST | /v1/menus/sync-updates | 변경된 메뉴 일괄 업데이트 |
| POST | /v1/menus/{id}/restore | 삭제된 메뉴 복원 |
**글로벌 메뉴 관리 (Admin):**
| Method | Path | 설명 |
|--------|------|------|
| GET | /v1/admin/global-menus | 글로벌 메뉴 목록 |
| POST | /v1/admin/global-menus | 글로벌 메뉴 생성 |
| GET | /v1/admin/global-menus/tree | 글로벌 메뉴 트리 |
| GET | /v1/admin/global-menus/stats | 통계 조회 |
| POST | /v1/admin/global-menus/reorder | 순서 변경 |
| GET | /v1/admin/global-menus/{id} | 단건 조회 |
| PUT | /v1/admin/global-menus/{id} | 수정 |
| DELETE | /v1/admin/global-menus/{id} | 삭제 |
| POST | /v1/admin/global-menus/{id}/sync-to-tenants | 모든 테넌트에 동기화 |
### 검증 결과
- PHP 문법 검사: ✅ 모든 파일 통과
- 라우트 등록: ✅ 9개 글로벌 메뉴 + 7개 테넌트 동기화 라우트 확인
### 다음 작업 (Phase 3-4)
- [ ] Phase 3: MNG 글로벌 메뉴 관리 화면
- [ ] Phase 3: MNG 동기화 센터 화면
- [ ] Phase 4: 마이그레이션 실행 및 테스트
---
## 2025-12-01 (일) - 메뉴 통합관리 시스템 설계
### 작업 목표
- PDF 기획서(SAM_ERP_인사관리전자결재_Storyboard)에서 메뉴 추출
- 글로벌 메뉴와 테넌트 메뉴 간의 연결(링크) 시스템 설계
- 메뉴 추가 SQL 쿼리 생성
### 추가된 파일
- `claudedocs/MENU_INTEGRATION_SYSTEM_DESIGN.md` (신규)
- 글로벌-테넌트 메뉴 연결 시스템 설계서
- global_menu_id, is_customized 컬럼 추가 계획
- API 엔드포인트 설계 (글로벌/테넌트 메뉴 관리)
- MNG 화면 설계 (복제, 동기화 기능)
- 구현 Phase 1~4 계획
- `claudedocs/MENU_INSERT_QUERIES.sql` (신규)
- PDF 기획서 기반 신규 메뉴 23개 INSERT 쿼리
- 인사관리 (근태/휴가/급여)
- 전자결재 (기안함/결재함/참조함)
- 게시판, 보고서, 계정정보, 회사정보, 구독관리, 결제내역, 고객센터
- 기준정보 관리 하위 8개 메뉴
### 정책 결정 사항
| 항목 | 결정 내용 |
|------|----------|
| 글로벌 메뉴 삭제 시 | 테넌트 메뉴 유지 (global_menu_id = NULL) |
| 활성 메뉴 (is_active=1) | 새 테넌트 생성 시 자동 복사 |
| 비활성 메뉴 (is_active=0) | 테넌트가 수동으로 복제 가능 |
| 숨김 메뉴 (hidden=1) | 복사되지만 테넌트에서 안 보임 |
| 기존 데이터 | 신규 테넌트부터 적용 |
### 다음 작업 (Phase별)
- [ ] Phase 1: 마이그레이션 (global_menu_id, is_customized)
- [ ] Phase 1: Menu 모델 수정
- [ ] Phase 1: MenuBootstrapService 수정
- [ ] Phase 2: GlobalMenuService 생성
- [ ] Phase 2: MenuService 메서드 추가
- [ ] Phase 2: API 엔드포인트 추가
- [ ] Phase 3: MNG 글로벌 메뉴 관리 화면
- [ ] Phase 3: MNG 테넌트 메뉴 관리 화면 개선
- [ ] Phase 4: 테스트
### Git 커밋
```
commit d7fdfa8
docs: 메뉴 통합관리 시스템 설계서 및 SQL 쿼리 추가
```
### 참고 문서
- PDF: SAM_ERP_인사관리전자결재_Storyboard_D0.6_251201.pdf
- 설계서: claudedocs/MENU_INTEGRATION_SYSTEM_DESIGN.md
---
## 2025-11-27 (수) - 시스템 게시판 기능 확장
### 작업 목표
- 기존 boards 테이블에 시스템 게시판 지원 추가
- mng에서 시스템 게시판 생성, sam에서 테넌트 게시판 + 시스템 게시판 조회
### 수정된 파일
**Migration**:
- `database/migrations/2025_11_27_205429_add_system_fields_to_boards_table.php` (NEW)
- `tenant_id` nullable 변경
- `is_system` boolean 컬럼 추가
- `board_type` VARCHAR(50) 컬럼 추가
- `deleted_at`, `deleted_by` SoftDeletes 추가
- 인덱스 추가
**Model**:
- `app/Models/Boards/Board.php`
- SoftDeletes 추가
- fillable, casts 업데이트
- `scopeAccessible(tenantId)`, `scopeSystemOnly()`, `scopeTenantOnly(tenantId)` 스코프 추가
- 권한 헬퍼 메서드 (canRead, canWrite, canManage)
- `app/Models/Boards/BoardSetting.php`
- casts, fillable 업데이트
- getMeta() 헬퍼 추가
**Service**:
- `app/Services/Boards/BoardService.php` (NEW)
- 시스템/테넌트 게시판 CRUD
- 필드 관리 (add, update, delete, reorder)
- 유틸리티 메서드
### DB 스키마 변경
```sql
-- boards 테이블 추가 컬럼
is_system TINYINT(1) DEFAULT 0 COMMENT '시스템 게시판 여부'
board_type VARCHAR(50) NULL COMMENT '게시판 유형'
deleted_at TIMESTAMP NULL
deleted_by BIGINT UNSIGNED NULL
```
### 다음 작업
- mng 게시판 관리 화면 개발 ✅
- sam API 개발 (Swagger 포함) ✅
- 검증 및 테스트 ✅
---
## 2025-11-27 (수) - sam API 게시판/게시글 API 개발
### 작업 목표
- 테넌트용 게시판/게시글 V1 API 개발
- Swagger 문서 작성
### 추가된 파일
**Service**:
- `app/Services/Boards/PostService.php` (NEW)
- 게시글 CRUD (boardCode 기반)
- 댓글 CRUD
- 커스텀 필드 관리
- 조회수 증가
**Controller**:
- `app/Http/Controllers/Api/V1/BoardController.php` (NEW)
- index: 접근 가능한 게시판 목록 (시스템 + 테넌트)
- tenantBoards: 테넌트 게시판만
- show: 게시판 상세 (코드 기반)
- store/update/destroy: 테넌트 게시판 CRUD
- fields: 게시판 필드 목록
- `app/Http/Controllers/Api/V1/PostController.php` (NEW)
- index/show/store/update/destroy: 게시글 CRUD
- comments/storeComment/updateComment/destroyComment: 댓글 CRUD
**FormRequest**:
- `app/Http/Requests/Boards/BoardStoreRequest.php` (NEW)
- `app/Http/Requests/Boards/BoardUpdateRequest.php` (NEW)
- `app/Http/Requests/Boards/PostStoreRequest.php` (NEW)
- `app/Http/Requests/Boards/PostUpdateRequest.php` (NEW)
- `app/Http/Requests/Boards/CommentStoreRequest.php` (NEW)
**Swagger**:
- `app/Swagger/v1/BoardApi.php` (NEW)
- Board, BoardField 스키마
- BoardCreateRequest, BoardUpdateRequest 스키마
- 7개 엔드포인트 문서화
- `app/Swagger/v1/PostApi.php` (NEW)
- Post, PostPagination, Comment 스키마
- PostCreateRequest, PostUpdateRequest, CommentCreateRequest 스키마
- 9개 엔드포인트 문서화
### 수정된 파일
- `routes/api.php`
- BoardController, PostController import 추가
- /v1/boards 프리픽스로 16개 라우트 등록
### API 엔드포인트 (16개)
**게시판 관리**:
| Method | Path | 설명 |
|--------|------|------|
| GET | /v1/boards | 접근 가능한 게시판 목록 |
| GET | /v1/boards/tenant | 테넌트 게시판만 |
| POST | /v1/boards | 테넌트 게시판 생성 |
| GET | /v1/boards/{code} | 게시판 상세 (코드 기반) |
| PUT | /v1/boards/{id} | 테넌트 게시판 수정 |
| DELETE | /v1/boards/{id} | 테넌트 게시판 삭제 |
| GET | /v1/boards/{code}/fields | 게시판 필드 목록 |
**게시글 관리**:
| Method | Path | 설명 |
|--------|------|------|
| GET | /v1/boards/{code}/posts | 게시글 목록 |
| POST | /v1/boards/{code}/posts | 게시글 작성 |
| GET | /v1/boards/{code}/posts/{id} | 게시글 상세 |
| PUT | /v1/boards/{code}/posts/{id} | 게시글 수정 |
| DELETE | /v1/boards/{code}/posts/{id} | 게시글 삭제 |
**댓글 관리**:
| Method | Path | 설명 |
|--------|------|------|
| GET | /v1/boards/{code}/posts/{postId}/comments | 댓글 목록 |
| POST | /v1/boards/{code}/posts/{postId}/comments | 댓글 작성 |
| PUT | /v1/boards/{code}/posts/{postId}/comments/{commentId} | 댓글 수정 |
| DELETE | /v1/boards/{code}/posts/{postId}/comments/{commentId} | 댓글 삭제 |
### 검증 결과
- PHP 문법 검사: ✅ 통과
- Pint 코드 포맷팅: ✅ 완료
- Swagger 문서 생성: ✅ 완료
- 라우트 등록: ✅ 16개 라우트 확인
---
## 2025-11-27 (수) - ItemMaster API group_id(계층번호) 추가 및 Swagger 보완
### 작업 목표
- FormRequest에 누락된 `group_id` 필드 추가
- Swagger 스키마에 `group_id` description="계층번호" 추가
- API 문서 정확도 개선
### 배경
- `POST /api/v1/item-master/pages/{pageId}/sections` Swagger 문서 점검 중 `group_id` 누락 발견
- Service에서 `$data['group_id'] ?? 1`로 사용하지만 FormRequest와 Swagger에 미정의
- `group_id`는 "계층번호"로 통일하여 주석 및 description 작성
### 수정된 파일 (4개)
**FormRequest (group_id 필드 추가)**:
- `app/Http/Requests/ItemMaster/ItemSectionStoreRequest.php`
- `app/Http/Requests/ItemMaster/ItemFieldStoreRequest.php`
- `app/Http/Requests/ItemMaster/ItemBomItemStoreRequest.php`
**Swagger**:
- `app/Swagger/v1/ItemMasterApi.php`
### Swagger 스키마 업데이트 상세
**모델 스키마 (3개)** - `description="계층번호"` 추가:
- `ItemSection`
- `ItemField`
- `ItemBomItem`
**Request 스키마 (6개)** - `group_id` 추가 또는 description 추가:
- `ItemSectionStoreRequest` - group_id 신규 추가
- `ItemFieldStoreRequest` - group_id 신규 추가
- `ItemBomItemStoreRequest` - group_id 신규 추가
- `IndependentSectionStoreRequest` - description 추가
- `IndependentFieldStoreRequest` - description 추가
- `IndependentBomItemStoreRequest` - description 추가
### 12개 EntityRelationship 엔드포인트
`EntityRelationshipApi.php`에 이미 문서화 완료 확인:
- POST/DELETE `/pages/{pageId}/link-section`, `/unlink-section/{sectionId}`
- POST/DELETE `/pages/{pageId}/link-field`, `/unlink-field/{fieldId}`
- GET `/pages/{pageId}/relationships`, `/structure`
- POST/DELETE `/sections/{sectionId}/link-field`, `/unlink-field/{fieldId}`
- POST/DELETE `/sections/{sectionId}/link-bom`, `/unlink-bom/{bomId}`
- GET `/sections/{sectionId}/relationships`
- POST `/relationships/reorder`
### 검증 결과
- Pint 코드 포맷팅: ✅ 통과
- Swagger 문서 생성: ✅ 완료
### Git 커밋
```
commit 4f78eed
feat: ItemMaster API group_id(계층번호) 추가 및 Swagger 보완
```
---
## 2025-11-26 (화) - AccessService permission_overrides 테이블 사용으로 수정
### 주요 작업
- API AccessService가 존재하지 않는 테이블(user_permission_overrides, department_permissions)을 참조하던 문제 수정
- 실제 DB의 `permission_overrides` 테이블을 사용하도록 수정
### 수정된 파일:
- `app/Services/Authz/AccessService.php`
- `hasUserOverride()`: `user_permission_overrides``permission_overrides` (model_type='App\Models\Members\User')
- `departmentAllows()`: `department_permissions``permission_overrides` (model_type='App\Models\Tenants\Department')
- 필드명 변경: `is_allowed``effect`, `user_id``model_id`
### 기술 상세:
**permission_overrides 테이블 구조:**
- `model_type`: 폴리모픽 타입 (User, Department)
- `model_id`: 대상 ID
- `permission_id`: 권한 ID
- `effect`: 0=DENY, 1=ALLOW
- `effective_from`, `effective_to`: 유효 기간
### 코드 품질:
- ✅ PHP 문법 검사 통과
- ✅ Pint 포맷팅 통과
---
## 2025-11-26 (화) - Item Master 독립 엔티티 API 추가 ✅ 완료
### 작업 목표
- 독립 엔티티(섹션, 필드, BOM) CRUD API 10개 추가
- `SectionTemplate` 모델 삭제 → `ItemSection.is_template` 플래그로 통합
- Swagger 문서 업데이트
### 변경 내용
**1. SectionTemplate → ItemSection 통합**
- `section_templates` 테이블 삭제
- `item_sections` 테이블에 `is_template` 컬럼 추가
- 기존 `/section-templates` API는 유지 (내부적으로 `is_template=true` 사용)
**2. 10개 독립 API 추가**
| API | 메서드 | 설명 |
|-----|--------|------|
| `/sections` | GET | 섹션 목록 (is_template 필터) |
| `/sections` | POST | 독립 섹션 생성 |
| `/sections/{id}/clone` | POST | 섹션 복제 |
| `/sections/{id}/usage` | GET | 섹션 사용처 조회 |
| `/fields` | GET | 필드 목록 |
| `/fields` | POST | 독립 필드 생성 |
| `/fields/{id}/clone` | POST | 필드 복제 |
| `/fields/{id}/usage` | GET | 필드 사용처 조회 |
| `/bom-items` | GET | BOM 항목 목록 |
| `/bom-items` | POST | 독립 BOM 생성 |
### 추가된 파일
- `app/Http/Requests/ItemMaster/IndependentSectionStoreRequest.php`
- `app/Http/Requests/ItemMaster/IndependentFieldStoreRequest.php`
- `app/Http/Requests/ItemMaster/IndependentBomItemStoreRequest.php`
### 삭제된 파일
- `app/Models/ItemMaster/SectionTemplate.php`
### 수정된 파일
- `app/Http/Controllers/Api/V1/ItemMaster/ItemSectionController.php`
- `app/Http/Controllers/Api/V1/ItemMaster/ItemFieldController.php`
- `app/Http/Controllers/Api/V1/ItemMaster/ItemBomItemController.php`
- `app/Services/ItemMaster/ItemSectionService.php`
- `app/Services/ItemMaster/ItemFieldService.php`
- `app/Services/ItemMaster/ItemBomItemService.php`
- `app/Models/ItemMaster/ItemSection.php` (is_template, scopeTemplates 추가)
- `routes/api.php`
- `app/Swagger/v1/ItemMasterApi.php`
### 마이그레이션
```bash
# 실행된 마이그레이션
2025_11_26_120000_add_is_template_to_item_sections_and_drop_section_templates.php
```
### 검증 결과
- PHP 문법 검사: ✅ 통과
- Pint 코드 포맷팅: ✅ 통과
- Swagger 문서 생성: ✅ 완료
---
## 2025-11-26 (화) - Item Master 하이브리드 구조 전환 (독립 엔티티 + 링크 테이블) ✅ 완료
### 작업 목표
기존 CASCADE FK 기반 계층 구조를 **독립 엔티티 + 링크 테이블** 구조로 전환
### 배경
- **문제점**: 현재 구조에서 섹션 삭제 시 항목(필드)도 함께 삭제됨 (CASCADE)
- **요구사항**:
- 페이지, 섹션, 항목은 독립적으로 존재
- 관계는 링크 테이블로 관리 (Many-to-Many)
- 페이지에서 섹션/항목 모두 직접 연결 가능
- 섹션에서 항목 연결 가능
- 엔티티 삭제 시 링크만 제거, 다른 엔티티는 유지
- `group_id`로 카테고리 격리 (품목관리=1, 향후 확장)
### 변경 구조
**Before (CASCADE FK)**:
```
item_pages
↓ page_id FK (CASCADE)
item_sections
↓ section_id FK (CASCADE)
item_fields / item_bom_items
```
**After (독립 + 링크)**:
```
item_pages (독립)
item_sections (독립)
item_fields (독립)
item_bom_items (독립)
⇄ entity_relationships (링크 테이블)
```
### Phase 계획
| Phase | 작업 내용 | 상태 |
|-------|----------|------|
| 1 | 마이그레이션: FK 제거 + group_id 추가 | ✅ 완료 |
| 2 | 마이그레이션: entity_relationships 테이블 생성 | ✅ 완료 |
| 3 | 마이그레이션: 기존 데이터 이관 | ✅ 완료 |
| 4 | 모델 및 Service 수정 | ✅ 완료 |
| 5 | 새로운 API 엔드포인트 추가 | ✅ 완료 |
| 6 | Swagger 문서 업데이트 | ✅ 완료 |
| 7 | 테스트 및 검증 | ✅ 완료 |
### 추가된 파일
**마이그레이션** (Batch 26으로 실행):
- `database/migrations/2025_11_26_100001_convert_item_tables_to_independent_entities.php`
- `database/migrations/2025_11_26_100002_create_entity_relationships_table.php`
- `database/migrations/2025_11_26_100003_migrate_existing_relationships_to_entity_relationships.php`
**모델**:
- `app/Models/ItemMaster/EntityRelationship.php` (신규)
**서비스**:
- `app/Services/ItemMaster/EntityRelationshipService.php` (신규)
**컨트롤러**:
- `app/Http/Controllers/Api/V1/ItemMaster/EntityRelationshipController.php` (신규)
**Request**:
- `app/Http/Requests/ItemMaster/LinkEntityRequest.php` (신규)
- `app/Http/Requests/ItemMaster/ReorderRelationshipsRequest.php` (신규)
**Swagger**:
- `app/Swagger/v1/EntityRelationshipApi.php` (신규)
### 수정된 파일
**모델 (group_id 추가 + relationship 메서드)**:
- `app/Models/ItemMaster/ItemPage.php`
- `app/Models/ItemMaster/ItemSection.php`
- `app/Models/ItemMaster/ItemField.php`
- `app/Models/ItemMaster/ItemBomItem.php`
- `app/Models/ItemMaster/SectionTemplate.php`
- `app/Models/ItemMaster/ItemMasterField.php`
**라우트**:
- `routes/api.php` (새로운 엔드포인트 추가)
**언어 파일**:
- `lang/ko/message.php` (linked, unlinked 추가)
- `lang/ko/error.php` (page_not_found, section_not_found, field_not_found, bom_not_found 추가)
### 새로운 API 엔드포인트 (14개)
**페이지-섹션 연결**:
- `POST /api/v1/item-master/pages/{pageId}/link-section`
- `DELETE /api/v1/item-master/pages/{pageId}/unlink-section/{sectionId}`
**페이지-필드 직접 연결**:
- `POST /api/v1/item-master/pages/{pageId}/link-field`
- `DELETE /api/v1/item-master/pages/{pageId}/unlink-field/{fieldId}`
**페이지 관계 조회**:
- `GET /api/v1/item-master/pages/{pageId}/relationships`
- `GET /api/v1/item-master/pages/{pageId}/structure`
**섹션-필드 연결**:
- `POST /api/v1/item-master/sections/{sectionId}/link-field`
- `DELETE /api/v1/item-master/sections/{sectionId}/unlink-field/{fieldId}`
**섹션-BOM 연결**:
- `POST /api/v1/item-master/sections/{sectionId}/link-bom`
- `DELETE /api/v1/item-master/sections/{sectionId}/unlink-bom/{bomId}`
**섹션 관계 조회**:
- `GET /api/v1/item-master/sections/{sectionId}/relationships`
**관계 순서 변경**:
- `POST /api/v1/item-master/relationships/reorder`
### 검증 결과
- PHP 문법 검사: ✅ 통과
- Pint 코드 포맷팅: ✅ 통과 (9개 신규 파일)
- Swagger 문서 생성: ✅ 완료
- 라우트 등록: ✅ 44개 item-master 라우트 확인
### 롤백 방법
```bash
php artisan migrate:rollback --step=3
```
### 다음 작업 (옵션)
- 기존 API (POST /pages/{pageId}/sections 등) 내부적으로 entity_relationships 사용하도록 수정
- 독립 엔티티 CRUD API 추가 (POST /sections, POST /fields 등)
---
## 2025-11-25 (월) - API 인증 에러 처리 개선 및 요청 로그 강화
### 문제 상황
- GET /item-master/init 요청 시 `Route [login] not defined` 에러 발생
- user_id null (인증 실패 상태)
- API Request 로그에 헤더 정보 누락
### 근본 원인
1. **Handler.php**: `expectsJson()` 체크만 있어서, Accept 헤더 없는 API 요청이 기본 동작(로그인 리다이렉트)으로 처리됨
2. **ApiKeyMiddleware**: 요청 로그에 헤더 정보 (X-API-KEY, Authorization) 미포함
### 해결 방안
**Handler.php (app/Exceptions/Handler.php)**
- Line 60: API 라우트 체크 추가 (`str_starts_with($request->path(), 'api/')`)
- Line 62: `$request->expectsJson() || $isApiRoute` 조건으로 변경
- 효과: Accept 헤더 없어도 API 라우트는 무조건 JSON 응답 반환
**ApiKeyMiddleware (app/Http/Middleware/ApiKeyMiddleware.php)**
- Line 52-57: headers 필드 추가
- X-API-KEY: 마스킹 ('***')
- Authorization: Bearer 토큰 마스킹 ('Bearer ***')
- Accept, Content-Type: 원본 그대로
- 효과: 인증 문제 디버깅 용이
### 수정된 파일
- `app/Exceptions/Handler.php`
- `app/Http/Middleware/ApiKeyMiddleware.php`
### 검증 결과
- PHP 문법 체크: ✅ 통과
- Pint 코드 포맷팅: ✅ 통과
### 기대 효과
1. API 요청 시 Accept 헤더 없어도 정상 JSON 응답
2. 인증 실패 시 로그인 리다이렉트 대신 401 JSON 응답
3. 요청 로그에서 헤더 정보 확인 가능 (디버깅 개선)
---
## 2025-11-24 (일) - 소프트삭제 및 타임스탬프 감사 컬럼 추가
### 작업 목표
- deleted_at이 있는 테이블에 deleted_by 컬럼 추가
- created_at, updated_at이 있는 테이블에 created_by, updated_by 컬럼 추가
### 작업 내용
**1. DB 스키마 분석 (INFORMATION_SCHEMA 쿼리)**
- deleted_at은 있지만 deleted_by가 없는 테이블: 30개
- created_at은 있지만 created_by, updated_by가 없는 테이블: 45개
**2. 마이그레이션 생성**
- `2025_11_24_192518_add_deleted_by_to_soft_delete_tables.php`
- 30개 테이블에 deleted_by 추가
- nullable, COMMENT('삭제자 사용자 ID')
- after('deleted_at') 배치
- `2025_11_24_192518_add_audit_columns_to_tables.php`
- 38개 비즈니스 테이블에 created_by, updated_by 추가
- 시스템 테이블 제외 (jobs, job_batches, password_reset_tokens, personal_access_tokens, taggables, tags)
- nullable, COMMENT
- after('updated_at'), after('created_by') 배치
**3. 마이그레이션 실행 및 검증**
- 실행 시간: deleted_by (429.53ms), audit_columns (1초)
- 샘플 테이블 검증: users, products, models, bom_templates, department_user 모두 정상
### 추가된 파일
- `database/migrations/2025_11_24_192518_add_deleted_by_to_soft_delete_tables.php`
- `database/migrations/2025_11_24_192518_add_audit_columns_to_tables.php`
### 마이그레이션 상태
- Batch 25로 실행 완료
- 롤백 가능 (down 메서드 구현)
---
## 2025-11-24 (일) - CORS Preflight 문제 해결
### 문제 상황
- React 프론트엔드(http://192.0.0.2:3001)에서 API 호출 시 CORS 에러
- 에러: `Request header field x-api-key is not allowed by Access-Control-Allow-Headers in preflight response`
- 서버 로그: OPTIONS 요청만 있고 Response 로그 없음 (401 차단)
### 근본 원인 (root-cause-analyst 스킬 활용)
1. CorsMiddleware에서 `Access-Control-Allow-Headers``X-API-KEY` 누락
2. OPTIONS 요청(Preflight)이 ApiKeyMiddleware에서 401로 차단
3. 브라우저는 커스텀 헤더 사용 시 Preflight 요청을 자동 전송
### 해결 방안
**CorsMiddleware 수정:**
- OPTIONS 요청을 미들웨어 체인 진입 전에 즉시 200 OK 처리
- `Access-Control-Allow-Headers``X-API-KEY` 추가
- PATCH 메서드 추가, Max-Age 86400초 설정
**ApiKeyMiddleware 정리:**
- 불필요한 OPTIONS 체크 제거
**CORS 설정 업데이트:**
- `config/cors.php`: exposed_headers, max_age 설정
### 수정된 파일
- `app/Http/Middleware/CorsMiddleware.php`
- `app/Http/Middleware/ApiKeyMiddleware.php`
- `config/cors.php`
### Git 커밋
- `2e96660` - CORS preflight 요청 처리 개선 및 X-API-KEY 헤더 허용
- `8e8ab65` - CORS preflight 응답에 x-api-key 헤더 허용 추가
### 다음 작업
- React에서 API 호출 테스트
- 개발 서버 로그 확인 (Request/Response 쌍 기록 여부)
---
## 2025-11-20 (수) - ItemMaster API 테스트 및 버그 수정
### 주요 작업
- ItemMaster API 통합 테스트 작성 (12개 테스트, 82개 assertion)
- 누락된 마이그레이션 실행 (section_templates, tab_columns)
- API Key 미들웨어 수정 (로그인 엔드포인트 API Key 필수화)
- ReorderRequest validation 수정 (범용성 확보)
- 네임스페이스 오류 수정 (5개 Controller)
- Route 순서 수정 (specific route 우선)
### 테스트 결과
✅ 12/12 테스트 통과 (100%)
---
## 2025-11-19 (화) - ItemMaster API Swagger 문서 작성
### 주요 작업
- ItemMaster 전체 API (32개 엔드포인트) Swagger 문서화 완료
- OpenAPI 3.0 표준 준수
- Model Schemas 8개, Request Schemas 12개 작성
---
## 2025-11-18 (월) - Category API 테스트 및 개선
### 주요 작업
- Category CRUD 테스트 작성 (9개 테스트, 98개 assertion)
- 계층 구조 및 필드 관리 테스트
- Validation 로직 개선
---
## 2025-12-19 (목) - Phase 6.2 팝업관리 API 구현
### 주요 작업
- Phase 6.2 팝업관리 기능 구현 완료
- PROJECT_DEVELOPMENT_POLICY.md 정책 준수 (string 타입, options JSON 가변 컬럼)
### 추가된 파일
**마이그레이션:**
- `database/migrations/2025_12_19_170001_create_popups_table.php`
**모델:**
- `app/Models/Popups/Popup.php`
- BelongsToTenant, SoftDeletes 적용
- target_type: all(전사), department(부서)
- status: active(사용), inactive(사용안함)
- 스코프: active(), status(), targetType(), forUser()
- 관계: department(), creator(), updater()
**서비스:**
- `app/Services/PopupService.php`
- index(): 관리자용 목록 (페이지네이션)
- getActivePopups(): 사용자용 활성 팝업
- show(): 상세 조회
- store(): 등록
- update(): 수정
- destroy(): 삭제 (Soft Delete)
**FormRequest:**
- `app/Http/Requests/V1/Popup/StorePopupRequest.php`
- `app/Http/Requests/V1/Popup/UpdatePopupRequest.php`
**컨트롤러:**
- `app/Http/Controllers/Api/V1/PopupController.php`
**Swagger:**
- `app/Swagger/v1/PopupApi.php`
### 수정된 파일
- `routes/api.php`: Popup 라우트 추가 (6개 엔드포인트)
### API 엔드포인트 (6개)
| Method | Path | Description |
|--------|------|-------------|
| GET | /api/v1/popups | 팝업 목록 (관리자용) |
| POST | /api/v1/popups | 팝업 등록 |
| GET | /api/v1/popups/active | 활성 팝업 (사용자용) |
| GET | /api/v1/popups/{id} | 팝업 상세 |
| PUT | /api/v1/popups/{id} | 팝업 수정 |
| DELETE | /api/v1/popups/{id} | 팝업 삭제 |
### 정책 준수 사항
- ✅ 기존 테이블 확인 후 신규 생성
- ✅ string 타입 사용 (enum 대신)
- ✅ options JSON 가변 컬럼
- ✅ BelongsToTenant, SoftDeletes 적용
- ✅ Service-First 아키텍처
- ✅ FormRequest 검증
---