environment('production')) { throw new \RuntimeException('SalesDemoSeeder запрещён на production.'); } $tariffs = $this->seedTariffs(); $managers = $this->seedManagers($tariffs); $this->seedAssignments($managers); $this->seedPayouts($managers); } /** * Тарифные экземпляры (2 вида: суточный оклад + процент от пополнений с порогом/бонусом). * Ключ идемпотентности — (kind, name). * * @return array */ private function seedTariffs(): array { // Суточный оклад — маркер режима; ставка ₽/день задаётся per-manager (base_salary_rub). $daily = SalesTariff::updateOrCreate( ['kind' => 'daily_salary', 'name' => 'Суточный оклад'], ['is_active' => true, 'params' => []], ); // Процент от пополнений: порог 30 000 → бонус 10 000, дальше ступени по сроку. $stepStd = SalesTariff::updateOrCreate( ['kind' => 'topup_step', 'name' => 'Стандарт-пополнения'], ['is_active' => true, 'params' => [ 'threshold' => 30000, 'reward' => 10000, 'periods' => [ ['from' => 1, 'to' => 3, 'rate' => 10], ['from' => 4, 'to' => 6, 'rate' => 7], ['from' => 7, 'to' => 9, 'rate' => 5], ['from' => 10, 'to' => 12, 'rate' => 3], ['from' => 13, 'to' => 24, 'rate' => 1], ], ]], ); // Агрессивный: порог ниже (20 000), бонус выше (15 000), ставки выше. $stepAgro = SalesTariff::updateOrCreate( ['kind' => 'topup_step', 'name' => 'Агрессивный'], ['is_active' => true, 'params' => [ 'threshold' => 20000, 'reward' => 15000, 'periods' => [ ['from' => 1, 'to' => 3, 'rate' => 12], ['from' => 4, 'to' => 6, 'rate' => 9], ['from' => 7, 'to' => 9, 'rate' => 6], ['from' => 10, 'to' => 12, 'rate' => 4], ['from' => 13, 'to' => 24, 'rate' => 2], ], ]], ); return [ 'daily' => $daily, 'step_std' => $stepStd, 'step_agro' => $stepAgro, ]; } /** * Начальник + 3 менеджера. Ключ идемпотентности — email. * * @param array $tariffs * @return array */ private function seedManagers(array $tariffs): array { $password = Hash::make('demo12345'); $head = SalesUser::updateOrCreate( ['email' => 'v.orlov@liderra.ru'], [ 'name' => 'Виктор Орлов', 'password' => $password, 'role' => 'head', 'is_active' => true, 'base_salary_rub' => 0, 'current_tariff_id' => null, 'created_by' => null, ], ); // Новичок на суточном окладе: ставка 2 000 ₽/день (base_salary_rub). $smirnov = SalesUser::updateOrCreate( ['email' => 'a.smirnov@liderra.ru'], [ 'name' => 'Алексей Смирнов', 'password' => $password, 'role' => 'manager', 'is_active' => true, 'base_salary_rub' => 2000, 'current_tariff_id' => $tariffs['daily']->id, 'created_by' => $head->id, ], ); // Опытный на проценте от пополнений (Стандарт). $kotova = SalesUser::updateOrCreate( ['email' => 'm.kotova@liderra.ru'], [ 'name' => 'Мария Котова', 'password' => $password, 'role' => 'manager', 'is_active' => true, 'base_salary_rub' => 0, 'current_tariff_id' => $tariffs['step_std']->id, 'created_by' => $head->id, ], ); $lebedev = SalesUser::updateOrCreate( ['email' => 'i.lebedev@liderra.ru'], [ 'name' => 'Игорь Лебедев', 'password' => $password, 'role' => 'manager', 'is_active' => false, // «отпуск» 'base_salary_rub' => 0, 'current_tariff_id' => $tariffs['step_agro']->id, 'created_by' => $head->id, ], ); return [ 'head' => $head, 'smirnov' => $smirnov, 'kotova' => $kotova, 'lebedev' => $lebedev, ]; } /** * Привязки клиентов со СНИМКОМ текущего тарифа менеджера. * Берём существующие тенанты; если их нет — пропускаем (не падаем). * * @param array $managers */ private function seedAssignments(array $managers): void { /** @var Collection $tenants */ $tenants = Tenant::query()->limit(8)->get(); if ($tenants->isEmpty()) { $this->command->warn('SalesDemoSeeder: тенантов нет — привязки клиентов пропущены.'); return; } // Распределяем тенанты по 3 менеджерам по кругу. $roundRobin = [$managers['smirnov'], $managers['kotova'], $managers['lebedev']]; foreach ($tenants as $index => $tenant) { $manager = $roundRobin[$index % count($roundRobin)]; $tariff = $manager->currentTariff; // снимок берём из тарифа менеджера SalesClientAssignment::firstOrCreate( ['tenant_id' => $tenant->id], // UNIQUE — повторный запуск не дублирует [ 'sales_user_id' => $manager->id, 'tariff_id' => $tariff->id, 'tariff_kind' => $tariff->kind, 'tariff_params' => $tariff->params, 'assigned_at' => now()->subMonthsNoOverflow(rand(1, 6)), ], ); } } /** * Выплаты (append-only) — по 1-2 на менеджера, суммы из прототипа. * Создаём только если у менеджера ещё нет выплат (нельзя дублировать). * * @param array $managers */ private function seedPayouts(array $managers): void { $head = $managers['head']; $plan = [ 'smirnov' => [35000, 25000], 'kotova' => [30000], 'lebedev' => [40000], ]; foreach ($plan as $key => $amounts) { $manager = $managers[$key]; if ($manager->payouts()->count() > 0) { continue; // уже есть выплаты — не дублируем append-only записи } foreach ($amounts as $offset => $amount) { SalesPayout::create([ 'sales_user_id' => $manager->id, 'amount_rub' => $amount, 'paid_on' => now()->subMonthsNoOverflow($offset)->startOfMonth()->addDays(24), 'comment' => $offset === 0 ? 'Зарплата (демо)' : 'Аванс (демо)', 'created_by' => $head->id, ]); } } } }