154 lines
8.8 KiB
PHP
154 lines
8.8 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use App\Models\Opportunity;
|
|
use App\Services\OfferAvailability;
|
|
use App\Services\OpportunityCanonicalContract;
|
|
use App\Services\OpportunityManagement;
|
|
use App\Services\OpportunityProfileWorkflow;
|
|
use App\Services\OpportunityResources;
|
|
use App\Services\PortalProfileAccess;
|
|
use App\Services\PrivateOfferConfidentiality;
|
|
use App\Services\PrivateOfferOperations;
|
|
use App\Services\PrivateOfferPreparation;
|
|
use App\Services\VerifiedDocumentBinary;
|
|
use Illuminate\Http\JsonResponse;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Inertia\Inertia;
|
|
use Inertia\Response;
|
|
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
|
|
|
|
final class PrivateOfferPreparationController extends Controller
|
|
{
|
|
public function resources(Request $request, PrivateOfferPreparation $service, int $id): JsonResponse
|
|
{
|
|
$record = $service->record($request->user(), $id);
|
|
$resources = [];
|
|
foreach (['images', 'documents'] as $kind) {
|
|
$resources[$kind] = array_map(fn ($row) => ['id' => $row->id, 'version' => $row->version, 'deleted' => $row->deleted_at !== null,
|
|
'caption' => $row->payload['caption'] ?? null, 'digitally_edited' => $row->payload['digitally_edited'] ?? null,
|
|
'edit_description' => $row->payload['edit_description'] ?? null,
|
|
'scan' => $row->file->malware_scan_status ?? 'pending'], app(OpportunityResources::class)->listing($id, $kind));
|
|
}
|
|
|
|
return response()->json(['resources' => $resources, 'etag' => app(OpportunityManagement::class)->etag($record),
|
|
'internalProofAccess' => app(PrivateOfferConfidentiality::class)->internal($request->user(), $record)]);
|
|
}
|
|
|
|
public function attachment(Request $request, PrivateOfferPreparation $service, int $id, string $kind, string $resourceId): \Symfony\Component\HttpFoundation\Response
|
|
{
|
|
$record = $service->record($request->user(), $id);
|
|
abort_unless(in_array($kind, ['documents', 'images'], true), 404);
|
|
$link = DB::table('opportunity_'.$kind)->where('opportunity_id', $id)->where('id', $resourceId)->whereNull('deleted_at')->first();
|
|
abort_unless($link !== null, 404);
|
|
$document = DB::table('documents')->where('id', $link->document_id)->whereNull('deleted_at')->first();
|
|
abort_unless($document !== null, 404);
|
|
|
|
return app(VerifiedDocumentBinary::class)->respond($document);
|
|
}
|
|
|
|
public function confidentiality(Request $request, PrivateOfferConfidentiality $privacy, int $id): JsonResponse|Response
|
|
{
|
|
$overview = $privacy->overview($request->user(), $id);
|
|
|
|
return $request->expectsJson() ? response()->json($overview) : Inertia::render('offers/confidentiality', ['offerId' => $id]);
|
|
}
|
|
|
|
public function confidentialityAction(Request $request, PrivateOfferConfidentiality $privacy, int $id): JsonResponse
|
|
{
|
|
return response()->json($privacy->act($request->user(), $id, $request->all()));
|
|
}
|
|
|
|
public function confidentialRead(Request $request, PrivateOfferConfidentiality $privacy, int $id): JsonResponse
|
|
{
|
|
try {
|
|
$result = $privacy->read($request->user(), $id);
|
|
} catch (HttpExceptionInterface $error) {
|
|
app(PortalProfileAccess::class)->audit($request->user(), 'offer_confidential_denied', ['record_id' => $id, 'http_status' => $error->getStatusCode()]);
|
|
throw $error;
|
|
}
|
|
abort_if(isset($result['denied']), 403, 'É necessária aceitação do NDA e autorização expressa do vendedor para esta versão.');
|
|
|
|
return response()->json($result);
|
|
}
|
|
|
|
public function nda(Request $request, PrivateOfferConfidentiality $privacy, int $id): \Symfony\Component\HttpFoundation\Response
|
|
{
|
|
$overview = $privacy->overview($request->user(), $id);
|
|
abort_unless($overview['nda'], 409, 'O NDA aprovado ainda não foi disponibilizado.');
|
|
app(PortalProfileAccess::class)->audit($request->user(), 'offer_nda_access', ['record_id' => $id, 'version_hash' => $overview['version_hash'], 'nda' => $overview['nda']]);
|
|
|
|
$document = DB::table('documents')->where('id', $overview['nda']['document_id'])->firstOrFail();
|
|
abort_unless((int) $document->version_no === $overview['nda']['version'] && hash_equals(trim($document->sha256), $overview['nda']['sha256']), 409, 'O exemplar do NDA mudou; actualize antes de continuar.');
|
|
|
|
return app(VerifiedDocumentBinary::class)->respond($document);
|
|
}
|
|
|
|
public function index(Request $request, PortalProfileAccess $access): Response
|
|
{
|
|
$filters = $request->validate(['state' => 'nullable|in:draft,submitted,in_review,needs_information,rejected,approved', 'operational' => 'nullable|boolean', 'pending_reports' => 'nullable|boolean']);
|
|
$query = ($filters['operational'] ?? false) ? app(PrivateOfferOperations::class)->staffQuery($request->user()) : $access->scope(Opportunity::query(), $request->user());
|
|
if ($filters['pending_reports'] ?? false) {
|
|
$query = app(PrivateOfferOperations::class)->staffQuery($request->user());
|
|
$query->whereRaw("jsonb_path_exists(COALESCE(workflow_context->'offer_reports','{}'::jsonb), '$.* ? (@.status == \"open\" || @.status == \"in_review\")')");
|
|
}
|
|
$records = $query->whereNotNull('workflow_context->private_offer_preparation')
|
|
->when($filters['state'] ?? null, fn ($q, $state) => $q->where('workflow_context->private_offer_preparation->state', $state))
|
|
->orderByDesc('id')->paginate(20)->withQueryString();
|
|
|
|
return Inertia::render('offers/preparation-index', ['records' => $records->through(fn ($r) => [
|
|
'id' => $r->id, 'title' => app(OpportunityManagement::class)->values($r)['title'] ?? null, 'reference' => $r->reference,
|
|
'state' => $access->state($r),
|
|
'availability' => app(OfferAvailability::class)->record($r)['state'],
|
|
]), 'filters' => $filters, 'canCreate' => count($access->navigation($request->user())['contexts']) > 0]);
|
|
}
|
|
|
|
public function edit(Request $request, PrivateOfferPreparation $service, ?int $id = null): Response|JsonResponse
|
|
{
|
|
$record = $id ? $service->record($request->user(), $id) : null;
|
|
$contexts = app(PortalProfileAccess::class)->navigation($request->user())['contexts'];
|
|
abort_unless($record || count($contexts) > 0, 403, 'É necessária uma atribuição de promotor no âmbito da oferta.');
|
|
$presentation = $record ? $service->presentation($request->user(), $record) : null;
|
|
if ($request->expectsJson() && ! $request->header('X-Inertia')) {
|
|
return response()->json(['record' => $presentation]);
|
|
}
|
|
$options = app(OpportunityCanonicalContract::class)->options($request->user(), $record);
|
|
|
|
return Inertia::render('offers/preparation', ['record' => $presentation, 'contexts' => $contexts,
|
|
'contacts' => $options['contacts'], 'contactInvestors' => $options['contactInvestors'],
|
|
'sectors' => DB::table('sectors')->where('is_active', true)->orderBy('name')->get(['id', 'name']),
|
|
'propertyTypes' => array_values(array_filter($options['definitions']['realEstatePatch']['properties']['property_type']['enum'], fn ($type) => is_string($type)))]);
|
|
}
|
|
|
|
public function save(Request $request, PrivateOfferPreparation $service, ?int $id = null): JsonResponse
|
|
{
|
|
if ($id) {
|
|
$service->record($request->user(), $id, true);
|
|
}
|
|
$record = $service->save($request->user(), $request->all(), $id);
|
|
|
|
return response()->json(['record' => $service->presentation($request->user(), $record)], $id ? 200 : 201);
|
|
}
|
|
|
|
public function submit(Request $request, PrivateOfferPreparation $service, int $id): JsonResponse
|
|
{
|
|
$record = $service->submit($request->user(), $id, $request->all());
|
|
|
|
return response()->json(['record' => $service->presentation($request->user(), $record),
|
|
'message' => 'Submissão privada registada. A oferta não foi publicada.']);
|
|
}
|
|
|
|
public function transition(Request $request, PrivateOfferPreparation $service, OpportunityProfileWorkflow $workflow, int $id): JsonResponse
|
|
{
|
|
$service->record($request->user(), $id);
|
|
$data = $request->validate(['etag' => 'required|string|size:64', 'action' => 'required|in:start_review,assign_review,request_correction,reject,approve,request_revision',
|
|
'assignee_user_id' => 'required_if:action,assign_review|integer|min:1',
|
|
'reason' => 'required|string|min:20|max:1000', 'correction_items' => 'sometimes|array', 'correction_items.*' => 'string']);
|
|
$record = $workflow->transition($request->user(), $id, $data);
|
|
|
|
return response()->json(['record' => $service->presentation($request->user(), $record), 'message' => 'Decisão privada registada. A oferta não foi publicada.']);
|
|
}
|
|
}
|