feat: 문서 관리 시스템 Route 및 Swagger 구현 (Phase 1.8)
- Document API Route 등록 (CRUD 5개 엔드포인트) - Swagger 문서 작성 (Document, DocumentApproval, DocumentData, DocumentAttachment 스키마) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
337
app/Swagger/v1/DocumentApi.php
Normal file
337
app/Swagger/v1/DocumentApi.php
Normal file
@@ -0,0 +1,337 @@
|
||||
<?php
|
||||
|
||||
namespace App\Swagger\v1;
|
||||
|
||||
/**
|
||||
* @OA\Tag(name="Documents", description="문서 관리")
|
||||
*
|
||||
* @OA\Schema(
|
||||
* schema="Document",
|
||||
* type="object",
|
||||
* description="문서 정보",
|
||||
*
|
||||
* @OA\Property(property="id", type="integer", example=1, description="문서 ID"),
|
||||
* @OA\Property(property="tenant_id", type="integer", example=1, description="테넌트 ID"),
|
||||
* @OA\Property(property="template_id", type="integer", example=5, description="템플릿 ID"),
|
||||
* @OA\Property(property="document_no", type="string", example="DOC-20260128-0001", description="문서번호"),
|
||||
* @OA\Property(property="title", type="string", example="2026년 1월 휴가 신청서", description="문서 제목"),
|
||||
* @OA\Property(property="status", type="string", enum={"DRAFT","PENDING","APPROVED","REJECTED","CANCELLED"}, example="DRAFT", description="상태"),
|
||||
* @OA\Property(property="linkable_type", type="string", example="App\\Models\\Order", nullable=true, description="연결 모델 타입"),
|
||||
* @OA\Property(property="linkable_id", type="integer", example=100, nullable=true, description="연결 모델 ID"),
|
||||
* @OA\Property(property="submitted_at", type="string", format="date-time", example="2026-01-28T10:00:00Z", nullable=true, description="결재 요청일"),
|
||||
* @OA\Property(property="completed_at", type="string", format="date-time", example="2026-01-28T15:00:00Z", nullable=true, description="결재 완료일"),
|
||||
* @OA\Property(property="created_by", type="integer", example=10, description="생성자 ID"),
|
||||
* @OA\Property(property="template", type="object", nullable=true, description="템플릿 정보",
|
||||
* @OA\Property(property="id", type="integer", example=5),
|
||||
* @OA\Property(property="name", type="string", example="휴가 신청서"),
|
||||
* @OA\Property(property="category", type="string", example="HR")
|
||||
* ),
|
||||
* @OA\Property(property="creator", type="object", nullable=true, description="생성자 정보",
|
||||
* @OA\Property(property="id", type="integer", example=10),
|
||||
* @OA\Property(property="name", type="string", example="홍길동")
|
||||
* ),
|
||||
* @OA\Property(property="approvals", type="array", description="결재선", @OA\Items(ref="#/components/schemas/DocumentApproval")),
|
||||
* @OA\Property(property="data", type="array", description="문서 데이터", @OA\Items(ref="#/components/schemas/DocumentData")),
|
||||
* @OA\Property(property="attachments", type="array", description="첨부파일", @OA\Items(ref="#/components/schemas/DocumentAttachment")),
|
||||
* @OA\Property(property="created_at", type="string", format="date-time", example="2026-01-28T09:00:00Z"),
|
||||
* @OA\Property(property="updated_at", type="string", format="date-time", example="2026-01-28T09:00:00Z")
|
||||
* )
|
||||
*
|
||||
* @OA\Schema(
|
||||
* schema="DocumentApproval",
|
||||
* type="object",
|
||||
* description="문서 결재 정보",
|
||||
*
|
||||
* @OA\Property(property="id", type="integer", example=1),
|
||||
* @OA\Property(property="document_id", type="integer", example=1),
|
||||
* @OA\Property(property="user_id", type="integer", example=5),
|
||||
* @OA\Property(property="step", type="integer", example=1, description="결재 순서"),
|
||||
* @OA\Property(property="role", type="string", example="승인", description="역할"),
|
||||
* @OA\Property(property="status", type="string", enum={"PENDING","APPROVED","REJECTED"}, example="PENDING", description="상태"),
|
||||
* @OA\Property(property="comment", type="string", example="승인합니다.", nullable=true, description="결재 의견"),
|
||||
* @OA\Property(property="acted_at", type="string", format="date-time", nullable=true, description="결재 처리일"),
|
||||
* @OA\Property(property="user", type="object", nullable=true, description="결재자 정보",
|
||||
* @OA\Property(property="id", type="integer", example=5),
|
||||
* @OA\Property(property="name", type="string", example="김부장")
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* @OA\Schema(
|
||||
* schema="DocumentData",
|
||||
* type="object",
|
||||
* description="문서 데이터 (EAV)",
|
||||
*
|
||||
* @OA\Property(property="id", type="integer", example=1),
|
||||
* @OA\Property(property="document_id", type="integer", example=1),
|
||||
* @OA\Property(property="section_id", type="integer", example=1, nullable=true, description="섹션 ID"),
|
||||
* @OA\Property(property="column_id", type="integer", example=1, nullable=true, description="컬럼 ID"),
|
||||
* @OA\Property(property="row_index", type="integer", example=0, description="행 인덱스"),
|
||||
* @OA\Property(property="field_key", type="string", example="start_date", description="필드 키"),
|
||||
* @OA\Property(property="field_value", type="string", example="2026-01-28", nullable=true, description="값")
|
||||
* )
|
||||
*
|
||||
* @OA\Schema(
|
||||
* schema="DocumentAttachment",
|
||||
* type="object",
|
||||
* description="문서 첨부파일",
|
||||
*
|
||||
* @OA\Property(property="id", type="integer", example=1),
|
||||
* @OA\Property(property="document_id", type="integer", example=1),
|
||||
* @OA\Property(property="file_id", type="integer", example=100),
|
||||
* @OA\Property(property="attachment_type", type="string", enum={"general","signature","image","reference"}, example="general", description="첨부 유형"),
|
||||
* @OA\Property(property="description", type="string", example="증빙서류", nullable=true, description="설명"),
|
||||
* @OA\Property(property="file", type="object", nullable=true, description="파일 정보",
|
||||
* @OA\Property(property="id", type="integer", example=100),
|
||||
* @OA\Property(property="original_name", type="string", example="document.pdf"),
|
||||
* @OA\Property(property="mime_type", type="string", example="application/pdf"),
|
||||
* @OA\Property(property="size", type="integer", example=1024000)
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* @OA\Schema(
|
||||
* schema="DocumentCreateRequest",
|
||||
* type="object",
|
||||
* required={"template_id", "title"},
|
||||
* description="문서 생성 요청",
|
||||
*
|
||||
* @OA\Property(property="template_id", type="integer", example=5, description="템플릿 ID"),
|
||||
* @OA\Property(property="title", type="string", example="2026년 1월 휴가 신청서", description="문서 제목"),
|
||||
* @OA\Property(property="linkable_type", type="string", example="App\\Models\\Order", nullable=true, description="연결 모델 타입"),
|
||||
* @OA\Property(property="linkable_id", type="integer", example=100, nullable=true, description="연결 모델 ID"),
|
||||
* @OA\Property(property="approvers", type="array", description="결재선", @OA\Items(
|
||||
* type="object",
|
||||
* required={"user_id"},
|
||||
* @OA\Property(property="user_id", type="integer", example=5, description="결재자 ID"),
|
||||
* @OA\Property(property="role", type="string", example="승인", description="역할")
|
||||
* )),
|
||||
* @OA\Property(property="data", type="array", description="문서 데이터", @OA\Items(
|
||||
* type="object",
|
||||
* required={"field_key"},
|
||||
* @OA\Property(property="section_id", type="integer", example=1, nullable=true),
|
||||
* @OA\Property(property="column_id", type="integer", example=1, nullable=true),
|
||||
* @OA\Property(property="row_index", type="integer", example=0),
|
||||
* @OA\Property(property="field_key", type="string", example="start_date"),
|
||||
* @OA\Property(property="field_value", type="string", example="2026-01-28", nullable=true)
|
||||
* )),
|
||||
* @OA\Property(property="attachments", type="array", description="첨부파일", @OA\Items(
|
||||
* type="object",
|
||||
* required={"file_id"},
|
||||
* @OA\Property(property="file_id", type="integer", example=100),
|
||||
* @OA\Property(property="attachment_type", type="string", enum={"general","signature","image","reference"}, example="general"),
|
||||
* @OA\Property(property="description", type="string", example="증빙서류", nullable=true)
|
||||
* ))
|
||||
* )
|
||||
*
|
||||
* @OA\Schema(
|
||||
* schema="DocumentUpdateRequest",
|
||||
* type="object",
|
||||
* description="문서 수정 요청",
|
||||
*
|
||||
* @OA\Property(property="title", type="string", example="2026년 1월 휴가 신청서 (수정)", description="문서 제목"),
|
||||
* @OA\Property(property="linkable_type", type="string", example="App\\Models\\Order", nullable=true, description="연결 모델 타입"),
|
||||
* @OA\Property(property="linkable_id", type="integer", example=100, nullable=true, description="연결 모델 ID"),
|
||||
* @OA\Property(property="approvers", type="array", description="결재선 (전체 교체)", @OA\Items(
|
||||
* type="object",
|
||||
* required={"user_id"},
|
||||
* @OA\Property(property="user_id", type="integer", example=5),
|
||||
* @OA\Property(property="role", type="string", example="승인")
|
||||
* )),
|
||||
* @OA\Property(property="data", type="array", description="문서 데이터 (전체 교체)", @OA\Items(
|
||||
* type="object",
|
||||
* required={"field_key"},
|
||||
* @OA\Property(property="section_id", type="integer", nullable=true),
|
||||
* @OA\Property(property="column_id", type="integer", nullable=true),
|
||||
* @OA\Property(property="row_index", type="integer"),
|
||||
* @OA\Property(property="field_key", type="string"),
|
||||
* @OA\Property(property="field_value", type="string", nullable=true)
|
||||
* )),
|
||||
* @OA\Property(property="attachments", type="array", description="첨부파일 (전체 교체)", @OA\Items(
|
||||
* type="object",
|
||||
* required={"file_id"},
|
||||
* @OA\Property(property="file_id", type="integer"),
|
||||
* @OA\Property(property="attachment_type", type="string", enum={"general","signature","image","reference"}),
|
||||
* @OA\Property(property="description", type="string", nullable=true)
|
||||
* ))
|
||||
* )
|
||||
*/
|
||||
class DocumentApi
|
||||
{
|
||||
/**
|
||||
* @OA\Get(
|
||||
* path="/api/v1/documents",
|
||||
* tags={"Documents"},
|
||||
* summary="문서 목록 조회",
|
||||
* description="필터/검색/페이지네이션으로 문서 목록을 조회합니다.",
|
||||
* security={{"ApiKeyAuth":{}},{"BearerAuth":{}}},
|
||||
*
|
||||
* @OA\Parameter(name="status", in="query", description="상태 필터", @OA\Schema(type="string", enum={"DRAFT","PENDING","APPROVED","REJECTED","CANCELLED"})),
|
||||
* @OA\Parameter(name="template_id", in="query", description="템플릿 ID 필터", @OA\Schema(type="integer")),
|
||||
* @OA\Parameter(name="search", in="query", description="검색어 (문서번호, 제목)", @OA\Schema(type="string")),
|
||||
* @OA\Parameter(name="from_date", in="query", description="시작일 이후", @OA\Schema(type="string", format="date")),
|
||||
* @OA\Parameter(name="to_date", in="query", description="종료일 이전", @OA\Schema(type="string", format="date")),
|
||||
* @OA\Parameter(name="sort_by", in="query", description="정렬 기준", @OA\Schema(type="string", enum={"created_at","document_no","title","status","submitted_at","completed_at"}, default="created_at")),
|
||||
* @OA\Parameter(name="sort_dir", in="query", description="정렬 방향", @OA\Schema(type="string", enum={"asc","desc"}, default="desc")),
|
||||
* @OA\Parameter(ref="#/components/parameters/Page"),
|
||||
* @OA\Parameter(ref="#/components/parameters/Size"),
|
||||
*
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="조회 성공",
|
||||
*
|
||||
* @OA\JsonContent(
|
||||
* allOf={
|
||||
*
|
||||
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
|
||||
* @OA\Schema(
|
||||
*
|
||||
* @OA\Property(
|
||||
* property="data",
|
||||
* type="object",
|
||||
* @OA\Property(property="current_page", type="integer", example=1),
|
||||
* @OA\Property(property="data", type="array", @OA\Items(ref="#/components/schemas/Document")),
|
||||
* @OA\Property(property="per_page", type="integer", example=20),
|
||||
* @OA\Property(property="total", type="integer", example=50)
|
||||
* )
|
||||
* )
|
||||
* }
|
||||
* )
|
||||
* ),
|
||||
*
|
||||
* @OA\Response(response=400, description="잘못된 요청", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||
* @OA\Response(response=401, description="인증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||
* @OA\Response(response=500, description="서버 에러", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
|
||||
* )
|
||||
*/
|
||||
public function index() {}
|
||||
|
||||
/**
|
||||
* @OA\Get(
|
||||
* path="/api/v1/documents/{id}",
|
||||
* tags={"Documents"},
|
||||
* summary="문서 상세 조회",
|
||||
* description="ID 기준 문서 상세 정보를 조회합니다. 템플릿, 결재선, 데이터, 첨부파일 포함.",
|
||||
* security={{"ApiKeyAuth":{}},{"BearerAuth":{}}},
|
||||
*
|
||||
* @OA\Parameter(name="id", in="path", required=true, description="문서 ID", @OA\Schema(type="integer", example=1)),
|
||||
*
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="조회 성공",
|
||||
*
|
||||
* @OA\JsonContent(
|
||||
* allOf={
|
||||
*
|
||||
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
|
||||
* @OA\Schema(@OA\Property(property="data", ref="#/components/schemas/Document"))
|
||||
* }
|
||||
* )
|
||||
* ),
|
||||
*
|
||||
* @OA\Response(response=401, description="인증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||
* @OA\Response(response=404, description="문서를 찾을 수 없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||
* @OA\Response(response=500, description="서버 에러", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
|
||||
* )
|
||||
*/
|
||||
public function show() {}
|
||||
|
||||
/**
|
||||
* @OA\Post(
|
||||
* path="/api/v1/documents",
|
||||
* tags={"Documents"},
|
||||
* summary="문서 생성",
|
||||
* description="템플릿 기반으로 새 문서를 생성합니다. 결재선, 데이터, 첨부파일을 함께 저장할 수 있습니다.",
|
||||
* security={{"ApiKeyAuth":{}},{"BearerAuth":{}}},
|
||||
*
|
||||
* @OA\RequestBody(
|
||||
* required=true,
|
||||
*
|
||||
* @OA\JsonContent(ref="#/components/schemas/DocumentCreateRequest")
|
||||
* ),
|
||||
*
|
||||
* @OA\Response(
|
||||
* response=201,
|
||||
* description="생성 성공",
|
||||
*
|
||||
* @OA\JsonContent(
|
||||
* allOf={
|
||||
*
|
||||
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
|
||||
* @OA\Schema(@OA\Property(property="data", ref="#/components/schemas/Document"))
|
||||
* }
|
||||
* )
|
||||
* ),
|
||||
*
|
||||
* @OA\Response(response=400, description="잘못된 요청", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||
* @OA\Response(response=401, description="인증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||
* @OA\Response(response=500, description="서버 에러", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
|
||||
* )
|
||||
*/
|
||||
public function store() {}
|
||||
|
||||
/**
|
||||
* @OA\Patch(
|
||||
* path="/api/v1/documents/{id}",
|
||||
* tags={"Documents"},
|
||||
* summary="문서 수정",
|
||||
* description="DRAFT 또는 REJECTED 상태의 문서만 수정 가능합니다. REJECTED 상태에서 수정하면 DRAFT로 변경됩니다.",
|
||||
* security={{"ApiKeyAuth":{}},{"BearerAuth":{}}},
|
||||
*
|
||||
* @OA\Parameter(name="id", in="path", required=true, description="문서 ID", @OA\Schema(type="integer")),
|
||||
*
|
||||
* @OA\RequestBody(
|
||||
* required=true,
|
||||
*
|
||||
* @OA\JsonContent(ref="#/components/schemas/DocumentUpdateRequest")
|
||||
* ),
|
||||
*
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="수정 성공",
|
||||
*
|
||||
* @OA\JsonContent(
|
||||
* allOf={
|
||||
*
|
||||
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
|
||||
* @OA\Schema(@OA\Property(property="data", ref="#/components/schemas/Document"))
|
||||
* }
|
||||
* )
|
||||
* ),
|
||||
*
|
||||
* @OA\Response(response=400, description="잘못된 요청 (수정 불가 상태)", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||
* @OA\Response(response=401, description="인증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||
* @OA\Response(response=404, description="문서를 찾을 수 없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||
* @OA\Response(response=500, description="서버 에러", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
|
||||
* )
|
||||
*/
|
||||
public function update() {}
|
||||
|
||||
/**
|
||||
* @OA\Delete(
|
||||
* path="/api/v1/documents/{id}",
|
||||
* tags={"Documents"},
|
||||
* summary="문서 삭제",
|
||||
* description="DRAFT 상태의 문서만 삭제 가능합니다 (소프트 삭제).",
|
||||
* security={{"ApiKeyAuth":{}},{"BearerAuth":{}}},
|
||||
*
|
||||
* @OA\Parameter(name="id", in="path", required=true, description="문서 ID", @OA\Schema(type="integer")),
|
||||
*
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="삭제 성공",
|
||||
*
|
||||
* @OA\JsonContent(
|
||||
*
|
||||
* @OA\Property(property="success", type="boolean", example=true),
|
||||
* @OA\Property(property="message", type="string", example="삭제되었습니다."),
|
||||
* @OA\Property(property="data", type="boolean", example=true)
|
||||
* )
|
||||
* ),
|
||||
*
|
||||
* @OA\Response(response=400, description="잘못된 요청 (삭제 불가 상태)", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||
* @OA\Response(response=401, description="인증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||
* @OA\Response(response=404, description="문서를 찾을 수 없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||
* @OA\Response(response=500, description="서버 에러", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
|
||||
* )
|
||||
*/
|
||||
public function destroy() {}
|
||||
}
|
||||
@@ -35,6 +35,7 @@
|
||||
require __DIR__.'/api/v1/design.php';
|
||||
require __DIR__.'/api/v1/files.php';
|
||||
require __DIR__.'/api/v1/boards.php';
|
||||
require __DIR__.'/api/v1/documents.php';
|
||||
require __DIR__.'/api/v1/common.php';
|
||||
|
||||
// 공유 링크 다운로드 (인증 불필요 - auth.apikey 그룹 밖)
|
||||
|
||||
26
routes/api/v1/documents.php
Normal file
26
routes/api/v1/documents.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 문서 관리 API 라우트 (v1)
|
||||
*
|
||||
* - 문서 CRUD
|
||||
* - 결재 워크플로우 (보류 - 기존 시스템 연동 필요)
|
||||
*/
|
||||
|
||||
use App\Http\Controllers\Api\V1\Documents\DocumentController;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::prefix('documents')->group(function () {
|
||||
// 문서 CRUD
|
||||
Route::get('/', [DocumentController::class, 'index'])->name('v1.documents.index');
|
||||
Route::get('/{id}', [DocumentController::class, 'show'])->whereNumber('id')->name('v1.documents.show');
|
||||
Route::post('/', [DocumentController::class, 'store'])->name('v1.documents.store');
|
||||
Route::patch('/{id}', [DocumentController::class, 'update'])->whereNumber('id')->name('v1.documents.update');
|
||||
Route::delete('/{id}', [DocumentController::class, 'destroy'])->whereNumber('id')->name('v1.documents.destroy');
|
||||
|
||||
// 결재 워크플로우 (보류 - 기존 시스템 연동 필요)
|
||||
// Route::post('/{id}/submit', [DocumentController::class, 'submit'])->name('v1.documents.submit');
|
||||
// Route::post('/{id}/approve', [DocumentController::class, 'approve'])->name('v1.documents.approve');
|
||||
// Route::post('/{id}/reject', [DocumentController::class, 'reject'])->name('v1.documents.reject');
|
||||
// Route::post('/{id}/cancel', [DocumentController::class, 'cancel'])->name('v1.documents.cancel');
|
||||
});
|
||||
Reference in New Issue
Block a user