This commit is contained in:
2026-11-03 11:51:44 +02:00
parent badaada043
commit e753f7a0a4
309 changed files with 14537 additions and 529 deletions
+71 -4
View File
@@ -21,13 +21,81 @@ class ReplenishmentQueue
public function status(User $actor): array
{
abort_unless(auth()->id() === $actor->id, 403);
$observation = $this->observation();
return ['recoveryReady' => $this->ready(), 'scanMinutes' => config('replenishment.scan_minutes'),
'schedulerStatus' => $observation['schedulerStatus'], 'schedulerRecent' => $observation['schedulerRecent'],
'work' => $this->ready() ? $this->workStatus($actor->tenant_id) : null];
}
/** Workdesk sources, with a bound observation required for technical monitoring.
* @return array<string,mixed>
*/
public function health(): array
{
abort_unless(app()->runningInConsole() && ! auth()->check() && ! request()->hasSession(), 403);
return DB::transaction(function () {
if (DB::getDriverName() === 'pgsql' && DB::transactionLevel() === 1) {
DB::statement('SET TRANSACTION ISOLATION LEVEL REPEATABLE READ READ ONLY');
}
$ready = $this->ready();
$observation = $this->observation();
$work = $ready ? $this->workStatus(null, true) : null;
$reasons = [];
if (! $ready) {
$reasons[] = 'not_installed';
}
if (! $observation['schedulerRecent']) {
$reasons[] = 'not_recent';
}
if (($observation['schedulerStatus']['result'] ?? null) !== 'passed') {
$reasons[] = 'last_run_not_successful';
}
if (! $observation['schedulerBound']) {
$reasons[] = 'unbound_observation';
}
if ((int) ($work->pending ?? 0) > 0) {
$reasons[] = 'pending_signals';
}
if ((int) ($work->failed ?? 0) > 0) {
$reasons[] = 'failed_work';
}
return ['checked_at' => now()->toIso8601String(), 'ok' => $reasons === [], 'reasons' => $reasons,
'recovery_ready' => $ready, 'scheduler_recent' => $observation['schedulerRecent'],
'last_result' => $observation['schedulerStatus']['result'] ?? null,
'last_finished_at' => $observation['schedulerStatus']['finished_at'] ?? null,
'work' => $work === null ? null : ['total' => (int) $work->total, 'pending' => (int) ($work->pending ?? 0), 'failed' => (int) ($work->failed ?? 0)]];
});
}
private function workStatus(?int $tenant, bool $global = false): \stdClass
{
return DB::table('replenishment_work')->when(! $global, fn ($query) => $query->where('tenant_id', $tenant))
->selectRaw('COUNT(*) as total, SUM(CASE WHEN generation > processed_generation THEN 1 ELSE 0 END) as pending, SUM(CASE WHEN failures > 0 THEN 1 ELSE 0 END) as failed, MAX(last_run_at) as last_run_at')->first() ?? throw new \LogicException('Estado técnico indisponível.');
}
/** @return array{schedulerStatus:array<string,mixed>|null,schedulerRecent:bool,schedulerBound:bool} */
private function observation(): array
{
$scheduler = null;
$recent = false;
$path = storage_path('framework/replenishment-status.json');
if (app()->environment('payable-testing') && is_file($path)) {
$bound = false;
$path = storage_path('framework/replenishment-command-status.json');
if (! is_file($path) && app()->environment('payable-testing')) {
$path = storage_path('framework/replenishment-status.json');
}
if (is_file($path)) {
$text = file_get_contents($path);
$data = $text === false ? null : json_decode($text, true);
if (is_array($data) && is_string($data['finished_at'] ?? null) && in_array($data['result'] ?? null, ['passed', 'retry_pending'], true)) {
if (array_key_exists('installation', $data)) {
if (! is_string($data['installation']) || ! hash_equals(app(ReplenishmentHeartbeat::class)->fingerprint(), $data['installation'])) {
return ['schedulerStatus' => null, 'schedulerRecent' => false, 'schedulerBound' => false];
}
$bound = true;
}
try {
$finished = CarbonImmutable::parse($data['finished_at']);
$recent = $finished->greaterThanOrEqualTo(now()->subMinutes(3)) && $finished->lessThanOrEqualTo(now()->addMinute());
@@ -37,8 +105,7 @@ class ReplenishmentQueue
}
}
return ['recoveryReady' => $this->ready(), 'scanMinutes' => config('replenishment.scan_minutes'), 'schedulerStatus' => $scheduler, 'schedulerRecent' => $recent,
'work' => $this->ready() ? DB::table('replenishment_work')->where('tenant_id', $actor->tenant_id)->selectRaw('COUNT(*) as total, SUM(CASE WHEN generation > processed_generation THEN 1 ELSE 0 END) as pending, SUM(CASE WHEN failures > 0 THEN 1 ELSE 0 END) as failed, MAX(last_run_at) as last_run_at')->first() : null];
return ['schedulerStatus' => $scheduler, 'schedulerRecent' => $recent, 'schedulerBound' => $bound];
}
public function ready(): bool