feat:영업관리 테이블 마이그레이션 추가

- sales_partners: 영업 파트너 정보
- sales_tenant_managements: 테넌트별 영업 관리 (tenant_id FK)
- sales_scenario_checklists: 시나리오 체크리스트
- sales_consultations: 상담 기록 (텍스트/음성/파일)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
pro
2026-01-29 06:42:25 +09:00
parent f163373fb0
commit 6208d90244
5 changed files with 299 additions and 0 deletions

View File

@@ -0,0 +1,57 @@
<?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('sales_partners', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('user_id')->comment('연결된 사용자 ID');
$table->string('partner_code', 20)->unique()->comment('파트너 고유 코드');
$table->enum('partner_type', ['individual', 'corporate'])->default('individual')->comment('파트너 유형');
// 수수료 정보
$table->decimal('commission_rate', 5, 2)->default(20.00)->comment('기본 수수료율 (%)');
$table->decimal('manager_commission_rate', 5, 2)->default(5.00)->comment('관리자 수수료율 (%)');
// 계좌 정보
$table->string('bank_name', 50)->nullable()->comment('은행명');
$table->string('account_number', 50)->nullable()->comment('계좌번호');
$table->string('account_holder', 50)->nullable()->comment('예금주');
// 상태 관리
$table->enum('status', ['pending', 'active', 'inactive', 'suspended'])->default('pending')->comment('상태');
$table->timestamp('approved_at')->nullable()->comment('승인 일시');
$table->unsignedBigInteger('approved_by')->nullable()->comment('승인자 ID');
// 실적 통계 (캐시용)
$table->unsignedInteger('total_contracts')->default(0)->comment('총 계약 건수');
$table->decimal('total_commission', 15, 2)->default(0)->comment('총 누적 수당');
$table->text('notes')->nullable()->comment('메모');
$table->timestamps();
$table->softDeletes();
// 인덱스
$table->index('user_id');
$table->index('status');
$table->index('partner_type');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('sales_partners');
}
};

View File

@@ -0,0 +1,75 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
* 테넌트별 영업 관리 테이블 (tenants 외래키 연결)
*/
public function up(): void
{
Schema::create('sales_tenant_managements', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('tenant_id')->unique()->comment('테넌트 ID (1:1 관계)');
$table->unsignedBigInteger('sales_partner_id')->nullable()->comment('영업 담당자 ID');
$table->unsignedBigInteger('manager_user_id')->nullable()->comment('관리 매니저 사용자 ID');
// 시나리오 진행 상태
$table->unsignedTinyInteger('sales_scenario_step')->default(1)->comment('영업 시나리오 현재 단계 (1-6)');
$table->unsignedTinyInteger('manager_scenario_step')->default(1)->comment('매니저 시나리오 현재 단계 (1-6)');
// 영업 상태
$table->enum('status', [
'prospect', // 잠재 고객
'approach', // 접근 중
'negotiation', // 협상 중
'contracted', // 계약 완료
'onboarding', // 온보딩 중
'active', // 활성 고객
'churned', // 이탈
])->default('prospect')->comment('영업 상태');
// 계약 정보
$table->timestamp('first_contact_at')->nullable()->comment('최초 접촉일');
$table->timestamp('contracted_at')->nullable()->comment('계약 체결일');
$table->timestamp('onboarding_completed_at')->nullable()->comment('온보딩 완료일');
// 가입비 정보
$table->decimal('membership_fee', 12, 2)->nullable()->comment('가입비');
$table->timestamp('membership_paid_at')->nullable()->comment('가입비 입금일');
$table->enum('membership_status', ['pending', 'partial', 'paid', 'refunded'])->nullable()->comment('가입비 상태');
// 수당 정보
$table->decimal('sales_commission', 12, 2)->nullable()->comment('영업 수당');
$table->decimal('manager_commission', 12, 2)->nullable()->comment('관리 수당');
$table->timestamp('commission_paid_at')->nullable()->comment('수당 지급일');
$table->enum('commission_status', ['pending', 'approved', 'paid'])->nullable()->comment('수당 상태');
// 진행률 캐시
$table->unsignedTinyInteger('sales_progress')->default(0)->comment('영업 시나리오 진행률 (%)');
$table->unsignedTinyInteger('manager_progress')->default(0)->comment('매니저 시나리오 진행률 (%)');
$table->text('notes')->nullable()->comment('메모');
$table->timestamps();
$table->softDeletes();
// 인덱스
$table->index('sales_partner_id');
$table->index('manager_user_id');
$table->index('status');
$table->index('contracted_at');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('sales_tenant_managements');
}
};

View File

@@ -0,0 +1,45 @@
<?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('sales_scenario_checklists', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('tenant_id')->comment('테넌트 ID');
$table->enum('scenario_type', ['sales', 'manager'])->comment('시나리오 유형');
$table->unsignedTinyInteger('step_id')->comment('단계 ID (1-6)');
$table->string('checkpoint_id', 50)->comment('체크포인트 ID');
$table->boolean('is_checked')->default(false)->comment('체크 여부');
$table->timestamp('checked_at')->nullable()->comment('체크 일시');
$table->unsignedBigInteger('checked_by')->nullable()->comment('체크한 사용자 ID');
$table->text('memo')->nullable()->comment('메모');
$table->timestamps();
// 복합 유니크 키 (테넌트 + 시나리오타입 + 단계 + 체크포인트)
$table->unique(['tenant_id', 'scenario_type', 'step_id', 'checkpoint_id'], 'unique_checklist_item');
// 인덱스
$table->index(['tenant_id', 'scenario_type']);
$table->index('checked_by');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('sales_scenario_checklists');
}
};

View File

@@ -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
{
/**
* Run the migrations.
* 영업 상담 기록 테이블 (텍스트, 음성, 첨부파일)
*/
public function up(): void
{
Schema::create('sales_consultations', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('tenant_id')->comment('테넌트 ID');
$table->enum('scenario_type', ['sales', 'manager'])->comment('시나리오 유형');
$table->unsignedTinyInteger('step_id')->nullable()->comment('관련 단계 ID (1-6)');
// 상담 유형 및 내용
$table->enum('consultation_type', ['text', 'audio', 'file'])->comment('상담 유형');
$table->text('content')->nullable()->comment('텍스트 내용');
// 파일 정보 (음성/첨부파일)
$table->string('file_path', 500)->nullable()->comment('파일 경로');
$table->string('file_name', 255)->nullable()->comment('원본 파일명');
$table->unsignedInteger('file_size')->nullable()->comment('파일 크기 (bytes)');
$table->string('file_type', 100)->nullable()->comment('MIME 타입');
// 음성 관련
$table->text('transcript')->nullable()->comment('음성 변환 텍스트 (STT)');
$table->unsignedInteger('duration')->nullable()->comment('녹음 길이 (초)');
// 작성자
$table->unsignedBigInteger('created_by')->comment('작성자 ID');
$table->timestamps();
$table->softDeletes();
// 인덱스
$table->index(['tenant_id', 'scenario_type']);
$table->index('consultation_type');
$table->index('step_id');
$table->index('created_by');
$table->index('created_at');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('sales_consultations');
}
};

View File

@@ -0,0 +1,66 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\DB;
return new class extends Migration
{
/**
* Run the migrations.
* sales_scenario_checklists 테이블에 필요한 컬럼 추가
*/
public function up(): void
{
Schema::table('sales_scenario_checklists', function (Blueprint $table) {
// scenario_type 컬럼 추가 (sales/manager 구분)
if (!Schema::hasColumn('sales_scenario_checklists', 'scenario_type')) {
$table->enum('scenario_type', ['sales', 'manager'])->default('sales')->after('tenant_id')->comment('시나리오 유형');
}
// checkpoint_id 컬럼 추가 (문자열 ID)
if (!Schema::hasColumn('sales_scenario_checklists', 'checkpoint_id')) {
$table->string('checkpoint_id', 50)->nullable()->after('step_id')->comment('체크포인트 ID');
}
// checked_at 컬럼 추가
if (!Schema::hasColumn('sales_scenario_checklists', 'checked_at')) {
$table->timestamp('checked_at')->nullable()->after('is_checked')->comment('체크 일시');
}
// checked_by 컬럼 추가 (user_id를 대체)
if (!Schema::hasColumn('sales_scenario_checklists', 'checked_by')) {
$table->unsignedBigInteger('checked_by')->nullable()->after('checked_at')->comment('체크한 사용자 ID');
}
// memo 컬럼 추가
if (!Schema::hasColumn('sales_scenario_checklists', 'memo')) {
$table->text('memo')->nullable()->after('checked_by')->comment('메모');
}
});
// 인덱스 추가 (존재 여부 확인)
$indexExists = DB::select("SHOW INDEX FROM sales_scenario_checklists WHERE Key_name = 'sales_scenario_checklists_tenant_id_scenario_type_index'");
if (empty($indexExists)) {
Schema::table('sales_scenario_checklists', function (Blueprint $table) {
$table->index(['tenant_id', 'scenario_type']);
});
}
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('sales_scenario_checklists', function (Blueprint $table) {
$columns = ['scenario_type', 'checkpoint_id', 'checked_at', 'checked_by', 'memo'];
foreach ($columns as $column) {
if (Schema::hasColumn('sales_scenario_checklists', $column)) {
$table->dropColumn($column);
}
}
});
}
};