@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class AttachSimulationToDraftRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/** @return array<string, list<mixed>> */
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'simulation_id' => ['required', 'uuid'],
|
||||
'consent_reuse' => ['required', 'accepted'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class ClaimSimulationFormDraftRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return $this->user()?->hasVerifiedEmail() === true;
|
||||
}
|
||||
|
||||
/** @return array<string, mixed> */
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'choice' => ['required', Rule::in(['continue_existing', 'save_as_new'])],
|
||||
'existing_draft_id' => ['required_if:choice,continue_existing', 'nullable', 'uuid'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class LinkSimulationDraftBusinessRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return $this->user()?->hasVerifiedEmail() === true;
|
||||
}
|
||||
|
||||
/** @return array<string, mixed> */
|
||||
public function rules(): array
|
||||
{
|
||||
return ['business_id' => ['required', 'uuid']];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Validator;
|
||||
|
||||
class ResolveSimulationTaxParametersRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/** @return array<string,list<mixed>> */
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'component' => ['required', 'in:IRPC,IVA,ISPC,IRPS,INSS,RETENCOES,DIREITOS_ADUANEIROS,ICE'],
|
||||
'period_start' => ['required', 'date_format:Y-m-d'],
|
||||
'period_end' => ['required', 'date_format:Y-m-d', 'after_or_equal:period_start'],
|
||||
'context' => ['sometimes', 'array:fiscal_periodicity,fiscal_operation,fiscal_residency_assessment,fiscal_residency_country,fiscal_income_category,fiscal_establishments,fiscal_documentation,fiscal_customs_code,fiscal_vat_refund_state'],
|
||||
'context.*' => ['string', 'max:160'],
|
||||
];
|
||||
}
|
||||
|
||||
public function withValidator(Validator $validator): void
|
||||
{
|
||||
$validator->after(function (Validator $validator): void {
|
||||
$start = $this->parseEffectiveDate('period_start');
|
||||
$end = $this->parseEffectiveDate('period_end');
|
||||
if ($start !== null && $end !== null && ($start->diff($end)->days ?? 0) > 365) {
|
||||
$validator->errors()->add('period_end', 'O período de resolução não pode exceder 366 dias.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private function parseEffectiveDate(string $key): ?\DateTimeImmutable
|
||||
{
|
||||
$value = $this->input($key);
|
||||
if (! is_string($value) || preg_match('/^\d{4}-\d{2}-\d{2}$/D', $value) !== 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$date = \DateTimeImmutable::createFromFormat('!Y-m-d', $value);
|
||||
|
||||
return $date !== false && $date->format('Y-m-d') === $value ? $date : null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use App\Services\InvestmentGeography;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
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'],
|
||||
'payload' => ['required', 'array:support_kind,request_description,project_reference,applicant_category,full_name,contact_person,email,phone,preferred_contact,country,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.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'],
|
||||
'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', []);
|
||||
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']);
|
||||
if ($gap || ! $record || $record->location_type !== $expectedType || ($expectedType === 'province' ? $record->parent_id !== null : (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.');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -3,11 +3,24 @@
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Validator;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Validation\Validator;
|
||||
|
||||
class StoreSimulationFormDraftRequest extends FormRequest
|
||||
{
|
||||
/** @return array{personal_snapshot: array<string,mixed>, economic_snapshot: array<string,mixed>} */
|
||||
public function snapshots(): array
|
||||
{
|
||||
$validated = $this->validated();
|
||||
$personal = $validated['personal_snapshot'] ?? null;
|
||||
$economic = $validated['economic_snapshot'] ?? null;
|
||||
if (! is_array($personal) || ! is_array($economic)) {
|
||||
throw new \LogicException('Validated simulation snapshots must be arrays.');
|
||||
}
|
||||
|
||||
return ['personal_snapshot' => $personal, 'economic_snapshot' => $economic];
|
||||
}
|
||||
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
@@ -17,13 +30,15 @@ class StoreSimulationFormDraftRequest extends FormRequest
|
||||
public function rules(): array
|
||||
{
|
||||
$personal = [
|
||||
'person_type', 'investor_origin', 'tax_residency_country', 'entity_stage', 'legal_form', 'nuit',
|
||||
'person_type', 'investor_origin', 'tax_residency_country', 'fiscal_residency_country', 'entity_stage', 'legal_form', 'nuit',
|
||||
'investor_profile_id', 'profile_confirmation', 'legal_name', 'commercial_name', 'registration_number',
|
||||
'representative_name', 'representative_capacity', 'email', 'phone', 'address', 'contact_country', 'selection_labels',
|
||||
'contact_province', 'contact_district', 'preferred_channel',
|
||||
];
|
||||
$economic = [
|
||||
'purpose', 'manifestation_type', 'preferred_language', 'project_title', 'project_summary', 'selection_labels', 'project_type',
|
||||
'journey_type', 'detail_level', 'business_plan_snapshot', 'financial_tools_snapshot', 'project_team_snapshot', 'project_execution_snapshot', 'purpose', 'manifestation_type', 'preferred_language', 'project_title', 'project_summary', 'project_problem', 'target_customers', 'value_proposition', 'market_assumption', 'market_source', 'project_milestones', 'selection_labels', 'project_type',
|
||||
'competitor_assumption', 'sales_channel_assumption', 'marketing_approach', 'location_rationale', 'project_risk_responses_snapshot', 'project_questionnaire_version',
|
||||
'customer_validation_status', 'cost_estimate_status', 'location_assessment_status', 'funding_status', 'receivables_timing_status',
|
||||
'project_type_other', 'sector_id', 'activity_id', 'products_services', 'maturity_stage', 'expected_start_date',
|
||||
'project_duration_years', 'existing_project_id', 'province_id', 'district_id', 'admin_post_id', 'locality_id',
|
||||
'site_known', 'latitude', 'longitude', 'area_required', 'area_unit', 'special_zone_id', 'land_status',
|
||||
@@ -31,9 +46,12 @@ class StoreSimulationFormDraftRequest extends FormRequest
|
||||
'grants', 'secured_amount', 'sought_amount', 'annual_revenue', 'annual_operating_costs', 'expected_profit',
|
||||
'financing_term_months', 'funds_origin_countries', 'direct_jobs', 'indirect_jobs', 'national_jobs',
|
||||
'foreign_jobs', 'skills_needed', 'local_content_percentage', 'exports_value', 'import_substitution_value',
|
||||
'environmental_category', 'community_benefits', 'tax_calculation', 'base', 'rate', 'reduction',
|
||||
'environmental_category', 'community_benefits', 'tax_calculation', 'fiscal_scenario_mode', 'base', 'rate', 'reduction',
|
||||
'sales_taxable', 'sales_exempt', 'sales_zero_rate', 'exports', 'deductible_costs', 'non_deductible_costs',
|
||||
'carried_losses', 'payroll', 'outbound_payments', 'imports', 'claimed_benefits', 'reference_date',
|
||||
'fiscal_period', 'fiscal_periodicity', 'fiscal_month', 'fiscal_quarter', 'fiscal_reference_date', 'irps_salary_view', 'fiscal_component', 'fiscal_income_category', 'fiscal_residency_assessment',
|
||||
'fiscal_operation', 'fiscal_activity', 'fiscal_establishments', 'fiscal_documentation',
|
||||
'fiscal_customs_code', 'fiscal_vat_refund_state',
|
||||
'offer_or_demand', 'opportunity_type', 'opportunity_type_other', 'opportunity_description',
|
||||
'opportunity_value', 'opportunity_currency', 'opportunity_conditions', 'opportunity_deadline',
|
||||
'required_documents', 'confidentiality', 'publish_requested', 'terms_accepted', 'truth_declaration', 'reuse_simulation',
|
||||
@@ -42,9 +60,24 @@ class StoreSimulationFormDraftRequest extends FormRequest
|
||||
];
|
||||
|
||||
$rules = [
|
||||
'personal_snapshot' => ['required', 'array:'.implode(',', $personal)],
|
||||
// Anonymous clients must send an empty snapshot; `required` rejects
|
||||
// an empty array before the privacy policy can enforce that shape.
|
||||
'personal_snapshot' => ['present', 'array:'.implode(',', $personal)],
|
||||
'economic_snapshot' => ['required', 'array:'.implode(',', $economic)],
|
||||
'economic_snapshot.purpose' => ['required', 'in:simulate_only,own_investment,seek_investor,seek_partner,present_opportunity,request_facilitation'],
|
||||
'economic_snapshot.journey_type' => ['nullable', 'in:personal,small_business,company,investor'],
|
||||
'economic_snapshot.detail_level' => ['nullable', 'in:simple,advanced'],
|
||||
'economic_snapshot.business_plan_snapshot' => ['nullable', 'string', 'max:60000'],
|
||||
'economic_snapshot.financial_tools_snapshot' => ['nullable', 'string', 'max:30000'],
|
||||
'economic_snapshot.project_team_snapshot' => ['nullable', 'string', 'max:30000'],
|
||||
'economic_snapshot.project_execution_snapshot' => ['nullable', 'string', 'max:30000'],
|
||||
'economic_snapshot.project_risk_responses_snapshot' => ['nullable', 'string', 'max:30000'],
|
||||
'economic_snapshot.project_questionnaire_version' => ['nullable', 'string', 'max:100'],
|
||||
'economic_snapshot.customer_validation_status' => ['nullable', 'in:yes,no,partial,unknown,not_applicable'],
|
||||
'economic_snapshot.cost_estimate_status' => ['nullable', 'in:yes,no,partial,unknown,not_applicable'],
|
||||
'economic_snapshot.location_assessment_status' => ['nullable', 'in:yes,no,partial,unknown,not_applicable'],
|
||||
'economic_snapshot.receivables_timing_status' => ['nullable', 'in:yes,no,partial,unknown,not_applicable'],
|
||||
'economic_snapshot.funding_status' => ['nullable', 'in:confirmed,in_process,not_started,unknown,not_applicable'],
|
||||
'economic_snapshot.manifestation_type' => ['nullable', 'in:investment,business'],
|
||||
'economic_snapshot.preferred_language' => ['nullable', 'in:pt-MZ,en'],
|
||||
'economic_snapshot.project_type' => ['nullable', 'in:new,expansion,acquisition,partnership,concession,supplier,buyer,other'],
|
||||
@@ -55,6 +88,19 @@ class StoreSimulationFormDraftRequest extends FormRequest
|
||||
'economic_snapshot.confidentiality' => ['nullable', 'in:public,registered_users,restricted,confidential'],
|
||||
'economic_snapshot.site_known' => ['nullable', 'in:yes,no'],
|
||||
'economic_snapshot.tax_calculation' => ['nullable', 'in:yes,no'],
|
||||
'economic_snapshot.fiscal_scenario_mode' => ['nullable', 'in:approved_parameters,custom_planning,no_calculation'],
|
||||
'economic_snapshot.fiscal_period' => ['nullable', 'integer', 'in:'.implode(',', range(2000, today()->year + 1))],
|
||||
'economic_snapshot.fiscal_periodicity' => ['nullable', 'in:monthly,quarterly,annual,one_off,unknown'],
|
||||
'economic_snapshot.fiscal_month' => ['nullable', 'regex:/^(0[1-9]|1[0-2])$/'],
|
||||
'economic_snapshot.fiscal_quarter' => ['nullable', 'in:Q1,Q2,Q3,Q4'],
|
||||
'economic_snapshot.fiscal_reference_date' => ['nullable', 'date_format:Y-m-d'],
|
||||
'economic_snapshot.irps_salary_view' => ['nullable', 'in:monthly_net_estimate,monthly_withholding,annual_assessment'],
|
||||
'economic_snapshot.fiscal_component' => ['nullable', 'in:irpc,iva,ispc,irps,inss,withholding,customs_ice'],
|
||||
'economic_snapshot.fiscal_income_category' => ['nullable', 'in:employment,rental,digital,capital_gain,business,other,unknown'],
|
||||
'economic_snapshot.fiscal_residency_assessment' => ['nullable', 'in:self_reported_resident,self_reported_non_resident,unknown'],
|
||||
'economic_snapshot.fiscal_operation' => ['nullable', 'in:domestic,non_resident,import,export,refund,other,unknown'],
|
||||
'economic_snapshot.fiscal_documentation' => ['nullable', 'in:documented,partly_documented,not_documented,unknown'],
|
||||
'economic_snapshot.fiscal_vat_refund_state' => ['nullable', 'in:estimate_only,request_filed,recognized,received,unknown'],
|
||||
'economic_snapshot.publish_requested' => ['nullable', 'in:yes,no'],
|
||||
'economic_snapshot.terms_accepted' => ['nullable', 'in:yes,no'],
|
||||
'economic_snapshot.truth_declaration' => ['nullable', 'in:yes,no'],
|
||||
@@ -85,17 +131,21 @@ class StoreSimulationFormDraftRequest extends FormRequest
|
||||
foreach (['direct_jobs', 'indirect_jobs', 'national_jobs', 'foreign_jobs', 'financing_term_months', 'project_duration_years'] as $field) {
|
||||
$rules['economic_snapshot.'.$field] = ['nullable', 'integer', 'min:0'];
|
||||
}
|
||||
$rules['economic_snapshot.fiscal_establishments'] = ['nullable', 'integer', 'min:1'];
|
||||
foreach (['fiscal_activity', 'fiscal_customs_code'] as $field) {
|
||||
$rules['economic_snapshot.'.$field] = ['nullable', 'string', 'max:300'];
|
||||
}
|
||||
foreach (['infrastructure_needs', 'funds_origin_countries', 'required_documents'] as $field) {
|
||||
$rules['economic_snapshot.'.$field] = ['nullable', 'array', 'max:100'];
|
||||
$rules['economic_snapshot.'.$field.'.*'] = ['nullable', 'string', 'max:3000'];
|
||||
}
|
||||
$rules['economic_snapshot.infrastructure_needs.*'] = ['nullable', 'in:energia,água,estrada,porto,ferrovia,telecomunicações,armazenamento'];
|
||||
$rules['economic_snapshot.infrastructure_needs.*'] = ['nullable', 'in:energia,água,estrada,porto,ferrovia,telecomunicações,armazenamento,customers,raw_materials,workers,facilities,costs,legal_requirements'];
|
||||
$rules['economic_snapshot.required_documents.*'] = ['nullable', 'in:identity_document,company_registration,nuit_certificate,company_profile,business_plan,feasibility_study,proof_of_funds,special_zone_certificate,licence,site_map'];
|
||||
foreach (['skills_needed', 'payroll', 'outbound_payments', 'imports', 'claimed_benefits', 'opportunity_conditions'] as $field) {
|
||||
$rules['economic_snapshot.'.$field] = ['nullable', 'string', 'max:10000'];
|
||||
}
|
||||
$rules['economic_snapshot.selection_labels'] = ['nullable', 'array'];
|
||||
foreach (['project_summary', 'opportunity_description', 'products_services', 'community_benefits'] as $field) {
|
||||
foreach (['project_summary', 'project_problem', 'target_customers', 'value_proposition', 'market_assumption', 'market_source', 'project_milestones', 'opportunity_description', 'products_services', 'community_benefits', 'competitor_assumption', 'sales_channel_assumption', 'marketing_approach', 'location_rationale'] as $field) {
|
||||
$rules['economic_snapshot.'.$field] = ['nullable', 'string', 'max:10000'];
|
||||
}
|
||||
$rules['personal_snapshot.email'] = ['nullable', 'email:rfc', 'max:254'];
|
||||
@@ -114,7 +164,7 @@ class StoreSimulationFormDraftRequest extends FormRequest
|
||||
foreach (['province_id', 'district_id', 'admin_post_id', 'locality_id'] as $field) {
|
||||
$rules['economic_snapshot.'.$field] = ['nullable', 'uuid', 'exists:geo_locations,id'];
|
||||
}
|
||||
foreach (['person_type', 'investor_origin', 'tax_residency_country', 'entity_stage', 'legal_form', 'commercial_name',
|
||||
foreach (['person_type', 'investor_origin', 'tax_residency_country', 'fiscal_residency_country', 'entity_stage', 'legal_form', 'commercial_name',
|
||||
'registration_number', 'representative_name', 'representative_capacity', 'phone', 'address', 'contact_country',
|
||||
'contact_province', 'contact_district', 'preferred_channel'] as $field) {
|
||||
$rules['personal_snapshot.'.$field] = ['nullable', 'string', 'max:3000'];
|
||||
@@ -131,14 +181,100 @@ class StoreSimulationFormDraftRequest extends FormRequest
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ([
|
||||
'project_team_snapshot' => [
|
||||
'keys' => ['competency', 'state', 'action', 'estimated_cost', 'due_date'],
|
||||
'states' => ['available', 'gap', 'unknown'],
|
||||
'amounts' => ['estimated_cost'],
|
||||
],
|
||||
'project_execution_snapshot' => [
|
||||
'keys' => ['title', 'due_date', 'status', 'planned_amount', 'actual_amount'],
|
||||
'states' => ['planned', 'in_progress', 'completed'],
|
||||
'amounts' => ['planned_amount', 'actual_amount'],
|
||||
],
|
||||
] as $field => $schema) {
|
||||
if (! isset($economic[$field]) || $economic[$field] === '') {
|
||||
continue;
|
||||
}
|
||||
$rows = json_decode((string) $economic[$field], true);
|
||||
$valid = is_array($rows) && count($rows) <= 100;
|
||||
foreach (is_array($rows) ? $rows : [] as $row) {
|
||||
if (! is_array($row) || array_diff(array_keys($row), $schema['keys']) !== []
|
||||
|| array_diff($schema['keys'], array_keys($row)) !== []
|
||||
|| array_filter($row, fn ($value) => ! is_string($value) || mb_strlen($value) > 500) !== []) {
|
||||
$valid = false;
|
||||
break;
|
||||
}
|
||||
$stateKey = $field === 'project_team_snapshot' ? 'state' : 'status';
|
||||
if (! in_array($row[$stateKey], $schema['states'], true)) {
|
||||
$valid = false;
|
||||
break;
|
||||
}
|
||||
foreach ($schema['amounts'] as $amountKey) {
|
||||
if ($row[$amountKey] !== '' && (! is_numeric($row[$amountKey]) || (float) $row[$amountKey] < 0)) {
|
||||
$valid = false;
|
||||
}
|
||||
}
|
||||
foreach (['due_date'] as $dateKey) {
|
||||
if ($row[$dateKey] !== '' && ! preg_match('/^\d{4}-\d{2}-\d{2}$/', $row[$dateKey])) {
|
||||
$valid = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (! $valid) {
|
||||
$validator->errors()->add('economic_snapshot.'.$field, 'O plano estruturado contém linhas ou valores inválidos.');
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($economic['project_risk_responses_snapshot']) && $economic['project_risk_responses_snapshot'] !== '') {
|
||||
$responses = json_decode((string) $economic['project_risk_responses_snapshot'], true);
|
||||
$validResponses = is_array($responses) && array_is_list($responses) && count($responses) <= 30;
|
||||
foreach (is_array($responses) ? $responses : [] as $response) {
|
||||
if (! is_array($response)
|
||||
|| array_diff(array_keys($response), ['risk_id', 'action', 'owner', 'estimated_cost', 'due_date']) !== []
|
||||
|| array_diff(['risk_id', 'action', 'owner', 'estimated_cost', 'due_date'], array_keys($response)) !== []
|
||||
|| array_filter($response, fn ($value) => ! is_string($value) || mb_strlen($value) > 500) !== []
|
||||
|| ! preg_match('/^[a-z0-9-]{1,80}$/', $response['risk_id'])
|
||||
|| ($response['estimated_cost'] !== '' && (! is_numeric($response['estimated_cost']) || (float) $response['estimated_cost'] < 0))
|
||||
|| ($response['due_date'] !== '' && ! preg_match('/^\d{4}-\d{2}-\d{2}$/', $response['due_date']))) {
|
||||
$validResponses = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (! $validResponses) {
|
||||
$validator->errors()->add('economic_snapshot.project_risk_responses_snapshot', 'As respostas planeadas para lacunas ou riscos não são válidas.');
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($economic['fiscal_scenario_mode'])) {
|
||||
$custom = $economic['fiscal_scenario_mode'] === 'custom_planning';
|
||||
$providedCount = 0;
|
||||
foreach (['base', 'rate', 'reduction'] as $field) {
|
||||
$present = isset($economic[$field]) && $economic[$field] !== '';
|
||||
$providedCount += $present ? 1 : 0;
|
||||
if (! $custom && $present) {
|
||||
$validator->errors()->add('economic_snapshot.'.$field, 'Valores hipotéticos só podem ser enviados no cenário personalizado.');
|
||||
}
|
||||
}
|
||||
if ($custom && $providedCount > 0 && $providedCount < 3) {
|
||||
foreach (['base', 'rate', 'reduction'] as $field) {
|
||||
if (! isset($economic[$field]) || $economic[$field] === '') {
|
||||
$validator->errors()->add('economic_snapshot.'.$field, 'Preencha todos os pressupostos se começar um cenário personalizado.');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$allowedLabels = [
|
||||
'economic_snapshot.selection_labels' => ['purpose', 'manifestation_type', 'preferred_language', 'project_type', 'sector_id', 'activity_id',
|
||||
'economic_snapshot.selection_labels' => ['journey_type', 'detail_level', 'purpose', 'manifestation_type', 'preferred_language', 'project_type', 'maturity_stage', 'sector_id', 'activity_id',
|
||||
'province_id', 'district_id', 'admin_post_id', 'locality_id', 'site_known', 'land_status', 'infrastructure_needs',
|
||||
'area_unit', 'required_documents', 'opportunity_currency',
|
||||
'currency', 'funds_origin_countries', 'environmental_category', 'tax_calculation', 'offer_or_demand',
|
||||
'currency', 'funds_origin_countries', 'environmental_category', 'tax_calculation', 'fiscal_scenario_mode', 'offer_or_demand',
|
||||
'opportunity_type', 'confidentiality', 'publish_requested', 'terms_accepted', 'truth_declaration',
|
||||
'reuse_simulation', 'contact_authorisation', 'publication_authorisation', 'public_fields_confirmation', 'validation_acknowledged'],
|
||||
'personal_snapshot.selection_labels' => ['person_type', 'investor_origin', 'tax_residency_country', 'entity_stage', 'legal_form', 'preferred_channel'],
|
||||
'reuse_simulation', 'contact_authorisation', 'publication_authorisation', 'public_fields_confirmation', 'validation_acknowledged',
|
||||
'fiscal_period', 'fiscal_periodicity', 'fiscal_month', 'fiscal_quarter', 'fiscal_reference_date', 'irps_salary_view', 'fiscal_component', 'fiscal_income_category', 'fiscal_residency_assessment', 'fiscal_operation',
|
||||
'fiscal_documentation', 'fiscal_vat_refund_state'],
|
||||
'personal_snapshot.selection_labels' => ['person_type', 'investor_origin', 'tax_residency_country', 'fiscal_residency_country', 'entity_stage', 'legal_form', 'preferred_channel'],
|
||||
];
|
||||
foreach ($allowedLabels as $path => $allowedFields) {
|
||||
$snapshot = str_starts_with($path, 'personal_') ? $this->input('personal_snapshot.selection_labels', []) : ($economic['selection_labels'] ?? []);
|
||||
|
||||
Reference in New Issue
Block a user