validate([ 'demand_version' => ['required', 'integer', 'min:1'], 'sector_id' => ['required', 'integer', Rule::exists('sectors', 'id')->whereNull('deleted_at')], 'title' => ['required', 'string', 'max:200'], 'intervention' => ['required', 'string', 'min:20', 'max:10000'], 'equipment_needs' => ['nullable', 'string', 'max:5000'], 'trainer_needs' => ['nullable', 'string', 'max:5000'], 'funding_plan' => ['nullable', 'string', 'max:5000'], 'risks' => ['required', 'string', 'min:10', 'max:5000'], 'source' => ['required', 'string', 'min:10', 'max:5000'], 'currency' => ['required', Rule::in(['MZN', 'USD', 'EUR', 'ZAR'])], 'province_id' => ['required', 'uuid'], 'district_id' => ['nullable', 'uuid'], 'admin_post_id' => ['nullable', 'uuid'], 'locality_id' => ['nullable', 'uuid'], ]); DB::transaction(function () use ($request, $id, $data): void { $demand = LabourDemand::whereKey($id)->lockForUpdate()->firstOrFail(); abort_unless((int) $demand->getAttribute('version_no') === $data['demand_version'], 409, 'A procura foi alterada. Recarregue a análise.'); abort_unless($demand->getAttribute('status') === 'reviewed' && $demand->getAttribute('valid_until') >= now()->toDateString(), 422, 'A proposta exige procura revista e fonte vigente.'); // Serialising on the demand and unique FKs make repeated submissions safe. if (EducationInvestmentProposal::withTrashed()->where('labour_demand_id', $id)->exists()) { return; } $territory = app(InvestmentGeography::class)->selection($data, true); $lastTerritoryKey = array_key_last($territory); if ($lastTerritoryKey === null) { throw new LogicException('A proposta educativa exige uma localização territorial.'); } $province = $territory[0]; $district = $territory[1] ?? null; $primaryTerritory = $territory[$lastTerritoryKey]; $reference = 'EDU-'.Str::uuid(); $opportunity = new Opportunity; PortalAudit::save($opportunity, [ 'sector_id' => $data['sector_id'], 'reference' => $reference, 'slug' => strtolower($reference), 'title' => $data['title'], 'summary' => 'Proposta educativa em preparação. Lacuna e viabilidade pendentes de validação institucional.', 'description' => $data['intervention'], 'estimated_investment_currency' => $data['currency'], 'province' => $province->name, 'district' => $district?->name, 'primary_territory_id' => $primaryTerritory->id, 'territory_version' => 1, 'status' => 'draft', 'is_featured' => false, ], $request->user()->id); PortalAudit::save(new EducationInvestmentProposal, [ 'labour_demand_id' => $id, 'demand_version' => $data['demand_version'], 'opportunity_id' => $opportunity->id, 'created_by' => $request->user()->id, ...array_intersect_key($data, array_flip(['intervention', 'equipment_needs', 'trainer_needs', 'funding_plan', 'risks', 'source'])), ], $request->user()->id); }); return redirect('/management/education/demands/'.$id.'/analysis') ->with('success', 'Ficha educativa guardada em rascunho. Publicação sujeita a validação institucional.'); } }