diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index a417bc568e..4244f893f8 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -7,6 +7,7 @@ use iEducar\Modules\ErrorTracking\Tracker; use iEducar\Support\Exceptions\DisciplinesWithoutInformedHoursException; use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; +use Illuminate\Validation\ValidationException; use Throwable; class Handler extends ExceptionHandler @@ -19,6 +20,7 @@ class Handler extends ExceptionHandler protected $dontReport = [ App_Model_Exception::class, DisciplinesWithoutInformedHoursException::class, + ValidationException::class, ]; /** diff --git a/app/Http/Controllers/AnnouncementPublishController.php b/app/Http/Controllers/AnnouncementPublishController.php new file mode 100644 index 0000000000..f34e189d1f --- /dev/null +++ b/app/Http/Controllers/AnnouncementPublishController.php @@ -0,0 +1,95 @@ +menu(Process::ANNOUNCEMENT); + $this->breadcrumb('Publicação de avisos', [ + url('/intranet/educar_configuracoes_index.php') => 'Configurações', + ]); + $announcements = Announcement::query() + ->withTrashed() + ->with([ + 'userTypes', + ]) + ->latest() + ->paginate(); + + return view('announcement.publish.index', [ + 'announcements' => $announcements, + ]); + } + + public function update(AnnouncementRequest $request, $announcementId) + { + try { + DB::beginTransaction(); + $announcement = Announcement::query()->withTrashed()->findOrFail($announcementId); + $announcement->fill($request->all()); + $announcement->save(); + $announcement->userTypes()->sync($request->get('tipo_usuario')); + $request->get('active') ? $announcement->restore() : $announcement->delete(); + DB::commit(); + session()->flash('success', 'Edição efetuada com sucesso.'); + } catch (Exception) { + DB::rollBack(); + session()->flash('error', 'Edição não realizada.'); + } + + return redirect()->route('announcement.publish.edit', $announcementId); + } + + public function store(AnnouncementRequest $request) + { + try { + DB::beginTransaction(); + $announcement = Announcement::create($request->all()); + $announcement->userTypes()->sync($request->get('tipo_usuario')); + $request->get('active') ? $announcement->restore() : $announcement->delete(); + DB::commit(); + session()->flash('success', 'Cadastro efetuado com sucesso.'); + } catch (Exception) { + DB::rollBack(); + session()->flash('error', 'Cadastro não realizado.'); + } + + return redirect()->route('announcement.publish.index'); + } + + public function create() + { + $this->menu(Process::ANNOUNCEMENT); + $this->breadcrumb('Publicação de avisos', [ + url('/intranet/educar_configuracoes_index.php') => 'Configurações', + ]); + + return view('announcement.publish.create', [ + 'announcement' => new Announcement(), + 'userTypes' => null, + ]); + } + + public function edit($announcementId) + { + $this->menu(Process::ANNOUNCEMENT); + $this->breadcrumb('Publicação de avisos', [ + url('/intranet/educar_configuracoes_index.php') => 'Configurações', + ]); + $announcement = Announcement::query()->withTrashed()->findOrFail($announcementId); + $userTypes = $announcement->userTypes->pluck('cod_tipo_usuario'); + + return view('announcement.publish.create', [ + 'announcement' => $announcement, + 'userTypes' => $userTypes, + ]); + } +} diff --git a/app/Http/Controllers/AnnouncementUserController.php b/app/Http/Controllers/AnnouncementUserController.php new file mode 100644 index 0000000000..31f80c8821 --- /dev/null +++ b/app/Http/Controllers/AnnouncementUserController.php @@ -0,0 +1,80 @@ +breadcrumb('Avisos'); + $this->menu(Process::ANNOUNCEMENT); + $announcement = Announcement::query()->latest()->first(); + $announcement->users()->sync([ + $request->user()->getKey() => ['read_at' => now()], + ]); + + return view('announcement.user.show', [ + 'announcement' => $announcement, + 'schools' => $this->getUserSchools($announcement->show_vacancy), + ]); + } + + private function getUserSchools(bool $show) + { + if (!$show) { + return []; + } + + return LegacyEnrollment::query() + ->selectRaw(" + nm_turma, + UPPER(pessoa.nome) as escola, + string_agg(distinct nm_serie, ', ') as serie, + string_agg(distinct nm_curso, ', ') as curso, + max_aluno - COUNT(distinct matricula.cod_matricula) as vagas + ") + ->join('relatorio.view_situacao', function ($join) { + $join->on('view_situacao.cod_matricula', 'ref_cod_matricula') + ->on('view_situacao.cod_turma', 'ref_cod_turma') + ->on('view_situacao.sequencial', 'matricula_turma.sequencial'); + }) + ->join('pmieducar.turma', fn ($join) => $join->on('turma.cod_turma', 'ref_cod_turma')->where('turma.ativo', 1)->where('turma.ano', Carbon::now()->year)) + ->join('pmieducar.escola', fn ($join) => $join->on('cod_escola', 'turma.ref_ref_cod_escola')->where('escola.ativo', 1)) + ->join('cadastro.pessoa', 'idpes', 'escola.ref_idpes') + ->join('pmieducar.matricula', 'matricula.cod_matricula', 'ref_cod_matricula') + ->join('pmieducar.serie', fn ($join) => $join->on('cod_serie', 'matricula.ref_ref_cod_serie')->where('matricula.ativo', 1)) + ->join('pmieducar.curso', fn ($join) => $join->on('cod_curso', 'matricula.ref_cod_curso')->where('curso.ativo', 1)) + ->when(Auth::user()->isSchooling(), fn ($q) => $q->whereIn('cod_escola', Auth::user()->schools()->pluck('ref_cod_escola'))) + ->having('max_aluno', '>', DB::raw('COUNT(distinct matricula.cod_matricula)')) + ->orderBy('escola') + ->orderBy('curso') + ->orderBy('serie') + ->where('matricula_turma.ativo', 1) + ->orderBy('nm_turma') + ->groupBy('nm_turma', 'max_aluno', 'pessoa.nome') + ->get() + ->groupBy([ + 'escola', + 'curso', + 'serie', + ]); + } + + public function confirm(Request $request) + { + $announcement = Announcement::query()->latest()->first(); + $announcement->users()->sync([ + $request->user()->getKey() => ['confirmed_at' => now()], + ]); + + return redirect('/'); + } +} diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index 1d204aa400..02cd608a7b 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -3,6 +3,7 @@ namespace App\Http\Controllers\Auth; use App\Http\Controllers\Controller; +use App\Models\Announcement; use App\Rules\ReCaptchaV3; use Illuminate\Foundation\Auth\AuthenticatesUsers; use Illuminate\Http\Request; @@ -77,4 +78,40 @@ public function validateLogin(Request $request) 'password.string' => 'O campo senha é obrigatório.', ]); } + + protected function authenticated(Request $request, $user) + { + $announcement = Announcement::query() + ->whereHas('userTypes', fn ($q) => $q->whereKey($user->ref_cod_tipo_usuario)) + ->latest()->first(); + + if (!$announcement) { + return; + } + + if ($announcement->repeat_on_login) { + $this->resetAnnouncementConfirmation($announcement, $user); + + return redirect()->route('announcement.user.show'); + } + + if (!$this->userReadAnnouncement($announcement, $user)) { + return redirect()->route('announcement.user.show'); + } + } + + private function resetAnnouncementConfirmation(Announcement $announcement, $user): void + { + if ($announcement->show_confirmation) { + $announcement->users()->updateExistingPivot($user->getKey(), ['confirmed_at' => null]); + } + } + + private function userReadAnnouncement(Announcement $announcement, $user): bool + { + return $announcement->users() + ->whereKey($user->getKey()) + ->wherePivotNotNull('read_at') + ->exists(); + } } diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 66e269285f..05453e5370 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -38,6 +38,7 @@ class Kernel extends HttpKernel \Illuminate\View\Middleware\ShareErrorsFromSession::class, \Illuminate\Routing\Middleware\SubstituteBindings::class, \App\Http\Middleware\SetLayoutVariables::class, + \App\Http\Middleware\AnnouncementMiddleware::class, ], 'api' => [ diff --git a/app/Http/Middleware/AnnouncementMiddleware.php b/app/Http/Middleware/AnnouncementMiddleware.php new file mode 100644 index 0000000000..d81383e83f --- /dev/null +++ b/app/Http/Middleware/AnnouncementMiddleware.php @@ -0,0 +1,36 @@ +user()) { + $announcement = Announcement::query() + ->whereHas('userTypes', fn ($q) => $q->whereKey($user->ref_cod_tipo_usuario) + )->latest()->first(); + + if ($announcement?->show_confirmation && !$this->userConfirmedAnnouncement($announcement, $user)) { + Session::flash('error', 'Confirme a ciência do aviso antes de prosseguir!'); + + return redirect()->route('announcement.user.show'); + } + } + + return $next($request); + } + + private function userConfirmedAnnouncement(Announcement $announcement, $user): bool + { + return $announcement->users() + ->whereKey($user->getKey()) + ->wherePivotNotNull('confirmed_at') + ->exists(); + } +} diff --git a/app/Http/Requests/AnnouncementRequest.php b/app/Http/Requests/AnnouncementRequest.php new file mode 100644 index 0000000000..38cccf6855 --- /dev/null +++ b/app/Http/Requests/AnnouncementRequest.php @@ -0,0 +1,56 @@ +merge([ + 'repeat_on_login' => $this->has('repeat_on_login'), + 'show_confirmation' => $this->has('show_confirmation'), + 'show_vacancy' => $this->has('show_vacancy'), + 'active' => $this->has('active'), + 'tipo_usuario' => Arr::flatten($this->get('tipo_usuario', [])), + 'created_by_user_id' => $this->user()->getKey(), + ]); + } + + public function rules() + { + return [ + 'name' => ['required', 'max:255'], + 'description' => ['required'], + 'repeat_on_login' => ['boolean'], + 'show_confirmation' => ['boolean'], + 'active' => ['boolean', function ($attribute, $value, $fail) { + if ($value) { + $exists = Announcement::query() + ->withoutTrashed() + ->where('id', '<>', $this->route('announcement')) + ->exists(); + + if ($exists) { + $fail('Já existe um aviso ativo.'); + } + } + }], + 'show_vacancy' => ['boolean'], + 'tipo_usuario' => ['required', 'array'], + 'tipo_usuario.*' => ['integer', Rule::exists('tipo_usuario', 'cod_tipo_usuario')], + ]; + } + + public function attributes() + { + return [ + 'description' => 'Conteúdo do aviso', + 'tipo_usuario' => 'Tipos de usuários que serão notificados', + ]; + } +} diff --git a/app/Models/Announcement.php b/app/Models/Announcement.php new file mode 100644 index 0000000000..cfaa8434bf --- /dev/null +++ b/app/Models/Announcement.php @@ -0,0 +1,47 @@ +belongsToMany(LegacyUserType::class, 'announcement_user_types', 'announcement_id', 'user_type_id'); + } + + public function users(): BelongsToMany + { + return $this->belongsToMany(LegacyUser::class, 'announcement_users', 'announcement_id', 'user_id'); + } + + public function createdByUser(): BelongsTo + { + return $this->belongsTo(LegacyUser::class); + } + + protected function description(): Attribute + { + return Attribute::make( + set: function (string $value) { + return strip_tags($value, '