Files
portal/app/tests/Feature/Sales/SalesProspectsAdvanceJobTest.php
T
Дмитрий 6ceec17439 feat(sales): Этап 3 — автожизнь воронки (регистрация + джоба стадий по деньгам)
Действие «Зарегистрировался» (PATCH action=registered): email→tenant, привязка
SalesClientAssignment со снимком тарифа, linked_tenant_id+stage=registered; клиент
занят другим → 422. Диалог карточки: пункт «Зарегистрировался» + поле e-mail.
SalesProspectsAdvanceJob (каждые 15 мин, pgsql_admin): по balance_transactions
считает стадию — Σtopup≥30000→user, >0→topped_up, есть расход при 0 topup→testing,
иначе registered; ручные/отказные стадии не трогает. Схема НЕ меняется.
Гейты: бэк 19/19, фронт 9/9, Larastan 0. 🪤 property $connection конфликтовал с
трейтом Queueable → переименовал в $dbConnection.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 19:00:16 +03:00

104 lines
3.3 KiB
PHP

<?php
declare(strict_types=1);
use App\Jobs\Sales\SalesProspectsAdvanceJob;
use App\Models\SalesProspect;
use App\Models\SalesUser;
use App\Models\Tenant;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\DB;
uses(DatabaseTransactions::class);
function adv_user(): SalesUser
{
return SalesUser::create([
'name' => 'A '.uniqid(), 'email' => 'adv'.uniqid().'@s.local',
'password' => bcrypt('x'), 'role' => 'manager', 'is_active' => true,
]);
}
function adv_prospect(SalesUser $u, ?int $tenantId, string $stage): SalesProspect
{
return SalesProspect::factory()->for($u, 'salesUser')->create([
'stage' => $stage, 'linked_tenant_id' => $tenantId,
]);
}
function adv_txn(int $tenantId, string $type, string $amountRub): void
{
DB::table('balance_transactions')->insert([
'tenant_id' => $tenantId, 'type' => $type, 'amount_rub' => $amountRub,
'amount_leads' => 0, 'balance_rub_after' => $amountRub, 'description' => 'test',
'created_at' => now(),
]);
}
test('пополнения ≥ 30000 → user', function () {
$u = adv_user();
$t = Tenant::factory()->create();
$p = adv_prospect($u, $t->id, 'registered');
adv_txn($t->id, 'topup', '30000');
(new SalesProspectsAdvanceJob)->handle();
expect(SalesProspect::find($p->id)->stage)->toBe('user');
});
test('есть реальное пополнение < 30000 → topped_up', function () {
$u = adv_user();
$t = Tenant::factory()->create();
$p = adv_prospect($u, $t->id, 'registered');
adv_txn($t->id, 'topup', '5000');
(new SalesProspectsAdvanceJob)->handle();
expect(SalesProspect::find($p->id)->stage)->toBe('topped_up');
});
test('нет пополнений, но есть расход (бонус пошёл в дело) → testing', function () {
$u = adv_user();
$t = Tenant::factory()->create();
$p = adv_prospect($u, $t->id, 'registered');
adv_txn($t->id, 'lead_charge', '20');
(new SalesProspectsAdvanceJob)->handle();
expect(SalesProspect::find($p->id)->stage)->toBe('testing');
});
test('нет активности → остаётся registered', function () {
$u = adv_user();
$t = Tenant::factory()->create();
$p = adv_prospect($u, $t->id, 'registered');
(new SalesProspectsAdvanceJob)->handle();
expect(SalesProspect::find($p->id)->stage)->toBe('registered');
});
test('карточку без linked_tenant_id и ручные стадии (rejected) джоба НЕ трогает', function () {
$u = adv_user();
$t = Tenant::factory()->create();
$rejected = adv_prospect($u, $t->id, 'rejected'); // linked, но отказ
$newNoLink = adv_prospect($u, null, 'new');
adv_txn($t->id, 'topup', '50000');
(new SalesProspectsAdvanceJob)->handle();
expect(SalesProspect::find($rejected->id)->stage)->toBe('rejected');
expect(SalesProspect::find($newNoLink->id)->stage)->toBe('new');
});
test('пользователь без новых денег не откатывается назад', function () {
$u = adv_user();
$t = Tenant::factory()->create();
$p = adv_prospect($u, $t->id, 'user');
adv_txn($t->id, 'topup', '35000');
(new SalesProspectsAdvanceJob)->handle();
expect(SalesProspect::find($p->id)->stage)->toBe('user');
});