diff --git a/database/migrations/2026_01_29_100000_create_sales_partners_table.php b/database/migrations/2026_01_29_100000_create_sales_partners_table.php new file mode 100644 index 0000000..6cd3018 --- /dev/null +++ b/database/migrations/2026_01_29_100000_create_sales_partners_table.php @@ -0,0 +1,57 @@ +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'); + } +}; diff --git a/database/migrations/2026_01_29_100100_create_sales_tenant_managements_table.php b/database/migrations/2026_01_29_100100_create_sales_tenant_managements_table.php new file mode 100644 index 0000000..320d387 --- /dev/null +++ b/database/migrations/2026_01_29_100100_create_sales_tenant_managements_table.php @@ -0,0 +1,75 @@ +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'); + } +}; diff --git a/database/migrations/2026_01_29_100200_create_sales_scenario_checklists_table.php b/database/migrations/2026_01_29_100200_create_sales_scenario_checklists_table.php new file mode 100644 index 0000000..61d215e --- /dev/null +++ b/database/migrations/2026_01_29_100200_create_sales_scenario_checklists_table.php @@ -0,0 +1,45 @@ +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'); + } +}; diff --git a/database/migrations/2026_01_29_100300_create_sales_consultations_table.php b/database/migrations/2026_01_29_100300_create_sales_consultations_table.php new file mode 100644 index 0000000..360f3fe --- /dev/null +++ b/database/migrations/2026_01_29_100300_create_sales_consultations_table.php @@ -0,0 +1,56 @@ +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'); + } +}; diff --git a/database/migrations/2026_01_29_100400_add_columns_to_sales_scenario_checklists_table.php b/database/migrations/2026_01_29_100400_add_columns_to_sales_scenario_checklists_table.php new file mode 100644 index 0000000..5df33e1 --- /dev/null +++ b/database/migrations/2026_01_29_100400_add_columns_to_sales_scenario_checklists_table.php @@ -0,0 +1,66 @@ +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); + } + } + }); + } +};