- 마이그레이션 생성: quotes, quote_items, quote_revisions 테이블 - Model 생성: Quote, QuoteItem, QuoteRevision - BelongsToTenant, SoftDeletes 트레이트 적용 - 상태 관리 메서드 및 스코프 구현 - 개발 계획서 작성 및 진행 상황 문서화
165 lines
8.3 KiB
PHP
165 lines
8.3 KiB
PHP
<?php
|
||
|
||
use Illuminate\Database\Migrations\Migration;
|
||
use Illuminate\Database\Schema\Blueprint;
|
||
use Illuminate\Support\Facades\Schema;
|
||
|
||
return new class extends Migration
|
||
{
|
||
/**
|
||
* Run the migrations.
|
||
*/
|
||
public function up(): void
|
||
{
|
||
// 1. 견적 마스터 테이블
|
||
Schema::create('quotes', function (Blueprint $table) {
|
||
$table->id();
|
||
$table->foreignId('tenant_id')->constrained()->comment('테넌트 ID');
|
||
|
||
// 기본 정보
|
||
$table->string('quote_number', 50)->comment('견적번호 (예: KD-SC-251204-01)');
|
||
$table->date('registration_date')->comment('등록일');
|
||
$table->date('receipt_date')->nullable()->comment('접수일');
|
||
$table->string('author', 100)->nullable()->comment('작성자');
|
||
|
||
// 발주처 정보
|
||
$table->foreignId('client_id')->nullable()->comment('거래처 ID (FK)');
|
||
$table->string('client_name', 100)->nullable()->comment('거래처명 (직접입력 대응)');
|
||
$table->string('manager', 100)->nullable()->comment('담당자');
|
||
$table->string('contact', 50)->nullable()->comment('연락처');
|
||
|
||
// 현장 정보
|
||
$table->unsignedBigInteger('site_id')->nullable()->comment('현장 ID');
|
||
$table->string('site_name', 200)->nullable()->comment('현장명');
|
||
$table->string('site_code', 50)->nullable()->comment('현장코드');
|
||
|
||
// 제품 정보
|
||
$table->enum('product_category', ['SCREEN', 'STEEL'])->comment('제품 카테고리');
|
||
$table->unsignedBigInteger('product_id')->nullable()->comment('선택된 제품 ID');
|
||
$table->string('product_code', 50)->nullable()->comment('제품코드');
|
||
$table->string('product_name', 200)->nullable()->comment('제품명');
|
||
|
||
// 규격 정보
|
||
$table->unsignedInteger('open_size_width')->nullable()->comment('오픈사이즈 폭 (mm)');
|
||
$table->unsignedInteger('open_size_height')->nullable()->comment('오픈사이즈 높이 (mm)');
|
||
$table->unsignedInteger('quantity')->default(1)->comment('수량');
|
||
$table->string('unit_symbol', 10)->nullable()->comment('부호');
|
||
$table->string('floors', 20)->nullable()->comment('층수');
|
||
|
||
// 금액 정보
|
||
$table->decimal('material_cost', 15, 2)->default(0)->comment('재료비 합계');
|
||
$table->decimal('labor_cost', 15, 2)->default(0)->comment('노무비');
|
||
$table->decimal('install_cost', 15, 2)->default(0)->comment('설치비');
|
||
$table->decimal('subtotal', 15, 2)->default(0)->comment('소계');
|
||
$table->decimal('discount_rate', 5, 2)->default(0)->comment('할인율 (%)');
|
||
$table->decimal('discount_amount', 15, 2)->default(0)->comment('할인금액');
|
||
$table->decimal('total_amount', 15, 2)->default(0)->comment('최종 금액');
|
||
|
||
// 상태 관리
|
||
$table->enum('status', ['draft', 'sent', 'approved', 'rejected', 'finalized', 'converted'])->default('draft')->comment('상태');
|
||
$table->unsignedInteger('current_revision')->default(0)->comment('현재 수정 차수');
|
||
$table->boolean('is_final')->default(false)->comment('최종확정 여부');
|
||
$table->dateTime('finalized_at')->nullable()->comment('확정일시');
|
||
$table->foreignId('finalized_by')->nullable()->comment('확정자 ID');
|
||
|
||
// 기타 정보
|
||
$table->date('completion_date')->nullable()->comment('납기일');
|
||
$table->text('remarks')->nullable()->comment('비고');
|
||
$table->text('memo')->nullable()->comment('메모');
|
||
$table->text('notes')->nullable()->comment('특이사항');
|
||
|
||
// 자동산출 입력값 저장
|
||
$table->json('calculation_inputs')->nullable()->comment('자동산출에 사용된 입력값');
|
||
|
||
// 감사
|
||
$table->foreignId('created_by')->nullable()->comment('생성자');
|
||
$table->foreignId('updated_by')->nullable()->comment('수정자');
|
||
$table->foreignId('deleted_by')->nullable()->comment('삭제자');
|
||
$table->timestamps();
|
||
$table->softDeletes();
|
||
|
||
// 인덱스
|
||
$table->index('tenant_id', 'idx_quotes_tenant_id');
|
||
$table->index('quote_number', 'idx_quotes_quote_number');
|
||
$table->index('status', 'idx_quotes_status');
|
||
$table->index('client_id', 'idx_quotes_client_id');
|
||
$table->index('product_category', 'idx_quotes_product_category');
|
||
$table->index('registration_date', 'idx_quotes_registration_date');
|
||
$table->unique(['tenant_id', 'quote_number', 'deleted_at'], 'uq_tenant_quote_number');
|
||
});
|
||
|
||
// 2. 견적 품목 테이블
|
||
Schema::create('quote_items', function (Blueprint $table) {
|
||
$table->id();
|
||
$table->foreignId('quote_id')->constrained()->cascadeOnDelete()->comment('견적 ID');
|
||
$table->foreignId('tenant_id')->constrained()->comment('테넌트 ID');
|
||
|
||
// 품목 정보
|
||
$table->unsignedBigInteger('item_id')->nullable()->comment('품목마스터 ID');
|
||
$table->string('item_code', 50)->comment('품목코드');
|
||
$table->string('item_name', 200)->comment('품명');
|
||
$table->string('specification', 100)->nullable()->comment('규격');
|
||
$table->string('unit', 20)->comment('단위');
|
||
|
||
// 수량/금액
|
||
$table->decimal('base_quantity', 15, 4)->default(1)->comment('기본수량');
|
||
$table->decimal('calculated_quantity', 15, 4)->default(1)->comment('계산된 수량');
|
||
$table->decimal('unit_price', 15, 2)->default(0)->comment('단가');
|
||
$table->decimal('total_price', 15, 2)->default(0)->comment('금액 (수량 × 단가)');
|
||
|
||
// 수식 정보
|
||
$table->string('formula', 500)->nullable()->comment('수식');
|
||
$table->string('formula_result', 200)->nullable()->comment('수식 계산 결과 표시');
|
||
$table->string('formula_source', 100)->nullable()->comment('수식 출처');
|
||
$table->string('formula_category', 50)->nullable()->comment('수식 카테고리');
|
||
$table->string('data_source', 200)->nullable()->comment('데이터 출처');
|
||
|
||
// 기타
|
||
$table->date('delivery_date')->nullable()->comment('품목별 납기일');
|
||
$table->text('note')->nullable()->comment('비고');
|
||
$table->unsignedInteger('sort_order')->default(0)->comment('정렬순서');
|
||
|
||
$table->timestamps();
|
||
|
||
// 인덱스
|
||
$table->index('quote_id', 'idx_quote_items_quote_id');
|
||
$table->index('tenant_id', 'idx_quote_items_tenant_id');
|
||
$table->index('item_code', 'idx_quote_items_item_code');
|
||
$table->index('sort_order', 'idx_quote_items_sort_order');
|
||
});
|
||
|
||
// 3. 견적 수정 이력 테이블
|
||
Schema::create('quote_revisions', function (Blueprint $table) {
|
||
$table->id();
|
||
$table->foreignId('quote_id')->constrained()->cascadeOnDelete()->comment('견적 ID');
|
||
$table->foreignId('tenant_id')->constrained()->comment('테넌트 ID');
|
||
|
||
$table->unsignedInteger('revision_number')->comment('수정 차수');
|
||
$table->date('revision_date')->comment('수정일');
|
||
$table->foreignId('revision_by')->comment('수정자 ID');
|
||
$table->string('revision_by_name', 100)->nullable()->comment('수정자 이름');
|
||
$table->text('revision_reason')->nullable()->comment('수정 사유');
|
||
|
||
// 이전 버전 데이터 (JSON 스냅샷)
|
||
$table->json('previous_data')->comment('수정 전 견적 전체 데이터');
|
||
|
||
$table->timestamp('created_at')->nullable();
|
||
|
||
// 인덱스
|
||
$table->index('quote_id', 'idx_quote_revisions_quote_id');
|
||
$table->index('tenant_id', 'idx_quote_revisions_tenant_id');
|
||
$table->index('revision_number', 'idx_quote_revisions_revision_number');
|
||
});
|
||
}
|
||
|
||
/**
|
||
* Reverse the migrations.
|
||
*/
|
||
public function down(): void
|
||
{
|
||
Schema::dropIfExists('quote_revisions');
|
||
Schema::dropIfExists('quote_items');
|
||
Schema::dropIfExists('quotes');
|
||
}
|
||
};
|