Skip to content

Commit

Permalink
Merge pull request #326 from gloggi/generateSonstiges_lindo
Browse files Browse the repository at this point in the history
Generate sonstiges lindo
  • Loading branch information
carlobeltrame authored Nov 8, 2023
2 parents a7c3008 + a158528 commit 985b6c9
Show file tree
Hide file tree
Showing 8 changed files with 286 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

##### November 2023
- Es können nun die Rückmeldungs-PDFs für alle TN gleichzeitig heruntergeladen werden [#325](https://github.com/gloggi/qualix/pull/325)
- Tägliche "Sonstiges"-Blöcke o.ä. können jetzt automatisch generiert werden [#326](https://github.com/gloggi/qualix/issues/76)

##### April 2023
- Es ist nun möglich, direkt via Haupt-Navigation zur Rückmeldungs-Matrix zu gelangen [#310](https://github.com/gloggi/qualix/issues/310)
Expand Down
48 changes: 48 additions & 0 deletions app/Http/Controllers/BlockController.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@
use App\Exceptions\ECamp2BlockOverviewParsingException;
use App\Exceptions\Handler;
use App\Exceptions\UnsupportedFormatException;
use App\Http\Requests\BlockGenerateRequest;
use App\Http\Requests\BlockImportRequest;
use App\Http\Requests\BlockRequest;
use App\Models\Block;
use App\Models\Course;
use App\Models\User;
use App\Util\HtmlString;
use Carbon\CarbonPeriod;
use Exception;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
Expand Down Expand Up @@ -93,6 +95,52 @@ public function import(BlockImportRequest $request, Course $course) {
return Redirect::route('admin.blocks', ['course' => $course->id])->with('alert-success', trans_choice('t.views.admin.block_import.import_success', $imported));
}

/**
* Display a form to generate blocks.
*
* @return View
*/
public function generate(): View
{
return view('admin.blocks.generate');
}

/**
* Store an generated list of blocks in storage.
*
* @param BlockGenerateRequest $request
* @param Course $course
* @return RedirectResponse
*/
public function generateStore(BlockGenerateRequest $request, Course $course): RedirectResponse
{
$generated = DB::transaction(function () use ($request, $course) {
$request->validated();
$startDate = $request->date('blocks_startdate');
$endDate = $request->date('blocks_enddate');
$days = $startDate->diffInDays($endDate);
if($days > 370) {
throw ValidationException::withMessages(['blocks_enddate' => trans('t.views.admin.block_generate.error_too_many_blocks')]);
}
$result = collect([]);
$data = $request->validated();

foreach (CarbonPeriod::create($startDate, $endDate) as $date) {
$block = Block::create(array_merge($data, [
'course_id' => $course->id,
'block_date' => $date,
'name' => $data['name'] . " - " . $date->format("d.m.y"),
]));
$block->requirements()->sync(array_filter(explode(',', $data['requirements'])));
$result->push($block);
}
$this->rememberBlockDate($endDate, $course);
return $result;
});

return Redirect::route('admin.blocks', ['course' => $course->id])->with('alert-success', trans_choice('t.views.admin.block_generate.generate_success', $generated));
}

/**
* Show the form for editing the specified resource.
*
Expand Down
26 changes: 26 additions & 0 deletions app/Http/Requests/BlockGenerateRequest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

namespace App\Http\Requests;

use Illuminate\Support\Facades\Lang;

class BlockGenerateRequest extends FormRequest {

/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules() {
return [
'name' => 'required|max:255',
'blocks_startdate' => 'date|required',
'blocks_enddate' => 'date|required',
'requirements' => 'nullable|regex:/^\d+(,\d+)*$/|allExistInCourse',
];
}

public function attributes() {
return Lang::get('t.models.block');
}
}
11 changes: 11 additions & 0 deletions lang/de/t.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@
"models" => array(
"block" => array(
"block_date" => "Datum",
"blocks_startdate" => "Startdatum",
"blocks_enddate" => "Enddatum",
"full_block_number" => "Blocknummer",
"name" => "Blockname",
"num_observations" => "Anzahl Beobachtungen",
Expand Down Expand Up @@ -174,6 +176,7 @@
"edit_success" => "Block \":name\" wurde erfolgreich gespeichert.",
"existing" => "Blöcke :courseName",
"import" => "Blöcke importieren...",
"generate" => "Blöcke generieren...",
"menu_name" => "Blöcke",
"new" => "Neuer Block",
"no_blocks" => "Bisher sind keine Blöcke erfasst.",
Expand Down Expand Up @@ -204,6 +207,14 @@
"unknown_error" => "Beim Import ist ein Fehler aufgetreten. Versuche es nochmals, oder erfasse deine Blöcke manuell.",
"warning_existing_blocks" => "In deinem Kurs sind bereits Blöcke definiert. Wenn beim Import eine Blocknummer schon existiert, wird der bestehende Block durch den Import aktualisiert.",
),
"block_generate" => array(
"generate" => "Generieren",
"page_title" => "Blöcke generieren",
"generate_from" => "Mehrere Blöcke generieren",
"generate_success" => "{0}Es wurden keine Blöcke generiert.|{1}Es wurde ein Block generiert.|[2,*]Es wurden :count Blöcke generiert.",
"error_too_many_blocks" => "Dein Datumsbereich ist zu gross!",
),

"categories" => array(
"are_categories_required" => array(
"answer" => "Nein, Kategorien sind komplett optional, falls ihr in eurem Kursteam keine Verwendung dafür habt.",
Expand Down
31 changes: 31 additions & 0 deletions resources/views/admin/blocks/generate.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
@extends('layouts.default')

@section('pagetitle'){{__('t.views.admin.block_generate.page_title') }}@endsection

@section('content')

<b-card>
<template #header>{{__('t.views.admin.block_generate.generate_from')}}</template>

<form-basic :action="['admin.block.generate_store', { course: {{ $course->id }} }]" enctype="multipart/form-data">

<input-text name="name" label="{{__('t.models.block.name')}}" required autofocus></input-text>

<input-date name="blocks_startdate" value="{{ Auth::user()->getLastUsedBlockDate($course)->format('Y-m-d') }}" label="{{__('t.models.block.blocks_startdate')}}" required></input-date>

<input-date name="blocks_enddate" value="{{ Auth::user()->getLastUsedBlockDate($course)->format('Y-m-d') }}" label="{{__('t.models.block.blocks_enddate')}}" required></input-date>

<input-multi-select
name="requirements"
label="{{__('t.models.block.requirements')}}"
:options="{{ json_encode($course->requirements->map->only('id', 'content')) }}"
display-field="content"
multiple></input-multi-select>

<button-submit label="{{__('t.views.admin.block_generate.generate')}}"></button-submit>

</form-basic>

</b-card>

@endsection
4 changes: 4 additions & 0 deletions resources/views/admin/blocks/index.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@
{{ __('t.views.admin.blocks.import') }}
</a>

<a class="btn btn-link mb-1" href="{{ route('admin.block.generate', ['course' => $course]) }}">
{{ __('t.views.admin.blocks.generate') }}
</a>

@component('components.help-text', ['key' => 't.views.admin.blocks.what_are_blocks', 'id' => 'blockHelp'])@endcomponent

</button-submit>
Expand Down
2 changes: 2 additions & 0 deletions routes/web.php
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@
Route::post('/course/{course}/admin/blocks', [BlockController::class, 'store'])->name('admin.block.store');
Route::get('/course/{course}/admin/blocks/import', [BlockController::class, 'upload'])->name('admin.block.upload');
Route::post('/course/{course}/admin/blocks/import', [BlockController::class, 'import'])->name('admin.block.import');
Route::get('/course/{course}/admin/blocks/generate', [BlockController::class, 'generate'])->name('admin.block.generate');
Route::post('/course/{course}/admin/blocks/generate', [BlockController::class, 'generateStore'])->name('admin.block.generate_store');
Route::get('/course/{course}/admin/blocks/{block}', [BlockController::class, 'edit'])->name('admin.block.edit');
Route::post('/course/{course}/admin/blocks/{block}', [BlockController::class, 'update'])->name('admin.block.update');
Route::delete('/course/{course}/admin/blocks/{block}', [BlockController::class, 'destroy'])->name('admin.block.delete');
Expand Down
163 changes: 163 additions & 0 deletions tests/Feature/Admin/Block/GenerateBlockTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
<?php

namespace Feature\Admin\Block;

use App\Models\Block;
use Carbon\Carbon;
use Illuminate\Testing\TestResponse;
use Illuminate\Validation\ValidationException;
use Tests\TestCaseWithCourse;

class GenerateBlockTest extends TestCaseWithCourse {

private $payload;

public function setUp(): void {
parent::setUp();

$this->payload = ['name' => 'SonstigesBlockGenTest', 'blocks_startdate' => '01.10.2023', 'blocks_enddate' => '07.10.2023', 'requirements' => null];
}

public function test_shouldRequireLogin() {
// given
auth()->logout();

// when
$response = $this->post('/course/' . $this->courseId . '/admin/blocks/generate', $this->payload);

// then
$response->assertStatus(302);
$response->assertRedirect('/login');
}

public function test_shouldGenerateAndDisplayOneBlock() {
// given
$payload = $this->payload;
$payload['blocks_enddate'] = $payload['blocks_startdate'];

// when
$response = $this->post('/course/' . $this->courseId . '/admin/blocks/generate', $payload);

// then
$response->assertStatus(302);
$response->assertRedirect('/course/' . $this->courseId . '/admin/blocks');
/** @var TestResponse $response */
$response = $response->followRedirects();
$response->assertSee($payload['name']);
$response->assertSee($payload['blocks_startdate']);
}

public function test_shouldGenerateAndDisplayBlocks() {
// given

// when
$response = $this->post('/course/' . $this->courseId . '/admin/blocks/generate', $this->payload);

// then
$response->assertStatus(302);
$response->assertRedirect('/course/' . $this->courseId . '/admin/blocks');
/** @var TestResponse $response */
$response = $response->followRedirects();
$response->assertSee($this->payload['name']);
$response->assertSee($this->payload['blocks_startdate']);
$response->assertSee($this->payload['blocks_enddate']);
}

public function test_shouldGenerateAndDisplayBlocksWithRequirements() {
// given
$payload = $this->payload;
$payload = $this->payload;
$requirementIds = [$this->createRequirement(), 'abc', $this->createRequirement()];
$payload['requirements'] = implode(',', $requirementIds);

// when
$response = $this->post('/course/' . $this->courseId . '/admin/blocks/generate', $payload);

// then
$this->assertInstanceOf(ValidationException::class, $response->exception);
/** @var ValidationException $exception */
$exception = $response->exception;
$this->assertEquals('Anforderungen Format ist ungültig.', $exception->validator->errors()->first('requirements'));
}

public function test_shouldValidateNewBlockData_invalidRequirements() {
// given
$payload = $this->payload;
$payload['name'] = ' ';

// when
$response = $this->post('/course/' . $this->courseId . '/admin/blocks/generate', $payload);

// then
$this->assertInstanceOf(ValidationException::class, $response->exception);
/** @var ValidationException $exception */
$exception = $response->exception;
$this->assertEquals('Blockname muss ausgefüllt sein.', $exception->validator->errors()->first('name'));
}

public function test_shouldNotCreateBlockWithNegativeTimespan() {
// given
$payload = $this->payload;
$payload['name'] = 'NegSonstigesBlock';
$payload['blocks_startdate'] = '02.10.2023';
$payload['blocks_enddate'] = '01.10.2023';

// when
$response = $this->post('/course/' . $this->courseId . '/admin/blocks/generate', $payload);

// then
$response->assertStatus(302);
$response->assertRedirect('/course/' . $this->courseId . '/admin/blocks');
/** @var TestResponse $response */
$response = $response->followRedirects();
$response->assertSee('Es wurden keine Blöcke generiert.');
$response->assertDontSee($this->payload['name']);
}

public function test_shouldValidateNewBlockData_noBlockName() {
// given
$payload = $this->payload;
unset($payload['name']);

// when
$response = $this->post('/course/' . $this->courseId . '/admin/blocks/generate', $payload);

// then
$this->assertInstanceOf(ValidationException::class, $response->exception);
/** @var ValidationException $exception */
$exception = $response->exception;
$this->assertEquals('Blockname muss ausgefüllt sein.', $exception->validator->errors()->first('name'));
}

public function test_shouldValidateNewBlockData_longBlockname() {
// given
$payload = $this->payload;
$payload['name'] = 'Extrem langer Blockname 1Extrem langer Blockname 2Extrem langer Blockname 3Extrem langer Blockname 4Extrem langer Blockname 5Extrem langer Blockname 6Extrem langer Blockname 7Extrem langer Blockname 8Extrem langer Blockname 9Extrem langer Blockname 10Extrem langer Blockname 11';

// when
$response = $this->post('/course/' . $this->courseId . '/admin/blocks/generate', $payload);

// then
$this->assertInstanceOf(ValidationException::class, $response->exception);
/** @var ValidationException $exception */
$exception = $response->exception;
$this->assertEquals('Blockname darf maximal 255 Zeichen haben.', $exception->validator->errors()->first('name'));
}

public function test_shouldValidateNewBlockData_invalidDate() {
// given
$payload = $this->payload;
$payload['blocks_startdate'] = 'asdf';

// when
$response = $this->post('/course/' . $this->courseId . '/admin/blocks/generate', $payload);

// then
$this->assertInstanceOf(ValidationException::class, $response->exception);
/** @var ValidationException $exception */
$exception = $response->exception;
$this->assertEquals('Startdatum muss ein gültiges Datum sein.', $exception->validator->errors()->first('blocks_startdate'));
}
}


0 comments on commit 985b6c9

Please sign in to comment.