hasFunctions(['admin']) && ! app(OrganizationAccess::class)->maintenanceOnly($actor); } /** @return array|null */ public function opportunity(int $tenantId, string $audience): ?array { if (! Schema::hasTable('submission_forms')) { return null; } $row = DB::table('submission_forms')->where('tenant_id', $tenantId)->where('audience', $audience)->where('modality', 'competition')->orderByDesc('version')->first(); return $row === null ? null : ['id' => $row->id, 'version' => $row->version, 'audience' => $row->audience, 'modality' => $row->modality, 'definition' => json_decode($row->definition, true, 32, JSON_THROW_ON_ERROR)]; } /** @return list> */ public function listing(User $actor): array { $rows = Schema::hasTable('submission_forms') ? DB::table('submission_forms')->where('tenant_id', $actor->tenant_id)->orderByDesc('version')->get() : collect(); $result = []; foreach (self::AUDIENCES as $audience) { foreach (self::MODALITIES as $modality) { $history = $rows->where('audience', $audience)->where('modality', $modality)->values(); $current = $history->first(); $result[] = ['audience' => $audience, 'modality' => $modality, 'version' => $current->version ?? 0, 'definition' => $current === null ? ['available' => false, 'fields' => $audience === 'supplier' ? [] : [['key' => 'availability_location', 'label' => 'Local de disponibilidade', 'type' => 'location', 'required' => true]], 'documents' => []] : json_decode($current->definition, true, 32, JSON_THROW_ON_ERROR), 'history' => $history->map(fn ($row) => ['id' => $row->id, 'version' => $row->version, 'created_by' => $row->created_by, 'created_at' => $row->created_at, 'reason' => $row->reason, 'definition' => json_decode($row->definition, true, 32, JSON_THROW_ON_ERROR)])->all()]; } } return $result; } /** @param array $input */ public function save(User $actor, array $input): void { DB::transaction(function () use ($actor, $input) { app(ConflictGuard::class)->lockCompany($actor); $actor = app(CurrentActor::class)->lock($actor); abort_unless($this->canManage($actor), 403); abort_unless(Schema::hasTable('submission_forms'), 503, 'Instalação dos formulários pendente.'); $data = Validator::make($input, [ 'audience' => ['required', Rule::in(self::AUDIENCES)], 'modality' => ['required', Rule::in(self::MODALITIES)], 'version' => 'required|integer|min:0', 'reason' => 'required|string|min:10|max:4000', 'definition' => 'required|array:available,fields,documents', 'definition.available' => 'required|boolean', 'definition.fields' => 'present|array|max:40', 'definition.fields.*' => 'required|array:key,label,type,required', 'definition.fields.*.key' => 'required|string|regex:/^[a-z][a-z0-9_]{0,49}$/|distinct', 'definition.fields.*.label' => 'required|string|max:200', 'definition.fields.*.type' => 'required|in:text,textarea,email,date,number,location', 'definition.fields.*.required' => 'required|boolean', 'definition.documents' => 'present|array|max:20', 'definition.documents.*' => 'required|array:key,label,required,formats,max_kb', 'definition.documents.*.key' => 'required|string|regex:/^[a-z][a-z0-9_]{0,49}$/|distinct', 'definition.documents.*.label' => 'required|string|max:200', 'definition.documents.*.required' => 'required|boolean', 'definition.documents.*.formats' => 'required|array|min:1|max:4', 'definition.documents.*.formats.*' => 'required|in:pdf,docx,jpg,png', 'definition.documents.*.max_kb' => 'required|integer|between:1,51200', ])->validate(); foreach ($data['definition']['documents'] as $index => $document) { if (count($document['formats']) !== count(array_unique($document['formats']))) { throw ValidationException::withMessages(['definition.documents.'.$index.'.formats' => 'Não repita formatos no mesmo documento.']); } } $previous = DB::table('submission_forms')->where('tenant_id', $actor->tenant_id)->where('audience', $data['audience'])->where('modality', $data['modality'])->orderByDesc('version')->first(); if ((int) $data['version'] !== (int) ($previous->version ?? 0)) { throw ValidationException::withMessages(['version' => 'O formulário mudou. Actualize e confira a versão antes de guardar.']); } if ($data['definition']['available'] && count($data['definition']['fields']) === 0) { throw ValidationException::withMessages(['definition.fields' => 'Defina os campos antes de disponibilizar esta modalidade.']); } $record = ['tenant_id' => $actor->tenant_id, 'created_by' => $actor->id, 'audience' => $data['audience'], 'modality' => $data['modality'], 'version' => (int) $data['version'] + 1, 'definition' => json_encode($data['definition'], JSON_THROW_ON_ERROR), 'reason' => $data['reason'], 'created_at' => now()]; $id = DB::table('submission_forms')->insertGetId($record); app(RequisitionWorkflow::class)->audit($actor, 'submission_forms.version_created', null, $previous === null ? null : (array) $previous, ['id' => $id, ...$record, 'scope' => 'future_submissions', 'external_portal_available' => false]); }); } }