merge: origin/develop 병합
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
<?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
|
||||
{
|
||||
// 문서양식 기본 테이블
|
||||
Schema::create('document_templates', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('tenant_id')->constrained()->cascadeOnDelete();
|
||||
$table->string('name', 100)->comment('양식명');
|
||||
$table->string('category', 50)->nullable()->comment('분류 (품질, 생산 등)');
|
||||
$table->string('title', 200)->nullable()->comment('문서 제목');
|
||||
$table->string('company_name', 100)->nullable()->comment('회사명');
|
||||
$table->string('company_address', 255)->nullable()->comment('회사 주소');
|
||||
$table->string('company_contact', 100)->nullable()->comment('회사 연락처');
|
||||
$table->string('footer_remark_label', 50)->default('부적합 내용')->comment('비고 라벨');
|
||||
$table->string('footer_judgement_label', 50)->default('종합판정')->comment('판정 라벨');
|
||||
$table->json('footer_judgement_options')->nullable()->comment('판정 옵션 (적합/부적합 등)');
|
||||
$table->boolean('is_active')->default(true)->comment('활성 여부');
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
|
||||
$table->index(['tenant_id', 'category']);
|
||||
$table->index(['tenant_id', 'is_active']);
|
||||
});
|
||||
|
||||
// 결재라인
|
||||
Schema::create('document_template_approval_lines', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('template_id')->constrained('document_templates')->cascadeOnDelete();
|
||||
$table->string('name', 50)->comment('결재 단계명 (작성, 검토, 승인)');
|
||||
$table->string('dept', 50)->nullable()->comment('부서');
|
||||
$table->string('role', 50)->nullable()->comment('직책/담당자');
|
||||
$table->unsignedSmallInteger('sort_order')->default(0);
|
||||
$table->timestamps();
|
||||
|
||||
$table->index(['template_id', 'sort_order']);
|
||||
});
|
||||
|
||||
// 기본 필드 (품명, LOT NO 등)
|
||||
Schema::create('document_template_basic_fields', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('template_id')->constrained('document_templates')->cascadeOnDelete();
|
||||
$table->string('label', 50)->comment('필드 라벨');
|
||||
$table->string('field_type', 20)->default('text')->comment('필드 타입 (text, date 등)');
|
||||
$table->string('default_value', 255)->nullable()->comment('기본값');
|
||||
$table->unsignedSmallInteger('sort_order')->default(0);
|
||||
$table->timestamps();
|
||||
|
||||
$table->index(['template_id', 'sort_order']);
|
||||
});
|
||||
|
||||
// 검사 기준서 섹션
|
||||
Schema::create('document_template_sections', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('template_id')->constrained('document_templates')->cascadeOnDelete();
|
||||
$table->string('title', 100)->comment('섹션 제목 (가이드레일, 연기차단재 등)');
|
||||
$table->string('image_path', 255)->nullable()->comment('도해 이미지 경로');
|
||||
$table->unsignedSmallInteger('sort_order')->default(0);
|
||||
$table->timestamps();
|
||||
|
||||
$table->index(['template_id', 'sort_order']);
|
||||
});
|
||||
|
||||
// 검사 기준서 섹션 항목
|
||||
Schema::create('document_template_section_items', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('section_id')->constrained('document_template_sections')->cascadeOnDelete();
|
||||
$table->string('category', 50)->nullable()->comment('구분 (겉모양, 치수 등)');
|
||||
$table->string('item', 100)->comment('검사항목');
|
||||
$table->string('standard', 255)->nullable()->comment('검사기준');
|
||||
$table->string('method', 50)->nullable()->comment('검사방법');
|
||||
$table->string('frequency', 50)->nullable()->comment('검사주기');
|
||||
$table->string('regulation', 100)->nullable()->comment('관련규정');
|
||||
$table->unsignedSmallInteger('sort_order')->default(0);
|
||||
$table->timestamps();
|
||||
|
||||
$table->index(['section_id', 'sort_order']);
|
||||
});
|
||||
|
||||
// 검사 데이터 테이블 컬럼 설정
|
||||
Schema::create('document_template_columns', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('template_id')->constrained('document_templates')->cascadeOnDelete();
|
||||
$table->string('label', 50)->comment('컬럼명');
|
||||
$table->string('width', 20)->default('100px')->comment('컬럼 너비');
|
||||
$table->string('column_type', 20)->default('text')->comment('타입 (text, check, measurement, select, complex)');
|
||||
$table->string('group_name', 50)->nullable()->comment('그룹명 (상단 헤더 병합용)');
|
||||
$table->json('sub_labels')->nullable()->comment('서브라벨 배열');
|
||||
$table->unsignedSmallInteger('sort_order')->default(0);
|
||||
$table->timestamps();
|
||||
|
||||
$table->index(['template_id', 'sort_order']);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('document_template_columns');
|
||||
Schema::dropIfExists('document_template_section_items');
|
||||
Schema::dropIfExists('document_template_sections');
|
||||
Schema::dropIfExists('document_template_basic_fields');
|
||||
Schema::dropIfExists('document_template_approval_lines');
|
||||
Schema::dropIfExists('document_templates');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
<?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
|
||||
{
|
||||
Schema::table('attendance_settings', function (Blueprint $table) {
|
||||
$table->boolean('use_auto')->default(false)->after('use_gps')->comment('자동 출퇴근 사용 여부');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('attendance_settings', function (Blueprint $table) {
|
||||
$table->dropColumn('use_auto');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,118 @@
|
||||
<?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
|
||||
{
|
||||
Schema::create('global_categories', function (Blueprint $table) {
|
||||
$table->id()->comment('글로벌 카테고리 PK');
|
||||
$table->unsignedBigInteger('parent_id')->nullable()->comment('상위 카테고리ID(NULL=최상위)');
|
||||
$table->string('code_group', 30)->comment('코드 그룹');
|
||||
$table->string('profile_code', 30)->nullable()->comment('역할 프로필 코드');
|
||||
$table->string('code', 30)->comment('카테고리 코드(영문, 고유)');
|
||||
$table->string('name', 100)->comment('카테고리명');
|
||||
$table->boolean('is_active')->default(true)->comment('활성여부');
|
||||
$table->integer('sort_order')->default(1)->comment('정렬순서');
|
||||
$table->string('description', 255)->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(['code_group', 'code'], 'uq_global_codegroup_code');
|
||||
$table->index('parent_id', 'idx_global_parent_id');
|
||||
$table->index('code_group', 'idx_global_code_group');
|
||||
|
||||
// 자기 참조 FK
|
||||
$table->foreign('parent_id', 'fk_global_category_parent')
|
||||
->references('id')
|
||||
->on('global_categories')
|
||||
->onDelete('set null');
|
||||
});
|
||||
|
||||
// tenant 1의 카테고리를 글로벌 카테고리로 복사
|
||||
$this->seedFromTenant1();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('global_categories');
|
||||
}
|
||||
|
||||
/**
|
||||
* tenant 1의 카테고리를 글로벌로 복사
|
||||
*/
|
||||
private function seedFromTenant1(): void
|
||||
{
|
||||
$categories = \DB::table('categories')
|
||||
->where('tenant_id', 1)
|
||||
->whereNull('deleted_at')
|
||||
->orderBy('parent_id')
|
||||
->orderBy('sort_order')
|
||||
->get();
|
||||
|
||||
if ($categories->isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$idMap = []; // old_id => new_id
|
||||
|
||||
// 1단계: parent_id가 NULL인 루트 카테고리
|
||||
foreach ($categories->where('parent_id', null) as $cat) {
|
||||
$newId = \DB::table('global_categories')->insertGetId([
|
||||
'parent_id' => null,
|
||||
'code_group' => $cat->code_group,
|
||||
'profile_code' => $cat->profile_code,
|
||||
'code' => $cat->code,
|
||||
'name' => $cat->name,
|
||||
'is_active' => $cat->is_active,
|
||||
'sort_order' => $cat->sort_order,
|
||||
'description' => $cat->description,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
$idMap[$cat->id] = $newId;
|
||||
}
|
||||
|
||||
// 2단계: 자식 카테고리 (재귀적으로 처리)
|
||||
$remaining = $categories->whereNotNull('parent_id');
|
||||
$maxIterations = 10;
|
||||
$iteration = 0;
|
||||
|
||||
while ($remaining->isNotEmpty() && $iteration < $maxIterations) {
|
||||
$processed = [];
|
||||
foreach ($remaining as $cat) {
|
||||
if (isset($idMap[$cat->parent_id])) {
|
||||
$newId = \DB::table('global_categories')->insertGetId([
|
||||
'parent_id' => $idMap[$cat->parent_id],
|
||||
'code_group' => $cat->code_group,
|
||||
'profile_code' => $cat->profile_code,
|
||||
'code' => $cat->code,
|
||||
'name' => $cat->name,
|
||||
'is_active' => $cat->is_active,
|
||||
'sort_order' => $cat->sort_order,
|
||||
'description' => $cat->description,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
$idMap[$cat->id] = $newId;
|
||||
$processed[] = $cat->id;
|
||||
}
|
||||
}
|
||||
$remaining = $remaining->whereNotIn('id', $processed);
|
||||
$iteration++;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
<?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
|
||||
{
|
||||
Schema::table('today_issues', function (Blueprint $table) {
|
||||
$table->unsignedBigInteger('target_user_id')
|
||||
->nullable()
|
||||
->after('source_id')
|
||||
->comment('특정 대상 사용자 ID (null이면 테넌트 전체)');
|
||||
|
||||
$table->foreign('target_user_id')
|
||||
->references('id')
|
||||
->on('users')
|
||||
->onDelete('cascade');
|
||||
|
||||
$table->index(['tenant_id', 'target_user_id']);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('today_issues', function (Blueprint $table) {
|
||||
$table->dropForeign(['target_user_id']);
|
||||
$table->dropIndex(['tenant_id', 'target_user_id']);
|
||||
$table->dropColumn('target_user_id');
|
||||
});
|
||||
}
|
||||
};
|
||||
115
database/migrations/2026_01_28_200000_create_documents_table.php
Normal file
115
database/migrations/2026_01_28_200000_create_documents_table.php
Normal file
@@ -0,0 +1,115 @@
|
||||
<?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
|
||||
{
|
||||
// 실제 문서
|
||||
Schema::create('documents', function (Blueprint $table) {
|
||||
$table->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');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
/**
|
||||
* 재고 입출고 거래 이력 테이블
|
||||
*
|
||||
* 모든 재고 변동을 스택으로 쌓아 이력 관리합니다.
|
||||
* audit_logs와 별개로 재고 전용 거래 이력을 제공합니다.
|
||||
*/
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('stock_transactions', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('tenant_id')->comment('테넌트 ID');
|
||||
|
||||
// 재고 참조
|
||||
$table->unsignedBigInteger('stock_id')->comment('재고 ID');
|
||||
$table->unsignedBigInteger('stock_lot_id')->nullable()->comment('LOT ID (입고/출고 시)');
|
||||
|
||||
// 거래 유형
|
||||
$table->string('type', 20)->comment('거래유형: IN(입고), OUT(출고), RESERVE(예약), RELEASE(예약해제)');
|
||||
|
||||
// 수량
|
||||
$table->decimal('qty', 15, 3)->comment('변동 수량 (양수: 증가, 음수: 감소)');
|
||||
$table->decimal('balance_qty', 15, 3)->comment('거래 후 재고 잔량');
|
||||
|
||||
// 참조 정보 (다형성)
|
||||
$table->string('reference_type', 50)->nullable()->comment('참조 유형: receiving, work_order, shipment, order');
|
||||
$table->unsignedBigInteger('reference_id')->nullable()->comment('참조 ID');
|
||||
|
||||
// 상세 정보
|
||||
$table->string('lot_no', 50)->nullable()->comment('LOT번호');
|
||||
$table->string('reason', 50)->nullable()->comment('사유: receiving, work_order_input, shipment, order_confirm, order_cancel');
|
||||
$table->string('remark', 500)->nullable()->comment('비고');
|
||||
|
||||
// 품목 스냅샷 (조회 성능용)
|
||||
$table->string('item_code', 50)->comment('품목코드');
|
||||
$table->string('item_name', 200)->comment('품목명');
|
||||
|
||||
// 감사 정보
|
||||
$table->unsignedBigInteger('created_by')->nullable()->comment('생성자');
|
||||
$table->timestamp('created_at')->useCurrent()->comment('생성일시');
|
||||
|
||||
// 인덱스
|
||||
$table->index('tenant_id');
|
||||
$table->index('stock_id');
|
||||
$table->index('stock_lot_id');
|
||||
$table->index('type');
|
||||
$table->index('reference_type');
|
||||
$table->index(['stock_id', 'created_at']);
|
||||
$table->index(['tenant_id', 'item_code']);
|
||||
$table->index(['tenant_id', 'type', 'created_at']);
|
||||
$table->index(['reference_type', 'reference_id']);
|
||||
|
||||
// 외래키
|
||||
$table->foreign('stock_id')->references('id')->on('stocks')->onDelete('cascade');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('stock_transactions');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
/**
|
||||
* 경동기업 전용 단가 테이블
|
||||
*
|
||||
* 5130 레거시 시스템의 price_* 테이블 데이터를 저장
|
||||
* - price_motor: 모터/제어기 단가
|
||||
* - price_shaft: 샤프트 계산 참조
|
||||
* - price_pipe: 파이프 계산 참조
|
||||
* - price_angle: 앵글 계산 참조
|
||||
* - price_raw_materials: 원자재 단가
|
||||
*/
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('kd_price_tables', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('tenant_id')->default(287)->comment('경동기업 테넌트 ID');
|
||||
$table->string('table_type', 50)->comment('테이블 유형: motor, shaft, pipe, angle, raw_material, bdmodels');
|
||||
$table->string('item_code', 100)->nullable()->comment('품목 코드 (연동용)');
|
||||
$table->string('item_name', 200)->nullable()->comment('품목명');
|
||||
|
||||
// 조회 조건 필드들
|
||||
$table->string('category', 100)->nullable()->comment('분류 (모터용량, 재질, 타입 등)');
|
||||
$table->string('spec1', 100)->nullable()->comment('규격1 (사이즈, 두께 등)');
|
||||
$table->string('spec2', 100)->nullable()->comment('규격2 (길이, 브라켓크기 등)');
|
||||
$table->string('spec3', 100)->nullable()->comment('규격3 (추가 조건)');
|
||||
|
||||
// 단가 정보
|
||||
$table->decimal('unit_price', 15, 2)->default(0)->comment('단가');
|
||||
$table->string('unit', 20)->default('EA')->comment('단위');
|
||||
|
||||
// 원본 JSON 데이터 (레거시 호환용)
|
||||
$table->json('raw_data')->nullable()->comment('원본 JSON 데이터');
|
||||
|
||||
// 메타 정보
|
||||
$table->boolean('is_active')->default(true)->comment('활성 여부');
|
||||
$table->timestamps();
|
||||
|
||||
// 인덱스
|
||||
$table->index(['tenant_id', 'table_type']);
|
||||
$table->index(['table_type', 'category']);
|
||||
$table->index(['table_type', 'spec1', 'spec2']);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('kd_price_tables');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
protected $connection = 'sam_stat';
|
||||
|
||||
public function up(): void
|
||||
{
|
||||
Schema::connection($this->connection)->create('stat_definitions', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('code', 100)->unique()->comment('통계 코드 (sales_daily_revenue)');
|
||||
$table->string('domain', 50)->index()->comment('도메인 (sales, finance, production)');
|
||||
$table->string('name', 200)->comment('통계명 (일일 매출액)');
|
||||
$table->text('description')->nullable();
|
||||
$table->json('source_tables')->comment('원본 테이블 목록');
|
||||
$table->string('aggregation', 20)->default('daily')->index()->comment('집계 주기');
|
||||
$table->text('query_template')->nullable()->comment('집계 SQL 템플릿');
|
||||
$table->boolean('is_active')->default(true)->index();
|
||||
$table->json('config')->nullable()->comment('추가 설정 (임계값, 단위 등)');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::connection($this->connection)->dropIfExists('stat_definitions');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
protected $connection = 'sam_stat';
|
||||
|
||||
public function up(): void
|
||||
{
|
||||
Schema::connection($this->connection)->create('stat_job_logs', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('tenant_id')->comment('테넌트 ID');
|
||||
$table->string('job_type', 100)->comment('작업 유형 (sales_daily, finance_monthly)');
|
||||
$table->date('target_date')->comment('집계 대상 날짜');
|
||||
$table->enum('status', ['pending', 'running', 'completed', 'failed'])->default('pending');
|
||||
$table->unsignedInteger('records_processed')->default(0);
|
||||
$table->text('error_message')->nullable();
|
||||
$table->timestamp('started_at')->nullable();
|
||||
$table->timestamp('completed_at')->nullable();
|
||||
$table->unsignedInteger('duration_ms')->nullable();
|
||||
$table->timestamp('created_at')->nullable();
|
||||
|
||||
$table->index(['tenant_id', 'job_type']);
|
||||
$table->index('status');
|
||||
$table->index('target_date');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::connection($this->connection)->dropIfExists('stat_job_logs');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
protected $connection = 'sam_stat';
|
||||
|
||||
public function up(): void
|
||||
{
|
||||
Schema::connection($this->connection)->create('dim_date', function (Blueprint $table) {
|
||||
$table->date('date_key')->primary()->comment('날짜 키');
|
||||
$table->smallInteger('year')->comment('연도');
|
||||
$table->tinyInteger('quarter')->comment('분기 (1~4)');
|
||||
$table->tinyInteger('month')->comment('월');
|
||||
$table->tinyInteger('week')->comment('ISO 주차');
|
||||
$table->tinyInteger('day_of_week')->comment('요일 (1:월~7:일)');
|
||||
$table->tinyInteger('day_of_month')->comment('일');
|
||||
$table->boolean('is_weekend')->comment('주말 여부');
|
||||
$table->boolean('is_holiday')->default(false)->comment('공휴일 여부');
|
||||
$table->string('holiday_name', 100)->nullable()->comment('공휴일명');
|
||||
$table->smallInteger('fiscal_year')->nullable()->comment('회계연도');
|
||||
$table->tinyInteger('fiscal_quarter')->nullable()->comment('회계분기');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::connection($this->connection)->dropIfExists('dim_date');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
protected $connection = 'sam_stat';
|
||||
|
||||
public function up(): void
|
||||
{
|
||||
Schema::connection($this->connection)->create('stat_sales_daily', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('tenant_id');
|
||||
$table->date('stat_date');
|
||||
|
||||
// 수주
|
||||
$table->unsignedInteger('order_count')->default(0)->comment('신규 수주 건수');
|
||||
$table->decimal('order_amount', 18, 2)->default(0)->comment('수주 금액');
|
||||
$table->unsignedInteger('order_item_count')->default(0)->comment('수주 품목 수');
|
||||
|
||||
// 매출
|
||||
$table->unsignedInteger('sales_count')->default(0)->comment('매출 건수');
|
||||
$table->decimal('sales_amount', 18, 2)->default(0)->comment('매출 금액');
|
||||
$table->decimal('sales_tax_amount', 18, 2)->default(0)->comment('세액');
|
||||
|
||||
// 고객
|
||||
$table->unsignedInteger('new_client_count')->default(0)->comment('신규 고객 수');
|
||||
$table->unsignedInteger('active_client_count')->default(0)->comment('활성 고객 수');
|
||||
|
||||
// 수주 상태별 건수
|
||||
$table->unsignedInteger('order_draft_count')->default(0);
|
||||
$table->unsignedInteger('order_confirmed_count')->default(0);
|
||||
$table->unsignedInteger('order_in_progress_count')->default(0);
|
||||
$table->unsignedInteger('order_completed_count')->default(0);
|
||||
$table->unsignedInteger('order_cancelled_count')->default(0);
|
||||
|
||||
// 출하
|
||||
$table->unsignedInteger('shipment_count')->default(0);
|
||||
$table->decimal('shipment_amount', 18, 2)->default(0);
|
||||
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(['tenant_id', 'stat_date'], 'uk_tenant_date');
|
||||
$table->index('stat_date');
|
||||
$table->index('tenant_id');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::connection($this->connection)->dropIfExists('stat_sales_daily');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
protected $connection = 'sam_stat';
|
||||
|
||||
public function up(): void
|
||||
{
|
||||
Schema::connection($this->connection)->create('stat_finance_daily', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('tenant_id');
|
||||
$table->date('stat_date');
|
||||
|
||||
// 입출금
|
||||
$table->unsignedInteger('deposit_count')->default(0);
|
||||
$table->decimal('deposit_amount', 18, 2)->default(0);
|
||||
$table->unsignedInteger('withdrawal_count')->default(0);
|
||||
$table->decimal('withdrawal_amount', 18, 2)->default(0);
|
||||
$table->decimal('net_cashflow', 18, 2)->default(0)->comment('입금 - 출금');
|
||||
|
||||
// 매입
|
||||
$table->unsignedInteger('purchase_count')->default(0);
|
||||
$table->decimal('purchase_amount', 18, 2)->default(0);
|
||||
$table->decimal('purchase_tax_amount', 18, 2)->default(0);
|
||||
|
||||
// 미수/미지급
|
||||
$table->decimal('receivable_balance', 18, 2)->default(0)->comment('미수금 잔액');
|
||||
$table->decimal('payable_balance', 18, 2)->default(0)->comment('미지급 잔액');
|
||||
$table->decimal('overdue_receivable', 18, 2)->default(0)->comment('연체 미수금');
|
||||
|
||||
// 어음
|
||||
$table->unsignedInteger('bill_issued_count')->default(0);
|
||||
$table->decimal('bill_issued_amount', 18, 2)->default(0);
|
||||
$table->unsignedInteger('bill_matured_count')->default(0);
|
||||
$table->decimal('bill_matured_amount', 18, 2)->default(0);
|
||||
|
||||
// 카드
|
||||
$table->unsignedInteger('card_transaction_count')->default(0);
|
||||
$table->decimal('card_transaction_amount', 18, 2)->default(0);
|
||||
|
||||
// 은행
|
||||
$table->decimal('bank_balance_total', 18, 2)->default(0)->comment('전 계좌 잔액 합계');
|
||||
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(['tenant_id', 'stat_date'], 'uk_tenant_date');
|
||||
$table->index('stat_date');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::connection($this->connection)->dropIfExists('stat_finance_daily');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
protected $connection = 'sam_stat';
|
||||
|
||||
public function up(): void
|
||||
{
|
||||
Schema::connection($this->connection)->create('stat_finance_monthly', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('tenant_id');
|
||||
$table->smallInteger('stat_year');
|
||||
$table->tinyInteger('stat_month');
|
||||
|
||||
$table->decimal('deposit_total', 18, 2)->default(0);
|
||||
$table->decimal('withdrawal_total', 18, 2)->default(0);
|
||||
$table->decimal('net_cashflow', 18, 2)->default(0);
|
||||
$table->decimal('purchase_total', 18, 2)->default(0);
|
||||
$table->decimal('card_total', 18, 2)->default(0);
|
||||
|
||||
$table->decimal('receivable_end', 18, 2)->default(0)->comment('월말 미수금');
|
||||
$table->decimal('payable_end', 18, 2)->default(0)->comment('월말 미지급');
|
||||
$table->decimal('bank_balance_end', 18, 2)->default(0)->comment('월말 잔액');
|
||||
|
||||
$table->decimal('mom_cashflow_change', 8, 2)->nullable()->comment('전월 대비 현금흐름 변화 (%)');
|
||||
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(['tenant_id', 'stat_year', 'stat_month'], 'uk_tenant_year_month');
|
||||
$table->index(['stat_year', 'stat_month']);
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::connection($this->connection)->dropIfExists('stat_finance_monthly');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
protected $connection = 'sam_stat';
|
||||
|
||||
public function up(): void
|
||||
{
|
||||
Schema::connection($this->connection)->create('stat_sales_monthly', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('tenant_id');
|
||||
$table->smallInteger('stat_year');
|
||||
$table->tinyInteger('stat_month');
|
||||
|
||||
// 일일 합산
|
||||
$table->unsignedInteger('order_count')->default(0);
|
||||
$table->decimal('order_amount', 18, 2)->default(0);
|
||||
$table->unsignedInteger('sales_count')->default(0);
|
||||
$table->decimal('sales_amount', 18, 2)->default(0);
|
||||
$table->unsignedInteger('shipment_count')->default(0);
|
||||
$table->decimal('shipment_amount', 18, 2)->default(0);
|
||||
|
||||
// 월간 고유 지표
|
||||
$table->unsignedInteger('unique_client_count')->default(0)->comment('거래 고객 수');
|
||||
$table->decimal('avg_order_amount', 18, 2)->default(0)->comment('평균 수주 금액');
|
||||
$table->unsignedBigInteger('top_client_id')->nullable()->comment('최다 거래 고객');
|
||||
$table->decimal('top_client_amount', 18, 2)->default(0);
|
||||
$table->decimal('mom_growth_rate', 8, 2)->nullable()->comment('전월 대비 성장률 (%)');
|
||||
$table->decimal('yoy_growth_rate', 8, 2)->nullable()->comment('전년동월 대비 (%)');
|
||||
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(['tenant_id', 'stat_year', 'stat_month'], 'uk_tenant_year_month');
|
||||
$table->index(['stat_year', 'stat_month']);
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::connection($this->connection)->dropIfExists('stat_sales_monthly');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
protected $connection = 'sam_stat';
|
||||
|
||||
public function up(): void
|
||||
{
|
||||
Schema::connection($this->connection)->create('stat_production_daily', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('tenant_id');
|
||||
$table->date('stat_date');
|
||||
|
||||
// 작업지시
|
||||
$table->unsignedInteger('wo_created_count')->default(0)->comment('신규 작업지시');
|
||||
$table->unsignedInteger('wo_completed_count')->default(0)->comment('완료 작업지시');
|
||||
$table->unsignedInteger('wo_in_progress_count')->default(0)->comment('진행중');
|
||||
$table->unsignedInteger('wo_overdue_count')->default(0)->comment('납기 초과');
|
||||
|
||||
// 생산량
|
||||
$table->decimal('production_qty', 18, 2)->default(0)->comment('생산 수량');
|
||||
$table->decimal('defect_qty', 18, 2)->default(0)->comment('불량 수량');
|
||||
$table->decimal('defect_rate', 5, 2)->default(0)->comment('불량률 (%)');
|
||||
|
||||
// 작업 효율
|
||||
$table->decimal('planned_hours', 10, 2)->default(0)->comment('계획 공수');
|
||||
$table->decimal('actual_hours', 10, 2)->default(0)->comment('실적 공수');
|
||||
$table->decimal('efficiency_rate', 5, 2)->default(0)->comment('효율 (%)');
|
||||
|
||||
// 작업자
|
||||
$table->unsignedInteger('active_worker_count')->default(0);
|
||||
$table->unsignedInteger('issue_count')->default(0)->comment('발생 이슈 수');
|
||||
|
||||
// 납기
|
||||
$table->unsignedInteger('on_time_delivery_count')->default(0);
|
||||
$table->unsignedInteger('late_delivery_count')->default(0);
|
||||
$table->decimal('delivery_rate', 5, 2)->default(0)->comment('납기준수율 (%)');
|
||||
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(['tenant_id', 'stat_date'], 'uk_tenant_date');
|
||||
$table->index('stat_date');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::connection($this->connection)->dropIfExists('stat_production_daily');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
protected $connection = 'sam_stat';
|
||||
|
||||
public function up(): void
|
||||
{
|
||||
Schema::connection($this->connection)->create('stat_production_monthly', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('tenant_id');
|
||||
$table->smallInteger('stat_year');
|
||||
$table->tinyInteger('stat_month');
|
||||
|
||||
$table->unsignedInteger('wo_total_count')->default(0);
|
||||
$table->unsignedInteger('wo_completed_count')->default(0);
|
||||
$table->decimal('production_qty', 18, 2)->default(0);
|
||||
$table->decimal('defect_qty', 18, 2)->default(0);
|
||||
$table->decimal('avg_defect_rate', 5, 2)->default(0);
|
||||
$table->decimal('avg_efficiency_rate', 5, 2)->default(0);
|
||||
$table->decimal('avg_delivery_rate', 5, 2)->default(0);
|
||||
$table->decimal('total_planned_hours', 10, 2)->default(0);
|
||||
$table->decimal('total_actual_hours', 10, 2)->default(0);
|
||||
$table->unsignedInteger('issue_total_count')->default(0);
|
||||
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(['tenant_id', 'stat_year', 'stat_month'], 'uk_tenant_year_month');
|
||||
$table->index(['stat_year', 'stat_month']);
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::connection($this->connection)->dropIfExists('stat_production_monthly');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
protected $connection = 'sam_stat';
|
||||
|
||||
public function up(): void
|
||||
{
|
||||
Schema::connection($this->connection)->create('dim_client', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('tenant_id');
|
||||
$table->unsignedBigInteger('client_id')->comment('원본 clients.id');
|
||||
$table->string('client_name', 200);
|
||||
$table->unsignedBigInteger('client_group_id')->nullable();
|
||||
$table->string('client_group_name', 200)->nullable();
|
||||
$table->string('client_type', 50)->nullable()->comment('고객/공급업체/양쪽');
|
||||
$table->string('region', 100)->nullable();
|
||||
$table->date('valid_from');
|
||||
$table->date('valid_to')->nullable()->comment('NULL = 현재 유효');
|
||||
$table->boolean('is_current')->default(true);
|
||||
|
||||
$table->index(['tenant_id', 'client_id'], 'idx_tenant_client');
|
||||
$table->index('is_current', 'idx_current');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::connection($this->connection)->dropIfExists('dim_client');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
protected $connection = 'sam_stat';
|
||||
|
||||
public function up(): void
|
||||
{
|
||||
Schema::connection($this->connection)->create('dim_product', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('tenant_id');
|
||||
$table->unsignedBigInteger('item_id')->comment('원본 items.id');
|
||||
$table->string('item_code', 100);
|
||||
$table->string('item_name', 300);
|
||||
$table->string('item_type', 50)->nullable()->comment('item_type from items');
|
||||
$table->unsignedBigInteger('category_id')->nullable();
|
||||
$table->string('category_name', 200)->nullable();
|
||||
$table->date('valid_from');
|
||||
$table->date('valid_to')->nullable()->comment('NULL = 현재 유효');
|
||||
$table->boolean('is_current')->default(true);
|
||||
|
||||
$table->index(['tenant_id', 'item_id'], 'idx_tenant_item');
|
||||
$table->index('is_current', 'idx_current');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::connection($this->connection)->dropIfExists('dim_product');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
protected $connection = 'sam_stat';
|
||||
|
||||
public function up(): void
|
||||
{
|
||||
Schema::connection($this->connection)->create('stat_inventory_daily', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('tenant_id');
|
||||
$table->date('stat_date');
|
||||
|
||||
// 재고 현황
|
||||
$table->unsignedInteger('total_sku_count')->default(0)->comment('총 SKU 수');
|
||||
$table->decimal('total_stock_qty', 18, 2)->default(0)->comment('총 재고 수량');
|
||||
$table->decimal('total_stock_value', 18, 2)->default(0)->comment('총 재고 금액');
|
||||
|
||||
// 입출고
|
||||
$table->unsignedInteger('receipt_count')->default(0)->comment('입고 건수');
|
||||
$table->decimal('receipt_qty', 18, 2)->default(0);
|
||||
$table->decimal('receipt_amount', 18, 2)->default(0);
|
||||
$table->unsignedInteger('issue_count')->default(0)->comment('출고 건수');
|
||||
$table->decimal('issue_qty', 18, 2)->default(0);
|
||||
$table->decimal('issue_amount', 18, 2)->default(0);
|
||||
|
||||
// 안전재고
|
||||
$table->unsignedInteger('below_safety_count')->default(0)->comment('안전재고 미달 품목 수');
|
||||
$table->unsignedInteger('zero_stock_count')->default(0)->comment('재고 0 품목 수');
|
||||
$table->unsignedInteger('excess_stock_count')->default(0)->comment('과잉 재고 품목 수');
|
||||
|
||||
// 품질검사
|
||||
$table->unsignedInteger('inspection_count')->default(0);
|
||||
$table->unsignedInteger('inspection_pass_count')->default(0);
|
||||
$table->unsignedInteger('inspection_fail_count')->default(0);
|
||||
$table->decimal('inspection_pass_rate', 5, 2)->default(0)->comment('합격률 (%)');
|
||||
|
||||
// 재고회전
|
||||
$table->decimal('turnover_rate', 8, 2)->default(0)->comment('재고회전율');
|
||||
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(['tenant_id', 'stat_date'], 'uk_tenant_date');
|
||||
$table->index('stat_date');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::connection($this->connection)->dropIfExists('stat_inventory_daily');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
protected $connection = 'sam_stat';
|
||||
|
||||
public function up(): void
|
||||
{
|
||||
Schema::connection($this->connection)->create('stat_quote_pipeline_daily', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('tenant_id');
|
||||
$table->date('stat_date');
|
||||
|
||||
// 견적
|
||||
$table->unsignedInteger('quote_created_count')->default(0);
|
||||
$table->decimal('quote_amount', 18, 2)->default(0);
|
||||
$table->unsignedInteger('quote_approved_count')->default(0);
|
||||
$table->unsignedInteger('quote_rejected_count')->default(0);
|
||||
$table->unsignedInteger('quote_conversion_count')->default(0)->comment('수주 전환 건수');
|
||||
$table->decimal('quote_conversion_rate', 5, 2)->default(0)->comment('전환율 (%)');
|
||||
|
||||
// 영업 기회 (sales_prospects - tenant_id 없어 manager_id로 연결)
|
||||
$table->unsignedInteger('prospect_created_count')->default(0);
|
||||
$table->unsignedInteger('prospect_won_count')->default(0);
|
||||
$table->unsignedInteger('prospect_lost_count')->default(0);
|
||||
$table->decimal('prospect_amount', 18, 2)->default(0)->comment('파이프라인 금액');
|
||||
|
||||
// 입찰
|
||||
$table->unsignedInteger('bidding_count')->default(0);
|
||||
$table->unsignedInteger('bidding_won_count')->default(0);
|
||||
$table->decimal('bidding_amount', 18, 2)->default(0);
|
||||
|
||||
// 상담
|
||||
$table->unsignedInteger('consultation_count')->default(0);
|
||||
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(['tenant_id', 'stat_date'], 'uk_tenant_date');
|
||||
$table->index('stat_date');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::connection($this->connection)->dropIfExists('stat_quote_pipeline_daily');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
protected $connection = 'sam_stat';
|
||||
|
||||
public function up(): void
|
||||
{
|
||||
Schema::connection($this->connection)->create('stat_hr_attendance_daily', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('tenant_id');
|
||||
$table->date('stat_date');
|
||||
|
||||
// 근태
|
||||
$table->unsignedInteger('total_employees')->default(0)->comment('전체 직원 수');
|
||||
$table->unsignedInteger('attendance_count')->default(0)->comment('출근 인원');
|
||||
$table->unsignedInteger('late_count')->default(0)->comment('지각');
|
||||
$table->unsignedInteger('absent_count')->default(0)->comment('결근');
|
||||
$table->decimal('attendance_rate', 5, 2)->default(0)->comment('출근율 (%)');
|
||||
|
||||
// 휴가
|
||||
$table->unsignedInteger('leave_count')->default(0)->comment('휴가 사용');
|
||||
$table->unsignedInteger('leave_annual_count')->default(0)->comment('연차');
|
||||
$table->unsignedInteger('leave_sick_count')->default(0)->comment('병가');
|
||||
$table->unsignedInteger('leave_other_count')->default(0)->comment('기타');
|
||||
|
||||
// 초과근무
|
||||
$table->decimal('overtime_hours', 10, 2)->default(0);
|
||||
$table->unsignedInteger('overtime_employee_count')->default(0);
|
||||
|
||||
// 인건비
|
||||
$table->decimal('total_labor_cost', 18, 2)->default(0);
|
||||
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(['tenant_id', 'stat_date'], 'uk_tenant_date');
|
||||
$table->index('stat_date');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::connection($this->connection)->dropIfExists('stat_hr_attendance_daily');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
protected $connection = 'sam_stat';
|
||||
|
||||
public function up(): void
|
||||
{
|
||||
Schema::connection($this->connection)->create('stat_kpi_targets', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('tenant_id');
|
||||
$table->smallInteger('stat_year');
|
||||
$table->tinyInteger('stat_month')->nullable()->comment('NULL = 연간 목표');
|
||||
|
||||
$table->string('domain', 50)->comment('sales, production, finance 등');
|
||||
$table->string('metric_code', 100)->comment('monthly_sales_amount 등');
|
||||
$table->decimal('target_value', 18, 2);
|
||||
$table->string('unit', 20)->default('KRW')->comment('KRW, %, count, hours');
|
||||
$table->string('description', 300)->nullable();
|
||||
|
||||
$table->unsignedBigInteger('created_by')->nullable();
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(['tenant_id', 'stat_year', 'stat_month', 'metric_code'], 'uk_tenant_metric');
|
||||
$table->index('domain', 'idx_domain');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::connection($this->connection)->dropIfExists('stat_kpi_targets');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
protected $connection = 'sam_stat';
|
||||
|
||||
public function up(): void
|
||||
{
|
||||
Schema::connection($this->connection)->create('stat_alerts', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('tenant_id');
|
||||
$table->string('domain', 50);
|
||||
$table->string('alert_type', 100)->comment('below_target, anomaly, threshold');
|
||||
$table->enum('severity', ['info', 'warning', 'critical'])->default('info');
|
||||
$table->string('title', 300);
|
||||
$table->text('message');
|
||||
$table->string('metric_code', 100)->nullable();
|
||||
$table->decimal('current_value', 18, 2)->nullable();
|
||||
$table->decimal('threshold_value', 18, 2)->nullable();
|
||||
$table->boolean('is_read')->default(false);
|
||||
$table->boolean('is_resolved')->default(false);
|
||||
$table->timestamp('resolved_at')->nullable();
|
||||
$table->unsignedBigInteger('resolved_by')->nullable();
|
||||
$table->timestamp('created_at')->nullable();
|
||||
|
||||
$table->index(['tenant_id', 'is_read'], 'idx_tenant_unread');
|
||||
$table->index('severity', 'idx_severity');
|
||||
$table->index('domain', 'idx_domain');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::connection($this->connection)->dropIfExists('stat_alerts');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
protected $connection = 'sam_stat';
|
||||
|
||||
public function up(): void
|
||||
{
|
||||
Schema::connection('sam_stat')->create('stat_project_monthly', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('tenant_id');
|
||||
$table->smallInteger('stat_year');
|
||||
$table->tinyInteger('stat_month');
|
||||
|
||||
// 프로젝트 현황
|
||||
$table->unsignedInteger('active_site_count')->default(0);
|
||||
$table->unsignedInteger('completed_site_count')->default(0);
|
||||
$table->unsignedInteger('new_contract_count')->default(0);
|
||||
$table->decimal('contract_total_amount', 18, 2)->default(0);
|
||||
|
||||
// 원가
|
||||
$table->decimal('expected_expense_total', 18, 2)->default(0);
|
||||
$table->decimal('actual_expense_total', 18, 2)->default(0);
|
||||
$table->decimal('labor_cost_total', 18, 2)->default(0);
|
||||
$table->decimal('material_cost_total', 18, 2)->default(0);
|
||||
|
||||
// 수익률
|
||||
$table->decimal('gross_profit', 18, 2)->default(0);
|
||||
$table->decimal('gross_profit_rate', 5, 2)->default(0);
|
||||
|
||||
// 이슈
|
||||
$table->unsignedInteger('handover_report_count')->default(0);
|
||||
$table->unsignedInteger('structure_review_count')->default(0);
|
||||
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(['tenant_id', 'stat_year', 'stat_month'], 'uk_tenant_year_month');
|
||||
$table->index(['stat_year', 'stat_month'], 'idx_year_month');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::connection('sam_stat')->dropIfExists('stat_project_monthly');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
protected $connection = 'sam_stat';
|
||||
|
||||
public function up(): void
|
||||
{
|
||||
Schema::connection('sam_stat')->create('stat_system_daily', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('tenant_id');
|
||||
$table->date('stat_date');
|
||||
|
||||
// API 사용량
|
||||
$table->unsignedInteger('api_request_count')->default(0);
|
||||
$table->unsignedInteger('api_error_count')->default(0);
|
||||
$table->unsignedInteger('api_avg_response_ms')->default(0);
|
||||
|
||||
// 사용자 활동
|
||||
$table->unsignedInteger('active_user_count')->default(0);
|
||||
$table->unsignedInteger('login_count')->default(0);
|
||||
|
||||
// 감사
|
||||
$table->unsignedInteger('audit_create_count')->default(0);
|
||||
$table->unsignedInteger('audit_update_count')->default(0);
|
||||
$table->unsignedInteger('audit_delete_count')->default(0);
|
||||
|
||||
// 알림
|
||||
$table->unsignedInteger('fcm_sent_count')->default(0);
|
||||
$table->unsignedInteger('fcm_failed_count')->default(0);
|
||||
|
||||
// 파일
|
||||
$table->unsignedInteger('file_upload_count')->default(0);
|
||||
$table->decimal('file_upload_size_mb', 10, 2)->default(0);
|
||||
|
||||
// 결재
|
||||
$table->unsignedInteger('approval_submitted_count')->default(0);
|
||||
$table->unsignedInteger('approval_completed_count')->default(0);
|
||||
$table->decimal('approval_avg_hours', 8, 2)->default(0);
|
||||
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(['tenant_id', 'stat_date'], 'uk_tenant_date');
|
||||
$table->index('stat_date', 'idx_date');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::connection('sam_stat')->dropIfExists('stat_system_daily');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
protected $connection = 'sam_stat';
|
||||
|
||||
public function up(): void
|
||||
{
|
||||
Schema::connection('sam_stat')->create('stat_events', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('tenant_id');
|
||||
$table->string('domain', 50);
|
||||
$table->string('event_type', 100);
|
||||
$table->string('entity_type', 100);
|
||||
$table->unsignedBigInteger('entity_id');
|
||||
$table->json('payload')->nullable();
|
||||
$table->timestamp('occurred_at');
|
||||
|
||||
$table->index(['tenant_id', 'domain'], 'idx_tenant_domain');
|
||||
$table->index('occurred_at', 'idx_occurred');
|
||||
$table->index(['entity_type', 'entity_id'], 'idx_entity');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::connection('sam_stat')->dropIfExists('stat_events');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
protected $connection = 'sam_stat';
|
||||
|
||||
public function up(): void
|
||||
{
|
||||
Schema::connection('sam_stat')->create('stat_snapshots', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('tenant_id');
|
||||
$table->date('snapshot_date');
|
||||
$table->string('domain', 50);
|
||||
$table->string('snapshot_type', 50)->default('daily');
|
||||
$table->json('data');
|
||||
$table->timestamp('created_at')->nullable();
|
||||
|
||||
$table->unique(['tenant_id', 'snapshot_date', 'domain', 'snapshot_type'], 'uk_tenant_date_domain');
|
||||
$table->index('snapshot_date', 'idx_date');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::connection('sam_stat')->dropIfExists('stat_snapshots');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
/**
|
||||
* 일간 통계 테이블 RANGE 파티셔닝 준비
|
||||
*
|
||||
* 실행 방법: php artisan migrate --path=database/migrations/stats/2026_01_29_300001_prepare_partitioning_daily_tables.php
|
||||
*
|
||||
* 주의: MySQL에서 파티셔닝 적용 시 UNIQUE KEY에 파티션 컬럼(stat_date)이 포함되어야 합니다.
|
||||
* 이 마이그레이션은 기존 데이터를 보존하며 파티셔닝을 적용합니다.
|
||||
*/
|
||||
return new class extends Migration
|
||||
{
|
||||
protected $connection = 'sam_stat';
|
||||
|
||||
/**
|
||||
* 파티셔닝 대상 테이블과 고유 키 정의
|
||||
*/
|
||||
private function getTargetTables(): array
|
||||
{
|
||||
return [
|
||||
'stat_sales_daily' => [
|
||||
'unique_columns' => 'tenant_id, stat_date',
|
||||
'unique_name' => 'uk_tenant_date',
|
||||
],
|
||||
'stat_finance_daily' => [
|
||||
'unique_columns' => 'tenant_id, stat_date',
|
||||
'unique_name' => 'uk_tenant_date',
|
||||
],
|
||||
'stat_production_daily' => [
|
||||
'unique_columns' => 'tenant_id, stat_date',
|
||||
'unique_name' => 'uk_tenant_date',
|
||||
],
|
||||
'stat_inventory_daily' => [
|
||||
'unique_columns' => 'tenant_id, stat_date',
|
||||
'unique_name' => 'uk_tenant_date',
|
||||
],
|
||||
'stat_quote_pipeline_daily' => [
|
||||
'unique_columns' => 'tenant_id, stat_date',
|
||||
'unique_name' => 'uk_tenant_date',
|
||||
],
|
||||
'stat_hr_attendance_daily' => [
|
||||
'unique_columns' => 'tenant_id, stat_date',
|
||||
'unique_name' => 'uk_tenant_date',
|
||||
],
|
||||
'stat_system_daily' => [
|
||||
'unique_columns' => 'tenant_id, stat_date',
|
||||
'unique_name' => 'uk_tenant_date',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 연도별 파티션 정의 생성 (2024~2028 + MAXVALUE)
|
||||
*/
|
||||
private function getPartitionDefinitions(): string
|
||||
{
|
||||
$partitions = [];
|
||||
for ($year = 2024; $year <= 2028; $year++) {
|
||||
$partitions[] = "PARTITION p{$year} VALUES LESS THAN ('{$year}-01-01')";
|
||||
}
|
||||
$partitions[] = 'PARTITION p_future VALUES LESS THAN MAXVALUE';
|
||||
|
||||
return implode(",\n ", $partitions);
|
||||
}
|
||||
|
||||
public function up(): void
|
||||
{
|
||||
$tables = $this->getTargetTables();
|
||||
$partitionDefs = $this->getPartitionDefinitions();
|
||||
|
||||
foreach ($tables as $table => $config) {
|
||||
// 테이블 존재 여부 확인
|
||||
$exists = DB::connection('sam_stat')
|
||||
->select("SHOW TABLES LIKE '{$table}'");
|
||||
|
||||
if (empty($exists)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 이미 파티셔닝되어 있는지 확인
|
||||
$partitionInfo = DB::connection('sam_stat')
|
||||
->select('SELECT PARTITION_NAME FROM INFORMATION_SCHEMA.PARTITIONS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND PARTITION_NAME IS NOT NULL',
|
||||
[$table]);
|
||||
|
||||
if (! empty($partitionInfo)) {
|
||||
continue; // 이미 파티셔닝됨
|
||||
}
|
||||
|
||||
// AUTO_INCREMENT PK를 일반 PK로 변경 (파티션 키 포함)
|
||||
// MySQL 파티셔닝 제약: UNIQUE KEY에 파티션 컬럼 포함 필수
|
||||
DB::connection('sam_stat')->statement("
|
||||
ALTER TABLE `{$table}`
|
||||
DROP PRIMARY KEY,
|
||||
ADD PRIMARY KEY (`id`, `stat_date`),
|
||||
DROP INDEX `{$config['unique_name']}`,
|
||||
ADD UNIQUE KEY `{$config['unique_name']}` ({$config['unique_columns']})
|
||||
");
|
||||
|
||||
// RANGE 파티셔닝 적용
|
||||
DB::connection('sam_stat')->statement("
|
||||
ALTER TABLE `{$table}`
|
||||
PARTITION BY RANGE COLUMNS(`stat_date`) (
|
||||
{$partitionDefs}
|
||||
)
|
||||
");
|
||||
}
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
$tables = $this->getTargetTables();
|
||||
|
||||
foreach ($tables as $table => $config) {
|
||||
$exists = DB::connection('sam_stat')
|
||||
->select("SHOW TABLES LIKE '{$table}'");
|
||||
|
||||
if (empty($exists)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 파티션 제거
|
||||
DB::connection('sam_stat')->statement("
|
||||
ALTER TABLE `{$table}` REMOVE PARTITIONING
|
||||
");
|
||||
|
||||
// PK 원복
|
||||
DB::connection('sam_stat')->statement("
|
||||
ALTER TABLE `{$table}`
|
||||
DROP PRIMARY KEY,
|
||||
ADD PRIMARY KEY (`id`)
|
||||
");
|
||||
}
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user