@@ -6,14 +6,20 @@ use App\Domain\Opportunities\Enums\OpportunityStatus;
|
||||
use App\Models\Opportunity;
|
||||
use App\Models\Sector;
|
||||
use App\Services\InvestmentGeography;
|
||||
use App\Services\OpportunityCanonicalContract;
|
||||
use App\Services\OpportunityManagement;
|
||||
use App\Services\OpportunityManagementInstallation;
|
||||
use App\Services\OpportunityMaturity;
|
||||
use App\Services\OpportunityProfileWorkflow;
|
||||
use App\Services\OpportunityResources;
|
||||
use App\Services\OpportunityWorkflowTasks;
|
||||
use App\Services\PortalProfileAccess;
|
||||
use Illuminate\Database\Eloquent\SoftDeletingScope;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
@@ -38,16 +44,32 @@ class OpportunityManagementController extends Controller
|
||||
if (! $service->installed()) {
|
||||
return app(PortalManagementController::class)->catalog($request, 'opportunities');
|
||||
}
|
||||
$filters = $request->validate(['q' => 'nullable|string|max:150', 'status' => ['nullable', Rule::in([...array_column(OpportunityStatus::cases(), 'value'), 'returned', 'auto_validation_failed', 'technical_review', 'legal_territorial_review', 'approval_pending', 'publication_pending'])], 'sector_id' => ['nullable', 'integer', Rule::exists('sectors', 'id')->whereNull('deleted_at')], 'geography_scope' => ['nullable', Rule::in(['unspecified', 'specific', 'multiple', 'national', 'digital'])], 'category' => ['nullable', Rule::in(OpportunityManagement::CATEGORIES)], 'deleted' => 'nullable|boolean', 'page' => 'nullable|integer|min:1']);
|
||||
$filters = $request->validate(['queue' => 'nullable|in:review,publication,appeal,assigned', 'q' => 'nullable|string|max:150', 'status' => ['nullable', Rule::in([...array_column(OpportunityStatus::cases(), 'value'), 'returned', 'auto_validation_failed', 'technical_review', 'legal_territorial_review', 'approval_pending', 'publication_pending'])], 'sector_id' => ['nullable', 'integer', Rule::exists('sectors', 'id')->whereNull('deleted_at')], 'geography_scope' => ['nullable', Rule::in(['unspecified', 'specific', 'multiple', 'national', 'digital'])], 'category' => ['nullable', Rule::in(OpportunityManagement::CATEGORIES)], 'deleted' => 'nullable|boolean', 'page' => 'nullable|integer|min:1']);
|
||||
$query = Opportunity::query();
|
||||
$access = app(PortalProfileAccess::class);
|
||||
if (Schema::hasColumn('opportunities', 'owner_user_id')) {
|
||||
$query = $access->scope($query, $request->user());
|
||||
if (str_starts_with($request->path(), 'minhas-oportunidades')) {
|
||||
$query->whereNotNull('owner_user_id');
|
||||
}
|
||||
}
|
||||
if ($filters['deleted'] ?? false) {
|
||||
$query->onlyTrashed();
|
||||
$query->withoutGlobalScope(SoftDeletingScope::class)->whereNotNull('opportunities.deleted_at');
|
||||
}
|
||||
if ($filters['q'] ?? null) {
|
||||
$query->where(fn ($q) => $q->whereLike('title', '%'.$filters['q'].'%')->orWhereLike('reference', '%'.$filters['q'].'%'));
|
||||
$query->where(function ($q) use ($filters) {
|
||||
$q->whereLike('title', '%'.$filters['q'].'%')->orWhereLike('reference', '%'.$filters['q'].'%');
|
||||
if (Schema::hasColumn('opportunities', 'owner_user_id')) {
|
||||
$q->orWhere(fn ($draft) => $draft->whereNotNull('owner_user_id')->whereRaw("management_draft::jsonb->'canonical'->>'official_title' ILIKE ?", ['%'.$filters['q'].'%']));
|
||||
}
|
||||
});
|
||||
}
|
||||
if ($filters['status'] ?? null) {
|
||||
$query->where('status', $filters['status']);
|
||||
if (Schema::hasColumn('opportunities', 'workflow_context')) {
|
||||
$query->whereRaw("COALESCE(workflow_context->>'working_state',status)=?", [$filters['status']]);
|
||||
} else {
|
||||
$query->where('status', $filters['status']);
|
||||
}
|
||||
}
|
||||
if ($filters['category'] ?? null) {
|
||||
$query->where('opportunity_category', $filters['category']);
|
||||
@@ -59,7 +81,32 @@ class OpportunityManagementController extends Controller
|
||||
$query->where('geography_scope', $filters['geography_scope']);
|
||||
}
|
||||
|
||||
return Inertia::render('management/opportunities', ['records' => $query->orderByDesc('id')->paginate(20)->withQueryString(), 'filters' => $filters, 'counts' => Opportunity::selectRaw('status,count(*) AS total')->groupBy('status')->pluck('total', 'status'), 'categories' => OpportunityManagement::CATEGORIES, 'geographyReviewCount' => Opportunity::where('geography_scope', 'unspecified')->count(), 'sectors' => Sector::orderBy('name')->get(['id', 'name'])]);
|
||||
if ($filters['queue'] ?? null) {
|
||||
$queue = $filters['queue'];
|
||||
if ($queue === 'review') {
|
||||
$query->whereRaw("COALESCE(workflow_context->>'working_state',status) IN ('submitted','in_review','information_pending')");
|
||||
} elseif ($queue === 'publication') {
|
||||
$query->whereRaw("COALESCE(workflow_context->>'working_state',status)='approved'");
|
||||
} elseif ($queue === 'appeal') {
|
||||
$query->whereRaw("workflow_context->'appeal'->>'pending'='true'");
|
||||
} else {
|
||||
$query->whereExists(fn ($q) => $q->selectRaw('1')->from('tasks')->where('tasks.source_system', 'opportunity-workflow')->where('tasks.assignee_user_id', $request->user()->id)->whereIn('tasks.status', ['open', 'in_progress'])->whereRaw("tasks.external_id LIKE 'portal-opportunity:' || opportunities.id::text || ':%'"));
|
||||
}
|
||||
}
|
||||
|
||||
$base = str_starts_with($request->path(), 'minhas-oportunidades') ? '/minhas-oportunidades' : '/management/opportunities';
|
||||
$stateSql = Schema::hasColumn('opportunities', 'workflow_context') ? "COALESCE(workflow_context->>'working_state',status)" : 'status';
|
||||
$counts = (clone $query)->selectRaw($stateSql.' AS working_status,count(*) AS total')->groupByRaw($stateSql)->pluck('total', 'working_status');
|
||||
$geographyReviewCount = (clone $query)->where('geography_scope', 'unspecified')->count();
|
||||
$records = $query->orderByDesc('id')->paginate(20)->withQueryString();
|
||||
$records->getCollection()->each(function (Opportunity $r) use ($access, $service, $request) {
|
||||
$r->setAttribute('can_edit', $access->canEdit($request->user(), $r));
|
||||
$r->setAttribute('working_status', $access->state($r));
|
||||
$r->setAttribute('publication', $r->publicationPresentation());
|
||||
$r->setAttribute('draft_title', $service->values($r)['canonical']['official_title'] ?? $r->title);
|
||||
});
|
||||
|
||||
return Inertia::render('management/opportunities', ['basePath' => $base, 'canCreate' => $base === '/management/opportunities' ? $access->legacyWriteAllowed($request->user()) : (bool) ($access->navigation($request->user())['contexts'] ?? []), 'records' => $records, 'filters' => $filters, 'counts' => $counts, 'categories' => OpportunityManagement::CATEGORIES, 'geographyReviewCount' => $geographyReviewCount, 'sectors' => Sector::orderBy('name')->get(['id', 'name'])]);
|
||||
}
|
||||
|
||||
public function edit(Request $request, OpportunityManagement $service, ?int $id = null): Response
|
||||
@@ -68,10 +115,27 @@ class OpportunityManagementController extends Controller
|
||||
return app(PortalManagementController::class)->edit('opportunities', $id);
|
||||
}
|
||||
$record = $id ? Opportunity::withTrashed()->findOrFail($id) : null;
|
||||
$access = app(PortalProfileAccess::class);
|
||||
if (! $record && ! str_starts_with($request->path(), 'minhas-oportunidades')) {
|
||||
abort_unless($access->legacyWriteAllowed($request->user()), 403);
|
||||
}
|
||||
if ($record) {
|
||||
$access->authorize($request->user(), 'view', $record);
|
||||
}
|
||||
Inertia::share(['basePath' => str_starts_with($request->path(), 'minhas-oportunidades') ? '/minhas-oportunidades' : '/management/opportunities',
|
||||
'canonicalOptions' => ($record?->owner_user_id !== null || str_starts_with($request->path(), 'minhas-oportunidades')) ? app(OpportunityCanonicalContract::class)->options($request->user(), $record) : null,
|
||||
'workflowState' => $record ? $access->state($record) : null,
|
||||
'publicationState' => $record?->publicationPresentation(),
|
||||
'canEditDraft' => $record ? $access->canEdit($request->user(), $record) : false,
|
||||
'workflowParticipants' => $record && $record->owner_user_id !== null ? app(OpportunityProfileWorkflow::class)->participants($request->user(), $record) : ['validators' => [], 'publishers' => []],
|
||||
'workflowTasks' => $record && $record->owner_user_id !== null ? app(OpportunityWorkflowTasks::class)->listing($record) : [],
|
||||
'profileMode' => $record ? $record->owner_user_id !== null : str_starts_with($request->path(), 'minhas-oportunidades'),
|
||||
'profileActions' => $record && $record->owner_user_id !== null ? app(OpportunityProfileWorkflow::class)->actions($request->user(), $record) : [], 'profileContext' => $record?->workflow_context, 'profileOrganization' => $record?->organization_id]);
|
||||
$values = $record ? $service->values($record) : [];
|
||||
Inertia::share('publicationPreview', app(OpportunityCanonicalContract::class)->publicProjection($values['canonical'] ?? []));
|
||||
$classification = $id ? DB::table('audit_events')->where('source_system', 'opportunity-origin-review')->where('after_data->opportunity_id', $id)->orderByDesc('occurred_at')->orderByDesc('id')->value('after_data') : null;
|
||||
|
||||
return Inertia::render('management/opportunity-draft', ['resourcesInstalled' => app(OpportunityResources::class)->installed(), 'record' => $record, 'values' => $values, 'etag' => $record ? $service->etag($record) : null, 'readOnly' => str_ends_with($request->path(), '/view'), 'closedVersion' => $record ? $service->closed($record) : false, 'classification' => $classification ? json_decode($classification, true)['classification'] : 'undetermined', 'sectors' => Sector::where('is_active', true)->orderBy('name')->get(['id', 'name']), 'categories' => OpportunityManagement::CATEGORIES,
|
||||
return Inertia::render('management/opportunity-draft', ['resourcesInstalled' => app(OpportunityResources::class)->installed(), 'record' => $record, 'values' => $values, 'etag' => $record ? $service->etag($record) : null, 'readOnly' => str_ends_with($request->path(), '/view') || ($record && ! $access->canEdit($request->user(), $record)), 'closedVersion' => $record ? $service->closed($record) : false, 'classification' => $classification ? json_decode($classification, true)['classification'] : 'undetermined', 'sectors' => Sector::where('is_active', true)->orderBy('name')->get(['id', 'name']), 'categories' => OpportunityManagement::CATEGORIES,
|
||||
'maturityCatalogue' => ['version' => OpportunityMaturity::VERSION, 'stages' => OpportunityMaturity::STAGES, 'legacyVersion' => OpportunityMaturity::LEGACY, 'legacyLabels' => OpportunityMaturity::LEGACY_LABELS],
|
||||
'territoryPath' => app(InvestmentGeography::class)->path($values['primary_territory_id'] ?? null, true),
|
||||
'history' => $id ? DB::table('audit_events')->where('source_system', 'opportunity-management')->where('after_data->record_id', $id)->orderByDesc('occurred_at')->get(['id', 'action', 'actor_user_id', 'occurred_at']) : []]);
|
||||
|
||||
Reference in New Issue
Block a user