Complete grouped collaborative editorial queues and native review coverage
tests / ci (push) Waiting to run

This commit is contained in:
2026-11-14 11:15:09 +02:00
parent f40924f1be
commit a4541298c0
36 changed files with 2691 additions and 89 deletions
@@ -19,6 +19,8 @@ use App\Services\InvestmentZones;
use App\Services\PublicSearchMetrics;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Illuminate\Validation\Rule;
@@ -291,6 +293,13 @@ class InvestmentZoneController extends Controller
return response()->json(app(InvestmentZoneOperationalProfile::class)->management($saved));
}
public function editorialReview(Request $request, string $id, string $action): JsonResponse
{
$data = $request->validate(['version_no' => 'required|integer|min:1', 'reason' => 'required|string|min:10|max:3000']);
return response()->json(app(InvestmentZoneOperationalProfile::class)->review($id, $request->user(), (int) $data['version_no'], $action, $data['reason']));
}
public function revisions(string $id): JsonResponse
{
InvestmentZone::findOrFail($id);
@@ -298,6 +307,30 @@ class InvestmentZoneController extends Controller
return response()->json(DB::table('investment_zone_revisions')->where('zone_id', $id)->orderByDesc('version_no')->paginate(20, ['id', 'version_no', 'actor_id', 'action', 'sha256', 'created_at']));
}
public function revision(Request $request, string $id, int $version): JsonResponse
{
abort_unless(app(InvestmentZoneOperationalProfile::class)->reviewAccess($request->user()), 403);
InvestmentZone::findOrFail($id);
$revision = DB::table('investment_zone_revisions')->where('zone_id', $id)->where('version_no', $version)->first();
abort_unless($revision !== null, 404);
try {
$json = Crypt::decryptString($revision->snapshot);
abort_unless(hash_equals($revision->sha256, hash('sha256', $json)), 409);
$snapshot = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
$fields = $snapshot['draft']['fields'] ?? $snapshot['public'] ?? $snapshot;
$details = $fields['operational_details'] ?? [];
if (isset($details['private_ciphertext'])) {
$details = json_decode(Crypt::decryptString($details['private_ciphertext']), true, 512, JSON_THROW_ON_ERROR);
}
} catch (\Throwable $e) {
abort(409, 'Não foi possível conferir esta versão. O histórico foi preservado.');
}
return response()->json(['version_no' => $revision->version_no, 'action' => $revision->action,
'fields' => Arr::only($fields, ['name', 'description']),
'observations' => array_map(fn ($row) => Arr::only($row, ['label', 'value', 'unit', 'scope', 'period_start', 'period_end', 'source_reference']), $details['integral']['observations'] ?? [])]);
}
public function validateLocation(Request $request, InvestmentZones $zones, string $id): JsonResponse
{
InvestmentZoneAccess::requireRole($request->user(), 'territory_reviewers');