96 lines
6.1 KiB
PHP
96 lines
6.1 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Models\User;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Schema;
|
|
use Illuminate\Support\Facades\Validator;
|
|
use Illuminate\Validation\Rule;
|
|
use Illuminate\Validation\ValidationException;
|
|
|
|
class SubmissionForms
|
|
{
|
|
public const AUDIENCES = ['staff', 'service', 'supplier'];
|
|
|
|
public const MODALITIES = ['competition', 'spontaneous'];
|
|
|
|
public function canManage(User $actor): bool
|
|
{
|
|
return $actor->hasFunctions(['admin']) && ! app(OrganizationAccess::class)->maintenanceOnly($actor);
|
|
}
|
|
|
|
/** @return array<string,mixed>|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<array<string,mixed>> */
|
|
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<string,mixed> $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]);
|
|
});
|
|
}
|
|
}
|