feat: [QMS] 1일차 기준/매뉴얼 심사 백엔드 구현 (Phase 2)

- 마이그레이션: audit_checklists, audit_checklist_categories, audit_checklist_items, audit_standard_documents (4테이블)
- 모델 4개: AuditChecklist, AuditChecklistCategory, AuditChecklistItem, AuditStandardDocument
- AuditChecklistService: CRUD, 완료처리, 항목 토글(lockForUpdate), 기준 문서 연결/해제, 카테고리+항목 일괄 동기화
- AuditChecklistController: 9개 엔드포인트
- FormRequest 2개: Store(카테고리+항목 중첩 검증), Update
- 라우트 9개 등록 (/api/v1/qms/checklists, checklist-items)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-10 16:41:20 +09:00
parent 334e39d2de
commit 30c2484440
10 changed files with 851 additions and 0 deletions

View File

@@ -0,0 +1,90 @@
<?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
{
// 1) 심사 점검표 마스터
Schema::create('audit_checklists', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('tenant_id')->comment('테넌트ID');
$table->smallInteger('year')->unsigned()->comment('연도');
$table->tinyInteger('quarter')->unsigned()->comment('분기 1~4');
$table->string('type', 30)->default('standard_manual')->comment('심사유형');
$table->string('status', 20)->default('draft')->comment('draft/in_progress/completed');
$table->json('options')->nullable()->comment('추가 설정');
$table->unsignedBigInteger('created_by')->nullable()->comment('생성자');
$table->unsignedBigInteger('updated_by')->nullable()->comment('수정자');
$table->unsignedBigInteger('deleted_by')->nullable()->comment('삭제자');
$table->timestamps();
$table->softDeletes();
$table->unique(['tenant_id', 'year', 'quarter', 'type'], 'uq_audit_checklists_tenant_period');
$table->index(['tenant_id', 'status'], 'idx_audit_checklists_status');
$table->foreign('tenant_id')->references('id')->on('tenants');
});
// 2) 점검표 카테고리
Schema::create('audit_checklist_categories', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('tenant_id')->comment('테넌트ID');
$table->unsignedBigInteger('checklist_id')->comment('점검표ID');
$table->string('title', 200)->comment('카테고리명');
$table->unsignedInteger('sort_order')->default(0)->comment('정렬순서');
$table->json('options')->nullable();
$table->timestamps();
$table->index(['checklist_id', 'sort_order'], 'idx_audit_categories_sort');
$table->foreign('checklist_id')->references('id')->on('audit_checklists')->onDelete('cascade');
});
// 3) 점검표 세부 항목
Schema::create('audit_checklist_items', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('tenant_id')->comment('테넌트ID');
$table->unsignedBigInteger('category_id')->comment('카테고리ID');
$table->string('name', 200)->comment('항목명');
$table->text('description')->nullable()->comment('항목 설명');
$table->boolean('is_completed')->default(false)->comment('완료여부');
$table->timestamp('completed_at')->nullable()->comment('완료일시');
$table->unsignedBigInteger('completed_by')->nullable()->comment('완료처리자');
$table->unsignedInteger('sort_order')->default(0)->comment('정렬순서');
$table->json('options')->nullable();
$table->timestamps();
$table->index(['category_id', 'sort_order'], 'idx_audit_items_sort');
$table->index(['category_id', 'is_completed'], 'idx_audit_items_completed');
$table->foreign('category_id')->references('id')->on('audit_checklist_categories')->onDelete('cascade');
$table->foreign('completed_by')->references('id')->on('users')->onDelete('set null');
});
// 4) 기준 문서 연결
Schema::create('audit_standard_documents', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('tenant_id')->comment('테넌트ID');
$table->unsignedBigInteger('checklist_item_id')->comment('점검항목ID');
$table->string('title', 200)->comment('문서명');
$table->string('version', 20)->nullable()->comment('버전');
$table->date('date')->nullable()->comment('시행일');
$table->unsignedBigInteger('document_id')->nullable()->comment('EAV 파일 FK');
$table->json('options')->nullable();
$table->timestamps();
$table->index('checklist_item_id', 'idx_audit_std_docs_item');
$table->foreign('checklist_item_id')->references('id')->on('audit_checklist_items')->onDelete('cascade');
$table->foreign('document_id')->references('id')->on('documents')->onDelete('set null');
});
}
public function down(): void
{
Schema::dropIfExists('audit_standard_documents');
Schema::dropIfExists('audit_checklist_items');
Schema::dropIfExists('audit_checklist_categories');
Schema::dropIfExists('audit_checklists');
}
};