*/ public function payload(): array { $payload = $this->validated('payload'); if (!is_array($payload)) return []; if (!empty($payload['investor_path_case_id'])) return app(\App\Services\InvestmentConsumerScope::class)->bind($this->user(), $payload['investor_path_case_id'], $payload, $this->routeIs('land-assistance.support')); return \Illuminate\Support\Arr::except($payload, ['context_opportunity', 'context_opportunity_snapshot']); } public function authorize(): bool { return true; } /** @return array> */ 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:context_opportunity,context_opportunity_snapshot,context_reuse,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.context_opportunity' => ['nullable', 'string', 'max:100'], 'payload.context_opportunity_snapshot' => ['nullable', 'string', 'max:200000'], 'payload.context_reuse' => ['nullable', 'string', 'max:10000'], '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.'); } }); } }