947cb3403a
Просьба владельца 01.08.2026: «убери вчера, наверх создай просроченные,
сегодня, завтра и т.д. и проверь что они правильно привязаны и реально
работают»; во втором режиме «убери завтра — завтра у тебя не может быть».
У каждого режима теперь СВОЙ список сроков, они не пересекаются:
Что надо сделать (вперёд): Просроченные / Сегодня / Завтра /
Ближайшие 7 дней / Ближайшие 30 дней / Произвольный
Что менялось (назад): Сегодня / Вчера / 7 дней / 30 дней / Произвольный
Каждый пункт показывает ровно то, что на нём написано. Раньше просроченное
подмешивалось в ЛЮБОЙ выбранный период, и «Сегодня» показывало не только
сегодняшнее — теперь это отдельный первый пункт (period=overdue), а подпись
«плюс всё просроченное» убрана за ненадобностью.
Вторая, невидимая глазом поломка: «7/30 дней» в режиме планов считались
НАЗАД (d7/d30) — «что надо сделать за прошедшую неделю». Добавлены зеркала
next7/next30 в SalesPeriodResolver.
Срок из чужого набора («что менялось завтра») сервер отвергает с 422, а не
подменяет молча текущим месяцем, как делал прежний резолвер по умолчанию.
Проверено:
- сервер: 30/30 (фильтр + резолвер), весь отдел продаж 498/498;
- фронт: 1739/1739 весь набор;
- приёмка вырезанием — подложил поломку в оба места, оба набора покраснели;
- глазами в браузере 1920×1080: списки сроков в обоих режимах, «Просроченные»
дают ровно забытые карточки, «Завтра» — ровно завтрашнюю (скрины 08–12).
Попутно починен чужой протухший тест advertising-channels: Телеграм давно
стал живым роутом, а тест продолжал считать его заглушкой и был красным.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 975175750a)
177 lines
7.5 KiB
PHP
177 lines
7.5 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Services\Sales\SalesPeriodRange;
|
|
use App\Services\Sales\SalesPeriodResolver;
|
|
use Carbon\CarbonImmutable;
|
|
|
|
// Заморозка времени: 15 июня 2026, 12:00 МСК
|
|
beforeEach(function (): void {
|
|
CarbonImmutable::setTestNow(
|
|
CarbonImmutable::parse('2026-06-15 12:00:00', 'Europe/Moscow'),
|
|
);
|
|
});
|
|
|
|
afterEach(function (): void {
|
|
CarbonImmutable::setTestNow();
|
|
});
|
|
|
|
// ─── kind=this ────────────────────────────────────────────────────────────────
|
|
|
|
it('kind=this returns current month range (June 2026)', function (): void {
|
|
$resolver = new SalesPeriodResolver;
|
|
$range = $resolver->resolve(['kind' => 'this']);
|
|
|
|
expect($range)->toBeInstanceOf(SalesPeriodRange::class);
|
|
expect($range->start->format('Y-m-d H:i:s'))->toBe('2026-06-01 00:00:00');
|
|
expect($range->end->format('Y-m-d H:i:s'))->toBe('2026-06-30 23:59:59');
|
|
});
|
|
|
|
// ─── kind=prev ────────────────────────────────────────────────────────────────
|
|
|
|
it('kind=prev returns previous month range (May 2026)', function (): void {
|
|
$resolver = new SalesPeriodResolver;
|
|
$range = $resolver->resolve(['kind' => 'prev']);
|
|
|
|
expect($range->start->format('Y-m-d H:i:s'))->toBe('2026-05-01 00:00:00');
|
|
expect($range->end->format('Y-m-d H:i:s'))->toBe('2026-05-31 23:59:59');
|
|
});
|
|
|
|
// ─── kind=prev2 ───────────────────────────────────────────────────────────────
|
|
|
|
it('kind=prev2 returns month-before-previous range (April 2026)', function (): void {
|
|
$resolver = new SalesPeriodResolver;
|
|
$range = $resolver->resolve(['kind' => 'prev2']);
|
|
|
|
expect($range->start->format('Y-m-d H:i:s'))->toBe('2026-04-01 00:00:00');
|
|
expect($range->end->format('Y-m-d H:i:s'))->toBe('2026-04-30 23:59:59');
|
|
});
|
|
|
|
// ─── kind=custom ──────────────────────────────────────────────────────────────
|
|
|
|
it('kind=custom returns exact from/to bounds', function (): void {
|
|
$resolver = new SalesPeriodResolver;
|
|
$range = $resolver->resolve([
|
|
'kind' => 'custom',
|
|
'from' => '2026-03-10',
|
|
'to' => '2026-05-20',
|
|
]);
|
|
|
|
expect($range->start->format('Y-m-d H:i:s'))->toBe('2026-03-10 00:00:00');
|
|
expect($range->end->format('Y-m-d H:i:s'))->toBe('2026-05-20 23:59:59');
|
|
});
|
|
|
|
it('kind=custom: monthsIn returns all three month-starts for Mar-May span', function (): void {
|
|
$resolver = new SalesPeriodResolver;
|
|
$range = $resolver->resolve([
|
|
'kind' => 'custom',
|
|
'from' => '2026-03-10',
|
|
'to' => '2026-05-20',
|
|
]);
|
|
|
|
$months = $resolver->monthsIn($range);
|
|
|
|
expect($months)->toHaveCount(3);
|
|
expect($months[0]->format('Y-m-d'))->toBe('2026-03-01');
|
|
expect($months[1]->format('Y-m-d'))->toBe('2026-04-01');
|
|
expect($months[2]->format('Y-m-d'))->toBe('2026-05-01');
|
|
});
|
|
|
|
it('kind=custom: throws InvalidArgumentException when from > to', function (): void {
|
|
$resolver = new SalesPeriodResolver;
|
|
expect(fn () => $resolver->resolve([
|
|
'kind' => 'custom',
|
|
'from' => '2026-05-20',
|
|
'to' => '2026-03-10',
|
|
]))->toThrow(InvalidArgumentException::class);
|
|
});
|
|
|
|
it('kind=custom: throws InvalidArgumentException when from is missing', function (): void {
|
|
$resolver = new SalesPeriodResolver;
|
|
expect(fn () => $resolver->resolve([
|
|
'kind' => 'custom',
|
|
'to' => '2026-05-20',
|
|
]))->toThrow(InvalidArgumentException::class);
|
|
});
|
|
|
|
it('kind=custom: throws InvalidArgumentException when to is missing', function (): void {
|
|
$resolver = new SalesPeriodResolver;
|
|
expect(fn () => $resolver->resolve([
|
|
'kind' => 'custom',
|
|
'from' => '2026-03-10',
|
|
]))->toThrow(InvalidArgumentException::class);
|
|
});
|
|
|
|
// ─── monthsIn ─────────────────────────────────────────────────────────────────
|
|
|
|
it('monthsIn for single-month "this" range returns one entry (June 2026)', function (): void {
|
|
$resolver = new SalesPeriodResolver;
|
|
$range = $resolver->resolve(['kind' => 'this']);
|
|
|
|
$months = $resolver->monthsIn($range);
|
|
|
|
expect($months)->toHaveCount(1);
|
|
expect($months[0]->format('Y-m-d'))->toBe('2026-06-01');
|
|
});
|
|
|
|
// ─── timezone ─────────────────────────────────────────────────────────────────
|
|
|
|
it('range start carries Europe/Moscow timezone', function (): void {
|
|
$resolver = new SalesPeriodResolver;
|
|
$range = $resolver->resolve(['kind' => 'this']);
|
|
|
|
expect($range->start->timezoneName)->toBe('Europe/Moscow');
|
|
});
|
|
|
|
// ─── новые периоды: сегодня / вчера / 7 дней / 30 дней ────────────────────────
|
|
|
|
it('kind=today — только сегодняшний день', function (): void {
|
|
$range = (new SalesPeriodResolver)->resolve(['kind' => 'today']);
|
|
|
|
expect($range->start->format('Y-m-d H:i:s'))->toBe('2026-06-15 00:00:00');
|
|
expect($range->end->format('Y-m-d H:i:s'))->toBe('2026-06-15 23:59:59');
|
|
});
|
|
|
|
it('kind=yesterday — только вчерашний день', function (): void {
|
|
$range = (new SalesPeriodResolver)->resolve(['kind' => 'yesterday']);
|
|
|
|
expect($range->start->format('Y-m-d H:i:s'))->toBe('2026-06-14 00:00:00');
|
|
expect($range->end->format('Y-m-d H:i:s'))->toBe('2026-06-14 23:59:59');
|
|
});
|
|
|
|
it('kind=tomorrow — только завтрашний день', function (): void {
|
|
$range = (new SalesPeriodResolver)->resolve(['kind' => 'tomorrow']);
|
|
|
|
expect($range->start->format('Y-m-d H:i:s'))->toBe('2026-06-16 00:00:00');
|
|
expect($range->end->format('Y-m-d H:i:s'))->toBe('2026-06-16 23:59:59');
|
|
});
|
|
|
|
it('kind=next7 — ближайшие 7 дней ВПЕРЁД, включая сегодня', function (): void {
|
|
$range = (new SalesPeriodResolver)->resolve(['kind' => 'next7']);
|
|
|
|
expect($range->start->format('Y-m-d H:i:s'))->toBe('2026-06-15 00:00:00');
|
|
expect($range->end->format('Y-m-d H:i:s'))->toBe('2026-06-21 23:59:59');
|
|
});
|
|
|
|
it('kind=next30 — ближайшие 30 дней ВПЕРЁД, включая сегодня', function (): void {
|
|
$range = (new SalesPeriodResolver)->resolve(['kind' => 'next30']);
|
|
|
|
expect($range->start->format('Y-m-d H:i:s'))->toBe('2026-06-15 00:00:00');
|
|
expect($range->end->format('Y-m-d H:i:s'))->toBe('2026-07-14 23:59:59');
|
|
});
|
|
|
|
it('kind=d7 — последние 7 дней, включая сегодня', function (): void {
|
|
$range = (new SalesPeriodResolver)->resolve(['kind' => 'd7']);
|
|
|
|
expect($range->start->format('Y-m-d H:i:s'))->toBe('2026-06-09 00:00:00');
|
|
expect($range->end->format('Y-m-d H:i:s'))->toBe('2026-06-15 23:59:59');
|
|
});
|
|
|
|
it('kind=d30 — последние 30 дней, включая сегодня', function (): void {
|
|
$range = (new SalesPeriodResolver)->resolve(['kind' => 'd30']);
|
|
|
|
expect($range->start->format('Y-m-d H:i:s'))->toBe('2026-05-17 00:00:00');
|
|
expect($range->end->format('Y-m-d H:i:s'))->toBe('2026-06-15 23:59:59');
|
|
});
|