e9edb206c8
feat(sales): кабинет начальника — единый период (без 500) + пополнения баланса Экраны начальника отстали от вчерашних правок кабинета менеджера (§16–§21). 1. Живой баг: «Произвольный» период до выбора дат ронял запрос в 500 на пяти точках из шести (сводка отдела, доход, результативность, выплаты, тарифы). Разбор периода вынесен в трейт ResolvesSalesPeriod: 422 вместо падения, период по умолчанию d30 вместо this — как показывает сам PeriodPicker. 2. Пополнения баланса (topup_rub) добавлены в dashboard/overview (kpi + строки менеджеров) и managers/performance. На экранах: плашка «Пополнили баланс» в сводке отдела и колонка «Пополнили ₽» в обеих таблицах результативности. Из подписей убрано «(мес)» — период больше не месяц. 3. «Воронка отдела» правок не потребовала: она рендерит те же доску и карточку, что экран менеджера, а карточка сама грузит журнал и сохраняет контакты. Тесты: SalesPeriodRequestTest проходит по всем шести точкам сразу. Pest 263/263, Vitest 1420/1420, vue-tsc чист. Спека — §22. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> @
100 lines
3.6 KiB
PHP
100 lines
3.6 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
use App\Models\SalesUser;
|
||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||
|
||
/**
|
||
* TDD: единый разбор периода на ВСЕХ экранах портала продаж.
|
||
*
|
||
* Владелец пожаловался «выборка по срокам не работает»: выбрав «Произвольный»,
|
||
* менеджер отправлял period=custom БЕЗ дат, резолвер бросал InvalidArgumentException,
|
||
* и экран получал 500. На «Моих клиентах» это уже починено (422), а экраны
|
||
* начальника (сводка отдела, результативность, тарифы, выплаты) и «Мой доход»
|
||
* продолжали падать.
|
||
*
|
||
* Здесь фиксируем инвариант для всех точек сразу:
|
||
* 1. period=custom без дат → 422 с понятным текстом, а не 500.
|
||
* 2. from позже to → 422.
|
||
* 3. Новые периоды (today/yesterday/d7/d30) принимаются везде.
|
||
* 4. Период по умолчанию — d30 (как в PeriodPicker), а не месяц.
|
||
*
|
||
* Изоляция: DatabaseTransactions. DB_DATABASE=liderra_testing обязателен.
|
||
*/
|
||
uses(DatabaseTransactions::class);
|
||
|
||
function per_head(): SalesUser
|
||
{
|
||
return SalesUser::create([
|
||
'name' => 'Начальник '.uniqid(),
|
||
'email' => 'perhead'.uniqid().'@sales.local',
|
||
'password' => bcrypt('secret'),
|
||
'role' => 'head',
|
||
'is_active' => true,
|
||
]);
|
||
}
|
||
|
||
/**
|
||
* Все GET-точки, которые принимают период. Начальник видит все,
|
||
* поэтому одним пользователем покрываем и менеджерские, и boss-only.
|
||
*
|
||
* @return list<string>
|
||
*/
|
||
function per_endpoints(): array
|
||
{
|
||
return [
|
||
'/api/sales/clients',
|
||
'/api/sales/dashboard/overview',
|
||
'/api/sales/income',
|
||
'/api/sales/managers/performance',
|
||
'/api/sales/payouts/remaining',
|
||
'/api/sales/tariffs',
|
||
];
|
||
}
|
||
|
||
it('произвольный период без дат отвечает 422, а не падает', function () {
|
||
$head = per_head();
|
||
|
||
foreach (per_endpoints() as $url) {
|
||
$this->actingAs($head, 'sales')
|
||
->getJson($url.'?period=custom')
|
||
->assertStatus(422)
|
||
->assertJsonValidationErrors('period');
|
||
}
|
||
});
|
||
|
||
it('произвольный период с одной датой отвечает 422', function () {
|
||
$head = per_head();
|
||
|
||
foreach (per_endpoints() as $url) {
|
||
$this->actingAs($head, 'sales')
|
||
->getJson($url.'?period=custom&from=2026-07-01')
|
||
->assertStatus(422)
|
||
->assertJsonValidationErrors('period');
|
||
}
|
||
});
|
||
|
||
it('перевёрнутый произвольный период отвечает 422', function () {
|
||
$head = per_head();
|
||
|
||
foreach (per_endpoints() as $url) {
|
||
$this->actingAs($head, 'sales')
|
||
->getJson($url.'?period=custom&from=2026-07-10&to=2026-07-01')
|
||
->assertStatus(422)
|
||
->assertJsonValidationErrors('period');
|
||
}
|
||
});
|
||
|
||
it('новые периоды календаря принимаются на всех экранах', function () {
|
||
$head = per_head();
|
||
|
||
foreach (per_endpoints() as $url) {
|
||
foreach (['today', 'yesterday', 'd7', 'd30'] as $kind) {
|
||
$this->actingAs($head, 'sales')
|
||
->getJson($url.'?period='.$kind)
|
||
->assertOk();
|
||
}
|
||
}
|
||
});
|