with(['tenant.workspace', 'sourceReviewPack.environmentReview.sections', 'operationRun', 'generatedBy']) ->find($this->storedReportId); $operationRun = OperationRun::query()->find($this->operationRunId); if (! $report instanceof StoredReport || ! $operationRun instanceof OperationRun) { Log::warning('GenerateManagementReportPdfJob: missing records', [ 'stored_report_id' => $this->storedReportId, 'operation_run_id' => $this->operationRunId, ]); return; } $tenant = $report->tenant; $reviewPack = $report->sourceReviewPack; if (! $tenant instanceof ManagedEnvironment || ! $reviewPack instanceof ReviewPack) { $this->markFailed($report, $operationRun, $operationRunService, $auditLogger, 'source_missing', 'Management PDF source records are unavailable.'); return; } $operationRun = $operationRunService->updateRun($operationRun, OperationRunStatus::Running->value, OperationRunOutcome::Pending->value, [ 'total' => 1, 'processed' => 0, 'succeeded' => 0, 'failed' => 0, ]); $report->update([ 'status' => StoredReport::STATUS_GENERATING, 'payload' => array_replace_recursive(is_array($report->payload) ? $report->payload : [], [ 'state' => StoredReport::STATUS_GENERATING, 'started_at' => now()->toIso8601String(), ]), ]); try { try { $payload = $payloadBuilder->build($reviewPack); } catch (InvalidArgumentException $exception) { $this->markBlocked( report: $report, operationRun: $operationRun, operationRunService: $operationRunService, auditLogger: $auditLogger, code: $this->blockedReasonCode($reviewPack, $exception), message: $exception->getMessage(), ); return; } $result = $renderer->render($payload, $this->correlationId($report, $operationRun)); if ($result->failed() || $result->pdfBytes === null) { $this->markFailed( report: $report, operationRun: $operationRun, operationRunService: $operationRunService, auditLogger: $auditLogger, code: $result->failureCode ?? 'renderer_failed', message: $result->safeMessage ?? 'PDF renderer failed to create the document.', payload: $payload, ); return; } $this->storePdf($report, $operationRun, $tenant, $payload, $result->pdfBytes, $operationRunService, $auditLogger); } catch (Throwable $exception) { $this->markFailed( report: $report, operationRun: $operationRun, operationRunService: $operationRunService, auditLogger: $auditLogger, code: 'generation_exception', message: 'Management report PDF generation failed.', ); throw $exception; } } /** * @param array $payload */ private function storePdf( StoredReport $report, OperationRun $operationRun, ManagedEnvironment $tenant, array $payload, string $pdfBytes, OperationRunService $operationRunService, WorkspaceAuditLogger $auditLogger, ): void { $filePath = sprintf( 'management-reports/%s/review-pack-%d-%s-%d.pdf', $this->safePathSegment((string) ($tenant->external_id ?: $tenant->getKey())), (int) $report->source_review_pack_id, now()->format('YmdHis'), (int) $report->getKey(), ); try { $stored = Storage::disk('exports')->put($filePath, $pdfBytes); } catch (Throwable $exception) { $this->markFailed( report: $report, operationRun: $operationRun, operationRunService: $operationRunService, auditLogger: $auditLogger, code: 'storage_failed', message: 'Management report PDF storage failed.', payload: $payload, ); throw $exception; } if (! $stored) { $this->markFailed( report: $report, operationRun: $operationRun, operationRunService: $operationRunService, auditLogger: $auditLogger, code: 'storage_failed', message: 'Management report PDF storage failed.', payload: $payload, ); return; } $sha256 = hash('sha256', $pdfBytes); $report->update([ 'status' => StoredReport::STATUS_READY, 'payload' => array_replace_recursive($payload, [ 'state' => StoredReport::STATUS_READY, 'artifact' => [ 'file_disk' => 'exports', 'file_path' => $filePath, 'file_size' => strlen($pdfBytes), 'sha256' => $sha256, ], ]), 'file_disk' => 'exports', 'file_path' => $filePath, 'file_size' => strlen($pdfBytes), 'sha256' => $sha256, 'generated_at' => now(), ]); $operationRunService->updateRun( $operationRun, status: OperationRunStatus::Completed->value, outcome: OperationRunOutcome::Succeeded->value, summaryCounts: [ 'total' => 1, 'processed' => 1, 'succeeded' => 1, 'failed' => 0, 'report_created' => 1, ], ); $this->audit($report->refresh(), $operationRun, $auditLogger, AuditActionId::ManagementReportPdfGenerated, 'success', 'Management report PDF generated.'); } /** * @param array|null $payload */ private function markFailed( StoredReport $report, OperationRun $operationRun, OperationRunService $operationRunService, WorkspaceAuditLogger $auditLogger, string $code, string $message, ?array $payload = null, ): void { $report->update([ 'status' => StoredReport::STATUS_FAILED, 'payload' => array_replace_recursive($payload ?? (is_array($report->payload) ? $report->payload : []), [ 'state' => StoredReport::STATUS_FAILED, 'failure' => [ 'code' => $code, 'message' => $message, ], ]), ]); $operationRunService->updateRun( $operationRun, status: OperationRunStatus::Completed->value, outcome: OperationRunOutcome::Failed->value, summaryCounts: [ 'total' => 1, 'processed' => 1, 'succeeded' => 0, 'failed' => 1, ], failures: [ [ 'code' => 'management_report_pdf.'.$code, 'message' => $message, ], ], ); $this->audit($report->refresh(), $operationRun, $auditLogger, AuditActionId::ManagementReportPdfGenerationFailed, 'failed', $message, [ 'failure_code' => $code, ]); } private function blockedReasonCode(ReviewPack $reviewPack, InvalidArgumentException $exception): string { try { $decision = ManagementReportPdfPayloadBuilder::customerExecutiveDisclosureDecision($reviewPack); if ((bool) ($decision['is_blocked'] ?? false) && filled($decision['reason_code'] ?? null)) { return (string) $decision['reason_code']; } } catch (Throwable) { // Fall through to message-based source blockers below. } $message = $exception->getMessage(); return match (true) { str_contains($message, 'current review pack') => 'review_pack_not_current', str_contains($message, 'tenant, workspace, and released review') => 'source_missing', str_contains($message, 'customer executive profile') => 'management_report_pdf_profile_invalid', str_contains($message, 'disclosure policy') => 'disclosure_blocked', default => 'generation_blocked', }; } /** * @param array|null $payload */ private function markBlocked( StoredReport $report, OperationRun $operationRun, OperationRunService $operationRunService, WorkspaceAuditLogger $auditLogger, string $code, string $message, ?array $payload = null, ): void { $report->update([ 'status' => StoredReport::STATUS_FAILED, 'payload' => array_replace_recursive($payload ?? (is_array($report->payload) ? $report->payload : []), [ 'state' => StoredReport::STATUS_FAILED, 'blocked' => true, 'failure' => [ 'code' => $code, 'message' => $message, ], ]), ]); $operationRunService->updateRun( $operationRun, status: OperationRunStatus::Completed->value, outcome: OperationRunOutcome::Blocked->value, summaryCounts: [ 'total' => 1, 'processed' => 1, 'succeeded' => 0, 'failed' => 0, ], failures: [ [ 'code' => 'management_report_pdf.'.$code, 'reason_code' => $code, 'message' => $message, ], ], ); $this->audit($report->refresh(), $operationRun, $auditLogger, AuditActionId::ManagementReportPdfGenerationBlocked, 'blocked', $message, [ 'failure_code' => $code, 'reason_code' => $code, ]); } /** * @param array $extraContext */ private function audit( StoredReport $report, OperationRun $operationRun, WorkspaceAuditLogger $auditLogger, AuditActionId $action, string $status, string $summary, array $extraContext = [], ): void { $tenant = $report->tenant; if (! $tenant instanceof ManagedEnvironment) { return; } $actor = $report->generatedBy; $auditLogger->log( workspace: $tenant->workspace, action: $action, context: array_replace_recursive([ 'metadata' => [ 'stored_report_id' => (int) $report->getKey(), 'review_pack_id' => $report->source_review_pack_id !== null ? (int) $report->source_review_pack_id : null, 'environment_review_id' => $report->source_environment_review_id !== null ? (int) $report->source_environment_review_id : null, 'profile' => (string) $report->profile, 'sha256' => $report->sha256, ], ], $extraContext), actor: $actor instanceof User ? $actor : null, status: $status, resourceType: 'stored_report', resourceId: (string) $report->getKey(), targetLabel: sprintf('Management report PDF #%d', (int) $report->getKey()), summary: $summary, operationRunId: (int) $operationRun->getKey(), tenant: $tenant, ); } private function correlationId(StoredReport $report, OperationRun $operationRun): string { return sprintf('management-report-pdf-%d-run-%d', (int) $report->getKey(), (int) $operationRun->getKey()); } private function safePathSegment(string $value): string { $value = preg_replace('/[^A-Za-z0-9._-]+/', '-', trim($value)) ?? ''; return trim($value, '-') !== '' ? trim($value, '-') : 'unknown'; } }