diff --git a/changes/20260128_document_management_phase1_5.md b/changes/20260128_document_management_phase1_5.md new file mode 100644 index 0000000..779ded9 --- /dev/null +++ b/changes/20260128_document_management_phase1_5.md @@ -0,0 +1,59 @@ +# 변경 내용 요약 + +**날짜:** 2026-01-28 +**작업자:** Claude +**Phase:** 1.5 - Service 생성 + +## 📋 변경 개요 +문서 관리 시스템의 DocumentService 클래스를 생성하여 문서 CRUD 및 결재 워크플로우 비즈니스 로직을 구현했습니다. + +## 📁 수정된 파일 +- `app/Services/DocumentService.php` (신규) - 문서 관리 서비스 + +## 🔧 상세 변경 사항 + +### 1. DocumentService 구현 + +**주요 기능:** + +#### 문서 목록/상세 +- `list(array $params)` - 페이징, 필터링, 검색 지원 +- `show(int $id)` - 상세 조회 (템플릿, 결재선, 데이터, 첨부파일 포함) + +#### 문서 생성/수정/삭제 +- `create(array $data)` - 문서 생성 (결재선, 데이터, 첨부파일 포함) +- `update(int $id, array $data)` - 문서 수정 (반려 상태 → DRAFT 전환) +- `destroy(int $id)` - 문서 삭제 (DRAFT 상태만 가능) + +#### 결재 처리 +- `submit(int $id)` - 결재 요청 (DRAFT → PENDING) +- `approve(int $id, ?string $comment)` - 결재 승인 +- `reject(int $id, string $comment)` - 결재 반려 +- `cancel(int $id)` - 결재 취소/회수 (작성자만) + +#### 헬퍼 메서드 +- `generateDocumentNo()` - 문서번호 생성 (DOC-YYYYMMDD-NNNN) +- `createApprovals()` - 결재선 생성 +- `saveDocumentData()` - 문서 데이터 저장 (EAV) +- `attachFiles()` - 첨부파일 연결 + +**구현 특징:** +- Service-First 아키텍처 준수 +- Multi-tenancy 지원 (tenantId() 필터링) +- DB 트랜잭션 처리 +- 순차 결재 로직 (이전 단계 완료 확인) +- i18n 에러 메시지 키 사용 + +## ✅ 테스트 체크리스트 +- [x] PHP 문법 검사 통과 +- [x] Service 클래스 로드 성공 +- [x] Pint 포맷팅 완료 +- [ ] API 통합 테스트 (Phase 1.6 이후) + +## ⚠️ 배포 시 주의사항 +특이사항 없음 + +## 🔗 관련 문서 +- Phase 1.1: 마이그레이션 (`20260128_document_management_phase1_1.md`) +- Phase 1.2: 모델 생성 (별도 문서 없음, 커밋 참조) +- 계획 문서: `docs/plans/document-management-system-plan.md` \ No newline at end of file diff --git a/plans/document-management-system-plan.md b/plans/document-management-system-plan.md new file mode 100644 index 0000000..9aff332 --- /dev/null +++ b/plans/document-management-system-plan.md @@ -0,0 +1,962 @@ +# 문서 관리 시스템 개발 계획 + +> **작성일**: 2025-01-28 +> **목적**: 문서 템플릿 기반 실제 문서 작성/결재/관리 시스템 +> **상태**: 📋 계획 수립 + +--- + +## 📍 현재 진행 상태 + +| 항목 | 내용 | +|------|------| +| **마지막 완료 작업** | Phase 1.5 - Service 생성 ✅ | +| **다음 작업** | Phase 1.6 - Controller 생성 (DocumentController) | +| **진행률** | 3/12 (25%) | +| **마지막 업데이트** | 2026-01-28 | + +--- + +## 0. 빠른 시작 가이드 + +### 0.1 전제 조건 + +```bash +# Docker 서비스 실행 확인 +docker ps | grep sam + +# 예상 결과: sam-api-1, sam-mng-1, sam-mysql-1, sam-nginx-1 실행 중 +``` + +### 0.2 프로젝트 경로 + +| 프로젝트 | 경로 | 설명 | +|----------|------|------| +| API | `/Users/kent/Works/@KD_SAM/SAM/api` | Laravel 12 REST API | +| MNG | `/Users/kent/Works/@KD_SAM/SAM/mng` | Laravel 12 + Blade 관리자 | +| React | `/Users/kent/Works/@KD_SAM/SAM/react` | Next.js 15 프론트엔드 | + +### 0.3 작업 시작 명령어 + +```bash +# 1. API 마이그레이션 상태 확인 +docker exec sam-api-1 php artisan migrate:status + +# 2. 새 마이그레이션 생성 +docker exec sam-api-1 php artisan make:migration create_documents_table + +# 3. 마이그레이션 실행 +docker exec sam-api-1 php artisan migrate + +# 4. 모델 생성 +docker exec sam-api-1 php artisan make:model Document + +# 5. 코드 포맷팅 +docker exec sam-api-1 ./vendor/bin/pint +``` + +### 0.4 작업 순서 요약 + +``` +Phase 1 (API) +├── 1.1 마이그레이션 파일 생성 → 컨펌 필요 +├── 1.2 마이그레이션 실행 +├── 1.3 모델 생성 (Document, DocumentApproval, DocumentData) +├── 1.4 Service 생성 (DocumentService) +├── 1.5 Controller 생성 (DocumentController) +└── 1.6 Swagger 문서 + +Phase 2 (MNG) +├── 2.1 모델 복사/수정 +├── 2.2 문서 목록 화면 +├── 2.3 문서 상세/편집 화면 +└── 2.4 문서 생성 화면 + +Phase 3 (React) +├── 3.1 문서 작성 컴포넌트 +├── 3.2 결재선 지정 UI +└── 3.3 수입검사 연동 +``` + +--- + +## 1. 개요 + +### 1.1 배경 + +현재 SAM 시스템에는 문서 템플릿 관리 기능이 존재하나, 실제 문서를 작성하고 관리하는 기능이 없음. + +**현재 상태:** +- ✅ MNG: 문서 템플릿 관리 (`/document-templates`) +- ❌ 실제 문서 작성/관리 기능 없음 +- ❌ 결재 시스템과 연동 없음 + +**목표:** +- 템플릿 기반 동적 문서 생성 +- 결재 시스템 연동 +- 수입검사/입고등록에서 실사용 + +### 1.2 시스템 구조 + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ 문서 관리 시스템 │ +├─────────────────────────────────────────────────────────────────┤ +│ │ +│ [MNG 관리자] [React 사용자] │ +│ ┌──────────────┐ ┌──────────────┐ │ +│ │ 템플릿 관리 │ │ 문서 작성 │ │ +│ │ 문서 관리 │ │ 결재 처리 │ │ +│ └──────────────┘ └──────────────┘ │ +│ │ │ │ +│ └──────────────┬───────────────┘ │ +│ ▼ │ +│ [API Server] │ +│ │ │ +│ ▼ │ +│ [Database] │ +│ documents, document_approvals, document_data │ +│ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +### 1.3 변경 승인 정책 + +| 분류 | 예시 | 승인 | +|------|------|------| +| ✅ 즉시 가능 | 필드 추가, 문서 수정 | 불필요 | +| ⚠️ 컨펌 필요 | 마이그레이션, 새 API | **필수** | +| 🔴 금지 | 기존 테이블 변경 | 별도 협의 | + +--- + +## 2. 대상 범위 + +### 2.1 Phase 1: Database & API + +| # | 작업 항목 | 상태 | 파일 경로 | +|---|----------|:----:|----------| +| 1.1 | 마이그레이션 생성 | ✅ | `api/database/migrations/2026_01_28_200000_create_documents_table.php` | +| 1.2 | Document 모델 | ✅ | `api/app/Models/Documents/Document.php` | +| 1.3 | DocumentApproval 모델 | ✅ | `api/app/Models/Documents/DocumentApproval.php` | +| 1.4 | DocumentData 모델 | ✅ | `api/app/Models/Documents/DocumentData.php` | +| 1.5 | DocumentService | ⏳ | `api/app/Services/DocumentService.php` | +| 1.6 | DocumentController | ⏳ | `api/app/Http/Controllers/Api/V1/DocumentController.php` | +| 1.7 | FormRequest | ⏳ | `api/app/Http/Requests/Document/` | +| 1.8 | Swagger 문서 | ⏳ | `api/app/Swagger/v1/DocumentApi.php` | + +### 2.2 Phase 2: MNG 관리 화면 + +| # | 작업 항목 | 상태 | 파일 경로 | +|---|----------|:----:|----------| +| 2.1 | Document 모델 | ⏳ | `mng/app/Models/Document.php` | +| 2.2 | DocumentController | ⏳ | `mng/app/Http/Controllers/DocumentController.php` | +| 2.3 | 문서 목록 뷰 | ⏳ | `mng/resources/views/documents/index.blade.php` | +| 2.4 | 문서 상세 뷰 | ⏳ | `mng/resources/views/documents/show.blade.php` | +| 2.5 | 문서 생성 뷰 | ⏳ | `mng/resources/views/documents/create.blade.php` | +| 2.6 | API Controller | ⏳ | `mng/app/Http/Controllers/Api/Admin/DocumentApiController.php` | + +### 2.3 Phase 3: React 연동 + +| # | 작업 항목 | 상태 | 파일 경로 | +|---|----------|:----:|----------| +| 3.1 | 문서 작성 컴포넌트 | ⏳ | `react/src/components/document-system/DocumentForm/` | +| 3.2 | API actions | ⏳ | `react/src/components/document-system/actions.ts` | +| 3.3 | 수입검사 연동 | ⏳ | `react/src/components/material/ReceivingManagement/` | + +--- + +## 3. 상세 설계 + +### 3.1 Database Schema (마이그레이션 파일) + +```php +id(); + $table->foreignId('tenant_id')->constrained()->cascadeOnDelete(); + $table->foreignId('template_id')->constrained('document_templates'); + + // 문서 정보 + $table->string('document_no', 50)->comment('문서번호'); + $table->string('title', 255)->comment('문서 제목'); + $table->enum('status', ['DRAFT', 'PENDING', 'APPROVED', 'REJECTED', 'CANCELLED']) + ->default('DRAFT')->comment('상태'); + + // 연결 정보 (다형성) + $table->string('linkable_type', 100)->nullable()->comment('연결 타입'); + $table->unsignedBigInteger('linkable_id')->nullable()->comment('연결 ID'); + + // 메타 정보 + $table->foreignId('created_by')->constrained('users'); + $table->timestamp('submitted_at')->nullable()->comment('결재 요청일'); + $table->timestamp('completed_at')->nullable()->comment('결재 완료일'); + + $table->timestamps(); + $table->softDeletes(); + + $table->index(['tenant_id', 'status']); + $table->index('document_no'); + $table->index(['linkable_type', 'linkable_id']); + }); + + // 문서 결재 + Schema::create('document_approvals', function (Blueprint $table) { + $table->id(); + $table->foreignId('document_id')->constrained()->cascadeOnDelete(); + $table->foreignId('user_id')->constrained(); + + $table->unsignedTinyInteger('step')->default(1)->comment('결재 순서'); + $table->string('role', 50)->comment('역할 (작성/검토/승인)'); + $table->enum('status', ['PENDING', 'APPROVED', 'REJECTED']) + ->default('PENDING')->comment('상태'); + + $table->text('comment')->nullable()->comment('결재 의견'); + $table->timestamp('acted_at')->nullable()->comment('결재 처리일'); + + $table->timestamps(); + + $table->index(['document_id', 'step']); + }); + + // 문서 데이터 + Schema::create('document_data', function (Blueprint $table) { + $table->id(); + $table->foreignId('document_id')->constrained()->cascadeOnDelete(); + + $table->unsignedBigInteger('section_id')->nullable()->comment('섹션 ID'); + $table->unsignedBigInteger('column_id')->nullable()->comment('컬럼 ID'); + $table->unsignedSmallInteger('row_index')->default(0)->comment('행 인덱스'); + + $table->string('field_key', 100)->comment('필드 키'); + $table->text('field_value')->nullable()->comment('값'); + + $table->timestamps(); + + $table->index(['document_id', 'section_id']); + }); + + // 문서 첨부파일 + Schema::create('document_attachments', function (Blueprint $table) { + $table->id(); + $table->foreignId('document_id')->constrained()->cascadeOnDelete(); + $table->foreignId('file_id')->constrained('files'); + + $table->string('attachment_type', 50)->default('general')->comment('유형'); + $table->string('description', 255)->nullable(); + + $table->timestamps(); + }); + } + + public function down(): void + { + Schema::dropIfExists('document_attachments'); + Schema::dropIfExists('document_data'); + Schema::dropIfExists('document_approvals'); + Schema::dropIfExists('documents'); + } +}; +``` + +### 3.2 Model 코드 템플릿 + +```php + 'datetime', + 'completed_at' => 'datetime', + ]; + + // === 상태 상수 === + public const STATUS_DRAFT = 'DRAFT'; + public const STATUS_PENDING = 'PENDING'; + public const STATUS_APPROVED = 'APPROVED'; + public const STATUS_REJECTED = 'REJECTED'; + public const STATUS_CANCELLED = 'CANCELLED'; + + // === 관계 === + public function template(): BelongsTo + { + return $this->belongsTo(\App\Models\DocumentTemplate::class, 'template_id'); + } + + public function approvals(): HasMany + { + return $this->hasMany(DocumentApproval::class)->orderBy('step'); + } + + public function data(): HasMany + { + return $this->hasMany(DocumentData::class); + } + + public function attachments(): HasMany + { + return $this->hasMany(DocumentAttachment::class); + } + + public function linkable(): MorphTo + { + return $this->morphTo(); + } + + public function creator(): BelongsTo + { + return $this->belongsTo(\App\Models\User::class, 'created_by'); + } + + // === 스코프 === + public function scopeStatus($query, string $status) + { + return $query->where('status', $status); + } + + // === 헬퍼 === + public function canEdit(): bool + { + return $this->status === self::STATUS_DRAFT; + } + + public function canSubmit(): bool + { + return $this->status === self::STATUS_DRAFT; + } +} +``` + +```php + 'integer', + 'acted_at' => 'datetime', + ]; + + public const STATUS_PENDING = 'PENDING'; + public const STATUS_APPROVED = 'APPROVED'; + public const STATUS_REJECTED = 'REJECTED'; + + public function document(): BelongsTo + { + return $this->belongsTo(Document::class); + } + + public function user(): BelongsTo + { + return $this->belongsTo(\App\Models\User::class); + } +} +``` + +```php + 'integer', + ]; + + public function document(): BelongsTo + { + return $this->belongsTo(Document::class); + } +} +``` + +### 3.3 Service 코드 템플릿 + +```php +where('tenant_id', $this->tenantId()) + ->with(['template:id,name,category', 'creator:id,name']); + + // 필터 + if (!empty($params['status'])) { + $query->where('status', $params['status']); + } + if (!empty($params['template_id'])) { + $query->where('template_id', $params['template_id']); + } + if (!empty($params['search'])) { + $search = $params['search']; + $query->where(function ($q) use ($search) { + $q->where('document_no', 'like', "%{$search}%") + ->orWhere('title', 'like', "%{$search}%"); + }); + } + + return $query->orderByDesc('id') + ->paginate($params['per_page'] ?? 20); + } + + /** + * 문서 상세 조회 + */ + public function show(int $id): Document + { + $document = Document::with([ + 'template.approvalLines', + 'template.sections.items', + 'template.columns', + 'approvals.user:id,name', + 'data', + 'creator:id,name', + ])->find($id); + + if (!$document || $document->tenant_id !== $this->tenantId()) { + throw new NotFoundHttpException(__('error.not_found')); + } + + return $document; + } + + /** + * 문서 생성 + */ + public function create(array $data): Document + { + $document = Document::create([ + 'tenant_id' => $this->tenantId(), + 'template_id' => $data['template_id'], + 'document_no' => $this->generateDocumentNo($data['template_id']), + 'title' => $data['title'], + 'status' => Document::STATUS_DRAFT, + 'linkable_type' => $data['linkable_type'] ?? null, + 'linkable_id' => $data['linkable_id'] ?? null, + 'created_by' => $this->apiUserId(), + ]); + + // 결재선 생성 + if (!empty($data['approvers'])) { + foreach ($data['approvers'] as $step => $approver) { + DocumentApproval::create([ + 'document_id' => $document->id, + 'user_id' => $approver['user_id'], + 'step' => $step + 1, + 'role' => $approver['role'], + 'status' => DocumentApproval::STATUS_PENDING, + ]); + } + } + + // 데이터 저장 + if (!empty($data['data'])) { + foreach ($data['data'] as $item) { + DocumentData::create([ + 'document_id' => $document->id, + 'section_id' => $item['section_id'] ?? null, + 'column_id' => $item['column_id'] ?? null, + 'row_index' => $item['row_index'] ?? 0, + 'field_key' => $item['field_key'], + 'field_value' => $item['field_value'], + ]); + } + } + + return $document->fresh(['approvals', 'data']); + } + + /** + * 결재 요청 (DRAFT → PENDING) + */ + public function submit(int $id): Document + { + $document = $this->show($id); + + if (!$document->canSubmit()) { + throw new BadRequestHttpException(__('error.invalid_status')); + } + + $document->update([ + 'status' => Document::STATUS_PENDING, + 'submitted_at' => now(), + ]); + + return $document->fresh(); + } + + /** + * 결재 승인 + */ + public function approve(int $id, ?string $comment = null): Document + { + $document = $this->show($id); + $userId = $this->apiUserId(); + + // 현재 사용자의 결재 단계 찾기 + $approval = $document->approvals + ->where('user_id', $userId) + ->where('status', DocumentApproval::STATUS_PENDING) + ->first(); + + if (!$approval) { + throw new BadRequestHttpException(__('error.not_your_turn')); + } + + $approval->update([ + 'status' => DocumentApproval::STATUS_APPROVED, + 'comment' => $comment, + 'acted_at' => now(), + ]); + + // 모든 결재 완료 확인 + $allApproved = $document->approvals() + ->where('status', '!=', DocumentApproval::STATUS_APPROVED) + ->doesntExist(); + + if ($allApproved) { + $document->update([ + 'status' => Document::STATUS_APPROVED, + 'completed_at' => now(), + ]); + } + + return $document->fresh(['approvals']); + } + + /** + * 결재 반려 + */ + public function reject(int $id, string $comment): Document + { + $document = $this->show($id); + $userId = $this->apiUserId(); + + $approval = $document->approvals + ->where('user_id', $userId) + ->where('status', DocumentApproval::STATUS_PENDING) + ->first(); + + if (!$approval) { + throw new BadRequestHttpException(__('error.not_your_turn')); + } + + $approval->update([ + 'status' => DocumentApproval::STATUS_REJECTED, + 'comment' => $comment, + 'acted_at' => now(), + ]); + + $document->update([ + 'status' => Document::STATUS_REJECTED, + 'completed_at' => now(), + ]); + + return $document->fresh(['approvals']); + } + + /** + * 문서번호 생성 + */ + private function generateDocumentNo(int $templateId): string + { + $prefix = 'DOC'; + $date = now()->format('Ymd'); + $count = Document::where('tenant_id', $this->tenantId()) + ->whereDate('created_at', today()) + ->count() + 1; + + return sprintf('%s-%s-%04d', $prefix, $date, $count); + } +} +``` + +### 3.4 Controller 코드 템플릿 + +```php + $this->service->list($request->all()), + __('message.fetched') + ); + } + + public function show(int $id): JsonResponse + { + return ApiResponse::handle( + fn () => $this->service->show($id), + __('message.fetched') + ); + } + + public function store(CreateDocumentRequest $request): JsonResponse + { + return ApiResponse::handle( + fn () => $this->service->create($request->validated()), + __('message.created') + ); + } + + public function submit(int $id): JsonResponse + { + return ApiResponse::handle( + fn () => $this->service->submit($id), + __('message.document.submitted') + ); + } + + public function approve(int $id, ApproveDocumentRequest $request): JsonResponse + { + return ApiResponse::handle( + fn () => $this->service->approve($id, $request->comment), + __('message.document.approved') + ); + } + + public function reject(int $id, RejectDocumentRequest $request): JsonResponse + { + return ApiResponse::handle( + fn () => $this->service->reject($id, $request->comment), + __('message.document.rejected') + ); + } +} +``` + +### 3.5 API Routes + +```php +// api/routes/api.php 에 추가 + +Route::prefix('v1')->middleware(['auth.apikey'])->group(function () { + // ... 기존 라우트 ... + + // 문서 관리 + Route::prefix('documents')->middleware(['auth:sanctum'])->group(function () { + Route::get('/', [DocumentController::class, 'index']); + Route::post('/', [DocumentController::class, 'store']); + Route::get('/{id}', [DocumentController::class, 'show']); + Route::put('/{id}', [DocumentController::class, 'update']); + Route::delete('/{id}', [DocumentController::class, 'destroy']); + + // 결재 + Route::post('/{id}/submit', [DocumentController::class, 'submit']); + Route::post('/{id}/approve', [DocumentController::class, 'approve']); + Route::post('/{id}/reject', [DocumentController::class, 'reject']); + Route::post('/{id}/cancel', [DocumentController::class, 'cancel']); + }); +}); +``` + +### 3.6 문서 상태 흐름 + +``` +┌──────────────────────────────────────────────────────────────┐ +│ │ +│ DRAFT ──submit──> PENDING ──approve──> APPROVED │ +│ │ │ │ +│ │ │──reject──> REJECTED │ +│ │ │ │ │ +│ │ │──cancel──> CANCELLED │ +│ │ │ │ +│ └──────────────────<──edit─────┘ (반려 시 수정 후 재요청) │ +│ │ +└──────────────────────────────────────────────────────────────┘ +``` + +--- + +## 4. 기존 코드 참조 (인라인) + +### 4.1 기존 템플릿 테이블 구조 + +``` +document_templates (기존) +├── id, tenant_id, name, category, title +├── company_name, company_address, company_contact +├── footer_remark_label, footer_judgement_label +├── footer_judgement_options (JSON) +└── is_active, timestamps, soft_deletes + +document_template_approval_lines (기존) +├── id, template_id, name, dept, role, sort_order +└── timestamps + +document_template_sections (기존) +├── id, template_id, title, image_path, sort_order +└── timestamps + +document_template_section_items (기존) +├── id, section_id, category, item, standard +├── method, frequency, regulation, sort_order +└── timestamps + +document_template_columns (기존) +├── id, template_id, label, width, column_type +├── group_name, sub_labels (JSON), sort_order +└── timestamps +``` + +### 4.2 API Service 기본 클래스 + +```php +// api/app/Services/Service.php (기존) +abstract class Service +{ + protected function tenantIdOrNull(): ?int; // 테넌트 ID (없으면 null) + protected function tenantId(): int; // 테넌트 ID (없으면 400 예외) + protected function apiUserId(): int; // 사용자 ID (없으면 401 예외) +} +``` + +### 4.3 API Response 헬퍼 + +```php +// api/app/Helpers/ApiResponse.php (기존) +use App\Helpers\ApiResponse; + +// 성공 응답 +ApiResponse::success($data, $message, $debug, $statusCode); + +// 에러 응답 +ApiResponse::error($message, $code, $error); + +// 컨트롤러에서 사용 (권장) +ApiResponse::handle(fn () => $this->service->method(), __('message.xxx')); +``` + +### 4.4 React 결재선 컴포넌트 위치 + +``` +react/src/components/approval/DocumentCreate/ApprovalLineSection.tsx +- 직원 목록에서 결재자 선택 +- getEmployees() 호출로 직원 목록 조회 +- ApprovalPerson[] 형태로 결재선 관리 +``` + +--- + +## 5. 작업 절차 + +### Step 1: 마이그레이션 생성 (⚠️ 컨펌 필요) + +```bash +# 1. 마이그레이션 파일 생성 +docker exec sam-api-1 php artisan make:migration create_documents_table + +# 2. 위 3.1 스키마 코드 붙여넣기 + +# 3. 마이그레이션 실행 +docker exec sam-api-1 php artisan migrate +``` + +### Step 2: 모델 생성 + +```bash +# Documents 폴더 생성 후 모델 파일 생성 +mkdir -p api/app/Models/Documents + +# 위 3.2 모델 코드 각각 생성 +``` + +### Step 3: Service & Controller + +```bash +# Service 생성 +# api/app/Services/DocumentService.php + +# Controller 생성 +# api/app/Http/Controllers/Api/V1/DocumentController.php + +# Routes 추가 +# api/routes/api.php +``` + +### Step 4: MNG 화면 + +```bash +# mng/app/Models/Document.php +# mng/app/Http/Controllers/DocumentController.php +# mng/resources/views/documents/*.blade.php +``` + +### Step 5: React 연동 + +```bash +# react/src/components/document-system/DocumentForm/ +# react/src/components/document-system/actions.ts +``` + +--- + +## 6. 컨펌 대기 목록 + +| # | 항목 | 변경 내용 | 영향 범위 | 상태 | +|---|------|----------|----------|------| +| 1 | DB 스키마 | 4개 테이블 신규 생성 | api/database | ⏳ 대기 | + +--- + +## 7. 변경 이력 + +| 날짜 | 항목 | 변경 내용 | 파일 | 승인 | +|------|------|----------|------|------| +| 2025-01-28 | - | 계획 문서 작성 | - | - | +| 2025-01-28 | - | 자기완결성 보완 | - | - | +| 2026-01-28 | Phase 1.1 | 마이그레이션 파일 생성 및 실행 | `2026_01_28_200000_create_documents_table.php` | ✅ | +| 2026-01-28 | Phase 1.2 | 모델 생성 (Document, DocumentApproval, DocumentData, DocumentAttachment) | `api/app/Models/Documents/` | ✅ | + +--- + +## 8. 검증 결과 + +> 작업 완료 후 이 섹션에 검증 결과 추가 + +### 8.1 테스트 케이스 + +| 시나리오 | 예상 결과 | 실제 결과 | 상태 | +|----------|----------|----------|------| +| 마이그레이션 실행 | 4개 테이블 생성 | 4개 테이블 생성 (524ms) | ✅ | +| 문서 생성 API | 201 Created | - | ⏳ | +| 결재 요청 | DRAFT → PENDING | - | ⏳ | +| 결재 승인 | PENDING → APPROVED | - | ⏳ | +| 결재 반려 | PENDING → REJECTED | - | ⏳ | + +--- + +## 9. 자기완결성 점검 결과 + +### 9.1 체크리스트 검증 + +| # | 검증 항목 | 상태 | 비고 | +|---|----------|:----:|------| +| 1 | 작업 목적이 명확한가? | ✅ | 1.1 배경 섹션 | +| 2 | 성공 기준이 정의되어 있는가? | ✅ | 8.1 테스트 케이스 | +| 3 | 작업 범위가 구체적인가? | ✅ | 2. 대상 범위 (파일 경로 포함) | +| 4 | 의존성이 명시되어 있는가? | ✅ | 0.1 전제 조건 | +| 5 | 참고 파일 경로가 정확한가? | ✅ | 4. 기존 코드 참조 | +| 6 | 단계별 절차가 실행 가능한가? | ✅ | 5. 작업 절차 | +| 7 | 검증 방법이 명시되어 있는가? | ✅ | 8. 검증 결과 | +| 8 | 모호한 표현이 없는가? | ✅ | 코드 템플릿 제공 | + +### 9.2 새 세션 시뮬레이션 테스트 + +| 질문 | 답변 가능 | 참조 섹션 | +|------|:--------:|----------| +| Q1. 이 작업의 목적은 무엇인가? | ✅ | 1.1 배경 | +| Q2. 어디서부터 시작해야 하는가? | ✅ | 0.4 작업 순서, 5. 작업 절차 | +| Q3. 어떤 파일을 수정해야 하는가? | ✅ | 2. 대상 범위 | +| Q4. 작업 완료 확인 방법은? | ✅ | 8. 검증 결과 | +| Q5. 막혔을 때 참고 문서는? | ✅ | 4. 기존 코드 참조 | + +**결과**: 5/5 통과 → ✅ 자기완결성 확보 + +--- + +*이 문서는 /plan 스킬로 생성되었습니다.* \ No newline at end of file