Дмитрий
|
9765ed760d
|
phase2(reports-stage3): retry/cancel/destroy + reports:cleanup-expired cron
- ReportJobController +3 endpoints под auth:sanctum:
- POST /api/reports/jobs/{id}/retry — CTO-6: только owner+failed, max 3 попытки
(parameters.retry_count), окно 7 дней с created_at, квота CTO-7 учитывается;
создаёт НОВЫЙ ReportJob (parameters.retry_of=original.id) + dispatch.
- POST /api/reports/jobs/{id}/cancel — только owner+pending; status=failed +
error_message=«Отменено пользователем.» + finished_at=NOW.
- DELETE /api/reports/jobs/{id} — только owner+terminal (done|failed); удаляет
файл из disk('local') + row.
- toResource +3 поля: is_expired (expires_at < NOW), retry_count, retry_max=3.
- App\Console\Commands\ReportsCleanupExpired (cron `reports:cleanup-expired`):
где status='done' AND expires_at < NOW AND file_path IS NOT NULL → delete file
+ UPDATE file_path=NULL. CTO-10: status='done' СОХРАНЯЕТСЯ. failed-jobs
игнорируются. --dry-run + --limit=1000. Запуск ежесуточно через Task Scheduler.
- routes/web.php: новые 3 routes под существующим prefix /api/reports/jobs.
- Pest +21 в ReportLifecycleTest.php (всего 403/403, +21 от 382, 1343 assertions):
retry 8 (404 unknown/foreign / 403 не владелец / 422 не failed / success+new+
retry_count=1+retry_of / 422 max retries / 422 окно 7 дней / 422 квота 3) +
cancel 4 (404 / 422 не pending / success / 403 не владелец) + destroy 5
(404 / 422 pending / 403 не владелец / success+file / success+file_path=NULL)
+ index +1 (is_expired/retry_count/retry_max в response) + cron 3 (удаление
expired+CTO-10 status сохраняется / --dry-run / failed игнорируются).
- phpstan-baseline регенерирован.
Этап 3/4 эпика Reports backend (закрыт). Этап 4: frontend integration —
заменить mock в ReportsView на реальный API + UI кнопки retry/cancel/delete.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-05-09 13:44:09 +03:00 |
|
Дмитрий
|
1a6a74c1a0
|
phase2(reports-stage2): provider+formatter архитектура + XLSX/JSON/PDF-stub
- Реструктура Services/Reports: вместо `Generator` per (type×format) комбинации
(16 классов) разделено на 4 Providers + 4 Formatters (8 классов).
- App\Services\Reports\Providers\ReportDataProvider interface + DealsExportProvider
(вынесен из старого DealsExportCsvGenerator; возвращает headers + rows).
- App\Services\Reports\Formatters\ReportFormatter interface + 4 реализации:
- CsvFormatter — Excel-friendly (BOM + ; + \r\n + escape).
- XlsxFormatter — PhpSpreadsheet 5.x (A1-нотация + bold headers + auto-size cols).
- JsonFormatter — pretty + UNESCAPED_UNICODE (кириллица в исходном виде).
- PdfStubFormatter — Post-MVP, throw RuntimeException.
- ReportGeneratorRegistry перепаспортирован: provider(type) + formatter(format).
- GenerateReportJob: вызывает provider->headers/rows + formatter->format вместо
старого generator->generate.
- Удалено: DealsExportCsvGenerator, ReportGenerator interface, GenerationResult DTO.
- Pest +3 (всего 382/382, +3 от 379, 1297 assertions): xlsx → done с XLSX-magic-bytes
PK\x03\x04; json → done + decoded ['rows', 'headers']; pdf → failed «Post-MVP»;
managers_summary (не реализован) → failed.
- phpstan-baseline регенерирован.
Этап 2/4 эпика Reports backend (закрыт). Этап 2b: 3 оставшихся типа провайдеров
(managers_summary / sources_summary / billing_summary) — каждый × 4 формата без
изменений в архитектуре. Этап 3: retry/cancel/delete + retention cron.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-05-09 13:39:32 +03:00 |
|
Дмитрий
|
19f319cd5d
|
phase2(reports-stage1): ReportJob model + GenerateReportJob + API + deals_export csv
- ReportJob Eloquent (schema §13.5 report_jobs): status pending/processing/done/failed,
parameters JSONB (format/date_from/date_to/project_id?/manager_id?), constants
TYPES + FORMATS, helpers isActive/isDone/isFailed.
- ReportJobFactory + states processing/done/failed.
- App\Services\Reports\* пакет: ReportGenerator interface, GenerationResult DTO,
ReportGeneratorRegistry с резолвом по (type,format), DealsExportCsvGenerator
(Excel-friendly CSV: BOM, ; separator, \r\n, escape; deals JOIN projects/users/
supplier_lead_costs за date_from..date_to, soft-deleted скрыты).
- App\Jobs\GenerateReportJob: tries=1 (auto-retry отключён, retry через UI кнопку
CTO-6); меняет status pending → processing → done|failed, заполняет file_path/
file_size/generation_seconds/finished_at/expires_at (=NOW+30д).
- App\Http\Controllers\Api\ReportJobController под auth:sanctum:
- GET /api/reports/jobs?status=&limit=&offset= → jobs+total+counts+quota
- GET /api/reports/jobs/{id}
- POST /api/reports/jobs (квота CTO-7: max 3 active per tenant → 422)
- dispatch GenerateReportJob (sync на dev → файл готов сразу).
- Storage local-disk на dev (storage/app/reports/{tenant_id}/{job_id}.csv);
на prod заменим на s3 (Yandex Object Storage) отдельным коммитом.
- Pest +20 в tests/Feature/Reports/ReportJobControllerTest.php (всего 379/379,
1280 assertions): 401 без auth / GET пустой+only-own+ORDER+filter+counts+limit/
show success+404 own/foreign / store 422 (без полей/неизвестный type/date_to<from)/
dispatch / sync queue → done с file (BOM проверен) / unsupported format → failed/
квота 3 → 422 на 4-м / квота не считает done+failed / квота per-tenant.
- phpstan-baseline регенерирован (+1 ignored для Factory typing).
Этап 1/4 эпика Reports backend (закрыт). Этап 2: 4 типа × 4 формата.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-05-09 13:34:03 +03:00 |
|