203 lines
14 KiB
PHP
203 lines
14 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Http\Requests;
|
|
|
|
use App\Models\LandAssistanceCase;
|
|
use App\Services\CountryCatalogue;
|
|
use App\Services\InvestmentGeography;
|
|
use Illuminate\Foundation\Http\FormRequest;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Str;
|
|
use Illuminate\Validation\Validator;
|
|
|
|
class StoreLandAssistanceCaseRequest extends FormRequest
|
|
{
|
|
/** @return array<string, mixed> */
|
|
public function payload(): array
|
|
{
|
|
$payload = $this->validated('payload');
|
|
|
|
return is_array($payload) ? $payload : [];
|
|
}
|
|
|
|
public function authorize(): bool
|
|
{
|
|
return true;
|
|
}
|
|
|
|
/** @return array<string,list<mixed>> */
|
|
public function rules(): array
|
|
{
|
|
return [
|
|
'version' => [$this->isMethod('post') && ! $this->routeIs('land-assistance.support') ? 'nullable' : 'required', 'integer', 'min:1'],
|
|
'context_revision' => ['nullable', 'integer', 'min:0'], 'context_reviewed' => ['nullable', 'boolean'],
|
|
'payload' => ['required', 'array:support_kind,request_description,project_reference,investor_path_case_id,applicant_category,full_name,contact_person,email,phone,preferred_contact,country,declared_origin_country_code,nationality,representative_name,representative_role,representation_basis,nuit,constitution_or_residency_note,area_status,parcels,purpose,activity,known_occupants,occupant_description,known_improvements,improvements_description,prior_right_status,prior_right_description,conflict_status,conflict_description,official_process,attachments_to_follow,exploration_plan,consultation,additional_information,truth_declaration,contact_consent'],
|
|
'payload.support_kind' => ['nullable', 'in:investment,home,family_exploitation,recognition,regularization,delimitation,change_of_use,transfer,existing_process,conflict_guidance,other'],
|
|
'payload.request_description' => ['nullable', 'string', 'max:4000'],
|
|
'payload.project_reference' => ['nullable', 'string', 'max:160'],
|
|
'payload.investor_path_case_id' => ['nullable', 'uuid'],
|
|
'payload.applicant_category' => ['nullable', 'in:individual,entity,community,other'],
|
|
'payload.full_name' => ['nullable', 'string', 'max:150'],
|
|
'payload.contact_person' => ['nullable', 'string', 'max:200'],
|
|
'payload.email' => ['nullable', 'email:rfc', 'max:150'],
|
|
'payload.phone' => ['nullable', 'string', 'max:30'],
|
|
'payload.preferred_contact' => ['nullable', 'in:email,phone,any'],
|
|
'payload.country' => ['nullable', 'string', 'max:100', app(CountryCatalogue::class)->legacyCountryRule()],
|
|
'payload.declared_origin_country_code' => ['nullable', 'string', app(CountryCatalogue::class)->rule()],
|
|
'payload.nationality' => ['nullable', 'string', 'max:120'],
|
|
'payload.representative_name' => ['nullable', 'string', 'max:200'],
|
|
'payload.representative_role' => ['nullable', 'string', 'max:120'],
|
|
'payload.representation_basis' => ['nullable', 'string', 'max:500'],
|
|
'payload.nuit' => ['nullable', 'string', 'max:40'],
|
|
'payload.constitution_or_residency_note' => ['nullable', 'string', 'max:1000'],
|
|
'payload.area_status' => ['nullable', 'in:identified,seeking,location_support,unknown'],
|
|
'payload.parcels' => ['nullable', 'array', 'max:10'],
|
|
'payload.parcels.*' => ['array:province_id,district_id,admin_post_id,locality_id,place_description,area,area_unit,cadastral_reference,coordinates,geometry_confidence'],
|
|
'payload.parcels.*.province_id' => ['nullable', 'uuid', 'exists:geo_locations,id'],
|
|
'payload.parcels.*.district_id' => ['nullable', 'uuid', 'exists:geo_locations,id'],
|
|
'payload.parcels.*.admin_post_id' => ['nullable', 'uuid', 'exists:geo_locations,id'],
|
|
'payload.parcels.*.locality_id' => ['nullable', 'uuid', 'exists:geo_locations,id'],
|
|
'payload.parcels.*.place_description' => ['nullable', 'string', 'max:1000'],
|
|
'payload.parcels.*.area' => ['nullable', 'numeric', 'min:0', 'max:999999999999'],
|
|
'payload.parcels.*.area_unit' => ['nullable', 'in:m2,hectares,other,unknown'],
|
|
'payload.parcels.*.cadastral_reference' => ['nullable', 'string', 'max:160'],
|
|
'payload.parcels.*.coordinates' => ['nullable', 'string', 'max:500'],
|
|
'payload.parcels.*.geometry_confidence' => ['nullable', 'in:declared,approximate,confirmed_by_authority,unknown'],
|
|
'payload.purpose' => ['nullable', 'string', 'max:1000'],
|
|
'payload.activity' => ['nullable', 'string', 'max:500'],
|
|
'payload.known_occupants' => ['nullable', 'in:yes,no_as_far_as_known,unknown'],
|
|
'payload.occupant_description' => ['nullable', 'string', 'max:2000'],
|
|
'payload.known_improvements' => ['nullable', 'in:yes,unknown,none_identified'],
|
|
'payload.improvements_description' => ['nullable', 'string', 'max:2000'],
|
|
'payload.prior_right_status' => ['nullable', 'in:yes,unknown,no_documents'],
|
|
'payload.prior_right_description' => ['nullable', 'string', 'max:2000'],
|
|
'payload.conflict_status' => ['nullable', 'in:yes,unknown,no_knowledge'],
|
|
'payload.conflict_description' => ['nullable', 'string', 'max:2000'],
|
|
'payload.official_process' => ['nullable', 'array:official_reference,receiving_entity,presented_at,reported_status,pending_items,proof_available'],
|
|
'payload.official_process.official_reference' => ['nullable', 'string', 'max:160'],
|
|
'payload.official_process.receiving_entity' => ['nullable', 'string', 'max:200'],
|
|
'payload.official_process.presented_at' => ['nullable', 'date_format:Y-m-d'],
|
|
'payload.official_process.reported_status' => ['nullable', 'string', 'max:1000'],
|
|
'payload.official_process.pending_items' => ['nullable', 'string', 'max:2000'],
|
|
'payload.official_process.proof_available' => ['nullable', 'in:yes,no,unknown'],
|
|
'payload.attachments_to_follow' => ['nullable', 'array', 'max:10'],
|
|
'payload.attachments_to_follow.*' => ['string', 'in:sketch,prior_right,exploration_plan,official_receipt,consultation_minutes,photo,other'],
|
|
'payload.exploration_plan' => ['nullable', 'array:activity_rows,notes'],
|
|
'payload.exploration_plan.activity_rows' => ['nullable', 'array', 'max:30'],
|
|
'payload.exploration_plan.activity_rows.*' => ['array:period,activity,area,area_unit,stage,targets,structures,investment'],
|
|
'payload.exploration_plan.activity_rows.*.period' => ['nullable', 'string', 'max:80'],
|
|
'payload.exploration_plan.activity_rows.*.activity' => ['nullable', 'string', 'max:300'],
|
|
'payload.exploration_plan.activity_rows.*.area' => ['nullable', 'numeric', 'min:0'],
|
|
'payload.exploration_plan.activity_rows.*.area_unit' => ['nullable', 'in:m2,hectares,other,unknown'],
|
|
'payload.exploration_plan.activity_rows.*.stage' => ['nullable', 'in:existing,planned,unknown'],
|
|
'payload.exploration_plan.activity_rows.*.targets' => ['nullable', 'string', 'max:1000'],
|
|
'payload.exploration_plan.activity_rows.*.structures' => ['nullable', 'string', 'max:1000'],
|
|
'payload.exploration_plan.activity_rows.*.investment' => ['nullable', 'numeric', 'min:0'],
|
|
'payload.exploration_plan.notes' => ['nullable', 'string', 'max:2000'],
|
|
'payload.consultation' => ['nullable', 'array:status,community,location,date,facilitators,language,participants,interventions,opposition,agreements,minutes_reference,opinions_received'],
|
|
'payload.consultation.status' => ['nullable', 'in:not_started,preparing,reported_completed,unknown'],
|
|
'payload.consultation.community' => ['nullable', 'string', 'max:200'],
|
|
'payload.consultation.location' => ['nullable', 'string', 'max:500'],
|
|
'payload.consultation.date' => ['nullable', 'date_format:Y-m-d'],
|
|
'payload.consultation.facilitators' => ['nullable', 'string', 'max:1000'],
|
|
'payload.consultation.language' => ['nullable', 'string', 'max:100'],
|
|
'payload.consultation.participants' => ['nullable', 'array', 'max:200'],
|
|
'payload.consultation.participants.*' => ['array:name,role'],
|
|
'payload.consultation.participants.*.name' => ['nullable', 'string', 'max:200'],
|
|
'payload.consultation.participants.*.role' => ['nullable', 'string', 'max:120'],
|
|
'payload.consultation.interventions' => ['nullable', 'string', 'max:4000'],
|
|
'payload.consultation.opposition' => ['nullable', 'string', 'max:2000'],
|
|
'payload.consultation.agreements' => ['nullable', 'string', 'max:2000'],
|
|
'payload.consultation.minutes_reference' => ['nullable', 'string', 'max:500'],
|
|
'payload.consultation.opinions_received' => ['nullable', 'string', 'max:2000'],
|
|
'payload.additional_information' => ['nullable', 'string', 'max:4000'],
|
|
'payload.truth_declaration' => ['nullable', 'boolean'],
|
|
'payload.contact_consent' => ['nullable', 'boolean'],
|
|
];
|
|
}
|
|
|
|
public function withValidator(Validator $validator): void
|
|
{
|
|
$validator->after(function (Validator $validator): void {
|
|
$payload = $this->input('payload', []);
|
|
$context = is_array($payload) ? ($payload['investor_path_case_id'] ?? null) : null;
|
|
$previous = $this->route('id') ? LandAssistanceCase::whereKey($this->route('id'))->where('user_id', $this->user()->id)->first() : null;
|
|
$bound = $previous?->payload['investor_path_case_id'] ?? null;
|
|
if ($context !== null) {
|
|
$case = is_string($context) && Str::isUuid($context)
|
|
? DB::table('desk_cases')->where('id', $context)->first() : null;
|
|
if ((! config('investor-path.enabled') && $bound !== $context) || ! $case || (int) $case->user_id !== $this->user()->id
|
|
|| ($case->closed_at && $bound !== $context) || ! $case->journey_id
|
|
|| ! DB::table('investor_journeys')->where('id', $case->journey_id)->whereNotNull('path_definition_id')->whereNull('deleted_at')->exists()) {
|
|
$validator->errors()->add('payload.investor_path_case_id', 'Use um dossier do seu percurso disponível para preparação. O acesso direto a Terra e DUAT continua disponível.');
|
|
}
|
|
}
|
|
if ($this->route('id')) {
|
|
if ($bound !== null && $context !== $bound) {
|
|
$validator->errors()->add('payload.investor_path_case_id', 'Preserve o vínculo deste pedido ao dossier original.');
|
|
}
|
|
}
|
|
if (is_array($payload) && is_array($payload['parcels'] ?? null)) {
|
|
$geography = app(InvestmentGeography::class);
|
|
$levels = ['province_id' => 'province', 'district_id' => 'district', 'admin_post_id' => 'admin_post', 'locality_id' => 'locality'];
|
|
foreach ($payload['parcels'] as $index => $parcel) {
|
|
if (! is_array($parcel)) {
|
|
continue;
|
|
}
|
|
$parent = null;
|
|
$gap = false;
|
|
foreach ($levels as $field => $expectedType) {
|
|
$id = $parcel[$field] ?? null;
|
|
if (! is_string($id) || $id === '') {
|
|
$gap = true;
|
|
|
|
continue;
|
|
}
|
|
$record = $geography->publicReferences()->where('id', $id)->first(['id', 'parent_id', 'location_type']);
|
|
// The populated workbook hierarchy has country -> province. The public
|
|
// selector deliberately starts at province; it is not a root in storage.
|
|
if ($gap || ! $record || $record->location_type !== $expectedType || ($expectedType !== 'province' && (string) $record->parent_id !== $parent)) {
|
|
$validator->errors()->add("payload.parcels.$index.$field", 'A hierarquia territorial declarada é incoerente ou não está publicada.');
|
|
$gap = true;
|
|
}
|
|
$parent = (string) $id;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (! $this->routeIs('land-assistance.support')) {
|
|
return;
|
|
}
|
|
|
|
if (! is_array($payload)) {
|
|
return;
|
|
}
|
|
foreach (['support_kind', 'request_description', 'applicant_category', 'full_name', 'preferred_contact', 'area_status', 'purpose'] as $required) {
|
|
if (! isset($payload[$required]) || trim((string) $payload[$required]) === '') {
|
|
$validator->errors()->add('payload.'.$required, 'Este campo é necessário para solicitar apoio.');
|
|
}
|
|
}
|
|
if (empty($payload['email']) && empty($payload['phone'])) {
|
|
$validator->errors()->add('payload.email', 'Indique pelo menos um contacto utilizável.');
|
|
}
|
|
if (! empty($payload['email']) && ! filter_var($payload['email'], FILTER_VALIDATE_EMAIL)) {
|
|
$validator->errors()->add('payload.email', 'Indique um email válido para registar o pedido no balcão de apoio.');
|
|
}
|
|
if (($payload['applicant_category'] ?? null) === 'community' && empty($payload['contact_person'])) {
|
|
$validator->errors()->add('payload.contact_person', 'Indique um contacto para a comunidade, se conhecido.');
|
|
}
|
|
if (($payload['applicant_category'] ?? null) === 'entity' && empty($payload['representative_role'])) {
|
|
$validator->errors()->add('payload.representative_role', 'Indique a qualidade da representação conhecida.');
|
|
}
|
|
if (empty($payload['contact_consent'])) {
|
|
$validator->errors()->add('payload.contact_consent', 'A autorização de contacto é necessária para solicitar apoio.');
|
|
}
|
|
if (empty($payload['truth_declaration'])) {
|
|
$validator->errors()->add('payload.truth_declaration', 'Confirme que as respostas refletem a informação de que dispõe.');
|
|
}
|
|
});
|
|
}
|
|
}
|