Schedule import: Add overview

main
Igor Scheller 4 years ago committed by msquare
parent 251f2cbfa6
commit ebab34ee67

@ -57,8 +57,10 @@ $route->addGroup(
'/schedule',
function (RouteCollector $route) {
$route->get('', 'Admin\\Schedule\\ImportSchedule@index');
$route->post('/load', 'Admin\\Schedule\\ImportSchedule@loadSchedule');
$route->post('/import', 'Admin\\Schedule\\ImportSchedule@importSchedule');
$route->get('/edit[/{id:\d+}]', 'Admin\\Schedule\\ImportSchedule@edit');
$route->post('/edit[/{id:\d+}]', 'Admin\\Schedule\\ImportSchedule@save');
$route->get('/load/{id:\d+}', 'Admin\\Schedule\\ImportSchedule@loadSchedule');
$route->post('/import/{id:\d+}', 'Admin\\Schedule\\ImportSchedule@importSchedule');
}
);

@ -0,0 +1,76 @@
<?php
namespace Engelsystem\Migrations;
use Carbon\Carbon;
use Engelsystem\Database\Migration\Migration;
use Engelsystem\Models\Shifts\Schedule;
use Illuminate\Database\Schema\Blueprint;
class AddNameMinutesAndTimestampsToSchedules extends Migration
{
use Reference;
/**
* Run the migration
*/
public function up()
{
$this->schema->table(
'schedules',
function (Blueprint $table) {
$table->string('name')->after('id');
$table->integer('shift_type')->after('name');
$table->integer('minutes_before')->after('shift_type');
$table->integer('minutes_after')->after('minutes_before');
$table->timestamps();
}
);
Schedule::query()
->update([
'created_at' => Carbon::now(),
'minutes_before' => 15,
'minutes_after' => 15,
]);
// Add legacy reference
if ($this->schema->hasTable('ShiftTypes')) {
$connection = $this->schema->getConnection();
$query = $connection
->table('Shifts')
->select('Shifts.shifttype_id')
->join('schedule_shift', 'Shifts.SID', 'schedule_shift.shift_id')
->where('schedule_shift.schedule_id', $connection->raw('schedules.id'))
->limit(1);
Schedule::query()
->update(['shift_type' => $connection->raw('(' . $query->toSql() . ')')]);
$this->schema->table(
'schedules',
function (Blueprint $table) {
$this->addReference($table, 'shift_type', 'ShiftTypes');
}
);
}
}
/**
* Reverse the migration
*/
public function down()
{
$this->schema->table(
'schedules',
function (Blueprint $table) {
$table->dropForeign('schedules_shift_type_foreign');
$table->dropColumn('name');
$table->dropColumn('shift_type');
$table->dropColumn('minutes_before');
$table->dropColumn('minutes_after');
$table->dropTimestamps();
}
);
}
}

@ -22,6 +22,7 @@ trait Reference
* @param string $targetTable
* @param string|null $fromColumn
* @param bool $setPrimary
*
* @return ColumnDefinition
*/
protected function references(
@ -37,11 +38,21 @@ trait Reference
$table->primary($fromColumn);
}
$this->addReference($table, $fromColumn, $targetTable);
return $col;
}
/**
* @param Blueprint $table
* @param string $fromColumn
* @param string $targetTable
*/
protected function addReference(Blueprint $table, string $fromColumn, string $targetTable)
{
$table->foreign($fromColumn)
->references('id')->on($targetTable)
->onUpdate('cascade')
->onDelete('cascade');
return $col;
}
}

@ -6,6 +6,8 @@ namespace Engelsystem\Controllers\Admin\Schedule;
use Carbon\Carbon;
use Engelsystem\Controllers\BaseController;
use Engelsystem\Controllers\CleanupModel;
use Engelsystem\Controllers\HasUserNotifications;
use Engelsystem\Helpers\Schedule\Event;
use Engelsystem\Helpers\Schedule\Room;
use Engelsystem\Helpers\Schedule\Schedule;
@ -20,7 +22,6 @@ use GuzzleHttp\Client as GuzzleClient;
use Illuminate\Database\Connection as DatabaseConnection;
use Illuminate\Database\Eloquent\Builder as QueryBuilder;
use Illuminate\Database\Eloquent\Collection as DatabaseCollection;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Psr\Log\LoggerInterface;
use stdClass;
@ -28,6 +29,9 @@ use Symfony\Component\HttpFoundation\Session\SessionInterface;
class ImportSchedule extends BaseController
{
use CleanupModel;
use HasUserNotifications;
/** @var DatabaseConnection */
protected $db;
@ -86,11 +90,76 @@ class ImportSchedule extends BaseController
return $this->response->withView(
'admin/schedule/index.twig',
[
'errors' => $this->getFromSession('errors'),
'success' => $this->getFromSession('success'),
'is_index' => true,
'schedules' => ScheduleUrl::all(),
] + $this->getNotifications()
);
}
/**
* @param Request $request
*
* @return Response
*/
public function edit(Request $request): Response
{
$schedule = ScheduleUrl::find($request->getAttribute('id'));
$this->cleanupModelNullValues($schedule);
return $this->response->withView(
'admin/schedule/edit.twig',
[
'schedule' => $schedule,
'shift_types' => $this->getShiftTypes(),
] + $this->getNotifications()
);
}
/**
* @param Request $request
*
* @return Response
*/
public function save(Request $request): Response
{
$id = $request->getAttribute('id');
/** @var ScheduleUrl $schedule */
$schedule = ScheduleUrl::findOrNew($id);
$data = $this->validate($request, [
'name' => 'required',
'url' => 'required',
'shift_type' => 'required|int',
'minutes_before' => 'required|int',
'minutes_after' => 'required|int',
]);
if (!isset($this->getShiftTypes()[$data['shift_type']])) {
throw new ErrorException('schedule.import.invalid-shift-type');
}
$schedule->name = $data['name'];
$schedule->url = $data['url'];
$schedule->shift_type = $data['shift_type'];
$schedule->minutes_before = $data['minutes_before'];
$schedule->minutes_after = $data['minutes_after'];
$schedule->save();
$this->log->info(
'Schedule {name}: Url {url}, Shift Type {shift_type}, minutes before/after {before}/{after}',
[
'name' => $schedule->name,
'url' => $schedule->name,
'shift_type' => $schedule->shift_type,
'before' => $schedule->minutes_before,
'after' => $schedule->minutes_after,
]
);
$this->addNotification('schedule.edit.success');
return redirect('/admin/schedule/load/' . $schedule->id);
}
/**
@ -116,24 +185,19 @@ class ImportSchedule extends BaseController
$changeEvents,
$deleteEvents,
$newRooms,
$shiftType,
,
$scheduleUrl,
$schedule,
$minutesBefore,
$minutesAfter
$schedule
) = $this->getScheduleData($request);
} catch (ErrorException $e) {
return back()->with('errors', [$e->getMessage()]);
$this->addNotification($e->getMessage(), 'errors');
return back();
}
return $this->response->withView(
'admin/schedule/load.twig',
[
'errors' => $this->getFromSession('errors'),
'schedule_url' => $scheduleUrl->url,
'shift_type' => $shiftType,
'minutes_before' => $minutesBefore,
'minutes_after' => $minutesAfter,
'schedule_id' => $scheduleUrl->id,
'schedule' => $schedule,
'rooms' => [
'add' => $newRooms,
@ -143,7 +207,7 @@ class ImportSchedule extends BaseController
'update' => $changeEvents,
'delete' => $deleteEvents,
],
]
] + $this->getNotifications()
);
}
@ -172,10 +236,11 @@ class ImportSchedule extends BaseController
$scheduleUrl
) = $this->getScheduleData($request);
} catch (ErrorException $e) {
return back()->with('errors', [$e->getMessage()]);
$this->addNotification($e->getMessage(), 'errors');
return back();
}
$this->log('Started schedule "{schedule}" import', ['schedule' => $scheduleUrl->url]);
$this->log('Started schedule "{name}" import', ['name' => $scheduleUrl->name]);
foreach ($newRooms as $room) {
$this->createRoom($room);
@ -207,10 +272,11 @@ class ImportSchedule extends BaseController
$this->deleteEvent($event);
}
$this->log('Ended schedule "{schedule}" import', ['schedule' => $scheduleUrl->url]);
$scheduleUrl->touch();
$this->log('Ended schedule "{name}" import', ['name' => $scheduleUrl->name]);
return redirect($this->url, 303)
->with('success', ['schedule.import.success']);
->with('messages', ['schedule.import.success']);
}
/**
@ -337,17 +403,11 @@ class ImportSchedule extends BaseController
*/
protected function getScheduleData(Request $request)
{
$data = $this->validate(
$request,
[
'schedule-url' => 'required|url',
'shift-type' => 'required|int',
'minutes-before' => 'optional|int',
'minutes-after' => 'optional|int',
]
);
$id = $request->getAttribute('id');
/** @var ScheduleUrl $scheduleUrl */
$scheduleUrl = ScheduleUrl::findOrFail($id);
$scheduleResponse = $this->guzzle->get($data['schedule-url']);
$scheduleResponse = $this->guzzle->get($scheduleUrl->url);
if ($scheduleResponse->getStatusCode() != 200) {
throw new ErrorException('schedule.import.request-error');
}
@ -357,15 +417,10 @@ class ImportSchedule extends BaseController
throw new ErrorException('schedule.import.read-error');
}
$shiftType = (int)$data['shift-type'];
if (!isset($this->getShiftTypes()[$shiftType])) {
throw new ErrorException('schedule.import.invalid-shift-type');
}
$scheduleUrl = $this->getScheduleUrl($data['schedule-url']);
$shiftType = $scheduleUrl->shift_type;
$schedule = $this->parser->getSchedule();
$minutesBefore = isset($data['minutes-before']) ? (int)$data['minutes-before'] : 15;
$minutesAfter = isset($data['minutes-after']) ? (int)$data['minutes-after'] : 15;
$minutesBefore = $scheduleUrl->minutes_before;
$minutesAfter = $scheduleUrl->minutes_after;
$newRooms = $this->newRooms($schedule->getRooms());
return array_merge(
$this->shiftsDiff($schedule, $scheduleUrl, $shiftType, $minutesBefore, $minutesAfter),
@ -373,18 +428,6 @@ class ImportSchedule extends BaseController
);
}
/**
* @param string $name
* @return Collection
*/
protected function getFromSession(string $name): Collection
{
$data = Collection::make(Arr::flatten($this->session->get($name, [])));
$this->session->remove($name);
return $data;
}
/**
* @param Room[] $scheduleRooms
* @return Room[]
@ -581,22 +624,6 @@ class ImportSchedule extends BaseController
return $return;
}
/**
* @param string $scheduleUrl
* @return ScheduleUrl
*/
protected function getScheduleUrl(string $scheduleUrl): ScheduleUrl
{
if (!$schedule = ScheduleUrl::whereUrl($scheduleUrl)->first()) {
$schedule = new ScheduleUrl(['url' => $scheduleUrl]);
$schedule->save();
$this->log('Created schedule "{schedule}"', ['schedule' => $schedule->url]);
}
return $schedule;
}
/**
* @param string $message
* @param array $context

@ -35,8 +35,8 @@ msgstr "Deine Passwörter stimmen nicht überein."
msgid "validation.password_confirmation.required"
msgstr "Du musst dein Passwort bestätigen."
msgid "schedule.import"
msgstr "Programm importieren"
msgid "schedule.edit.success"
msgstr "Das Programm wurde erfolgreich konfiguriert."
msgid "schedule.import.request-error"
msgstr "Das Programm konnte nicht abgerufen werden."

@ -2787,12 +2787,27 @@ msgstr "Programm laden"
msgid "form.import"
msgstr "Importieren"
msgid "form.edit"
msgstr "Bearbeiten"
msgid "form.save"
msgstr "Speichern"
msgid "schedule.import"
msgstr "Programm importieren"
msgid "schedule.edit.title"
msgstr "Programm bearbeiten"
msgid "schedule.import.title"
msgstr "Programm importieren"
msgid "schedule.last_update"
msgstr "Aktualisiert am: %s"
msgid "schedule.import.text"
msgstr ""
"Dieser Import erstellt Räume und erstellt, aktualisiert und löscht Schichten anhand des schedule.xml exportes."
"Importe erstellen Räume und erstellen, aktualisieren und löschen Schichten anhand eines schedule.xml exportes."
msgid "schedule.import.load.title"
msgstr "Programm importieren: Vorschau"
@ -2800,6 +2815,9 @@ msgstr "Programm importieren: Vorschau"
msgid "schedule.import.load.info"
msgstr "Importiere \"%s\" (Version \"%s\")"
msgid "schedule.name"
msgstr "Programm Name"
msgid "schedule.url"
msgstr "Programm URL (schedule.xml)"

@ -33,8 +33,8 @@ msgstr "Your passwords are not equal."
msgid "validation.password_confirmation.required"
msgstr "You have to confirm your password."
msgid "schedule.import"
msgstr "Import schedule"
msgid "schedule.edit.success"
msgstr "The schedule was configured successfully."
msgid "schedule.import.request-error"
msgstr "The schedule could not be requested."

@ -52,11 +52,26 @@ msgstr "Load schedule"
msgid "form.import"
msgstr "Import"
msgid "form.save"
msgstr "Save"
msgid "form.edit"
msgstr "Bearbeiten"
msgid "schedule.import"
msgstr "Import schedule"
msgid "schedule.edit.title"
msgstr "Edit schedule"
msgid "schedule.import.title"
msgstr "Import schedule"
msgid "schedule.last_update"
msgstr "Last updated: %s"
msgid "schedule.import.text"
msgstr "This import creates rooms and creates, updates and deletes shifts according to the schedule.xml export."
msgstr "Imports create rooms and create, update and delete shifts according to a schedule.xml export."
msgid "schedule.import.load.title"
msgstr "Import schedule: Preview"
@ -64,6 +79,9 @@ msgstr "Import schedule: Preview"
msgid "schedule.import.load.info"
msgstr "Import \"%s\" (version \"%s\")"
msgid "schedule.name"
msgstr "Programm name"
msgid "schedule.url"
msgstr "Schedule URL (schedule.xml)"

@ -0,0 +1,29 @@
{% extends 'admin/schedule/index.twig' %}
{% import 'macros/base.twig' as m %}
{% import 'macros/form.twig' as f %}
{% block title %}{{ schedule ? __('schedule.edit.title') : __('schedule.import.title') }}{% endblock %}
{% block row_content %}
{% if schedule and schedule.updated_at %}
<div class="col-md-12">
<p>{{ __('schedule.last_update', [schedule.updated_at.format(__('Y-m-d H:i'))]) }}</p>
</div>
{% endif %}
<form method="post">
{{ csrf() }}
<div class="col-lg-12">
{{ f.input('name', __('schedule.name'), null, {'required': true, 'value': schedule ? schedule.name : ''}) }}
{{ f.input('url', __('schedule.url'), 'url', {'required': true, 'value': schedule ? schedule.url : ''}) }}
{{ f.select('shift_type', shift_types|default([]), __('schedule.shift-type'), schedule ? schedule.shift_type : '') }}
{{ f.input('minutes_before', __('schedule.minutes-before'), 'number', {'required': true, 'value': schedule ? schedule.minutes_before : 15}) }}
{{ f.input('minutes_after', __('schedule.minutes-after'), 'number', {'required': true, 'value': schedule ? schedule.minutes_after : 15}) }}
{{ f.submit(__('form.save')) }}
</div>
</form>
{% endblock %}

@ -6,35 +6,56 @@
{% block content %}
<div class="container">
<h1>{% block content_title %}{{ title }}{% endblock %}</h1>
<h1>
{% block content_title %}{{ title }}{% endblock %}
{% for message in errors|default([]) %}
{{ m.alert(__(message), 'danger') }}
{% endfor %}
{% for message in success|default([]) %}
{{ m.alert(__(message), 'success') }}
{% endfor %}
{% if is_index|default(false) %}
{{ m.button(m.glyphicon('plus'), url('/admin/schedule/edit')) }}
{% endif %}
</h1>
{% include 'layouts/parts/messages.twig' %}
<div class="row">
{% block row_content %}
<form method="POST" action="{{ url('/admin/schedule/load') }}">
{{ csrf() }}
<div class="col-md-12">
<p>{{ __('schedule.import.text') }}</p>
</div>
<div class="col-lg-6">
{{ f.input('schedule-url', __('schedule.url'), 'url', {'required': true}) }}
{{ f.select('shift-type', shift_types|default([]), __('schedule.shift-type')) }}
{{ f.input('minutes-before', __('schedule.minutes-before'), 'number', {'value': 15, 'required': true}) }}
{{ f.input('minutes-after', __('schedule.minutes-after'), 'number', {'value': 15, 'required': true}) }}
{{ f.submit(__('form.load_schedule')) }}
<div class="col-md-12">
<p>{{ __('schedule.import.text') }}</p>
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th>{{ __('schedule.name') }}</th>
<th>{{ __('schedule.url') }}</th>
<th></th>
</tr>
</thead>
<tbody>
{% for schedule in schedules %}
<tr>
<td>{{ schedule.name }}</td>
<td>{{ schedule.url }}</td>
<td>
<div class="btn-group">
<a
href="{{ url('/admin/schedule/load/' ~ schedule.id) }}"
class="btn btn-xs btn-default">
{{ __('form.import') }}
</a>
<a
href="{{ url('/admin/schedule/edit/' ~ schedule.id) }}"
class="btn btn-xs btn-default">
{{ __('form.edit') }}
</a>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</form>
</div>
{% endblock %}
</div>
</div>

@ -4,12 +4,8 @@
{% block title %}{{ __('schedule.import.load.title') }}{% endblock %}
{% block row_content %}
<form method="POST" action="{{ url('/admin/schedule/import') }}">
<form method="POST" action="{{ url('/admin/schedule/import/' ~ schedule_id) }}">
{{ csrf() }}
{{ f.hidden('schedule-url', schedule_url) }}
{{ f.hidden('shift-type', shift_type) }}
{{ f.hidden('minutes-before', minutes_before) }}
{{ f.hidden('minutes-after', minutes_after) }}
<div class="col-lg-12">
<p>{{ __('schedule.import.load.info', [schedule.conference.title, schedule.version]) }}</p>

@ -43,7 +43,7 @@
{% endif %}
<select id="{{ name }}" name="{{ name }}" class="form-control">
{% for value,decription in data -%}
<option value="{{ value }}" {% if name == selected %} selected{% endif %}>{{ decription }}</option>
<option value="{{ value }}" {% if value == selected %} selected{% endif %}>{{ decription }}</option>
{% endfor %}
</select>
</div>

@ -17,6 +17,10 @@ trait CleanupModel
*/
protected function cleanupModelNullValues($models, array $attributes = [])
{
if (!$models) {
return;
}
$models = $models instanceof Model ? [$models] : $models;
foreach ($models as $model) {
/** @var Model $model */

@ -2,6 +2,7 @@
namespace Engelsystem\Models\Shifts;
use Carbon\Carbon;
use Engelsystem\Models\BaseModel;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Relations\HasMany;
@ -9,17 +10,38 @@ use Illuminate\Database\Query\Builder as QueryBuilder;
/**
* @property int $id
* @property string $name
* @property string $url
* @property int $shift_type
* @property int $minutes_before
* @property int $minutes_after
* @property Carbon $created_at
* @property Carbon $updated_at
*
* @property-read QueryBuilder|Collection|ScheduleShift[] $scheduleShifts
*
* @method static QueryBuilder|Schedule[] whereId($value)
* @method static QueryBuilder|Schedule[] whereName($value)
* @method static QueryBuilder|Schedule[] whereUrl($value)
* @method static QueryBuilder|Schedule[] whereShiftType($value)
* @method static QueryBuilder|Schedule[] whereMinutesBefore($value)
* @method static QueryBuilder|Schedule[] whereMinutesAfter($value)
* @method static QueryBuilder|Schedule[] whereCreatedAt($value)
* @method static QueryBuilder|Schedule[] whereUpdatedAt($value)
*/
class Schedule extends BaseModel
{
/** @var bool enable timestamps */
public $timestamps = true;
/** @var array Values that are mass assignable */
protected $fillable = ['url'];
protected $fillable = [
'name',
'url',
'shift_type',
'minutes_before',
'minutes_after',
];
/**
* @return HasMany

@ -22,10 +22,12 @@ class CleanupModelTest extends TestCase
$model->foo = null;
$model2 = new TestModel();
$model3 = new TestModel();
$model4 = null;
$cleanup->cleanup($model);
$cleanup->cleanup([$model2]);
$cleanup->cleanup($model3, ['text']);
$cleanup->cleanup($model4);
$this->assertTrue(isset($model->text));
$this->assertTrue(isset($model->created_at));
@ -37,6 +39,8 @@ class CleanupModelTest extends TestCase
$this->assertTrue(isset($model3->text));
$this->assertNull($model3->another_text);
$this->assertNull($model3->foo);
$this->assertNull($model4);
}
/**

@ -14,7 +14,13 @@ class ScheduleShiftTest extends ModelTest
*/
public function testScheduleShifts()
{
$schedule = new Schedule(['url' => 'https://lorem.ipsum/schedule.xml']);
$schedule = new Schedule([
'url' => 'https://lorem.ipsum/schedule.xml',
'name' => 'Test',
'shift_type' => 0,
'minutes_before' => 15,
'minutes_after' => 15,
]);
$schedule->save();
$scheduleShift = new ScheduleShift(['shift_id' => 1, 'guid' => 'a']);

@ -13,7 +13,13 @@ class ScheduleTest extends ModelTest
*/
public function testScheduleShifts()
{
$schedule = new Schedule(['url' => 'https://foo.bar/schedule.xml']);
$schedule = new Schedule([
'url' => 'https://foo.bar/schedule.xml',
'name' => 'Testing',
'shift_type' => 0,
'minutes_before' => 10,
'minutes_after' => 10,
]);
$schedule->save();
(new ScheduleShift(['shift_id' => 1, 'schedule_id' => $schedule->id, 'guid' => 'a']))->save();

Loading…
Cancel
Save