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:
@@ -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');
|
||||
}
|
||||
};
|
||||
@@ -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');
|
||||
}
|
||||
};
|
||||
@@ -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');
|
||||
}
|
||||
};
|
||||
@@ -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');
|
||||
}
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user