validate(['q' => ['nullable', 'string', 'max:150']]); $model = $structure === 'projects' ? Project::class : EconomicCluster::class; $query = $model::query(); if ($term = $filters['q'] ?? null) { $query->where(function ($q) use ($term): void { $q->whereLike('name', "%{$term}%")->orWhereLike('code', "%{$term}%"); }); } return Inertia::render('structures/index', ['structure' => $structure, 'filters' => $filters, 'records' => $query->orderBy('name')->orderBy('id')->paginate(20)->withQueryString()]); } public function edit(string $structure, ?string $id = null): Response { $model = $structure === 'projects' ? Project::class : EconomicCluster::class; $record = $id ? $model::findOrFail($id) : null; return Inertia::render('structures/editor', ['structure' => $structure, 'record' => $record, 'projects' => $structure === 'clusters' && $id ? Project::orderBy('name')->get(['id', 'name', 'code']) : [], 'chains' => $structure === 'clusters' && $id ? ValueChain::where('cluster_id', $id)->orderBy('code')->get() : [], 'gaps' => $structure === 'clusters' && $id ? InvestmentGap::where('cluster_id', $id)->orderBy('created_at')->get() : [], 'chainLinks' => $structure === 'clusters' && $id ? DB::table('value_chain_links') ->join('value_chains as origins', 'origins.id', '=', 'value_chain_links.from_value_chain_id') ->join('value_chains as destinations', 'destinations.id', '=', 'value_chain_links.to_value_chain_id') ->where('origins.cluster_id', $id)->where('destinations.cluster_id', $id) ->whereNull('origins.deleted_at')->whereNull('destinations.deleted_at')->whereNull('value_chain_links.deleted_at') ->orderBy('origins.code')->orderBy('destinations.code') ->get(['value_chain_links.id', 'origins.name as origin', 'destinations.name as destination', 'link_type']) : [], 'memberships' => $structure === 'clusters' && $id ? DB::table('cluster_projects') ->join('projects', 'projects.id', '=', 'cluster_projects.project_id') ->where('cluster_id', $id)->whereNull('cluster_projects.deleted_at')->whereNull('projects.deleted_at') ->orderBy('projects.name')->get(['cluster_projects.id', 'project_id', 'role', 'projects.name']) : [], ]); } public function save(Request $request, string $structure, ?string $id = null): RedirectResponse { $isProject = $structure === 'projects'; $model = $isProject ? Project::class : EconomicCluster::class; $record = $id ? $model::findOrFail($id) : new $model; $rules = [ 'code' => ['required', 'string', 'max:100', Rule::unique($record->getTable(), 'code')->whereNull('deleted_at')->ignore($record->getKey())], 'name' => ['required', 'string', 'max:200'], 'status' => ['required', Rule::in($isProject ? ['draft', 'planned', 'active', 'suspended', 'completed', 'cancelled'] : ['planned', 'active', 'inactive'])], ]; if ($isProject) { $rules += ['description' => ['nullable', 'string', 'max:10000'], 'budget_amount' => ['nullable', 'required_with:budget_currency', 'regex:/^\d{1,18}(\.\d{1,2})?$/'], 'budget_currency' => ['nullable', 'required_with:budget_amount', 'regex:/^[A-Z]{3}$/'], 'start_date' => ['nullable', 'date_format:Y-m-d'], 'end_date' => ['nullable', 'date_format:Y-m-d', ...($request->filled('start_date') ? ['after_or_equal:start_date'] : [])]]; } else { $rules['sector'] = ['required', 'string', 'max:200']; } if (! $id) { foreach (InvestmentGeography::FIELDS as $field => $type) { $rules[$field] = ['nullable', 'uuid']; } } $data = $request->validate($rules); if (! $id && $request->has('province_id')) { $path = app(InvestmentGeography::class)->selection($data); $data = array_diff_key($data, InvestmentGeography::FIELDS); $data['primary_territory_id'] = $path === [] ? null : $path[array_key_last($path)]->id; $data['territory_version'] = $path === [] ? 0 : 1; } PortalAudit::save($record, $data, $request->user()->id); return redirect('/management/'.$structure.'/'.$record->getKey().'/edit')->with('success', 'Registo guardado.'); } public function linkProject(Request $request, EconomicCluster $cluster): RedirectResponse { $data = $request->validate(['project_id' => ['required', 'uuid', Rule::exists('projects', 'id')->whereNull('deleted_at')], 'role' => ['required', Rule::in(['anchor', 'complementary', 'supporting'])]]); DB::transaction(function () use ($cluster, $data, $request) { EconomicCluster::whereKey($cluster->id)->lockForUpdate()->firstOrFail(); $link = ClusterProject::where('cluster_id', $cluster->id)->where('project_id', $data['project_id'])->first() ?? new ClusterProject; PortalAudit::save($link, [...$data, 'cluster_id' => $cluster->id], $request->user()->id); }); return back()->with('success', 'Associação ao projecto guardada.'); } public function saveChain(Request $request, EconomicCluster $cluster, ?ValueChain $chain = null): RedirectResponse { abort_if($chain && $chain->cluster_id !== $cluster->id, 404); $data = $request->validate(['code' => ['required', 'string', 'max:100', Rule::unique('value_chains', 'code')->where('cluster_id', $cluster->id)->whereNull('deleted_at')->ignore($chain?->id)], 'name' => ['required', 'string', 'max:200'], 'stage' => ['required', Rule::in(['input', 'production', 'processing', 'logistics', 'market'])]]); PortalAudit::save($chain ?? new ValueChain, [...$data, 'cluster_id' => $cluster->id], $request->user()->id); return back()->with('success', 'Etapa da cadeia de valor guardada.'); } }