30 KiB
30 KiB
문서 관리 시스템 개발 계획
작성일: 2025-01-28 목적: 문서 템플릿 기반 실제 문서 작성/결재/관리 시스템 상태: 📋 계획 수립
📍 현재 진행 상태
| 항목 | 내용 |
|---|---|
| 마지막 완료 작업 | Phase 1.8 - Route 등록 및 Swagger 문서 ✅ |
| 다음 작업 | Phase 2 - MNG 관리자 패널 구현 |
| 진행률 | 6/12 (50%) |
| 보류 항목 | 결재 워크플로우 (submit/approve/reject/cancel) - 기존 시스템 연동 필요 |
| 마지막 업데이트 | 2026-01-28 |
0. 빠른 시작 가이드
0.1 전제 조건
# 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 작업 시작 명령어
# 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
// api/database/migrations/2026_01_29_000000_create_documents_table.php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
// 실제 문서
Schema::create('documents', function (Blueprint $table) {
$table->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
// api/app/Models/Documents/Document.php
namespace App\Models\Documents;
use App\Traits\BelongsToTenant;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\MorphTo;
use Illuminate\Database\Eloquent\SoftDeletes;
class Document extends Model
{
use BelongsToTenant, SoftDeletes;
protected $fillable = [
'tenant_id',
'template_id',
'document_no',
'title',
'status',
'linkable_type',
'linkable_id',
'created_by',
'submitted_at',
'completed_at',
];
protected $casts = [
'submitted_at' => '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
// api/app/Models/Documents/DocumentApproval.php
namespace App\Models\Documents;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class DocumentApproval extends Model
{
protected $fillable = [
'document_id',
'user_id',
'step',
'role',
'status',
'comment',
'acted_at',
];
protected $casts = [
'step' => '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
// api/app/Models/Documents/DocumentData.php
namespace App\Models\Documents;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class DocumentData extends Model
{
protected $table = 'document_data';
protected $fillable = [
'document_id',
'section_id',
'column_id',
'row_index',
'field_key',
'field_value',
];
protected $casts = [
'row_index' => 'integer',
];
public function document(): BelongsTo
{
return $this->belongsTo(Document::class);
}
}
3.3 Service 코드 템플릿
<?php
// api/app/Services/DocumentService.php
namespace App\Services;
use App\Models\Documents\Document;
use App\Models\Documents\DocumentApproval;
use App\Models\Documents\DocumentData;
use Illuminate\Pagination\LengthAwarePaginator;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
class DocumentService extends Service
{
/**
* 문서 목록 조회
*/
public function list(array $params): LengthAwarePaginator
{
$query = Document::query()
->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
// api/app/Http/Controllers/Api/V1/DocumentController.php
namespace App\Http\Controllers\Api\V1;
use App\Helpers\ApiResponse;
use App\Http\Controllers\Controller;
use App\Http\Requests\Document\CreateDocumentRequest;
use App\Http\Requests\Document\ApproveDocumentRequest;
use App\Http\Requests\Document\RejectDocumentRequest;
use App\Services\DocumentService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class DocumentController extends Controller
{
public function __construct(
private readonly DocumentService $service
) {}
public function index(Request $request): JsonResponse
{
return ApiResponse::handle(
fn () => $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
// 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 기본 클래스
// 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 헬퍼
// 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: 마이그레이션 생성 (⚠️ 컨펌 필요)
# 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: 모델 생성
# Documents 폴더 생성 후 모델 파일 생성
mkdir -p api/app/Models/Documents
# 위 3.2 모델 코드 각각 생성
Step 3: Service & Controller
# Service 생성
# api/app/Services/DocumentService.php
# Controller 생성
# api/app/Http/Controllers/Api/V1/DocumentController.php
# Routes 추가
# api/routes/api.php
Step 4: MNG 화면
# mng/app/Models/Document.php
# mng/app/Http/Controllers/DocumentController.php
# mng/resources/views/documents/*.blade.php
Step 5: React 연동
# 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 스킬로 생성되었습니다.