|null $workflow_context * @property string|null $primary_territory_id * @property int $territory_version * @property int $management_version * @property string|null $management_draft * @property string|null $management_creation_key * @property string|null $first_submitted_at * @property bool $management_closed_once * @property string|null $opportunity_category * @property string|null $estimated_investment_currency */ class Opportunity extends Model { use SoftDeletes; protected $hidden = ['management_draft', 'management_creation_key', 'management_version', 'first_submitted_at', 'management_closed_once', 'owner_user_id', 'organization_id', 'workflow_context']; protected $appends = ['maturity_catalogue_version']; public function getMaturityCatalogueVersionAttribute(): ?string { // Only published metadata is public; never expose the current private draft. $version = data_get($this->workflow_context, 'publication.maturity.catalogue_version'); return in_array($version, [\App\Services\OpportunityMaturity::VERSION, \App\Services\OpportunityMaturity::LEGACY], true) ? $version : null; } protected $fillable = [ 'sector_id', 'reference', 'title', 'slug', 'summary', 'description', 'province', 'district', 'promoter_name', 'promoter_type', 'maturity_level', 'maturity_label', 'estimated_investment_amount', 'estimated_investment_currency', 'financing_modality', 'expected_impact', 'status', 'is_featured', 'published_at', 'last_validated_at', 'primary_territory_id', 'territory_version', 'geography_scope', ]; protected $casts = [ 'workflow_context' => 'array', 'is_featured' => 'boolean', 'territory_version' => 'integer', 'published_at' => 'datetime', 'last_validated_at' => 'datetime', 'estimated_investment_amount' => 'decimal:2', ]; /** @return array{scope:string,label:string,public_catalog:bool} */ public function publicationPresentation(): array { $public = $this->exists && self::query()->whereKey($this->id)->published()->exists(); $visible = ! $this->trashed() && $this->status === 'published' && $this->published_at !== null && $this->published_at->lte(now()); $scope = $public ? 'public' : ($visible && $this->owner_user_id !== null ? 'restricted' : 'unpublished'); return ['scope' => $scope, 'public_catalog' => $public, 'label' => match ($scope) { 'public' => 'Publicada no catálogo público', 'restricted' => 'Disponibilizada com acesso restrito — perfis e organização autorizados', default => 'Sem versão disponibilizada; aprovação interna é uma etapa distinta', }]; } /** @return BelongsTo */ public function sector(): BelongsTo { return $this->belongsTo(Sector::class); } /** @return BelongsTo */ public function primaryTerritory(): BelongsTo { return $this->belongsTo(GeoLocation::class, 'primary_territory_id'); } /** @return BelongsToMany */ public function additionalTerritories(): BelongsToMany { return $this->belongsToMany(GeoLocation::class, 'opportunity_locations', 'opportunity_id', 'geo_location_id'); } /** @param Builder $query * @return Builder */ public function scopePublished(Builder $query): Builder { if (Schema::hasColumn('opportunities', 'workflow_context')) { $query->where(fn ($q) => $q->whereNull('owner_user_id')->orWhere('workflow_context->publication->visibility', 'public')); } return $query->where('status', 'published')->whereNotNull('published_at')->where('published_at', '<=', now()) ->whereRaw("COALESCE((SELECT origin.after_data->>'classification' FROM audit_events origin WHERE origin.source_system='opportunity-origin-review' AND origin.after_data->>'opportunity_id'=opportunities.id::text ORDER BY origin.occurred_at DESC,origin.id DESC LIMIT 1),'undetermined') <> 'example'"); } }