> */ public function rules(): array { $personal = [ 'person_type', 'investor_origin', 'tax_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', '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', 'infrastructure_needs', 'currency', 'total_investment', 'own_capital', 'domestic_debt', 'foreign_debt', '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', 'sales_taxable', 'sales_exempt', 'sales_zero_rate', 'exports', 'deductible_costs', 'non_deductible_costs', 'carried_losses', 'payroll', 'outbound_payments', 'imports', 'claimed_benefits', 'reference_date', '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', 'contact_authorisation', 'publication_authorisation', 'public_fields_confirmation', 'validation_acknowledged', 'final_declaration_name', ]; $rules = [ 'personal_snapshot' => ['required', '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.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'], 'economic_snapshot.maturity_stage' => ['nullable', 'in:idea,concept,pre_feasibility,feasibility,financing,implementation,operation'], 'economic_snapshot.land_status' => ['nullable', 'in:owned,duat,leased,requested,unknown'], 'economic_snapshot.offer_or_demand' => ['nullable', 'in:offer,demand,both'], 'economic_snapshot.opportunity_type' => ['nullable', 'in:capital,partner,buyer,supplier,land,technology,licence,services,other'], '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.publish_requested' => ['nullable', 'in:yes,no'], 'economic_snapshot.terms_accepted' => ['nullable', 'in:yes,no'], 'economic_snapshot.truth_declaration' => ['nullable', 'in:yes,no'], 'economic_snapshot.reuse_simulation' => ['nullable', 'in:yes,no'], 'economic_snapshot.contact_authorisation' => ['nullable', 'in:yes,no'], 'economic_snapshot.publication_authorisation' => ['nullable', 'in:yes,no'], 'economic_snapshot.public_fields_confirmation' => ['nullable', 'in:yes,no'], 'economic_snapshot.validation_acknowledged' => ['nullable', 'in:yes,no'], 'economic_snapshot.currency' => ['nullable', 'string', 'regex:/^[A-Z]{3}$/'], 'economic_snapshot.opportunity_currency' => ['nullable', 'string', 'regex:/^[A-Z]{3}$/'], 'economic_snapshot.rate' => ['nullable', 'numeric', 'between:0,100'], 'economic_snapshot.reduction' => ['nullable', 'numeric', 'between:0,100'], 'economic_snapshot.local_content_percentage' => ['nullable', 'numeric', 'between:0,100'], 'economic_snapshot.latitude' => ['nullable', 'numeric', 'between:-90,90'], 'economic_snapshot.longitude' => ['nullable', 'numeric', 'between:-180,180'], 'economic_snapshot.expected_start_date' => ['nullable', 'date'], 'economic_snapshot.opportunity_deadline' => ['nullable', 'date'], 'economic_snapshot.reference_date' => ['nullable', 'date'], 'economic_snapshot.*' => ['nullable'], ]; foreach (['base', 'area_required', 'total_investment', 'own_capital', 'domestic_debt', 'foreign_debt', 'grants', 'secured_amount', 'sought_amount', 'annual_revenue', 'annual_operating_costs', 'expected_profit', 'exports_value', 'import_substitution_value', 'sales_taxable', 'sales_exempt', 'sales_zero_rate', 'exports', 'deductible_costs', 'non_deductible_costs', 'carried_losses', 'opportunity_value'] as $field) { $rules['economic_snapshot.'.$field] = ['nullable', 'numeric', 'min:0']; } 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']; } 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.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) { $rules['economic_snapshot.'.$field] = ['nullable', 'string', 'max:10000']; } $rules['personal_snapshot.email'] = ['nullable', 'email:rfc', 'max:254']; $rules['personal_snapshot.nuit'] = ['nullable', 'string', 'max:32']; $rules['personal_snapshot.investor_profile_id'] = ['nullable', 'uuid']; $rules['personal_snapshot.profile_confirmation'] = ['nullable', 'boolean']; $rules['personal_snapshot.selection_labels'] = ['nullable', 'array']; $rules['personal_snapshot.person_type'] = ['nullable', 'in:singular,collective,sole_trader,consortium']; $rules['personal_snapshot.investor_origin'] = ['nullable', 'in:national,foreign,mixed']; $rules['personal_snapshot.entity_stage'] = ['nullable', 'in:existing,being_formed']; $rules['economic_snapshot.sector_id'] = ['nullable', 'integer', 'exists:sectors,id']; $rules['economic_snapshot.activity_id'] = ['nullable', 'uuid', 'exists:economic_activities,id']; foreach (['existing_project_id', 'special_zone_id'] as $field) { $rules['economic_snapshot.'.$field] = ['nullable', 'string', 'max:100']; } 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', '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']; } return $rules; } public function withValidator(Validator $validator): void { $validator->after(function (Validator $validator): void { $economic = $this->input('economic_snapshot', []); if (! is_array($economic)) { return; } $allowedLabels = [ 'economic_snapshot.selection_labels' => ['purpose', 'manifestation_type', 'preferred_language', 'project_type', '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', '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'], ]; foreach ($allowedLabels as $path => $allowedFields) { $snapshot = str_starts_with($path, 'personal_') ? $this->input('personal_snapshot.selection_labels', []) : ($economic['selection_labels'] ?? []); foreach (is_array($snapshot) ? $snapshot : [] as $field => $label) { $labels = is_array($label) ? $label : [$label]; if (! is_string($field) || ! in_array($field, $allowedFields, true) || count($labels) > 100 || collect($labels)->contains(fn ($value) => ! is_string($value) || mb_strlen($value) > 255)) { $validator->errors()->add($path, 'Os rótulos de selecção não são válidos.'); break 2; } } } if (isset($economic['activity_id'], $economic['sector_id'])) { $matchesSector = DB::table('economic_activities as a') ->join('reference_catalog_versions as v', 'v.id', '=', 'a.catalog_version_id') ->where('a.id', $economic['activity_id'])->where('a.sector_id', $economic['sector_id']) ->where('a.status', 'published')->where('v.status', 'published') ->whereDate('a.valid_from', '<=', today()) ->where(fn ($query) => $query->whereNull('a.valid_to')->orWhereDate('a.valid_to', '>', today()))->exists(); if (! $matchesSector) { $validator->errors()->add('economic_snapshot.activity_id', 'A actividade não pertence ao sector seleccionado ou já não está publicada.'); } } elseif (! empty($economic['activity_id'])) { $validator->errors()->add('economic_snapshot.sector_id', 'Seleccione o sector antes da actividade económica.'); } $parents = ['district_id' => ['province_id', 'district'], 'admin_post_id' => ['district_id', 'admin_post'], 'locality_id' => ['admin_post_id', 'locality']]; foreach ($parents as $child => [$parentField, $type]) { if (empty($economic[$child])) { continue; } $parentType = ['district' => 'province', 'admin_post' => 'district', 'locality' => 'admin_post'][$type]; $valid = ! empty($economic[$parentField]) && DB::table('geo_locations as child') ->join('geo_locations as parent', 'parent.id', '=', 'child.parent_id') ->where('child.id', $economic[$child])->where('child.location_type', $type) ->where('child.source_system', 'cartografia-workbook')->where('child.data_classification', 'public')->whereNull('child.deleted_at') ->where('parent.id', $economic[$parentField])->where('parent.location_type', $parentType) ->where('parent.source_system', 'cartografia-workbook')->where('parent.data_classification', 'public')->whereNull('parent.deleted_at')->exists(); if (! $valid) { $validator->errors()->add('economic_snapshot.'.$child, 'Seleccione uma opção publicada dependente do nível territorial anterior.'); } } $financeKeys = ['total_investment', 'own_capital', 'domestic_debt', 'foreign_debt', 'grants']; if (collect($financeKeys)->every(fn (string $key) => array_key_exists($key, $economic) && $economic[$key] !== null && $economic[$key] !== '')) { $funding = (float) $economic['own_capital'] + (float) $economic['domestic_debt'] + (float) $economic['foreign_debt'] + (float) $economic['grants']; if (abs($funding - (float) $economic['total_investment']) > 0.01) { $validator->errors()->add('economic_snapshot.total_investment', 'A soma do capital, dívida e subvenções deve reconciliar com o investimento total.'); } } }); } }