From f76fd2f865b480a35688cfdfdee65568e5b39d5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B6=8C=ED=98=81=EC=84=B1?= Date: Wed, 28 Jan 2026 19:22:05 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EB=AC=B8=EC=84=9C=20=EA=B4=80=EB=A6=AC?= =?UTF-8?q?=20=EC=8B=9C=EC=8A=A4=ED=85=9C=20DB=20=EC=8A=A4=ED=82=A4?= =?UTF-8?q?=EB=A7=88=20=EA=B5=AC=ED=98=84=20(Phase=201.1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - documents 테이블 생성 (문서 기본 정보, 상태, 다형성 연결) - document_approvals 테이블 생성 (결재 처리) - document_data 테이블 생성 (EAV 패턴 데이터 저장) - document_attachments 테이블 생성 (파일 첨부) - SAM 규칙 준수 (tenant_id, 감사 컬럼, softDeletes, comment) Co-Authored-By: Claude Opus 4.5 --- ...26_01_28_200000_create_documents_table.php | 115 ++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 database/migrations/2026_01_28_200000_create_documents_table.php diff --git a/database/migrations/2026_01_28_200000_create_documents_table.php b/database/migrations/2026_01_28_200000_create_documents_table.php new file mode 100644 index 0000000..5beae8b --- /dev/null +++ b/database/migrations/2026_01_28_200000_create_documents_table.php @@ -0,0 +1,115 @@ +id()->comment('ID'); + $table->foreignId('tenant_id')->constrained()->cascadeOnDelete()->comment('테넌트 ID'); + $table->foreignId('template_id')->constrained('document_templates')->comment('템플릿 ID'); + + // 문서 정보 + $table->string('document_no', 50)->comment('문서번호'); + $table->string('title', 255)->comment('문서 제목'); + $table->enum('status', ['DRAFT', 'PENDING', 'APPROVED', 'REJECTED', 'CANCELLED']) + ->default('DRAFT')->comment('상태 (DRAFT:임시저장, PENDING:결재중, APPROVED:승인, REJECTED:반려, CANCELLED:취소)'); + + // 연결 정보 (다형성) + $table->string('linkable_type', 100)->nullable()->comment('연결 모델 타입'); + $table->unsignedBigInteger('linkable_id')->nullable()->comment('연결 모델 ID'); + + // 결재 정보 + $table->timestamp('submitted_at')->nullable()->comment('결재 요청일'); + $table->timestamp('completed_at')->nullable()->comment('결재 완료일'); + + // 감사 컬럼 + $table->foreignId('created_by')->nullable()->constrained('users')->comment('생성자 ID'); + $table->foreignId('updated_by')->nullable()->constrained('users')->comment('수정자 ID'); + $table->foreignId('deleted_by')->nullable()->constrained('users')->comment('삭제자 ID'); + $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()->comment('ID'); + $table->foreignId('document_id')->constrained()->cascadeOnDelete()->comment('문서 ID'); + $table->foreignId('user_id')->constrained()->comment('결재자 ID'); + + $table->unsignedTinyInteger('step')->default(1)->comment('결재 순서'); + $table->string('role', 50)->comment('역할 (작성/검토/승인)'); + $table->enum('status', ['PENDING', 'APPROVED', 'REJECTED']) + ->default('PENDING')->comment('상태 (PENDING:대기, APPROVED:승인, REJECTED:반려)'); + + $table->text('comment')->nullable()->comment('결재 의견'); + $table->timestamp('acted_at')->nullable()->comment('결재 처리일'); + + // 감사 컬럼 + $table->foreignId('created_by')->nullable()->constrained('users')->comment('생성자 ID'); + $table->foreignId('updated_by')->nullable()->constrained('users')->comment('수정자 ID'); + $table->timestamps(); + + // 인덱스 + $table->index(['document_id', 'step']); + $table->index(['user_id', 'status']); + }); + + // 문서 데이터 (EAV 패턴) + Schema::create('document_data', function (Blueprint $table) { + $table->id()->comment('ID'); + $table->foreignId('document_id')->constrained()->cascadeOnDelete()->comment('문서 ID'); + + $table->unsignedBigInteger('section_id')->nullable()->comment('섹션 ID (document_template_sections 참조)'); + $table->unsignedBigInteger('column_id')->nullable()->comment('컬럼 ID (document_template_columns 참조)'); + $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']); + $table->index(['document_id', 'field_key']); + }); + + // 문서 첨부파일 + Schema::create('document_attachments', function (Blueprint $table) { + $table->id()->comment('ID'); + $table->foreignId('document_id')->constrained()->cascadeOnDelete()->comment('문서 ID'); + $table->foreignId('file_id')->constrained('files')->comment('파일 ID'); + + $table->string('attachment_type', 50)->default('general')->comment('첨부 유형 (general, signature, image 등)'); + $table->string('description', 255)->nullable()->comment('설명'); + + // 감사 컬럼 + $table->foreignId('created_by')->nullable()->constrained('users')->comment('생성자 ID'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('document_attachments'); + Schema::dropIfExists('document_data'); + Schema::dropIfExists('document_approvals'); + Schema::dropIfExists('documents'); + } +};