'integer', 'manager_scenario_step' => 'integer', 'membership_fee' => 'decimal:2', 'sales_commission' => 'decimal:2', 'manager_commission' => 'decimal:2', 'sales_progress' => 'integer', 'manager_progress' => 'integer', 'first_contact_at' => 'datetime', 'contracted_at' => 'datetime', 'onboarding_completed_at' => 'datetime', 'membership_paid_at' => 'datetime', 'commission_paid_at' => 'datetime', 'handover_at' => 'datetime', // 입금 정보 'deposit_amount' => 'decimal:2', 'deposit_paid_date' => 'date', 'balance_amount' => 'decimal:2', 'balance_paid_date' => 'date', 'total_registration_fee' => 'decimal:2', ]; /** * 상태 상수 */ const STATUS_PROSPECT = 'prospect'; const STATUS_APPROACH = 'approach'; const STATUS_NEGOTIATION = 'negotiation'; const STATUS_CONTRACTED = 'contracted'; const STATUS_ONBOARDING = 'onboarding'; const STATUS_ACTIVE = 'active'; const STATUS_CHURNED = 'churned'; /** * 상태 라벨 */ public static array $statusLabels = [ self::STATUS_PROSPECT => '잠재 고객', self::STATUS_APPROACH => '접근 중', self::STATUS_NEGOTIATION => '협상 중', self::STATUS_CONTRACTED => '계약 완료', self::STATUS_ONBOARDING => '온보딩 중', self::STATUS_ACTIVE => '활성 고객', self::STATUS_CHURNED => '이탈', ]; /** * 본사 진행 상태 상수 */ const HQ_STATUS_PENDING = 'pending'; // 대기 const HQ_STATUS_REVIEW = 'review'; // 검토 const HQ_STATUS_PLANNING = 'planning'; // 기획안작성 const HQ_STATUS_CODING = 'coding'; // 개발코드작성 const HQ_STATUS_DEV_TEST = 'dev_test'; // 개발테스트 const HQ_STATUS_DEV_DONE = 'dev_done'; // 개발완료 const HQ_STATUS_INT_TEST = 'int_test'; // 통합테스트 const HQ_STATUS_HANDOVER = 'handover'; // 인계 /** * 본사 진행 상태 라벨 */ public static array $hqStatusLabels = [ self::HQ_STATUS_PENDING => '대기', self::HQ_STATUS_REVIEW => '검토', self::HQ_STATUS_PLANNING => '기획안작성', self::HQ_STATUS_CODING => '개발코드작성', self::HQ_STATUS_DEV_TEST => '개발테스트', self::HQ_STATUS_DEV_DONE => '개발완료', self::HQ_STATUS_INT_TEST => '통합테스트', self::HQ_STATUS_HANDOVER => '인계', ]; /** * 본사 진행 상태 순서 (프로그레스바용) */ public static array $hqStatusOrder = [ self::HQ_STATUS_PENDING => 0, self::HQ_STATUS_REVIEW => 1, self::HQ_STATUS_PLANNING => 2, self::HQ_STATUS_CODING => 3, self::HQ_STATUS_DEV_TEST => 4, self::HQ_STATUS_DEV_DONE => 5, self::HQ_STATUS_INT_TEST => 6, self::HQ_STATUS_HANDOVER => 7, ]; /** * 수당 지급 상태 상수 */ const INCENTIVE_PENDING = 'pending'; // 대기 const INCENTIVE_ELIGIBLE = 'eligible'; // 지급대상 const INCENTIVE_PAID = 'paid'; // 지급완료 /** * 수당 지급 상태 라벨 */ public static array $incentiveStatusLabels = [ self::INCENTIVE_PENDING => '대기', self::INCENTIVE_ELIGIBLE => '지급대상', self::INCENTIVE_PAID => '지급완료', ]; /** * 테넌트 관계 */ public function tenant(): BelongsTo { return $this->belongsTo(Tenant::class); } /** * 가망고객 관계 */ public function tenantProspect(): BelongsTo { return $this->belongsTo(TenantProspect::class, 'tenant_prospect_id'); } /** * 영업 담당자 관계 */ public function salesPartner(): BelongsTo { return $this->belongsTo(SalesPartner::class, 'sales_partner_id'); } /** * 관리 매니저 관계 */ public function manager(): BelongsTo { return $this->belongsTo(User::class, 'manager_user_id'); } /** * 체크리스트 관계 */ public function checklists(): HasMany { return $this->hasMany(SalesScenarioChecklist::class, 'tenant_id', 'tenant_id'); } /** * 상담 기록 관계 */ public function consultations(): HasMany { return $this->hasMany(SalesConsultation::class, 'tenant_id', 'tenant_id'); } /** * 수수료 정산 관계 */ public function commissions(): HasMany { return $this->hasMany(SalesCommission::class, 'management_id'); } /** * 계약 상품 관계 */ public function contractProducts(): HasMany { return $this->hasMany(SalesContractProduct::class, 'management_id'); } /** * 테넌트 ID로 조회 또는 생성 */ public static function findOrCreateByTenant(int $tenantId): self { return self::firstOrCreate( ['tenant_id' => $tenantId], [ 'status' => self::STATUS_PROSPECT, 'sales_scenario_step' => 1, 'manager_scenario_step' => 1, ] ); } /** * 가망고객 ID로 조회 또는 생성 */ public static function findOrCreateByProspect(int $prospectId): self { return self::firstOrCreate( ['tenant_prospect_id' => $prospectId], [ 'status' => self::STATUS_PROSPECT, 'sales_scenario_step' => 1, 'manager_scenario_step' => 1, ] ); } /** * 테넌트 또는 가망고객 ID로 조회 또는 생성 */ public static function findOrCreateByTenantOrProspect(?int $tenantId, ?int $prospectId): self { if ($tenantId) { return self::findOrCreateByTenant($tenantId); } if ($prospectId) { return self::findOrCreateByProspect($prospectId); } throw new \InvalidArgumentException('tenant_id 또는 prospect_id 중 하나는 필수입니다.'); } /** * 진행률 업데이트 */ public function updateProgress(string $scenarioType, int $progress): void { $field = $scenarioType === 'sales' ? 'sales_progress' : 'manager_progress'; $this->update([$field => $progress]); } /** * 현재 단계 업데이트 */ public function updateStep(string $scenarioType, int $step): void { $field = $scenarioType === 'sales' ? 'sales_scenario_step' : 'manager_scenario_step'; $this->update([$field => $step]); } /** * 상태 라벨 Accessor */ public function getStatusLabelAttribute(): string { return self::$statusLabels[$this->status] ?? $this->status; } /** * 본사 진행 상태 라벨 Accessor */ public function getHqStatusLabelAttribute(): string { return self::$hqStatusLabels[$this->hq_status ?? self::HQ_STATUS_PENDING] ?? '대기'; } /** * 본사 진행 상태 순서 (0-7) */ public function getHqStatusStepAttribute(): int { return self::$hqStatusOrder[$this->hq_status ?? self::HQ_STATUS_PENDING] ?? 0; } /** * 수당 지급 상태 라벨 Accessor */ public function getIncentiveStatusLabelAttribute(): string { return self::$incentiveStatusLabels[$this->incentive_status ?? self::INCENTIVE_PENDING] ?? '대기'; } /** * 본사 진행 가능 여부 (매니저 100% 완료 시) */ public function isHqProgressEnabled(): bool { return $this->manager_progress >= 100; } /** * 특정 상태 스코프 */ public function scopeByStatus($query, string $status) { return $query->where('status', $status); } /** * 계약 완료 스코프 */ public function scopeContracted($query) { return $query->whereIn('status', [ self::STATUS_CONTRACTED, self::STATUS_ONBOARDING, self::STATUS_ACTIVE, ]); } }