@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Opportunity;
|
||||
use App\Services\InvestmentCatalogue;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class InvestmentContextController extends Controller
|
||||
{
|
||||
public function edit(int $id, InvestmentCatalogue $catalogue): Response
|
||||
{
|
||||
return Inertia::render('investment/context', ['record' => Opportunity::findOrFail($id), 'context' => $catalogue->context($id, true), 'references' => $catalogue->references(true)]);
|
||||
}
|
||||
|
||||
public function save(Request $request, int $id): RedirectResponse|JsonResponse
|
||||
{
|
||||
$rules = ['version' => ['required', 'integer', 'min:0'], 'is_education' => ['required', 'boolean'],
|
||||
'business_model' => ['nullable', 'string', 'max:5000'], 'requirements' => ['nullable', 'string', 'max:10000'],
|
||||
'risks' => ['nullable', 'string', 'max:5000'], 'target_date' => ['nullable', 'date_format:Y-m-d'],
|
||||
'source_reference' => ['required', 'string', 'min:10', 'max:2000'], 'facets' => ['array', 'max:100'],
|
||||
'facets.*' => ['integer', 'distinct', Rule::exists('investment_reference_options', 'id')->where('active', true)]];
|
||||
foreach (InvestmentCatalogue::RELATIONS as $relation => [$foreign, $table]) {
|
||||
$rules[$relation] = ['array', 'max:100'];
|
||||
$rules[$relation.'.*'] = ['uuid', 'distinct', Rule::exists($table, 'id')->whereNull('deleted_at')];
|
||||
}
|
||||
$data = $request->validate($rules);
|
||||
$version = DB::transaction(function () use ($request, $id, $data) {
|
||||
$opportunity = Opportunity::whereKey($id)->lockForUpdate()->firstOrFail();
|
||||
$profile = DB::table('opportunity_profiles')->where('opportunity_id', $id)->first();
|
||||
abort_unless((int) ($profile->version ?? 0) === (int) $data['version'], 409, 'Contexto alterado. Recarregue.');
|
||||
$version = $data['version'] + 1;
|
||||
DB::table('opportunity_profiles')->updateOrInsert(['opportunity_id' => $id], [
|
||||
...array_intersect_key($data, array_flip(['is_education', 'business_model', 'requirements', 'risks', 'target_date', 'source_reference'])),
|
||||
'version' => $version, 'created_at' => $profile->created_at ?? now(), 'updated_at' => now()]);
|
||||
foreach (InvestmentCatalogue::RELATIONS as $relation => [$foreign, $table]) {
|
||||
DB::table('opportunity_'.$relation)->where('opportunity_id', $id)->delete();
|
||||
foreach ($data[$relation] ?? [] as $value) {
|
||||
DB::table('opportunity_'.$relation)->insert(['opportunity_id' => $id, $foreign => $value]);
|
||||
}
|
||||
}
|
||||
DB::table('opportunity_facets')->where('opportunity_id', $id)->delete();
|
||||
foreach ($data['facets'] ?? [] as $value) {
|
||||
DB::table('opportunity_facets')->insert(['opportunity_id' => $id, 'option_id' => $value]);
|
||||
}
|
||||
// Editing public context produces a draft for explicit approval, never silent publication.
|
||||
$opportunity->update(['status' => 'draft', 'published_at' => null]);
|
||||
$this->audit($request, 'opportunity_context', $id, 'context_saved', ['version' => $version]);
|
||||
|
||||
return $version;
|
||||
});
|
||||
|
||||
return $request->expectsJson() ? response()->json(['version' => $version]) : back()->with('success', 'Contexto guardado. A ficha está em rascunho para aprovação.');
|
||||
}
|
||||
|
||||
public function references(): Response
|
||||
{
|
||||
return Inertia::render('investment/references', ['options' => DB::table('investment_reference_options')->orderBy('group')->orderBy('label')->get()]);
|
||||
}
|
||||
|
||||
public function saveReference(Request $request, ?int $id = null): RedirectResponse|JsonResponse
|
||||
{
|
||||
$data = $request->validate(['group' => ['required', 'string', Rule::exists('investment_reference_options', 'group')],
|
||||
'code' => ['required', 'alpha_dash:ascii', 'max:80'], 'label' => ['required', 'string', 'max:200'],
|
||||
'active' => ['required', 'boolean'], 'requires_description' => ['required', 'boolean'], 'sort_order' => ['required', 'integer', 'between:0,10000']]);
|
||||
$savedId = DB::transaction(function () use ($request, $id, $data) {
|
||||
DB::select('select pg_advisory_xact_lock(270927, hashtext(?))', [$data['group']]);
|
||||
if ($id) {
|
||||
$current = DB::table('investment_reference_options')->where('id', $id)->lockForUpdate()->first();
|
||||
abort_unless($current !== null, 404);
|
||||
abort_unless($current->group === $data['group'] && $current->code === $data['code'], 422, 'Grupo e código são estáveis para preservar referências.');
|
||||
abort_if($current->code === 'other' && (! $data['active'] || ! $data['requires_description']), 422, 'Preserve a opção Outro — especificar.');
|
||||
DB::table('investment_reference_options')->where('id', $id)->update([...$data, 'updated_at' => now()]);
|
||||
$savedId = $id;
|
||||
} else {
|
||||
abort_if(DB::table('investment_reference_options')->where('group', $data['group'])->where('code', $data['code'])->exists(), 422, 'Código já existente neste grupo.');
|
||||
$savedId = DB::table('investment_reference_options')->insertGetId([...$data, 'created_at' => now(), 'updated_at' => now()]);
|
||||
}
|
||||
$this->audit($request, 'investment_reference_options', $savedId, 'reference_saved', ['active' => $data['active']]);
|
||||
|
||||
return $savedId;
|
||||
});
|
||||
|
||||
return $request->expectsJson() ? response()->json(['id' => $savedId]) : back()->with('success', 'Opção guardada. Os registos anteriores foram preservados.');
|
||||
}
|
||||
|
||||
/** @param array<string,mixed> $values */
|
||||
private function audit(Request $request, string $entity, int $id, string $action, array $values): void
|
||||
{
|
||||
DB::table('audit_events')->insert(['source_system' => 'investment-context', 'entity_type' => $entity,
|
||||
'actor_user_id' => $request->user()->id, 'action' => $action,
|
||||
'after_data' => json_encode(['record_id' => $id, ...$values], JSON_THROW_ON_ERROR)]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user