- 사용자 초대 API: role 문자열 지원 추가 (React 호환) - 알림 설정 API: 그룹 기반 계층 구조 구현 - notification_setting_groups 테이블 추가 - notification_setting_group_items 테이블 추가 - notification_setting_group_states 테이블 추가 - GET/PUT /api/v1/settings/notifications 엔드포인트 추가 - Pint 코드 스타일 정리
69 KiB
SAM API 작업 현황
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 커밋
eeca8d3feat: 견적수식 케이스 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 커밋
c15a245feat: Phase 7.2 보완 - 나의 게시글 API 추가
2025-12-19 (목) - Phase 6.1 악성채권 추심관리 API 개발
작업 목표
docs/plans/erp-api-development-plan-d1.0-changes.mdPhase 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 커밋
c0af888feat: Phase 6.1 악성채권 추심관리 API 구현
2025-12-18 (수) - 가지급금 관리 API 개발
작업 목표
docs/plans/erp-api-development-plan.mdPhase 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.mdPhase 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.mdPhase 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' |
실행 명령어
# 마이그레이션 실행 (순서대로)
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.mddocs/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.phpemployee_statusENUM('active', 'leave', 'resigned') DEFAULT 'active'json_extraJSON nullable (유연한 사원 정보)
-
2025_12_09_084231_create_attendances_table.phpuser_id,base_date,status,json_details,remarksjson_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.phpapp/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
// 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() 메서드가 기존 데이터 발견
해결
# 잔류 테스트 데이터 삭제
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\Userapp/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 반환
사용 예시
// Controller에서 201 반환
return ApiResponse::handle(function () use ($request) {
$data = $this->service->store($request->validated());
return ['data' => $data, 'message' => __('message.created'), 'statusCode' => 201];
});
검증 필요
- Flow Tester 재실행하여 201 응답 확인 ✅
- 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.php2025_12_08_154634_create_price_revisions_table.php2025_12_08_154635_migrate_price_histories_to_prices.php2025_12_08_154636_drop_price_histories_table.php
모델 (2개):
app/Models/Price.php- BelongsToTenant, SoftDeletesapp/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.phpapp/Http/Requests/Pricing/PriceStoreRequest.phpapp/Http/Requests/Pricing/PriceUpdateRequest.phpapp/Http/Requests/Pricing/PriceByItemsRequest.phpapp/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.mddocs/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_deletablequote_not_finalizable,quote_not_finalized,quote_already_convertedquote_not_convertible,quote_email_not_found,quote_phone_not_foundformula_empty,formula_parentheses_mismatch,formula_unsupported_function,formula_calculation_error
성공 메시지 (message.php):
quote.fetched,quote.created,quote.updated,quote.deletedquote.bulk_deleted,quote.finalized,quote.finalize_cancelledquote.converted,quote.calculated,quote.pdf_generatedquote_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 | 품목별 단가 계산 |
실행 명령어
# 순서대로 실행
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.phpglobal_menu_id컬럼: 원본 글로벌 메뉴 IDis_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(): 동기화 비교 대상 필드 목록
- fillable:
-
app/Services/MenuBootstrapService.phpcloneGlobalMenusForTenant(): 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.phpupdate(): 글로벌 복제 메뉴 수정 시 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_idnullable 변경is_systemboolean 컬럼 추가board_typeVARCHAR(50) 컬럼 추가deleted_at,deleted_bySoftDeletes 추가- 인덱스 추가
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 스키마 변경
-- 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_iddescription="계층번호" 추가 - API 문서 정확도 개선
배경
POST /api/v1/item-master/pages/{pageId}/sectionsSwagger 문서 점검 중group_id누락 발견- Service에서
$data['group_id'] ?? 1로 사용하지만 FormRequest와 Swagger에 미정의 group_id는 "계층번호"로 통일하여 주석 및 description 작성
수정된 파일 (4개)
FormRequest (group_id 필드 추가):
app/Http/Requests/ItemMaster/ItemSectionStoreRequest.phpapp/Http/Requests/ItemMaster/ItemFieldStoreRequest.phpapp/Http/Requests/ItemMaster/ItemBomItemStoreRequest.php
Swagger:
app/Swagger/v1/ItemMasterApi.php
Swagger 스키마 업데이트 상세
모델 스키마 (3개) - description="계층번호" 추가:
ItemSectionItemFieldItemBomItem
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.phphasUserOverride():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: 대상 IDpermission_id: 권한 IDeffect: 0=DENY, 1=ALLOWeffective_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-templatesAPI는 유지 (내부적으로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.phpapp/Http/Requests/ItemMaster/IndependentFieldStoreRequest.phpapp/Http/Requests/ItemMaster/IndependentBomItemStoreRequest.php
삭제된 파일
app/Models/ItemMaster/SectionTemplate.php
수정된 파일
app/Http/Controllers/Api/V1/ItemMaster/ItemSectionController.phpapp/Http/Controllers/Api/V1/ItemMaster/ItemFieldController.phpapp/Http/Controllers/Api/V1/ItemMaster/ItemBomItemController.phpapp/Services/ItemMaster/ItemSectionService.phpapp/Services/ItemMaster/ItemFieldService.phpapp/Services/ItemMaster/ItemBomItemService.phpapp/Models/ItemMaster/ItemSection.php(is_template, scopeTemplates 추가)routes/api.phpapp/Swagger/v1/ItemMasterApi.php
마이그레이션
# 실행된 마이그레이션
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.phpdatabase/migrations/2025_11_26_100002_create_entity_relationships_table.phpdatabase/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.phpapp/Models/ItemMaster/ItemSection.phpapp/Models/ItemMaster/ItemField.phpapp/Models/ItemMaster/ItemBomItem.phpapp/Models/ItemMaster/SectionTemplate.phpapp/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-sectionDELETE /api/v1/item-master/pages/{pageId}/unlink-section/{sectionId}
페이지-필드 직접 연결:
POST /api/v1/item-master/pages/{pageId}/link-fieldDELETE /api/v1/item-master/pages/{pageId}/unlink-field/{fieldId}
페이지 관계 조회:
GET /api/v1/item-master/pages/{pageId}/relationshipsGET /api/v1/item-master/pages/{pageId}/structure
섹션-필드 연결:
POST /api/v1/item-master/sections/{sectionId}/link-fieldDELETE /api/v1/item-master/sections/{sectionId}/unlink-field/{fieldId}
섹션-BOM 연결:
POST /api/v1/item-master/sections/{sectionId}/link-bomDELETE /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 라우트 확인
롤백 방법
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 로그에 헤더 정보 누락
근본 원인
- Handler.php:
expectsJson()체크만 있어서, Accept 헤더 없는 API 요청이 기본 동작(로그인 리다이렉트)으로 처리됨 - 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.phpapp/Http/Middleware/ApiKeyMiddleware.php
검증 결과
- PHP 문법 체크: ✅ 통과
- Pint 코드 포맷팅: ✅ 통과
기대 효과
- API 요청 시 Accept 헤더 없어도 정상 JSON 응답
- 인증 실패 시 로그인 리다이렉트 대신 401 JSON 응답
- 요청 로그에서 헤더 정보 확인 가능 (디버깅 개선)
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.phpdatabase/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 스킬 활용)
- CorsMiddleware에서
Access-Control-Allow-Headers에X-API-KEY누락 - OPTIONS 요청(Preflight)이 ApiKeyMiddleware에서 401로 차단
- 브라우저는 커스텀 헤더 사용 시 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.phpapp/Http/Middleware/ApiKeyMiddleware.phpconfig/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.phpapp/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 검증