deca81c2d7
CsvReconcileJob — hourly резерв-канал приёма лидов через CSV-экспорт поставщика: - Cache::lock 600s (overlap protection). - Окно [now-25h, now] (запас 1ч над hourly cron). - INSERT supplier_csv_reconcile_log status='running' → 'ok' | 'drift_alert' | 'failed'. - Missing vids → INSERT supplier_leads (platform extracted из project, source='csv_recovery', recovered_from_csv_at=now) + dispatch RouteSupplierLeadJob. - Drift > 5% → CsvDriftAlertMail на services.supplier.alert_email. - UNIQUE-vid conflict → log + skip (idempotency). - На SupplierTransientException/любой Throwable → status='failed', error_message, rethrow. CsvDriftAlertMail + blade-template emails/csv_drift_alert. routes/console.php — Schedule::job(new CsvReconcileJob)->hourly(). config/services.php — supplier.alert_email default 'ops@liderra.ru'. 6 integration tests (CsvReconcileJobTest) + Schedule registration test (через Http::fake + Bus::fake + Mail::fake + SharesSupplierPdo trait для cross-connection). Parallel-test race fix: putSupplierSession() вызывается прямо перед SUT, потому что Sync/Cleanup tests'ы в afterEach делают forget('supplier:session'), а в --parallel режиме воркеры делят Redis DB+prefix. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
53 lines
2.6 KiB
PHP
53 lines
2.6 KiB
PHP
<?php
|
|
|
|
use App\Jobs\Supplier\CleanupInactiveSupplierProjectsJob;
|
|
use App\Jobs\Supplier\CsvReconcileJob;
|
|
use App\Jobs\Supplier\RefreshSupplierSessionJob;
|
|
use App\Jobs\Supplier\SyncSupplierProjectsJob;
|
|
use Illuminate\Foundation\Inspiring;
|
|
use Illuminate\Support\Facades\Artisan;
|
|
use Illuminate\Support\Facades\Schedule;
|
|
|
|
Artisan::command('inspire', function () {
|
|
$this->comment(Inspiring::quote());
|
|
})->purpose('Display an inspiring quote');
|
|
|
|
// Spec §6.1: ежедневный сброс projects.delivered_today=0 в 00:00 МСК.
|
|
// delivered_in_month НЕ трогаем — это месячный счётчик, отдельный cron Plan 4.
|
|
//
|
|
// NB: без `withoutOverlapping()` — операция идемпотентна (UPDATE WHERE delivered_today <> 0)
|
|
// и завершается за < 1 сек на любом ожидаемом объёме, overlap физически невозможен.
|
|
// Кроме того, `withoutOverlapping` требует таблицу `cache_locks`, которой в нашей
|
|
// schema.sql нет (Laravel-default-миграции удалены, см. project_state.md фаза 1).
|
|
Schedule::command('projects:reset-delivered-today')
|
|
->dailyAt('00:00')
|
|
->timezone('Europe/Moscow');
|
|
|
|
// Plan 4: monthly reset 1-го числа в 00:00 МСК для tier-lookup в LedgerService.
|
|
Schedule::command('projects:reset-monthly')
|
|
->monthlyOn(1, '00:00')
|
|
->timezone('Europe/Moscow');
|
|
|
|
// Plan 3 Task 8: 5 Schedule entries для supplier-flow.
|
|
//
|
|
// NB: ->onOneServer() требует cache_locks таблицу, которой у нас нет
|
|
// (см. project_state.md фаза 1). Операции идемпотентны: SyncSupplierProjectsJob
|
|
// делает diff'ы (skip-no-diff), CleanupJob — UPDATE WHERE conditions, RefreshSession
|
|
// — Cache::lock guard внутри handle, RetryFailedSupplierJobs — WHERE retried_at
|
|
// фильтр. На multi-server prod может потребовать cache_locks таблицу.
|
|
Schedule::job(new RefreshSupplierSessionJob)->hourly();
|
|
Schedule::job(new RefreshSupplierSessionJob)
|
|
->dailyAt('20:15')
|
|
->timezone('Europe/Moscow');
|
|
Schedule::job(new SyncSupplierProjectsJob)
|
|
->dailyAt('20:30')
|
|
->timezone('Europe/Moscow');
|
|
Schedule::job(new CleanupInactiveSupplierProjectsJob)
|
|
->dailyAt('02:00')
|
|
->timezone('Europe/Moscow');
|
|
Schedule::command('supplier:retry-failed')->hourly();
|
|
|
|
// Plan 4 Task 8: hourly CSV reconciliation (резерв-канал приёма лидов).
|
|
// Spec: docs/superpowers/specs/2026-05-11-plan4-billing-csv-admin-design.md §5.3
|
|
Schedule::job(new CsvReconcileJob)->hourly();
|