71 lines
3.9 KiB
PHP
71 lines
3.9 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Models\Opportunity;
|
|
use App\Models\User;
|
|
|
|
/** Read adapter only: preserve persisted identifiers and make unresolved metadata explicit. */
|
|
class OpportunityCanonicalResponse
|
|
{
|
|
public const VERSION = 'opportunity-response-existing-ids-20261010-1';
|
|
|
|
/** @return array<string,mixed> */
|
|
public function read(User $actor, Opportunity $record): array
|
|
{
|
|
$access = app(PortalProfileAccess::class);
|
|
$access->authorize($actor, 'view', $record);
|
|
$payload = app(OpportunityManagement::class)->values($record)['canonical'] ?? [];
|
|
$context = $record->workflow_context ?? [];
|
|
$data = [...$payload, 'id' => $record->id, 'opportunity_code' => $record->reference,
|
|
'opportunity_category' => $record->opportunity_category, 'status' => $access->state($record),
|
|
'version' => (int) $record->management_version, 'is_deleted' => $record->trashed(),
|
|
'deleted_at' => $record->deleted_at?->toIso8601String()];
|
|
foreach (['submitted' => 'submission', 'approved' => 'approval', 'published' => 'publication'] as $prefix => $key) {
|
|
$data[$prefix.'_by'] = $context[$key]['actor_id'] ?? null;
|
|
$data[$prefix.'_at'] = $context[$key]['at'] ?? null;
|
|
}
|
|
$references = [];
|
|
foreach (OpportunityResources::KINDS as $kind) {
|
|
$references[$kind] = $payload[$kind] ?? [];
|
|
$rows = [];
|
|
if (app(OpportunityResources::class)->installed()) {
|
|
foreach (app(OpportunityResources::class)->listing($record->id, $kind) as $row) {
|
|
$rows[$row->id] = $row;
|
|
}
|
|
}
|
|
$data[$kind] = array_map(function (string $id) use ($rows, $kind): array {
|
|
$row = $rows[$id] ?? null;
|
|
if ($row === null) {
|
|
return ['id' => $id, 'availability' => 'unavailable'];
|
|
}
|
|
$value = [...$row->payload, 'id' => $row->id, 'version' => (int) $row->version,
|
|
'is_deleted' => $row->deleted_at !== null, 'deleted_at' => $row->deleted_at,
|
|
'created_by' => $row->created_by, 'created_at' => $row->created_at, 'updated_at' => $row->updated_at];
|
|
if ($kind !== 'locations') {
|
|
$value['original_name'] = $row->file?->file_name;
|
|
$value['mime_type'] = $row->file?->media_type;
|
|
$value['sha256'] = $row->file?->sha256;
|
|
$value['scan_recorded_status'] = $row->file?->malware_scan_status;
|
|
}
|
|
|
|
return $value;
|
|
}, $references[$kind]);
|
|
}
|
|
|
|
return ['adapter_version' => self::VERSION,
|
|
'source_schema_sha256' => hash_file('sha256', resource_path('data/investmoz-opportunity-modular.schema.json')),
|
|
'data' => $data, 'resource_references' => $references,
|
|
'validation_result' => ['scope' => 'recorded_submission_only', 'submission_hash' => $context['submission']['hash'] ?? null,
|
|
'submitted_content_revision' => $context['submission']['content_revision'] ?? null,
|
|
'current_content_revision' => $context['content_revision'] ?? 1,
|
|
'revalidated_on_read' => false, 'external_decision' => 'not_inferred'],
|
|
'publication' => $record->publicationPresentation(),
|
|
'compatibility' => ['normative_response_complete' => false, 'opportunity_actor_sector_ids' => 'existing bigint, not converted to UUID',
|
|
'opportunity_code' => 'existing reference preserved; no INV-MZ code invented',
|
|
'states' => 'existing workflow values preserved; no implicit stage equivalence',
|
|
'inline_modules' => 'management_draft canonical JSON with audited revisions; no parallel specialized tables',
|
|
'metadata_gaps' => ['completion_percentage algorithm', 'inline item IDs and mutation metadata', 'resource updated_by/deleted_by recovery']]];
|
|
}
|
|
}
|