2026-06-30 12:08:52 +03:00
|
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
|
|
|
|
use App\Models\SalesAttachmentRequest;
|
|
|
|
|
|
use App\Models\SalesClientAssignment;
|
|
|
|
|
|
use App\Models\SalesPayout;
|
|
|
|
|
|
use App\Models\SalesTariff;
|
|
|
|
|
|
use App\Models\SalesUser;
|
|
|
|
|
|
use App\Models\Tenant;
|
2026-06-30 13:07:47 +03:00
|
|
|
|
use Carbon\Carbon;
|
2026-06-30 12:08:52 +03:00
|
|
|
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
2026-06-30 13:07:47 +03:00
|
|
|
|
use Illuminate\Database\QueryException;
|
2026-06-30 12:08:52 +03:00
|
|
|
|
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Smoke-тесты Eloquent-моделей портала продаж.
|
|
|
|
|
|
* Проверяют: создание записей, каsты, связи, вспомогательные методы.
|
|
|
|
|
|
*
|
|
|
|
|
|
* Изоляция: DatabaseTransactions — каждый тест оборачивается в транзакцию
|
|
|
|
|
|
* и откатывается, не оставляя строк в liderra_testing.
|
|
|
|
|
|
*
|
|
|
|
|
|
* Используем DEFAULT connection (pgsql → liderra_testing в тестах).
|
|
|
|
|
|
* sales_* таблицы созданы Task 0.1 (migration 2026_07_01_100000_create_sales_portal_tables.php).
|
|
|
|
|
|
*/
|
|
|
|
|
|
uses(DatabaseTransactions::class);
|
|
|
|
|
|
|
|
|
|
|
|
// ── helpers ────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
function makeSalesTariff(array $attrs = []): SalesTariff
|
|
|
|
|
|
{
|
|
|
|
|
|
return SalesTariff::create(array_merge([
|
2026-06-30 13:07:47 +03:00
|
|
|
|
'name' => 'Test tariff '.uniqid(),
|
|
|
|
|
|
'kind' => 'topup_step',
|
|
|
|
|
|
'params' => ['step' => 1000, 'bonus' => 100],
|
2026-06-30 12:08:52 +03:00
|
|
|
|
'is_active' => true,
|
|
|
|
|
|
], $attrs));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function makeSalesUser(array $attrs = []): SalesUser
|
|
|
|
|
|
{
|
|
|
|
|
|
return SalesUser::create(array_merge([
|
2026-06-30 13:07:47 +03:00
|
|
|
|
'name' => 'Manager '.uniqid(),
|
|
|
|
|
|
'email' => 'mgr'.uniqid().'@test.local',
|
2026-06-30 12:08:52 +03:00
|
|
|
|
'password' => bcrypt('secret'),
|
2026-06-30 13:07:47 +03:00
|
|
|
|
'role' => 'manager',
|
2026-06-30 12:08:52 +03:00
|
|
|
|
], $attrs));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ── 1. SalesTariff ─────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
test('SalesTariff можно создать и params отдаётся массивом', function () {
|
|
|
|
|
|
$tariff = makeSalesTariff([
|
|
|
|
|
|
'params' => ['step' => 5000, 'bonus' => 500],
|
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
expect($tariff->id)->toBeInt()
|
|
|
|
|
|
->and($tariff->name)->toStartWith('Test tariff')
|
|
|
|
|
|
->and($tariff->kind)->toBe('topup_step')
|
|
|
|
|
|
->and($tariff->params)->toBeArray()
|
|
|
|
|
|
->and($tariff->params['step'])->toBe(5000)
|
|
|
|
|
|
->and($tariff->is_active)->toBeTrue();
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// ── 2. SalesUser ───────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
test('SalesUser создаётся с role=manager и current_tariff_id', function () {
|
|
|
|
|
|
$tariff = makeSalesTariff();
|
|
|
|
|
|
$user = makeSalesUser([
|
2026-06-30 13:07:47 +03:00
|
|
|
|
'role' => 'manager',
|
2026-06-30 12:08:52 +03:00
|
|
|
|
'current_tariff_id' => $tariff->id,
|
2026-06-30 13:07:47 +03:00
|
|
|
|
'base_salary_rub' => '50000.00',
|
2026-06-30 12:08:52 +03:00
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
expect($user->id)->toBeInt()
|
|
|
|
|
|
->and($user->role)->toBe('manager')
|
|
|
|
|
|
->and($user->current_tariff_id)->toBe($tariff->id)
|
|
|
|
|
|
->and($user->fresh()->is_active)->toBeTrue(); // DB default=TRUE, fresh() синхронизирует
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
test('SalesUser::isHead() верен для role=head и false для role=manager', function () {
|
2026-06-30 13:07:47 +03:00
|
|
|
|
$head = makeSalesUser(['role' => 'head']);
|
2026-06-30 12:08:52 +03:00
|
|
|
|
$manager = makeSalesUser(['role' => 'manager']);
|
|
|
|
|
|
|
|
|
|
|
|
expect($head->isHead())->toBeTrue()
|
|
|
|
|
|
->and($manager->isHead())->toBeFalse();
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
test('SalesUser is_active приходит булевым', function () {
|
|
|
|
|
|
$user = makeSalesUser(['is_active' => true]);
|
|
|
|
|
|
|
|
|
|
|
|
expect($user->is_active)->toBeBool()->toBeTrue();
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// ── 3. SalesClientAssignment ───────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
test('SalesClientAssignment связывает пользователя с тенантом и tariff_params — массив', function () {
|
|
|
|
|
|
$tariff = makeSalesTariff(['kind' => 'percent_oborot', 'params' => ['percent' => 5]]);
|
2026-06-30 13:07:47 +03:00
|
|
|
|
$user = makeSalesUser(['current_tariff_id' => $tariff->id]);
|
2026-06-30 12:08:52 +03:00
|
|
|
|
$tenant = Tenant::factory()->create(); // реальный тенант (FK tenants.id)
|
|
|
|
|
|
|
|
|
|
|
|
$assignment = SalesClientAssignment::create([
|
|
|
|
|
|
'sales_user_id' => $user->id,
|
2026-06-30 13:07:47 +03:00
|
|
|
|
'tenant_id' => $tenant->id,
|
|
|
|
|
|
'tariff_id' => $tariff->id,
|
|
|
|
|
|
'tariff_kind' => $tariff->kind,
|
2026-06-30 12:08:52 +03:00
|
|
|
|
'tariff_params' => ['percent' => 5],
|
2026-06-30 13:07:47 +03:00
|
|
|
|
'assigned_at' => now(),
|
2026-06-30 12:08:52 +03:00
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
expect($assignment->id)->toBeInt()
|
|
|
|
|
|
->and($assignment->tariff_params)->toBeArray()
|
|
|
|
|
|
->and($assignment->tariff_params['percent'])->toBe(5)
|
2026-06-30 13:07:47 +03:00
|
|
|
|
->and($assignment->assigned_at)->toBeInstanceOf(Carbon::class);
|
2026-06-30 12:08:52 +03:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
test('SalesUser->assignments() возвращает HasMany-коллекцию', function () {
|
2026-06-30 13:07:47 +03:00
|
|
|
|
$user = makeSalesUser();
|
2026-06-30 12:08:52 +03:00
|
|
|
|
$tenant = Tenant::factory()->create();
|
|
|
|
|
|
|
|
|
|
|
|
SalesClientAssignment::create([
|
|
|
|
|
|
'sales_user_id' => $user->id,
|
2026-06-30 13:07:47 +03:00
|
|
|
|
'tenant_id' => $tenant->id,
|
2026-06-30 12:08:52 +03:00
|
|
|
|
'tariff_params' => [],
|
2026-06-30 13:07:47 +03:00
|
|
|
|
'assigned_at' => now(),
|
2026-06-30 12:08:52 +03:00
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
$relation = $user->assignments();
|
|
|
|
|
|
expect($relation)->toBeInstanceOf(HasMany::class);
|
|
|
|
|
|
|
|
|
|
|
|
$collection = $user->assignments;
|
|
|
|
|
|
expect($collection)->toHaveCount(1)
|
|
|
|
|
|
->and($collection->first())->toBeInstanceOf(SalesClientAssignment::class);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// ── 4. SalesAttachmentRequest ──────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
test('SalesAttachmentRequest создаётся со статусом pending', function () {
|
|
|
|
|
|
$user = makeSalesUser();
|
|
|
|
|
|
|
|
|
|
|
|
$req = SalesAttachmentRequest::create([
|
|
|
|
|
|
'sales_user_id' => $user->id,
|
2026-06-30 13:07:47 +03:00
|
|
|
|
'login_input' => 'client@example.com',
|
|
|
|
|
|
'status' => 'pending',
|
2026-06-30 12:08:52 +03:00
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
expect($req->id)->toBeInt()
|
|
|
|
|
|
->and($req->status)->toBe('pending')
|
|
|
|
|
|
->and($req->decided_at)->toBeNull();
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// ── 5. SalesPayout ─────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
test('SalesPayout создаётся, amount_rub — decimal, paid_on — date', function () {
|
|
|
|
|
|
$user = makeSalesUser();
|
|
|
|
|
|
|
|
|
|
|
|
$payout = SalesPayout::create([
|
|
|
|
|
|
'sales_user_id' => $user->id,
|
2026-06-30 13:07:47 +03:00
|
|
|
|
'amount_rub' => '12500.50',
|
|
|
|
|
|
'paid_on' => today(),
|
|
|
|
|
|
'created_by' => $user->id,
|
2026-06-30 12:08:52 +03:00
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
expect($payout->id)->toBeInt()
|
|
|
|
|
|
->and((float) $payout->amount_rub)->toBe(12500.50)
|
2026-06-30 13:07:47 +03:00
|
|
|
|
->and($payout->paid_on)->toBeInstanceOf(Carbon::class);
|
2026-06-30 12:08:52 +03:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
test('SalesPayout append-only: UPDATE бросает исключение', function () {
|
|
|
|
|
|
$user = makeSalesUser();
|
|
|
|
|
|
|
|
|
|
|
|
$payout = SalesPayout::create([
|
|
|
|
|
|
'sales_user_id' => $user->id,
|
2026-06-30 13:07:47 +03:00
|
|
|
|
'amount_rub' => '100.00',
|
|
|
|
|
|
'paid_on' => today(),
|
|
|
|
|
|
'created_by' => $user->id,
|
2026-06-30 12:08:52 +03:00
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
expect(fn () => $payout->update(['amount_rub' => '999.00']))
|
2026-06-30 13:07:47 +03:00
|
|
|
|
->toThrow(QueryException::class);
|
2026-06-30 12:08:52 +03:00
|
|
|
|
});
|