@@ -15,7 +15,7 @@ class ReferenceCatalogController extends Controller
|
||||
|
||||
public function index(Request $request, string $catalog): JsonResponse
|
||||
{
|
||||
abort_unless(in_array($catalog, self::CATALOGS, true), 404);
|
||||
$catalog = $this->validatedCatalog($catalog);
|
||||
|
||||
$rules = [
|
||||
'q' => ['nullable', 'string', 'max:100'],
|
||||
@@ -71,7 +71,7 @@ class ReferenceCatalogController extends Controller
|
||||
};
|
||||
|
||||
$page = $query->orderBy('code')->orderBy('id')->paginate($data['per_page'] ?? 20)->withQueryString();
|
||||
$items = collect($page->items())->map(fn (object $row): array => $this->present($catalog, $row))->values();
|
||||
$items = collect($page->items())->map(fn (object $row): array => $this->present($catalog, (array) $row))->values();
|
||||
|
||||
return response()->json([
|
||||
'data' => $items,
|
||||
@@ -156,32 +156,47 @@ class ReferenceCatalogController extends Controller
|
||||
return $query;
|
||||
}
|
||||
|
||||
/** @param list<string> $columns */
|
||||
/** @param list<literal-string> $columns */
|
||||
private function search(Builder $query, string $search, array $columns): void
|
||||
{
|
||||
$term = '%'.addcslashes($search, '%_\\').'%';
|
||||
$query->where(function (Builder $nested) use ($columns, $term): void {
|
||||
foreach ($columns as $index => $column) {
|
||||
$method = $index === 0 ? 'whereRaw' : 'orWhereRaw';
|
||||
$nested->{$method}($column.' ILIKE ?', [$term]);
|
||||
if ($index === 0) {
|
||||
$nested->whereRaw($column.' ILIKE ?', [$term]);
|
||||
} else {
|
||||
$nested->orWhereRaw($column.' ILIKE ?', [$term]);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** @return array<string,mixed> */
|
||||
private function present(string $catalog, object $row): array
|
||||
/** @return 'authorities'|'jurisdictions'|'economic-activities' */
|
||||
private function validatedCatalog(string $catalog): string
|
||||
{
|
||||
abort_unless(in_array($catalog, self::CATALOGS, true), 404);
|
||||
|
||||
return $catalog;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param 'authorities'|'jurisdictions'|'economic-activities' $catalog
|
||||
* @param array<string, mixed> $row
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
private function present(string $catalog, array $row): array
|
||||
{
|
||||
return match ($catalog) {
|
||||
'jurisdictions' => ['id' => $row->id, 'value' => $row->id, 'code' => $row->code, 'name' => $row->name,
|
||||
'label' => $row->name, 'locale' => 'pt-MZ', 'parent_id' => $row->parent_id, 'type' => $row->location_type,
|
||||
'source_code' => $row->source_code, 'version_no' => $row->version_no],
|
||||
'economic-activities' => ['id' => $row->id, 'value' => $row->code, 'code' => $row->code, 'name' => $row->name,
|
||||
'label' => $row->name, 'locale' => 'pt-MZ', 'parent_id' => $row->parent_id, 'sector_id' => $row->sector_id,
|
||||
'version_no' => $row->version_no],
|
||||
'authorities' => ['id' => $row->id, 'value' => $row->code, 'code' => $row->code, 'name' => $row->name,
|
||||
'label' => $row->name, 'locale' => 'pt-MZ', 'authority_type' => $row->authority_type,
|
||||
'organization_id' => $row->organization_id, 'jurisdiction_id' => $row->jurisdiction_id,
|
||||
'jurisdiction_name' => $row->jurisdiction_name, 'version_no' => $row->version_no],
|
||||
'jurisdictions' => ['id' => $row['id'], 'value' => $row['id'], 'code' => $row['code'], 'name' => $row['name'],
|
||||
'label' => $row['name'], 'locale' => 'pt-MZ', 'parent_id' => $row['parent_id'], 'type' => $row['location_type'],
|
||||
'source_code' => $row['source_code'], 'version_no' => $row['version_no']],
|
||||
'economic-activities' => ['id' => $row['id'], 'value' => $row['code'], 'code' => $row['code'], 'name' => $row['name'],
|
||||
'label' => $row['name'], 'locale' => 'pt-MZ', 'parent_id' => $row['parent_id'], 'sector_id' => $row['sector_id'],
|
||||
'version_no' => $row['version_no']],
|
||||
'authorities' => ['id' => $row['id'], 'value' => $row['code'], 'code' => $row['code'], 'name' => $row['name'],
|
||||
'label' => $row['name'], 'locale' => 'pt-MZ', 'authority_type' => $row['authority_type'],
|
||||
'organization_id' => $row['organization_id'], 'jurisdiction_id' => $row['jurisdiction_id'],
|
||||
'jurisdiction_name' => $row['jurisdiction_name'], 'version_no' => $row['version_no']],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\StoreSimulationRequest;
|
||||
use App\Http\Requests\UpdateSimulationScenarioRequest;
|
||||
use App\Models\QuestionnaireVersion;
|
||||
use App\Models\Simulation;
|
||||
use App\Models\SimulationJob;
|
||||
use App\Policies\SimulationPolicy;
|
||||
use App\Services\SimulationExecution;
|
||||
@@ -65,6 +66,56 @@ class SimulationController extends Controller
|
||||
return response()->json(['data' => $simulation])->header('ETag', $execution->etag($simulation));
|
||||
}
|
||||
|
||||
public function history(Request $request, SimulationExecution $execution): JsonResponse
|
||||
{
|
||||
$user = $request->user();
|
||||
abort_unless($user !== null, 401);
|
||||
abort_unless($user->hasVerifiedEmail(), 403, 'Verifique o correio electrónico para consultar o histórico.');
|
||||
$perPage = min(50, max(1, $request->integer('per_page', 20)));
|
||||
$page = max(1, $request->integer('page', 1));
|
||||
|
||||
$history = DB::transaction(function () use ($execution, $user, $perPage, $page) {
|
||||
$execution->setContext($user->id, null);
|
||||
|
||||
return Simulation::query()
|
||||
->select([
|
||||
'simulations.id', 'simulations.organization_id', 'visible_organization.name as organization_name',
|
||||
'simulations.status', 'simulations.version_no', 'simulations.created_at', 'simulations.updated_at',
|
||||
])
|
||||
->leftJoin('organizations as visible_organization', 'visible_organization.id', '=', 'simulations.organization_id')
|
||||
->where(function ($scope) use ($user) {
|
||||
$scope->where(fn ($personal) => $personal->whereNull('simulations.organization_id')->where('simulations.owner_user_id', $user->id))
|
||||
->orWhere(fn ($organization) => $organization->whereNotNull('simulations.organization_id')->whereExists(
|
||||
fn ($membership) => $membership->selectRaw('1')->from('memberships')
|
||||
->whereColumn('memberships.organization_id', 'simulations.organization_id')
|
||||
->where('memberships.user_id', $user->id)->whereNull('memberships.deleted_at'),
|
||||
));
|
||||
})
|
||||
->withExists(['reports as has_ready_report' => fn ($query) => $query->where('status', 'ready')])
|
||||
->orderByDesc('simulations.created_at')->orderByDesc('simulations.id')
|
||||
->paginate($perPage, ['*'], 'page', $page);
|
||||
});
|
||||
|
||||
return response()->json([
|
||||
'data' => $history->getCollection()->map(fn (Simulation $simulation): array => [
|
||||
'id' => $simulation->id,
|
||||
'scope' => $simulation->organization_id === null ? 'personal' : 'organization',
|
||||
'organization_name' => $simulation->getAttribute('organization_name'),
|
||||
'status' => $simulation->status,
|
||||
'version_no' => $simulation->version_no,
|
||||
'created_at' => $simulation->created_at?->toISOString(),
|
||||
'updated_at' => $simulation->updated_at?->toISOString(),
|
||||
'has_ready_report' => (bool) $simulation->getAttribute('has_ready_report'),
|
||||
]),
|
||||
'meta' => [
|
||||
'current_page' => $history->currentPage(),
|
||||
'last_page' => $history->lastPage(),
|
||||
'per_page' => $history->perPage(),
|
||||
'total' => $history->total(),
|
||||
],
|
||||
])->header('Cache-Control', 'no-store, private');
|
||||
}
|
||||
|
||||
public function updateScenario(UpdateSimulationScenarioRequest $request, string $id, string $scenarioId, SimulationExecution $execution): JsonResponse
|
||||
{
|
||||
abort_unless(Str::isUuid($id) && Str::isUuid($scenarioId), 404);
|
||||
|
||||
@@ -3,11 +3,16 @@
|
||||
namespace App\Http\Controllers\Api\V1;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\AttachSimulationToDraftRequest;
|
||||
use App\Http\Requests\ClaimSimulationFormDraftRequest;
|
||||
use App\Http\Requests\LinkSimulationDraftBusinessRequest;
|
||||
use App\Http\Requests\StoreSimulationFormDraftRequest;
|
||||
use App\Models\SimulationFormDraft;
|
||||
use App\Policies\SimulationPolicy;
|
||||
use App\Services\SimulationExecution;
|
||||
use App\Services\SimulationFormDrafts;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class SimulationFormDraftController extends Controller
|
||||
@@ -16,13 +21,65 @@ class SimulationFormDraftController extends Controller
|
||||
{
|
||||
$key = $request->header('Idempotency-Key');
|
||||
abort_unless(is_string($key) && strlen($key) >= 16 && strlen($key) <= 128, 428, 'Idempotency-Key é obrigatório.');
|
||||
$result = $drafts->create($request->validated(), $request, $key);
|
||||
$result = $drafts->create($request->snapshots(), $request, $key);
|
||||
$draft = $result['draft'];
|
||||
|
||||
return response()->json(['data' => ['id' => $draft->id, 'version_no' => $draft->version_no, 'replayed' => $result['replayed']]], $result['replayed'] ? 200 : 201)
|
||||
return response()->json(['data' => ['id' => $draft->id, 'version_no' => $draft->version_no, 'replayed' => $result['replayed']], 'expires_at' => $draft->expires_at?->toISOString()], $result['replayed'] ? 200 : 201)
|
||||
->header('ETag', $drafts->etag($draft))->header('Cache-Control', 'no-store, private');
|
||||
}
|
||||
|
||||
public function index(Request $request, SimulationFormDrafts $drafts): JsonResponse
|
||||
{
|
||||
abort_unless($request->user() !== null, 401);
|
||||
|
||||
return response()->json(['data' => $drafts->ownedDrafts($request->user())])->header('Cache-Control', 'no-store, private');
|
||||
}
|
||||
|
||||
public function claimPreview(Request $request, SimulationFormDrafts $drafts): JsonResponse
|
||||
{
|
||||
abort_unless($request->user() !== null, 401);
|
||||
|
||||
return response()->json(['data' => $drafts->claimPreview($request)])->header('Cache-Control', 'no-store, private');
|
||||
}
|
||||
|
||||
public function claim(ClaimSimulationFormDraftRequest $request, SimulationFormDrafts $drafts): JsonResponse
|
||||
{
|
||||
$result = $drafts->claim($request, $request->validated('choice'), $request->validated('existing_draft_id'));
|
||||
$draft = $result['draft'];
|
||||
|
||||
return response()->json(['data' => $drafts->present($draft, true), 'choice' => $result['choice'], 'replayed' => $result['replayed'] ?? false, 'expires_at' => $draft->expires_at?->toISOString()])
|
||||
->header('ETag', $drafts->etag($draft))->header('Cache-Control', 'no-store, private');
|
||||
}
|
||||
|
||||
public function linkableBusinesses(Request $request, SimulationFormDrafts $drafts): JsonResponse
|
||||
{
|
||||
abort_unless($request->user()?->hasVerifiedEmail(), 403);
|
||||
|
||||
return response()->json(['data' => $drafts->linkableBusinesses($request->user())])->header('Cache-Control', 'no-store, private');
|
||||
}
|
||||
|
||||
public function businessLink(Request $request, string $id, SimulationFormDrafts $drafts): JsonResponse
|
||||
{
|
||||
abort_unless(Str::isUuid($id) && $request->user()?->hasVerifiedEmail(), 404);
|
||||
|
||||
return response()->json(['data' => $drafts->activeBusinessLink($request->user(), $id)])->header('Cache-Control', 'no-store, private');
|
||||
}
|
||||
|
||||
public function linkBusiness(LinkSimulationDraftBusinessRequest $request, string $id, SimulationFormDrafts $drafts): JsonResponse
|
||||
{
|
||||
abort_unless(Str::isUuid($id), 404);
|
||||
$result = $drafts->linkBusiness($request->user(), $id, $request->validated('business_id'));
|
||||
|
||||
return response()->json(['data' => $result], isset($result['replayed']) ? 200 : 201)->header('Cache-Control', 'no-store, private');
|
||||
}
|
||||
|
||||
public function unlinkBusiness(Request $request, string $id, SimulationFormDrafts $drafts): JsonResponse
|
||||
{
|
||||
abort_unless(Str::isUuid($id) && $request->user()?->hasVerifiedEmail(), 404);
|
||||
|
||||
return response()->json(['data' => $drafts->unlinkBusiness($request->user(), $id)])->header('Cache-Control', 'no-store, private');
|
||||
}
|
||||
|
||||
public function current(Request $request, SimulationFormDrafts $drafts): JsonResponse
|
||||
{
|
||||
$draft = $drafts->current($request);
|
||||
@@ -30,7 +87,14 @@ class SimulationFormDraftController extends Controller
|
||||
return response()->json(['data' => null], 200)->header('Cache-Control', 'no-store, private');
|
||||
}
|
||||
|
||||
return response()->json(['data' => $drafts->present($draft)])->header('ETag', $drafts->etag($draft))->header('Cache-Control', 'no-store, private');
|
||||
return response()->json(['data' => $drafts->present($draft, $request->user() !== null)])->header('ETag', $drafts->etag($draft))->header('Cache-Control', 'no-store, private');
|
||||
}
|
||||
|
||||
public function forgetCurrent(Request $request, SimulationFormDrafts $drafts): JsonResponse
|
||||
{
|
||||
$drafts->forgetCurrent($request);
|
||||
|
||||
return response()->json([], 204)->header('Cache-Control', 'no-store, private');
|
||||
}
|
||||
|
||||
public function show(Request $request, string $id, SimulationFormDrafts $drafts): JsonResponse
|
||||
@@ -38,18 +102,82 @@ class SimulationFormDraftController extends Controller
|
||||
abort_unless(Str::isUuid($id), 404);
|
||||
$draft = $drafts->find($id, $request);
|
||||
|
||||
return response()->json(['data' => $drafts->present($draft)])->header('ETag', $drafts->etag($draft))->header('Cache-Control', 'no-store, private');
|
||||
return response()->json(['data' => $drafts->present($draft, $request->user() !== null)])->header('ETag', $drafts->etag($draft))->header('Cache-Control', 'no-store, private');
|
||||
}
|
||||
|
||||
public function update(StoreSimulationFormDraftRequest $request, string $id, SimulationFormDrafts $drafts): JsonResponse
|
||||
{
|
||||
abort_unless(Str::isUuid($id), 404);
|
||||
$draft = $drafts->update($id, $request->validated(), $request);
|
||||
$draft = $drafts->update($id, $request->snapshots(), $request);
|
||||
|
||||
return response()->json(['data' => ['id' => $draft->id, 'version_no' => $draft->version_no]])
|
||||
return response()->json(['data' => ['id' => $draft->id, 'version_no' => $draft->version_no], 'expires_at' => $draft->expires_at?->toISOString()])
|
||||
->header('ETag', $drafts->etag($draft))->header('Cache-Control', 'no-store, private');
|
||||
}
|
||||
|
||||
public function renew(Request $request, string $id, SimulationFormDrafts $drafts): JsonResponse
|
||||
{
|
||||
abort_unless(Str::isUuid($id), 404);
|
||||
$draft = $drafts->renew($id, $request);
|
||||
|
||||
return response()->json(['data' => ['id' => $draft->id, 'version_no' => $draft->version_no], 'expires_at' => $draft->expires_at?->toISOString()])
|
||||
->header('ETag', $drafts->etag($draft))->header('Cache-Control', 'no-store, private');
|
||||
}
|
||||
|
||||
public function attachSimulation(AttachSimulationToDraftRequest $request, string $id, SimulationFormDrafts $drafts, SimulationExecution $execution): JsonResponse
|
||||
{
|
||||
abort_unless(Str::isUuid($id), 404);
|
||||
$validated = $request->validated();
|
||||
$simulation = $execution->find($validated['simulation_id'], $request);
|
||||
$tokenOwned = $simulation->anonymous_token_hash !== null
|
||||
&& hash_equals($simulation->anonymous_token_hash, (string) $execution->tokenHash($request));
|
||||
if ($request->user()) {
|
||||
$policy = app(SimulationPolicy::class);
|
||||
$canTransfer = $simulation->organization_id === null
|
||||
? $policy->view($request->user(), $simulation)
|
||||
: $policy->update($request->user(), $simulation);
|
||||
abort_unless($canTransfer || $tokenOwned, 404);
|
||||
} else {
|
||||
abort_unless($tokenOwned, 404);
|
||||
}
|
||||
abort_unless($simulation->status === 'completed' && is_array($simulation->result_snapshot), 409, 'Só é possível reutilizar uma simulação concluída.');
|
||||
$report = $simulation->reports->where('status', 'ready')->sortByDesc('created_at')->first();
|
||||
abort_unless($report !== null && is_array($report->report_snapshot), 409, 'O relatório versionado da simulação ainda não está disponível.');
|
||||
abort_unless(preg_match('/^[a-f0-9]{64}$/D', $report->report_sha256) === 1, 409,
|
||||
'O relatório da simulação não tem um hash verificável.');
|
||||
$canonicalSnapshots = DB::selectOne('select CAST(? AS jsonb)::text as report_canonical, CAST(? AS jsonb)::text as result_canonical', [
|
||||
json_encode($report->report_snapshot, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE),
|
||||
json_encode($simulation->result_snapshot, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE),
|
||||
]);
|
||||
abort_unless(hash_equals($report->report_sha256, hash('sha256', $canonicalSnapshots->report_canonical))
|
||||
&& hash_equals($canonicalSnapshots->report_canonical, $canonicalSnapshots->result_canonical), 409,
|
||||
'O relatório não corresponde ao resultado versionado da simulação.');
|
||||
$inputSnapshot = array_intersect_key($simulation->input_snapshot, array_flip(['base', 'rate', 'reduction']));
|
||||
|
||||
$sourceSnapshot = [
|
||||
'simulation_id' => $simulation->id,
|
||||
'source_version_no' => $simulation->version_no,
|
||||
'source_created_at' => $simulation->created_at?->toISOString(),
|
||||
'source_updated_at' => $simulation->updated_at?->toISOString(),
|
||||
'input_snapshot' => $inputSnapshot,
|
||||
'calculation_snapshot' => $simulation->calculation_snapshot,
|
||||
'result_snapshot' => $simulation->result_snapshot,
|
||||
'result_sha256' => $report->report_sha256,
|
||||
'report_id' => $report->id,
|
||||
'report_version_no' => $report->version_no,
|
||||
'report_sha256' => $report->report_sha256,
|
||||
'report_snapshot' => $report->report_snapshot,
|
||||
'consented_at' => now()->toISOString(),
|
||||
];
|
||||
$attached = $drafts->attachSimulation($id, $sourceSnapshot, $request);
|
||||
$draft = $attached['draft'];
|
||||
|
||||
return response()->json([
|
||||
'data' => $drafts->present($draft, $request->user() !== null),
|
||||
'replayed' => $attached['replayed'],
|
||||
'expires_at' => $draft->expires_at?->toISOString(),
|
||||
])->header('ETag', $drafts->etag($draft))->header('Cache-Control', 'no-store, private');
|
||||
}
|
||||
|
||||
public function destroy(Request $request, string $id, SimulationFormDrafts $drafts): JsonResponse
|
||||
{
|
||||
abort_unless(Str::isUuid($id), 404);
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api\V1;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\ResolveSimulationTaxParametersRequest;
|
||||
use App\Services\SimulationTaxParameterResolver;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class SimulationTaxParameterController extends Controller
|
||||
{
|
||||
public function resolve(ResolveSimulationTaxParametersRequest $request, SimulationTaxParameterResolver $resolver): JsonResponse
|
||||
{
|
||||
$input = $request->validated();
|
||||
$data = $resolver->resolvePublishedForPeriod(
|
||||
$input['component'],
|
||||
$input['period_start'],
|
||||
$input['period_end'],
|
||||
$input['context'] ?? [],
|
||||
);
|
||||
|
||||
return response()->json([
|
||||
'component' => $input['component'],
|
||||
'period' => ['start' => $input['period_start'], 'end' => $input['period_end']],
|
||||
'assumptions' => $input['context'] ?? [],
|
||||
'parameters' => $data,
|
||||
])->header('Cache-Control', 'no-store');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user