Initial xentral_oss_20.3.c9ffacf

This commit is contained in:
Alex
2021-05-21 08:49:41 +02:00
parent 5406e4a551
commit 34e5ac43d9
18884 changed files with 2109867 additions and 0 deletions
+124
View File
@@ -0,0 +1,124 @@
<?php
namespace Xentral\Modules\Resubmission;
use Xentral\Core\DependencyInjection\ContainerInterface;
use Xentral\Modules\Resubmission\Service\ResubmissionGateway;
use Xentral\Modules\Resubmission\Service\ResubmissionTaskGateway;
use Xentral\Modules\Resubmission\Service\ResubmissionTaskService;
use Xentral\Modules\Resubmission\Service\ResubmissionTextFieldGateway;
use Xentral\Modules\Resubmission\Service\ResubmissionTextFieldService;
use Xentral\Modules\Resubmission\Service\ResubmissionTaskTemplateService;
use Xentral\Modules\Resubmission\Service\ResubmissionTaskTemplateGateway;
class Bootstrap
{
/**
* @return array
*/
public static function registerServices()
{
return [
'ResubmissionGateway' => 'onInitResubmissionGateway',
'ResubmissionTaskService' => 'onInitResubmissionTaskService',
'ResubmissionTaskGateway' => 'onInitResubmissionTaskGateway',
'ResubmissionTextFieldService' => 'onInitResubmissionTextFieldService',
'ResubmissionTextFieldGateway' => 'onInitResubmissionTextFieldGateway',
'ResubmissionTaskTemplateService' => 'onInitResubmissionTaskTemplateService',
'ResubmissionTaskTemplateGateway' => 'onInitResubmissionTaskTemplateGateway',
];
}
/**
* @param ContainerInterface $container
*
* @return ResubmissionGateway
*/
public static function onInitResubmissionGateway(ContainerInterface $container)
{
return new ResubmissionGateway($container->get('Database'));
}
/**
* @param ContainerInterface $container
*
* @return ResubmissionTaskService
*/
public static function onInitResubmissionTaskService(ContainerInterface $container)
{
return new ResubmissionTaskService(
$container->get('Database'),
$container->get('ResubmissionTaskGateway'),
$container->get('ResubmissionGateway')
);
}
/**
* @param ContainerInterface $container
*
* @return ResubmissionTaskGateway
*/
public static function onInitResubmissionTaskGateway(ContainerInterface $container)
{
return new ResubmissionTaskGateway(
$container->get('Database'),
$container->get('ResubmissionGateway')
);
}
/**
* @param ContainerInterface $container
*
* @return ResubmissionTextFieldService
*/
public static function onInitResubmissionTextFieldService(ContainerInterface $container)
{
return new ResubmissionTextFieldService(
$container->get('Database'),
$container->get('ResubmissionTextFieldGateway'),
$container->get('ResubmissionGateway')
);
}
/**
* @param ContainerInterface $container
*
* @return ResubmissionTextFieldGateway
*/
public static function onInitResubmissionTextFieldGateway(ContainerInterface $container)
{
return new ResubmissionTextFieldGateway(
$container->get('Database'),
$container->get('ResubmissionGateway')
);
}
/**
* @param ContainerInterface $container
*
* @return ResubmissionTaskTemplateService
*/
public static function onInitResubmissionTaskTemplateService(ContainerInterface $container)
{
return new ResubmissionTaskTemplateService(
$container->get('Database'),
$container->get('ResubmissionTaskTemplateGateway'),
$container->get('ResubmissionGateway')
);
}
/**
* @param ContainerInterface $container
*
* @return ResubmissionTaskTemplateGateway
*/
public static function onInitResubmissionTaskTemplateGateway(ContainerInterface $container)
{
return new ResubmissionTaskTemplateGateway(
$container->get('Database'),
$container->get('ResubmissionGateway')
);
}
}
@@ -0,0 +1,100 @@
<?php
namespace Xentral\Modules\Resubmission\Data;
use Xentral\Modules\Resubmission\Exception\ValidationFailedException;
final class FreeTextFieldConfigData
{
/** @var int|null $id */
public $id;
/** @var string $title */
public $title;
/** @var bool $showInPipeline */
public $showInPipeline = false;
/** @var bool $showInTables */
public $showInTables = false;
/** @var int $availableFromStageId */
public $availableFromStageId = 0;
/** @var int */
public $requiredFromStageId = 0;
/**
* @param array $formData
*
* @throws ValidationFailedException
*
* @return self
*/
public static function fromFromData(array $formData)
{
$data = new self();
$data->id = $formData['id'];
$data->title = trim($formData['title']);
$data->showInPipeline = (bool)$formData['show_in_pipeline'];
$data->showInTables = (bool)$formData['show_in_tables'];
$data->availableFromStageId = $formData['available_from_stage_id'];
$data->requiredFromStageId = $formData['required_from_stage_id'];
$errors = $data->validate();
if (!empty($errors)) {
throw ValidationFailedException::fromErrors($errors);
}
return $data;
}
/**
* @return array
*/
public function validate()
{
$errors = [];
// id-Property
if ($this->id !== null && !is_int($this->id)) {
$errors['id'][] = 'The "id" property must be an integer.';
}
if ($this->id !== null && $this->id <= 0) {
$errors['id'][] = 'The "id" property must be greater than zero.';
}
// title-Property
if (!is_string($this->title) || empty($this->title)) {
$errors['title'][] = 'The "title" property can not be empty.';
}
// showInPipeline-Property
if (!is_bool($this->showInPipeline)) {
$errors['showInPipeline'][] = 'The "showInPipeline" property must be type boolean.';
}
// showInTables-Property
if (!is_bool($this->showInTables)) {
$errors['showInTables'][] = 'The "showInTables" property must be type boolean.';
}
// availableFromStageId-Property
if (!is_int($this->availableFromStageId)) {
$errors['availableFromStageId'][] = 'The "availableFromStageId" property must be type integer.';
}
if ($this->availableFromStageId < 0) {
$errors['availableFromStageId'][] = 'The "availableFromStageId" property must be zero or greater than zero.';
}
// requiredFromStageId-Property
if (!is_int($this->requiredFromStageId)) {
$errors['requiredFromStageId'][] = 'The "requiredFromStageId" property must be type integer.';
}
if ($this->requiredFromStageId < 0) {
$errors['requiredFromStageId'][] = 'The "requiredFromStageId" property must be zero or greater than zero.';
}
return $errors;
}
}
@@ -0,0 +1,72 @@
<?php
namespace Xentral\Modules\Resubmission\Data;
use Xentral\Modules\Resubmission\Exception\ValidationFailedException;
final class FreeTextFieldContentData
{
/** @var int $resubmissionId Wiedervorlagen-ID */
public $resubmissionId;
/** @var int $configId Textfield-Config-ID */
public $configId;
/** @var string|null $content */
public $content;
/**
* @param array $formData
*
* @throws ValidationFailedException
*
* @return self
*/
public static function fromFormData(array $formData)
{
$formData['content'] = trim($formData['content']);
$data = new self();
$data->configId = (int)$formData['textfield_config_id'];
$data->resubmissionId = (int)$formData['resubmission_id'];
$data->content = !empty($formData['content']) ? $formData['content'] : null;
$errors = $data->validate();
if (!empty($errors)) {
throw ValidationFailedException::fromErrors($errors);
}
return $data;
}
/**
* @return array
*/
public function validate()
{
$errors = [];
// resubmissionId-Property
if (!is_int($this->resubmissionId)) {
$errors['resubmission_id'][] = 'The "resubmissionId" property must be an integer.';
}
if ($this->resubmissionId <= 0) {
$errors['resubmission_id'][] = 'The "resubmissionId" property must be greater than zero.';
}
// configId-Property
if (!is_int($this->configId)) {
$errors['config_id'][] = 'The "configId" property must be type integer.';
}
if ($this->configId <= 0) {
$errors['config_id'][] = 'The "configId" property must be greater than zero.';
}
// content-Property
if ($this->content !== null && !is_string($this->content)) {
$errors['content'][] = 'The "content" property must be null or a non empty string.';
}
return $errors;
}
}
@@ -0,0 +1,434 @@
<?php
namespace Xentral\Modules\Resubmission\Data;
use DateTimeImmutable;
use DateTimeInterface;
use Exception;
use Xentral\Modules\Resubmission\Exception\InvalidArgumentException;
final class ResubmissionTaskData
{
/** @var string STATE_OPEN "Offen" */
const STATE_OPEN = 'open';
/** @var string STATE_PROCESSING "In Bearbeitung" */
const STATE_PROCESSING = 'processing';
/** @var string STATE_COMPLETED "Abgeschlossen" */
const STATE_COMPLETED = 'completed';
/** @var string PRIORITY_HIGH */
const PRIORITY_HIGH = 'high';
/** @var string PRIORITY_MEDIUM */
const PRIORITY_MEDIUM = 'medium';
/** @var string PRIORITY_LOW */
const PRIORITY_LOW = 'low';
/** @var int|null $id ID der Aufgabe; null wenn neue Aufgabe */
private $id;
/** @var int $resubmissionId ID der Wiedervorlagen */
private $resubmissionId;
/** @var int|null $creatorAddressId Address-ID des Erstellers */
private $creatorAddressId;
/** @var int|null $employeeAddressId Für Feld "Bearbeiter/Mitarbeiter" */
private $employeeAddressId;
/** @var int|null $customerAddressId Für Feld "Adresse" */
private $customerAddressId;
/** @var int|null $projectId */
private $projectId;
/** @var int|null $subProjectId Teilprojekt/Arbeitspaket */
private $subProjectId;
/** @var string $title */
private $title;
/** @var string|null $description */
private $description;
/** @var string $state */
private $state;
/** @var string $priority */
private $priority;
/** @var DateTimeInterface|null $completionDateTime Abgeschlossen am */
private $completionDateTime;
/** @var DateTimeInterface|null $submissionDateTime Abschliessen bis */
private $submissionDateTime;
/** @var int $finshedOnStage Stage-ID ab der die Aufgabe abgeschlossen sein muss */
private $requiredCompletionStageId;
/**
* Private constructor
*
* @internal Don't change visibility. Use self::fromFormData instead.
*/
private function __construct()
{
}
/**
* @param array $formData
*
* @return self
*/
public static function fromFormData(array $formData)
{
$task = new self();
$task->setResubmissionId($formData['resubmission_id']);
$task->setRequiredCompletionStageId($formData['required_completion_stage_id']);
$task->setTitle($formData['title']);
$task->setPriority($formData['priority']);
$task->setState($formData['state']);
// Optional properties
if (isset($formData['task_id'])) {
$task->setId($formData['task_id']);
}
if (isset($formData['description'])) {
$task->setDescription($formData['description']);
}
if (isset($formData['employee_address_id'])) {
$task->setEmployeeAddressId($formData['employee_address_id']);
}
if (isset($formData['customer_address_id'])) {
$task->setCustomerAddressId($formData['customer_address_id']);
}
if (isset($formData['project_id'])) {
$task->setProjectId($formData['project_id']);
}
if (isset($formData['creator_address_id'])) {
$task->setCreatorAddressId($formData['creator_address_id']);
}
if (isset($formData['subproject_id'])) {
$task->setSubProjectId($formData['subproject_id']);
}
$submissionDate = trim(sprintf('%s %s', $formData['submission_date'], $formData['submission_time']));
if (!empty($submissionDate)) {
$task->setSubmissionDateTimeByString($submissionDate);
}
return $task;
}
/**
* @return array
*/
public static function getValidStates()
{
return [
self::STATE_OPEN,
self::STATE_PROCESSING,
self::STATE_COMPLETED,
];
}
/**
* @return int|null Null only when new task
*/
public function getId()
{
return $this->id;
}
/**
* @return int
*/
public function getResubmissionId()
{
return $this->resubmissionId;
}
/**
* @return int|null
*/
public function getEmployeeAddressId()
{
return $this->employeeAddressId;
}
/**
* @return int|null
*/
public function getCustomerAddressId()
{
return $this->customerAddressId;
}
/**
* @return int
*/
public function getRequiredCompletionStageId()
{
return $this->requiredCompletionStageId;
}
/**
* @return int|null
*/
public function getProjectId()
{
return $this->projectId;
}
/**
* @return int|null
*/
public function getSubProjectId()
{
return $this->subProjectId;
}
/**
* @return int|null
*/
public function getCreatorAddressId()
{
return $this->creatorAddressId;
}
/**
* @return string
*/
public function getTitle()
{
return $this->title;
}
/**
* @return string|null
*/
public function getDescription()
{
return $this->description;
}
/**
* @return string
*/
public function getState()
{
return $this->state;
}
/**
* @return string
*/
public function getPriority()
{
return $this->priority;
}
/**
* @return DateTimeInterface|null
*/
public function getSubmissionDateTime()
{
return $this->submissionDateTime;
}
/**
* @return DateTimeInterface|null
*/
public function getCompletionDateTime()
{
return $this->completionDateTime;
}
/**
* @param int $id
*
* @return void
*/
private function setId($id)
{
$this->id = (int)$id;
}
/**
* @param int $resubmissionId
*
* @return void
*/
private function setResubmissionId($resubmissionId)
{
$resubmissionId = (int)$resubmissionId;
if ($resubmissionId <= 0) {
throw new InvalidArgumentException('Resubmission ID can not be empty.');
}
$this->resubmissionId = $resubmissionId;
}
/**
* @param string $title
*
* @throws InvalidArgumentException
*
* @return void
*/
private function setTitle($title)
{
$title = trim($title);
if (empty($title)) {
throw new InvalidArgumentException('Title can not be empty.');
}
$this->title = $title;
}
/**
* @param string $description
*
* @return void
*/
private function setDescription($description)
{
$this->description = trim($description);
}
/**
* @param string $priority
*
* @throws InvalidArgumentException
*
* @return void
*/
private function setPriority($priority)
{
if (!in_array($priority, [self::PRIORITY_HIGH, self::PRIORITY_MEDIUM, self::PRIORITY_LOW], true)) {
throw new InvalidArgumentException(sprintf('Priority value is invalid: "%s"', $priority));
}
$this->priority = $priority;
}
/**
* @param string $state
*
* @throws InvalidArgumentException
*
* @return void
*/
private function setState($state)
{
if (!in_array($state, [self::STATE_COMPLETED, self::STATE_PROCESSING, self::STATE_OPEN], true)) {
throw new InvalidArgumentException(sprintf('State value is invalid: "%s"', $state));
}
try {
if ($state === self::STATE_COMPLETED) {
$this->completionDateTime = new DateTimeImmutable('now');
}
if ($state === self::STATE_OPEN || $state === self::STATE_PROCESSING) {
$this->completionDateTime = new DateTimeImmutable('0000-00-00 00:00:00');
}
$this->state = $state;
} catch (Exception $exception) {
throw new InvalidArgumentException($exception->getMessage(), $exception->getCode(), $exception);
}
}
/**
* @param int $employeeAddressId
*
* @return void
*/
private function setEmployeeAddressId($employeeAddressId)
{
$this->employeeAddressId = (int)$employeeAddressId;
}
/**
* @param int $customerAddressId
*
* @return void
*/
private function setCustomerAddressId($customerAddressId)
{
$this->customerAddressId = (int)$customerAddressId;
}
/**
* @param int $projectId
*
* @return void
*/
private function setProjectId($projectId)
{
$this->projectId = (int)$projectId;
}
/**
* @param int $subProjectId
*
* @return void
*/
private function setSubProjectId($subProjectId)
{
$this->subProjectId = (int)$subProjectId;
}
/**
* @param int $creatorAddressId
*
* @return void
*/
private function setCreatorAddressId($creatorAddressId)
{
$this->creatorAddressId = (int)$creatorAddressId;
}
/**
* @param DateTimeInterface $dateTime
*
* @return void
*/
private function setSubmissionDateTime(DateTimeInterface $dateTime)
{
$this->submissionDateTime = $dateTime;
}
/**
* @param string $dateTimeString
*
* @throws InvalidArgumentException
*
* @return void
*/
private function setSubmissionDateTimeByString($dateTimeString)
{
try {
$submissionDate = new DateTimeImmutable($dateTimeString);
} catch (Exception $exception) {
throw new InvalidArgumentException(
'Invalid submission date: ' . $exception->getMessage(),
$exception->getCode(),
$exception
);
}
$this->setSubmissionDateTime($submissionDate);
}
/**
* @param int $stageId
*
* @return void
*/
private function setRequiredCompletionStageId($stageId)
{
$this->requiredCompletionStageId = (int)$stageId;
}
}
@@ -0,0 +1,92 @@
<?php
namespace Xentral\Modules\Resubmission\Data;
use Xentral\Modules\Resubmission\Exception\ValidationFailedException;
final class TaskTemplateData
{
/** @var int|null $id */
public $id;
/** @var string $title */
public $title;
/** @var int $requiredFromStageId */
public $requiredFromStageId = 0;
/** @var int $addTaskAtStageId */
public $addTaskAtStageId = 0;
/**
* @param array $formData
*
* @throws ValidationFailedException
*
* @return self
*/
public static function fromFormData(array $formData)
{
$data = new self();
$data->id = $formData['id'];
$data->requiredFromStageId = $formData['required_from_stage_id'];
$data->addTaskAtStageId = $formData['add_task_at_stage_id'];
$data->employeeAddressId = $formData['employee_address_id'];
$data->projectId = $formData['project_id'];
$data->subprojectId = $formData['subproject_id'];
$data->title = trim($formData['title']);
$data->submissionDateDays = trim($formData['submission_date_days']);
$data->submissionTime = trim($formData['submission_time']);
$data->state = $formData['state'];
$data->priority = $formData['priority'];
$data->description = $formData['description'];
$errors = $data->validate();
if (!empty($errors)) {
throw ValidationFailedException::fromErrors($errors);
}
return $data;
}
/**
* @return array
*/
public function validate()
{
$errors = [];
// id-Property
if ($this->id !== null && !is_int($this->id)) {
$errors['id'][] = 'The "id" property must be an integer.'."\n";
}
if ($this->id !== null && $this->id <= 0) {
$errors['id'][] = 'The "id" property must be greater than zero.'."\n";
}
// title-Property
if (!is_string($this->title) || empty($this->title)) {
$errors['title'][] = 'Bitte Bezeichnung ausfüllen.'."\n";
}
// requiredFromStageId-Property
if (!is_int($this->requiredFromStageId)) {
$errors['requiredFromStageId'][] = 'The "requiredFromStageId" property must be type integer.'."\n";
}
if ($this->requiredFromStageId < 0) {
$errors['requiredFromStageId'][] = 'The "requiredFromStageId" property must be zero or greater than zero.'."\n";
}
// addTaskAtStageId-Property
if (!is_int($this->addTaskAtStageId)) {
$errors['addTaskAtStageId'][] = 'The "addTaskAtStageId" property must be type integer.'."\n";
}
if ($this->addTaskAtStageId <= 0) {
$errors['addTaskAtStageId'][] = 'The "addTaskAtStageId" property must be greater than zero.'."\n";
}
return $errors;
}
}
@@ -0,0 +1,117 @@
<?php
namespace Xentral\Modules\Resubmission\DataTable;
use Aura\SqlQuery\Exception as AuraSqlQueryException;
use Xentral\Components\Database\SqlQuery\SelectQuery;
use Xentral\Widgets\DataTable\Column\Column;
use Xentral\Widgets\DataTable\Column\ColumnCollection;
use Xentral\Widgets\DataTable\Column\ColumnFormatter;
use Xentral\Widgets\DataTable\Feature\FeatureCollection;
use Xentral\Widgets\DataTable\Feature\TableControlFeature;
use Xentral\Widgets\DataTable\Options\DataTableOptions;
use Xentral\Widgets\DataTable\Type\AbstractDataTableType;
final class ResubmissionTaskTemplateDataTable extends AbstractDataTableType
{
/**
* @param SelectQuery $query
*
* @throws AuraSqlQueryException
*
* @return void
*/
public function configureQuery(SelectQuery $query)
{
$query
->cols([
'wav.id',
'wav.title',
'a.name',
'wav.submission_date_days',
'wsr.name' => 'required_from_stage',
'wsa.name' => 'add_task_at_stage',
'wav.state'
])
->from('wiedervorlage_aufgabe_vorlage AS wav')
->leftJoin('wiedervorlage_stages AS wsr', 'wsr.id = wav.required_from_stage_id')
->leftJoin('wiedervorlage_stages AS wsa', 'wsa.id = wav.add_task_at_stage_id')
->leftJoin('adresse AS a', 'a.id = wav.employee_address_id');
}
/**
* @param ColumnCollection $columns
*
* @return void
*/
public function configureColumns(ColumnCollection $columns)
{
$menu = Column::fixed('menu', 'Menü', 'center', '1%');
$menu->setFormatter(static function ($value, $row) {
$html =
'<table class="datatable-menu" align="center" border="0" cellpadding="0" cellspacing="0"><tr>' .
'<td><a href="#" class="resubmissiontasktemplate-edit-button" data-tasktemplate-config-id="{ID}" ' .
'title="Vorlage bearbeiten">' .
'<img src="themes/new/images/edit.svg" alt="Vorlage bearbeiten" border="0" align="center">' .
'</a></td>' .
'<td><a href="#" class="resubmissiontasktemplate-delete-button" data-tasktemplate-config-id="{ID}" ' .
'title="Vorlage löschen">' .
'<img src="themes/new/images/delete.svg" alt="Vorlage löschen" border="0" align="center">' .
'</a></td>' .
'</tr></table>';
$html = str_replace('{ID}', $row['id'], $html);
return $html;
});
$requiredFromStage = Column::searchable('required_from_stage', 'Pflichtfeld ab');
$requiredFromStage->setFormatter(ColumnFormatter::ifEmpty('- Nie -'));
$columns->add(Column::searchable('title', 'Bezeichnung'));
$columns->add(Column::searchable('name', 'Bearbeiter'));
$columns->add(Column::searchable('submission_date_days', 'Intervall'));
$columns->add($requiredFromStage);
$columns->add(Column::searchable('add_task_at_stage', 'Hinzufügen ab'));
$state = Column::searchable('state', 'Status');
$state->setFormatter(function ($value, $row) {
if ($row['state'] == 'open') {
return 'Offen';
}elseif ($row['state'] == 'processing') {
return 'In Bearbeitung';
}elseif ($row['state'] == 'completed') {
return 'Abgeschlossen';
}
return (string)$row['state'];
});
$columns->add($state);
$columns->add($menu);
}
/**
* @param DataTableOptions $options
*
* @return void
*/
public function configureOptions(DataTableOptions $options)
{
$options->setDefaultSorting(['required_from_stage' => 'ASC', 'add_task_at_stage' => 'ASC']);
}
/**
* @param FeatureCollection $features
*
* @return void
*/
public function configureFeatures(FeatureCollection $features)
{
parent::configureFeatures($features);
/** @var TableControlFeature $control */
$control = $features->get(TableControlFeature::class);
$control->hideButtons();
}
}
@@ -0,0 +1,149 @@
<?php
namespace Xentral\Modules\Resubmission\DataTable;
use Aura\SqlQuery\Exception as AuraSqlQueryException;
use Xentral\Components\Database\SqlQuery\SelectQuery;
use Xentral\Widgets\DataTable\Column\Column;
use Xentral\Widgets\DataTable\Column\ColumnCollection;
use Xentral\Widgets\DataTable\Feature\FeatureCollection;
use Xentral\Widgets\DataTable\Feature\TableControlFeature;
use Xentral\Widgets\DataTable\Filter\CustomFilter;
use Xentral\Widgets\DataTable\Filter\FilterCollection;
use Xentral\Widgets\DataTable\Options\DataTableOptions;
use Xentral\Widgets\DataTable\Request\DataTableRequest;
use Xentral\Widgets\DataTable\Type\AbstractDataTableType;
final class ResubmissionTasksDataTable extends AbstractDataTableType
{
/**
* @param SelectQuery $query
*
* @throws AuraSqlQueryException
*
* @return void
*/
public function configureQuery(SelectQuery $query)
{
$query
->cols([
'a.id',
'a.aufgabe' => 'title',
'adr.name' => 'employee_name',
'adr.mitarbeiternummer' => 'employee_number',
'a.abgabe_bis' => 'completion_date',
'a.startdatum' => 'start_date',
'a.startzeit' => 'start_time',
'a.prio' => 'priority',
'a.status' => 'state',
])
->from('aufgabe AS a')
->innerJoin('wiedervorlage_aufgabe AS wa', 'wa.task_id = a.id')
->leftJoin('adresse AS adr', 'a.adresse = adr.id');
}
/**
* @param ColumnCollection $columns
*
* @return void
*/
public function configureColumns(ColumnCollection $columns)
{
$priority = Column::searchable('priority', 'Priorität', Column::ALIGN_CENTER);
$priority->setFormatter(static function ($value) {
$prio = (int)$value;
if ($prio === 1) {
return 'hoch';
}
if ($prio === -1) {
return 'niedrig';
}
return 'mittel';
});
$employee = Column::searchable('employee', 'Mitarbeiter');
$employee->setFormatter(function ($value, $row) {
if (!empty($row['employee_number'])) {
return sprintf('%s %s', $row['employee_number'], $row['employee_name']);
}
return (string)$row['employee_name'];
});
$menu = Column::fixed('menu', 'Menü', 'center', '1%');
$menu->setFormatter(function ($value, $row) {
$stateText = $row['state'] === 'abgeschlossen' ? 'completed' : 'open';
$stateIcon = $row['state'] === 'abgeschlossen' ? 'check_circle_filled.svg' : 'check_circle_outlined.svg';
$html =
'<table class="datatable-menu" align="center" border="0" cellpadding="0" cellspacing="0"><tr>' .
'<td><a href="#" class="resubmissiontask-state-button" data-task-id="{ID}" ' .
'data-task-state="{STATE_TEXT}" title="Status ändern">' .
'<img src="themes/new/images/{STATE_ICON}" alt="Status ändern" border="0" align="center">' .
'</a></td>' .
'<td><a href="#" class="resubmissiontask-edit-button" data-task-id="{ID}" title="Aufgabe bearbeiten">' .
'<img src="themes/new/images/edit.svg" alt="Aufgabe bearbeiten" border="0" align="center">' .
'</a></td>' .
'<td><a href="#" class="resubmissiontask-delete-button" data-task-id="{ID}" title="Aufgabe löschen">' .
'<img src="themes/new/images/delete.svg" alt="Aufgabe löschen" border="0" align="center">' .
'</a></td>' .
'</tr></table>';
$html = str_replace('{ID}', $row['id'], $html);
$html = str_replace('{STATE_TEXT}', $stateText, $html);
$html = str_replace('{STATE_ICON}', $stateIcon, $html);
return $html;
});
$columns->add(Column::searchable('title', 'Aufgabe'));
$columns->add($employee);
$columns->add(Column::searchable('completion_date', 'Abgabe bis'));
$columns->add($priority);
$columns->add($menu);
}
/**
* @param DataTableOptions $options
*
* @return void
*/
public function configureOptions(DataTableOptions $options)
{
$options->setDefaultSorting(['completion_date' => 'ASC', 'title' => 'ASC']);
}
/**
* @param FeatureCollection $features
*
* @return void
*/
public function configureFeatures(FeatureCollection $features)
{
parent::configureFeatures($features);
/** @var TableControlFeature $control */
$control = $features->get(TableControlFeature::class);
$control->disablePaging();
$control->disableSearching();
$control->hideLengthChange();
$control->hideButtons();
$control->hideInfo();
}
/**
* @param FilterCollection $filters
*
* @return void
*/
public function configureFilters(FilterCollection $filters)
{
$closure = static function (SelectQuery $query, DataTableRequest $request) {
$resubmissionId = (int)$request->getOriginalRequest()->getParam('id');
if ($resubmissionId > 0) {
$query->where('wa.resubmission_id = ?', $resubmissionId);
}
};
$filters->add(new CustomFilter($closure));
}
}
@@ -0,0 +1,113 @@
<?php
namespace Xentral\Modules\Resubmission\DataTable;
use Aura\SqlQuery\Exception as AuraSqlQueryException;
use Xentral\Components\Database\SqlQuery\SelectQuery;
use Xentral\Widgets\DataTable\Column\Column;
use Xentral\Widgets\DataTable\Column\ColumnCollection;
use Xentral\Widgets\DataTable\Column\ColumnFormatter;
use Xentral\Widgets\DataTable\Feature\FeatureCollection;
use Xentral\Widgets\DataTable\Feature\TableControlFeature;
use Xentral\Widgets\DataTable\Options\DataTableOptions;
use Xentral\Widgets\DataTable\Type\AbstractDataTableType;
final class ResubmissionTextFieldDataTable extends AbstractDataTableType
{
/**
* @param SelectQuery $query
*
* @throws AuraSqlQueryException
*
* @return void
*/
public function configureQuery(SelectQuery $query)
{
$query
->cols([
'wfk.id',
'wfk.title',
'wsa.name' => 'available_from_stage',
'wsr.name' => 'required_from_stage',
'wfk.show_in_pipeline',
'wfk.show_in_tables',
])
->from('wiedervorlage_freifeld_konfiguration AS wfk')
->leftJoin('wiedervorlage_stages AS wsa', 'wsa.id = wfk.available_from_stage_id')
->leftJoin('wiedervorlage_stages AS wsr', 'wsr.id = wfk.required_from_stage_id');
}
/**
* @param ColumnCollection $columns
*
* @return void
*/
public function configureColumns(ColumnCollection $columns)
{
$menu = Column::fixed('menu', 'Menü', 'center', '1%');
$menu->setFormatter(static function ($value, $row) {
$html =
'<table class="datatable-menu" align="center" border="0" cellpadding="0" cellspacing="0"><tr>' .
'<td><a href="#" class="resubmissiontextfield-edit-button" data-textfield-config-id="{ID}" ' .
'title="Freifeld bearbeiten">' .
'<img src="themes/new/images/edit.svg" alt="Freifeld bearbeiten" border="0" align="center">' .
'</a></td>' .
'<td><a href="#" class="resubmissiontextfield-delete-button" data-textfield-config-id="{ID}" ' .
'title="Freifeld löschen">' .
'<img src="themes/new/images/delete.svg" alt="Freifeld löschen" border="0" align="center">' .
'</a></td>' .
'</tr></table>';
$html = str_replace('{ID}', $row['id'], $html);
return $html;
});
$availableFromStage = Column::searchable('available_from_stage', 'Verfügbar ab Stage');
$availableFromStage->setFormatter(ColumnFormatter::ifEmpty('- Immer -'));
$requiredFromStage = Column::searchable('required_from_stage', 'Pflichtfeld ab Stage');
$requiredFromStage->setFormatter(ColumnFormatter::ifEmpty('- Nie -'));
$showInPipeline = Column::sortable('show_in_pipeline', 'Anzeigen in Pipeline');
$showInPipeline->setFormatter(static function ($value) {
return (int)$value === 1 ? 'Ja' : 'Nein';
});
$showInTables = Column::sortable('show_in_tables', 'Anzeigen in Tabellen');
$showInTables->setFormatter(static function ($value) {
return (int)$value === 1 ? 'Ja' : 'Nein';
});
$columns->add(Column::searchable('title', 'Bezeichnung'));
$columns->add($availableFromStage);
$columns->add($requiredFromStage);
$columns->add($showInPipeline);
$columns->add($showInTables);
$columns->add($menu);
}
/**
* @param DataTableOptions $options
*
* @return void
*/
public function configureOptions(DataTableOptions $options)
{
$options->setDefaultSorting(['available_from_stage' => 'ASC', 'required_from_stage' => 'ASC']);
}
/**
* @param FeatureCollection $features
*
* @return void
*/
public function configureFeatures(FeatureCollection $features)
{
parent::configureFeatures($features);
/** @var TableControlFeature $control */
$control = $features->get(TableControlFeature::class);
$control->hideButtons();
}
}
@@ -0,0 +1,7 @@
<?php
namespace Xentral\Modules\Resubmission\Exception;
class InvalidArgumentException extends \InvalidArgumentException implements ResubmissionExceptionInterface
{
}
@@ -0,0 +1,9 @@
<?php
namespace Xentral\Modules\Resubmission\Exception;
use Xentral\Core\Exception\ModuleExceptionInterface;
interface ResubmissionExceptionInterface extends ModuleExceptionInterface
{
}
@@ -0,0 +1,9 @@
<?php
namespace Xentral\Modules\Resubmission\Exception;
use RuntimeException;
class ResubmissionNotFoundException extends RuntimeException implements ResubmissionExceptionInterface
{
}
@@ -0,0 +1,9 @@
<?php
namespace Xentral\Modules\Resubmission\Exception;
use RuntimeException;
class ResubmissionTaskNotFoundException extends RuntimeException implements ResubmissionExceptionInterface
{
}
@@ -0,0 +1,9 @@
<?php
namespace Xentral\Modules\Resubmission\Exception;
use RuntimeException;
class StageNotFoundException extends RuntimeException implements ResubmissionExceptionInterface
{
}
@@ -0,0 +1,102 @@
<?php
namespace Xentral\Modules\Resubmission\Exception;
use DomainException;
class TaskMustBeCompletedException extends DomainException implements ResubmissionExceptionInterface
{
/** @var string $requiredStageName */
private $requiredStageName;
/** @var string $currentStageName */
private $currentStageName;
/**
* Exception wird geworfen wenn eine Aufgabe als "offen" angelegt werden soll
* und die aktuelle Stage und die Einstellung in "Pflichtfeld ab Stage" das nicht zulässt.
*
* @param string $requiredStageName
* @param string $currentStageName
*
* @return TaskMustBeCompletedException
*/
public static function onCreation($requiredStageName, $currentStageName)
{
$instance = new self(sprintf(
'The Task cannot be created. The task must be completed from stage "%s" on. ' .
'The resubmission is currently in stage "%s".',
$requiredStageName,
$currentStageName
));
$instance->requiredStageName = $requiredStageName;
$instance->currentStageName = $currentStageName;
return $instance;
}
/**
* Exception wird geworfen wenn eine Aufgabe beim Bearbeiten auf "offen" angelegt werden soll
* und die aktuelle Stage und die Einstellung in "Pflichtfeld ab Stage" das nicht zulässt.
*
* @param string $requiredStageName
* @param string $currentStageName
*
* @return TaskMustBeCompletedException
*/
public static function onModification($requiredStageName, $currentStageName)
{
$instance = new self(sprintf(
'The Task modification is invalid. The task must be completed from stage "%s" on. ' .
'The resubmission is currently in stage "%s".',
$requiredStageName,
$currentStageName
));
$instance->requiredStageName = $requiredStageName;
$instance->currentStageName = $currentStageName;
return $instance;
}
/**
* Exception wird geworfen wenn eine abgeschlossene Aufgabe zurück auf "offen" gestellt werden soll
* und die aktuelle Stage und die Einstellung in "Pflichtfeld ab Stage" das nicht zulässt.
*
* @param $requiredStageName
* @param $currentStageName
*
* @return TaskMustBeCompletedException
*/
public static function onChangingStateToOpen($requiredStageName, $currentStageName)
{
$instance = new self(sprintf(
'The Task cannot be changed to "open". The task needs to be completed from stage "%s" on. ' .
'The resubmission is currently in stage "%s".',
$requiredStageName,
$currentStageName
));
$instance->requiredStageName = $requiredStageName;
$instance->currentStageName = $currentStageName;
return $instance;
}
/**
* @return string
*/
public function getRequiredStageName()
{
return $this->requiredStageName;
}
/**
* @return string
*/
public function getCurrentStageName()
{
return $this->currentStageName;
}
}
@@ -0,0 +1,9 @@
<?php
namespace Xentral\Modules\Resubmission\Exception;
use RuntimeException;
class TaskTemplateNotFoundException extends RuntimeException implements ResubmissionExceptionInterface
{
}
@@ -0,0 +1,9 @@
<?php
namespace Xentral\Modules\Resubmission\Exception;
use RuntimeException;
class TextFieldConfigNotFoundException extends RuntimeException implements ResubmissionExceptionInterface
{
}
@@ -0,0 +1,52 @@
<?php
namespace Xentral\Modules\Resubmission\Exception;
use DomainException;
class TextFieldRequiredException extends DomainException implements ResubmissionExceptionInterface
{
/** @var string $fieldLabel */
private $fieldLabel;
/** @var string $requiredStageName */
private $requiredStageName;
/**
* Exception wird geworfen wenn ein benötigtes Freitextfeld beim Speichern leer ist.
*
* @param string $fieldLabel
* @param string $requiredStageName
*
* @return TextFieldRequiredException
*/
public static function onEmpty($fieldLabel, $requiredStageName)
{
$instance = new self(sprintf(
'The text field "%s" can not be saved. The text field is required from stage "%s" on.',
$fieldLabel,
$requiredStageName
));
$instance->fieldLabel = $fieldLabel;
$instance->requiredStageName = $requiredStageName;
return $instance;
}
/**
* @return string
*/
public function getFieldLabel()
{
return $this->fieldLabel;
}
/**
* @return string
*/
public function getRequiredStageName()
{
return $this->requiredStageName;
}
}
@@ -0,0 +1,37 @@
<?php
namespace Xentral\Modules\Resubmission\Exception;
use RuntimeException;
final class ValidationFailedException extends RuntimeException implements ResubmissionExceptionInterface
{
/** @var array $errors */
private $errors = [];
/**
* @param array $errors
*
* @return self
*/
public static function fromErrors(array $errors)
{
$errorString = '';
foreach ($errors as $propertyName => $propertyErrors) {
$errorString .= implode("\r\n", $propertyErrors);
}
$exception = new self('Validation failed with following errors: ' . "\n\n" . $errorString);
$exception->errors = $errors;
return $exception;
}
/**
* @return array
*/
public function getErrors()
{
return $this->errors;
}
}
@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\Resubmission\Exception;
use RuntimeException;
final class ViewNotFoundException extends RuntimeException implements ResubmissionExceptionInterface
{
}
@@ -0,0 +1,369 @@
<?php
namespace Xentral\Modules\Resubmission\Service;
use Xentral\Components\Database\Database;
use Xentral\Modules\Resubmission\Exception\StageNotFoundException;
use Xentral\Modules\Resubmission\Exception\ResubmissionNotFoundException;
use Xentral\Modules\Resubmission\Exception\ViewNotFoundException;
final class ResubmissionGateway
{
/** @var Database $db */
private $db;
/**
* @param Database $database
*/
public function __construct(Database $database)
{
$this->db = $database;
}
/**
* @param int $resubmissionId
*
* @return bool
*/
public function existsResubmission($resubmissionId)
{
$sql = 'SELECT w.id FROM `wiedervorlage` AS `w` WHERE w.id = :resubmission_id';
$check = $this->db->fetchValue($sql, ['resubmission_id' => (int)$resubmissionId]);
return (int)$check === (int)$resubmissionId;
}
/**
* @param int $resubmissionId
*
* @throws ResubmissionNotFoundException
*
* @return int
*/
public function getViewIdByResubmission($resubmissionId)
{
$sql =
'SELECT ws.view AS `view_id`
FROM `wiedervorlage` AS `w`
LEFT JOIN `wiedervorlage_stages` AS `ws` ON w.stages = ws.id
WHERE w.id = :resubmission_id';
$viewId = $this->db->fetchValue($sql, ['resubmission_id' => (int)$resubmissionId]);
if ($viewId === false) {
throw new ResubmissionNotFoundException(sprintf('Resubmission not found: ID%s', $resubmissionId));
}
return (int)$viewId;
}
/**
* @param int $stageId
*
* @throws StageNotFoundException
*
* @return int
*/
public function getViewIdByStage($stageId)
{
$sql = 'SELECT ws.view AS `view_id` FROM `wiedervorlage_stages` AS `ws` WHERE ws.id = :stage_id';
$viewId = $this->db->fetchValue($sql, ['stage_id' => (int)$stageId]);
if ($viewId === false) {
throw new StageNotFoundException(sprintf('Stage not found: ID%s', $stageId));
}
return (int)$viewId;
}
/**
* @param int $viewId
*
* @return array
*/
public function getStagesByView($viewId)
{
$sql =
'SELECT
ws.id,
IF(ws.kurzbezeichnung != \'\', ws.kurzbezeichnung, ws.name) AS `shortname`,
ws.name AS `longname`
FROM `wiedervorlage_stages` AS `ws`
LEFT JOIN `wiedervorlage_view` AS `wv` ON ws.view = wv.id AND wv.active = 1
WHERE ws.view = :view_id
ORDER BY ws.sort, ws.id';
$stages = $this->db->fetchAll($sql, ['view_id' => (int)$viewId]);
$rank = 1;
foreach ($stages as &$stage) {
$stage['rank'] = $rank;
$rank++;
}
unset($stage);
return $stages;
}
/**
* Alle Geschwister-Stages ermitteln
*
* D.h. alle Stages ermitteln die sich in der gleichen View befinden wie die übergebene Stage
*
* @param int $stageId
*
* @return array
*/
public function getSiblingStages($stageId)
{
$viewId = $this->getViewIdByStage($stageId);
return $this->getStagesByView($viewId);
}
/**
* @param int $stageId
*
* @throws StageNotFoundException
*
* @return array
*/
public function getStage($stageId)
{
$sql =
'SELECT
ws.id,
IF(ws.kurzbezeichnung != \'\', ws.kurzbezeichnung, ws.name) AS `shortname`,
ws.name AS `longname`
FROM `wiedervorlage_stages` AS `ws`
WHERE ws.id = :stage_id';
$stage = $this->db->fetchRow($sql, ['stage_id' => (int)$stageId]);
if (empty($stage)) {
throw new StageNotFoundException(sprintf('Stage ID "%s" not found', $stageId));
}
return $stage;
}
/**
* @param int $resubmissionId
*
* @throws ResubmissionNotFoundException
* @throws StageNotFoundException
*
* @return array
*/
public function getStageByResubmission($resubmissionId)
{
$sql =
'SELECT
w.id AS resubmission_id,
ws.view AS view_id,
ws.id,
IF(ws.kurzbezeichnung != \'\', ws.kurzbezeichnung, ws.name) AS `shortname`,
ws.name AS `longname`
FROM `wiedervorlage` AS `w`
LEFT JOIN `wiedervorlage_stages` AS `ws` ON w.stages = ws.id
WHERE w.id = :resubmissionId';
$result = $this->db->fetchRow($sql, ['resubmissionId' => (int)$resubmissionId]);
if (empty($result['resubmission_id'])) {
throw new ResubmissionNotFoundException(
sprintf(
'Resubmission not found: ID%s',
$resubmissionId
)
);
}
if (empty($result['id'])) {
throw new StageNotFoundException(
sprintf(
'Stage not found for resubmission: Resubmission-ID%s',
$resubmissionId
)
);
}
unset($result['resubmission_id']);
return $result;
}
/**
* @param int $resubmissionId Wiedervorlagen-ID
*
* @return array
*/
public function getStagesByResubmission($resubmissionId)
{
// View-ID der aktuellen Stage ermitteln; View-ID darf 0 sein; 0 = Standard-View
$viewId = $this->getViewIdByResubmission($resubmissionId);
$sql =
'SELECT
ws.id,
IF(ws.kurzbezeichnung != \'\', ws.kurzbezeichnung, ws.name) AS `shortname`,
ws.name AS `longname`
FROM `wiedervorlage_stages` AS `ws`
LEFT JOIN `wiedervorlage_view` AS `wv` ON ws.view = wv.id AND wv.active = 1
WHERE ws.view = :view_id
ORDER BY ws.sort, ws.id';
return $this->db->fetchAll($sql, ['view_id' => $viewId]);
}
/**
* @return array
*/
public function getStagesWithViews()
{
$sql =
'SELECT
ws.id,
ws.name AS stage_name,
wv.name AS view_name
FROM `wiedervorlage_stages` AS `ws`
LEFT JOIN `wiedervorlage_view` AS `wv` ON wv.id = ws.view
ORDER BY ws.view, ws.sort ';
$stages = $this->db->fetchAll($sql);
$result = [];
foreach ($stages as $stage) {
$viewName = $stage['view_name'] !== null ? $stage['view_name'] : 'Standard';
$result[] = [
'id' => (int)$stage['id'],
'label' => sprintf('%s > %s', $viewName, $stage['stage_name']),
];
}
return $result;
}
/**
* @param int $sourceStageId ID der Stage von der aus verschoben wird
* @param int $targetStageId ID der Stage in die verschoben werden soll
*
* @throws StageNotFoundException
*
* @return int Positiver Wert = Target-Stage befindet sich in aufsteigender Position
* Negativer Wert = Target-Stage befindet sich vor der Ursprungs-Stage
* Null = Source- und Target-Stage sind identisch
*/
public function getDistanceBetweenStages($sourceStageId, $targetStageId)
{
$sourceStageId = (int)$sourceStageId;
$targetStageId = (int)$targetStageId;
$stages = $this->getSiblingStages($targetStageId);
// Prüfen ob übergebene Stage-ID überhaupt valide ist; Muss in der selben Ansicht/View sein
$validStageIds = array_column($stages, 'id');
$isTargetStageIdValid = in_array($targetStageId, $validStageIds, true);
if ($isTargetStageIdValid === false) {
throw new StageNotFoundException(sprintf('Target stage ID "%s" not found', $targetStageId));
}
$targetStageRank = 0;
$sourceStageRank = 0;
$rank = 1;
foreach ($stages as $stage) {
if ((int)$stage['id'] === $sourceStageId) {
$sourceStageRank = $rank;
}
if ((int)$stage['id'] === $targetStageId) {
$targetStageRank = $rank;
}
$rank++;
}
return $targetStageRank - $sourceStageRank;
}
/**
* @param int $resubmissionId
*
* @return array
*/
public function getById($resubmissionId)
{
if (!is_numeric($resubmissionId)) {
throw new ResubmissionNotFoundException(
sprintf(
'Resubmission not found: ID%s',
$resubmissionId
)
);
}
$sql = 'SELECT w.id,
w.adresse,
w.projekt,
w.parameter,
w.abgeschlossen,
w.action,
w.adresse_mitarbeiter,
w.bearbeiter,
w.beschreibung,
w.bezeichnung,
w.betrag,
w.ergebnis,
w.erinnerung,
w.erinnerung_empfaenger,
w.datum_erinnerung,
w.zeit_erinnerung,
w.datum_status,
w.datum_abschluss,
w.datum_angelegt,
w.stages,
w.prio,
w.color,
w.chance,
w.status,
w.module,
w.link,
w.oeffentlich,
w.erinnerung_per_mail,
w.zeit_angelegt
FROM `wiedervorlage` AS `w` WHERE w.id = :resubmission_id';
return $this->db->fetchRow($sql, ['resubmission_id' => (int)$resubmissionId]);
}
/**
* @param string $name
* @param string $description
*
* @throws ViewNotFoundException
*
* @return int
*/
public function getViewIdByNameAndDescription(string $name, string $description): int
{
$viewId = $this->db->fetchValue(
'SELECT wv.id FROM `wiedervorlage_view` AS `wv`
WHERE wv.name = :name AND wv.shortname = :desc AND wv.active = :active AND wv.project = 0',
[
'name' => $name,
'desc' => $description,
'active' => 1,
]
);
if ($viewId === false) {
throw new ViewNotFoundException(sprintf('View not found for name:%s and desc:%s', $name, $description));
}
return (int)$viewId;
}
/**
* @param int $viewId
*
* @return int
*/
public function getMaxSortByViewId(int $viewId): int
{
$maxPosition = $this->db->fetchValue(
'SELECT MAX(ws.sort) FROM `wiedervorlage_stages` AS `ws` WHERE ws.`view` = :id',
['id' => $viewId]
);
return (int)$maxPosition;
}
}
@@ -0,0 +1,187 @@
<?php
namespace Xentral\Modules\Resubmission\Service;
use Xentral\Components\Database\Database;
use Xentral\Modules\Resubmission\Exception\ResubmissionTaskNotFoundException;
final class ResubmissionTaskGateway
{
/** @var ResubmissionGateway $gateway */
private $gateway;
/** @var Database $db */
private $db;
/**
* @param Database $db
* @param ResubmissionGateway $gateway
*/
public function __construct(Database $db, ResubmissionGateway $gateway)
{
$this->gateway = $gateway;
$this->db = $db;
}
/**
* @param int $resubmissionId Wiedervorlagen-ID
*
* @return array Empty array if no result
*/
public function getTasksByResubmission($resubmissionId)
{
$sql =
'SELECT
a.id,
CASE
WHEN a.status = \'offen\' THEN \'open\'
WHEN a.status = \'inbearbeitung\' THEN \'processing\'
WHEN a.status = \'abgeschlossen\' THEN \'completed\'
END AS `state`,
CASE
WHEN a.prio = \'1\' THEN \'high\'
WHEN a.prio = \'-1\' THEN \'low\'
ELSE \'medium\'
END AS `priority`,
a.adresse AS `address_id`,
a.initiator AS `creator_address_id`,
a.aufgabe AS `title`,
a.beschreibung AS `description`,
DATE_FORMAT(a.abgabe_bis, \'%d.%m.%Y\') AS `submission_date`,
TIME_FORMAT(a.abgabe_bis_zeit, \'%H:%i\') AS `submission_time`,
wa.required_completion_stage_id
FROM `aufgabe` AS `a`
INNER JOIN `wiedervorlage_aufgabe` AS `wa` ON wa.task_id = a.id
INNER JOIN `wiedervorlage` AS `w` ON wa.resubmission_id = w.id
WHERE w.id = :resubmission_id';
return $this->db->fetchAll($sql, ['resubmission_id' => (int)$resubmissionId]);
}
/**
* Ist die Aufgabe der übergebenen Wiedervorlage zugeordnet?
*
* Gleichzeitig wird auch geprüft ob Aufgabe existiert.
*
* @param int $taskId
* @param int $resubmissionId
*
* @return bool
*/
public function isTaskAssigendToResubmission($taskId, $resubmissionId)
{
$taskId = (int)$taskId;
$resubmissionId = (int)$resubmissionId;
if ($taskId <= 0 || $resubmissionId <= 0) {
return false;
}
$sql = 'SELECT a.id
FROM `aufgabe` AS `a`
INNER JOIN `wiedervorlage_aufgabe` AS `wa` ON wa.task_id = a.id
WHERE wa.resubmission_id = :resubmission_id
AND a.id = :task_id';
$taskIdCheck = $this->db->fetchValue($sql, [
'task_id' => $taskId,
'resubmission_id' => $resubmissionId,
]);
return $taskId === $taskIdCheck;
}
/**
* @param int $taskId
*
* @throws ResubmissionTaskNotFoundException
*
* @return array
*/
public function getTask($taskId)
{
$sql =
'SELECT
a.id,
w.stages AS `stage_id`,
a.projekt AS `project_id`,
a.teilprojekt AS `subproject_id`,
a.adresse AS `employee_id`,
CONCAT(adr.mitarbeiternummer, \' \', adr.name) AS `employee_name`,
CONCAT(a.kunde, \' \', cus.name, \' (Kdr: \', cus.kundennummer, \')\') as `customer`,
a.aufgabe AS `title`,
a.beschreibung AS `description`,
DATE_FORMAT(a.abgabe_bis, \'%d.%m.%Y\') AS `submission_date`,
TIME_FORMAT(a.abgabe_bis_zeit, \'%H:%i\') AS `submission_time`,
CASE
WHEN a.status = \'offen\' THEN \'open\'
WHEN a.status = \'inbearbeitung\' THEN \'processing\'
WHEN a.status = \'abgeschlossen\' THEN \'completed\'
END AS `state`,
CASE
WHEN a.prio = \'1\' THEN \'high\'
WHEN a.prio = \'-1\' THEN \'low\'
ELSE \'medium\'
END AS `priority`,
wa.required_completion_stage_id
FROM `aufgabe` AS `a`
INNER JOIN `wiedervorlage_aufgabe` AS `wa` ON wa.task_id = a.id
INNER JOIN `wiedervorlage` AS `w` ON wa.resubmission_id = w.id
LEFT JOIN `adresse` AS adr ON a.adresse = adr.id
LEFT JOIN `adresse` AS cus ON a.kunde = cus.id
WHERE a.id = :task_id';
$result = $this->db->fetchRow($sql, ['task_id' => (int)$taskId,]);
if (empty($result)) {
throw new ResubmissionTaskNotFoundException(sprintf('Task ID%s not found.', $taskId));
}
if (empty($result['submission_date']) || $result['submission_date'] === '00.00.0000') {
$result['submission_date'] = null;
}
if (empty($result['submission_time']) || $result['submission_time'] === '00:00') {
$result['submission_time'] = null;
}
return $result;
}
/**
* @param int $resubmissionId Wiedervorlagen-ID
* @param int $taskId Aufgaben-ID
*
* @return array
*/
public function getStages($resubmissionId, $taskId)
{
$sql =
'SELECT w.stages AS `stage_id`, wa.required_completion_stage_id
FROM `wiedervorlage` AS `w`
INNER JOIN `wiedervorlage_aufgabe` AS `wa` ON w.id = wa.resubmission_id
WHERE w.id = :resubmission_id AND wa.task_id = :task_id';
$resubmission = $this->db->fetchRow($sql, [
'resubmission_id' => (int)$resubmissionId,
'task_id' => (int)$taskId,
]);
// View-ID der aktuellen Stage ermitteln; View-ID darf 0 sein; 0 = Standard-View
$viewId = $this->gateway->getViewIdByStage($resubmission['stage_id']);
// Alle Stages im gleichen View laden
$stages = $this->gateway->getStagesByView($viewId);
$rank = 1;
$requiredBefore = false;
foreach ($stages as &$stage) {
$stage['rank'] = $rank++;
$stage['current'] = (int)$stage['id'] === (int)$resubmission['stage_id'];
$stage['required'] = (int)$stage['id'] === (int)$resubmission['required_completion_stage_id'];
if ((int)$stage['id'] === (int)$resubmission['required_completion_stage_id']) {
$requiredBefore = true;
}
$stage['required_before'] = $requiredBefore;
}
unset($stage);
return $stages;
}
}
@@ -0,0 +1,462 @@
<?php
namespace Xentral\Modules\Resubmission\Service;
use Xentral\Components\Database\Database;
use Xentral\Components\Database\Exception\DatabaseExceptionInterface;
use Xentral\Modules\Resubmission\Data\ResubmissionTaskData;
use Xentral\Modules\Resubmission\Exception\InvalidArgumentException;
use Xentral\Modules\Resubmission\Exception\ResubmissionNotFoundException;
use Xentral\Modules\Resubmission\Exception\ResubmissionTaskNotFoundException;
use Xentral\Modules\Resubmission\Exception\TaskMustBeCompletedException;
final class ResubmissionTaskService
{
/** @var Database $db */
private $db;
/** @var ResubmissionTaskGateway $taskGateway */
private $taskGateway;
/** @var ResubmissionGateway $resubmissionGateway */
private $resubmissionGateway;
/**
* @param Database $database
* @param ResubmissionTaskGateway $taskGateway
* @param ResubmissionGateway $resubmissionGateway
*/
public function __construct(
Database $database,
ResubmissionTaskGateway $taskGateway,
ResubmissionGateway $resubmissionGateway
) {
$this->db = $database;
$this->taskGateway = $taskGateway;
$this->resubmissionGateway = $resubmissionGateway;
}
/**
* Aufgabe auf "abgeschlossen" stellen
*
* @param int $taskId
* @param int $resubmissionId
*
* @throws ResubmissionTaskNotFoundException
*
* @return void
*/
public function markTaskAsCompleted($taskId, $resubmissionId)
{
if (!$this->taskGateway->isTaskAssigendToResubmission($taskId, $resubmissionId)) {
throw new ResubmissionTaskNotFoundException(sprintf(
'Task not found. Task-ID: %s - Resubmission-ID: %s', $taskId, $resubmissionId
));
}
$sql = 'UPDATE `aufgabe`
SET `status` = :state, `abgeschlossen_am` = :completion_date, `logdatei` = NOW()
WHERE `id` = :task_id
LIMIT 1';
$this->db->perform($sql, [
'state' => 'abgeschlossen',
'completion_date' => date('Y-m-d'),
'task_id' => (int)$taskId,
]);
}
/**
* Aufgabe auf "offen" stellen
*
* @param int $taskId
* @param int $resubmissionId
*
* @throws ResubmissionTaskNotFoundException
* @throws TaskMustBeCompletedException
*
* @return void
*/
public function markTaskAsOpen($taskId, $resubmissionId)
{
if (!$this->taskGateway->isTaskAssigendToResubmission($taskId, $resubmissionId)) {
throw new ResubmissionTaskNotFoundException(sprintf(
'Task not found. Task-ID: %s - Resubmission-ID: %s', $taskId, $resubmissionId
));
}
$task = $this->taskGateway->getTask($taskId);
// Vor der Änderung prüfen ob Änderung überhaupt gültig wäre
$check = $this->isTaskStageChangeAllowed(
$task['stage_id'],
$task['required_completion_stage_id'],
ResubmissionTaskData::STATE_OPEN
);
if (!$check) {
$currentStage = $this->resubmissionGateway->getStage($task['stage_id']);
$requiredStage = $this->resubmissionGateway->getStage($task['required_completion_stage_id']);
throw TaskMustBeCompletedException::onChangingStateToOpen(
$requiredStage['shortname'],
$currentStage['shortname']
);
}
$sql = 'UPDATE `aufgabe`
SET `status` = :state, `abgeschlossen_am` = :completion_date, `logdatei` = NOW()
WHERE `id` = :task_id
LIMIT 1';
$this->db->fetchAffected($sql, [
'state' => 'offen',
'completion_date' => '0000-00-00',
'task_id' => (int)$taskId,
]);
}
/**
* @param ResubmissionTaskData $task
*
* @throws ResubmissionTaskNotFoundException
* @throws TaskMustBeCompletedException
* @throws DatabaseExceptionInterface
*
* @return void
*/
public function createTask(ResubmissionTaskData $task)
{
if (!$this->resubmissionGateway->existsResubmission($task->getResubmissionId())) {
throw new ResubmissionNotFoundException(sprintf(
'Resubmission not found. ID: %s', $task->getResubmissionId()
));
}
// Vor der Erstellung prüfen ob Task-Status überhaupt gültig wäre
$currentStage = $this->resubmissionGateway->getStageByResubmission($task->getResubmissionId());
$check = $this->isTaskStageChangeAllowed(
$currentStage['id'],
$task->getRequiredCompletionStageId(),
$task->getState()
);
if (!$check) {
$requiredStage = $this->resubmissionGateway->getStage($task->getRequiredCompletionStageId());
throw TaskMustBeCompletedException::onCreation(
$requiredStage['shortname'],
$currentStage['shortname']
);
}
$priority = $this->translatePriorityToDbValue($task->getPriority());
$state = $this->translateStateToDbValue($task->getState());
$insert = $this->db->insert();
$insert->into('aufgabe');
$insert->col('aufgabe', $task->getTitle());
$insert->col('prio', $priority);
$insert->col('status', $state);
$insert->col('initiator', (int)$task->getCreatorAddressId());
// Optionale Felder
$insert->col('adresse', (int)$task->getEmployeeAddressId());
$insert->col('kunde', (int)$task->getCustomerAddressId());
$insert->col('projekt', (int)$task->getProjectId());
$insert->col('teilprojekt', (int)$task->getSubProjectId());
$insert->col('beschreibung', (string)$task->getDescription());
if ($task->getSubmissionDateTime() !== null) {
$insert->col('abgabe_bis', $task->getSubmissionDateTime()->format('Y-m-d'));
$insert->col('abgabe_bis_zeit', $task->getSubmissionDateTime()->format('H:i:s'));
} else {
$insert->col('abgabe_bis', '0000-00-00');
$insert->col('abgabe_bis_zeit', '00:00:00');
}
$insert->col('angelegt_am', date('Y-m-d'));
$insert->set('logdatei', 'NOW()');
$this->db->beginTransaction();
try {
$this->db->perform($insert->getStatement(), $insert->getBindValues());
$insertId = $this->db->lastInsertId();
$this->db->perform(
'INSERT INTO `wiedervorlage_aufgabe` (`task_id`, `resubmission_id`, `required_completion_stage_id`)
VALUES (:task_id, :resubmission_id, :required_completion_stage_id)',
[
'task_id' => $insertId,
'resubmission_id' => $task->getResubmissionId(),
'required_completion_stage_id' => $task->getRequiredCompletionStageId(),
]
);
$this->db->commit();
//
} catch (DatabaseExceptionInterface $exception) {
$this->db->rollBack();
throw $exception;
}
}
/**
* @param ResubmissionTaskData $task
*
* @throws ResubmissionTaskNotFoundException
* @throws TaskMustBeCompletedException
*
* @return void
*/
public function editTask(ResubmissionTaskData $task)
{
if (!$this->taskGateway->isTaskAssigendToResubmission($task->getId(), $task->getResubmissionId())) {
throw new ResubmissionTaskNotFoundException(sprintf(
'Task not found. Task-ID: %s - Resubmission-ID: %s', $task->getId(), $task->getResubmissionId()
));
}
// Vor der Änderung prüfen ob Änderung überhaupt gültig wäre
$currentStage = $this->resubmissionGateway->getStageByResubmission($task->getResubmissionId());
$check = $this->isTaskStageChangeAllowed(
$currentStage['id'],
$task->getRequiredCompletionStageId(),
$task->getState()
);
if (!$check) {
$requiredStage = $this->resubmissionGateway->getStage($task->getRequiredCompletionStageId());
throw TaskMustBeCompletedException::onModification(
$requiredStage['shortname'],
$currentStage['shortname']
);
}
$priority = $this->translatePriorityToDbValue($task->getPriority());
$state = $this->translateStateToDbValue($task->getState());
$update = $this->db->update();
$update->table('aufgabe');
$update->col('aufgabe', $task->getTitle());
$update->col('prio', $priority);
$update->col('status', $state);
if ($task->getEmployeeAddressId() !== null) {
$update->col('adresse', $task->getEmployeeAddressId());
}
if ($task->getProjectId() !== null) {
$update->col('projekt', $task->getProjectId());
}
if ($task->getSubProjectId() !== null) {
$update->col('teilprojekt', $task->getSubProjectId());
}
if ($task->getDescription() !== null) {
$update->col('beschreibung', $task->getDescription());
} else {
$update->col('beschreibung', '');
}
if ($task->getCustomerAddressId() !== null) {
$update->col('kunde', $task->getCustomerAddressId());
} else {
$update->col('kunde', 0);
}
if ($task->getCompletionDateTime() !== null) {
if ($task->getCompletionDateTime()->getTimestamp() > 0) {
$update->col('abgeschlossen_am', $task->getCompletionDateTime()->format('Y-m-d'));
} else {
$update->col('abgeschlossen_am', '0000-00-00');
}
}
if ($task->getSubmissionDateTime() !== null) {
$update->col('abgabe_bis', $task->getSubmissionDateTime()->format('Y-m-d'));
$update->col('abgabe_bis_zeit', $task->getSubmissionDateTime()->format('H:i:s'));
} else {
$update->col('abgabe_bis', '0000-00-00');
$update->col('abgabe_bis_zeit', '00:00:00');
}
$update->set('logdatei', 'NOW()');
$update->where('id = ?', $task->getId());
$update->limit(1);
try {
$this->db->beginTransaction();
$this->db->perform($update->getStatement(), $update->getBindValues());
$this->db->perform(
'UPDATE `wiedervorlage_aufgabe` SET `required_completion_stage_id` = :required_completion_stage_id
WHERE `task_id` = :task_id AND `resubmission_id` = :resubmission_id LIMIT 1',
[
'task_id' => $task->getId(),
'resubmission_id' => $task->getResubmissionId(),
'required_completion_stage_id' => $task->getRequiredCompletionStageId(),
]
);
$this->db->commit();
//
} catch (DatabaseExceptionInterface $exception) {
$this->db->rollBack();
throw $exception;
}
}
/**
* Aufgabe löschen
*
* @param int $taskId
* @param int $resubmissionId
*
* @throws ResubmissionTaskNotFoundException
*
* @return void
*/
public function deleteTask($taskId, $resubmissionId)
{
$taskId = (int)$taskId;
$resubmissionId = (int)$resubmissionId;
if (!$this->taskGateway->isTaskAssigendToResubmission($taskId, $resubmissionId)) {
throw new ResubmissionTaskNotFoundException(sprintf(
'Task not found. Task-ID: %s - Resubmission-ID: %s', $taskId, $resubmissionId
));
}
$this->db->beginTransaction();
try {
// Aufgabe löschen
$sql = 'DELETE FROM `aufgabe` WHERE `id` = :task_id LIMIT 1 ';
$this->db->perform($sql, ['task_id' => $taskId]);
// Verknüpfung zur Wiedervorlage löschen
$sql =
'DELETE FROM `wiedervorlage_aufgabe`
WHERE `task_id` = :task_id AND `resubmission_id` = :resubmission_id
LIMIT 1 ';
$this->db->perform($sql, [
'resubmission_id' => $resubmissionId,
'task_id' => $taskId,
]);
$this->db->commit();
} catch (DatabaseExceptionInterface $exception) {
$this->db->rollBack();
throw $exception;
}
}
/**
* Ermittelt alle Aufgaben die das Verschieben einer Wiedervorlage blockieren
*
* @param int $resubmissionId
* @param int $targetStageId
*
* @return array Empty array if none item is blocking
*/
public function getBlockingTasksForTargetStage($resubmissionId, $targetStageId)
{
$resubmissionId = (int)$resubmissionId;
$targetStageId = (int)$targetStageId;
$tasks = $this->taskGateway->getTasksByResubmission($resubmissionId);
$blocking = [];
foreach ($tasks as $task) {
if ($task['state'] === ResubmissionTaskData::STATE_COMPLETED) {
continue; // Aufgabe ist bereits abgeschlossen > Aufgabe darf in jede Stage geschoben werden
}
if ($task['required_completion_stage_id'] === 0) {
continue; // Aufgabe hat keine Fertigstellungs-Stage hinterlegt > Aufgabe darf in jede Stage geschoben werden
}
$distance = $this->resubmissionGateway->getDistanceBetweenStages(
$targetStageId,
$task['required_completion_stage_id']
);
if ($distance <= 0) {
$blocking[] = [
'id' => $task['id'],
'title' => $task['title'],
'state' => $task['state'],
];
}
}
return $blocking;
}
/**
* Prüft ob eine Aufgabe in die Target-Stage wecheln darf
*
* @param int $currentStageId ID der Stage von der aus verschoben wird
* @param int $targetStageId ID der Stage in die verschoben werden soll
* @param string $targetState Aufgaben-Status der zugewiesen soll bzw. aktuell gesetzt ist
*
* @throws InvalidArgumentException
*
* @return bool true = Aufgabe darf auf Target-Stage wechseln
* false = Aufgabe muss abgeschlossen sein, um auf die Target-Stage wechseln zu dürfen
*/
private function isTaskStageChangeAllowed($currentStageId, $targetStageId, $targetState)
{
$currentStageId = (int)$currentStageId;
$targetStageId = (int)$targetStageId;
if (!in_array($targetState, ResubmissionTaskData::getValidStates(), true)) {
throw new InvalidArgumentException(sprintf('Target state is invalid: "%s"', $targetState));
}
// Aufgabe auf abgeschlossen stellen => Immer OK; solange die Aufgabe existiert
if ($targetState === ResubmissionTaskData::STATE_COMPLETED) {
return true;
}
// Es ist keine Stage-ID festgelegt bei der die Aufgabe abgeschlossen sein muss
// > Alles Roger, solange die Aufgabe existiert
if ($targetStageId === 0) {
return true;
}
$distance = $this->resubmissionGateway->getDistanceBetweenStages($currentStageId, $targetStageId);
return $distance > 0;
}
/**
* @param string $state
*
* @return string|null
*/
private function translateStateToDbValue($state)
{
$dbValue = null;
switch ($state) {
case ResubmissionTaskData::STATE_COMPLETED:
$dbValue = 'abgeschlossen';
break;
case ResubmissionTaskData::STATE_PROCESSING:
$dbValue = 'inbearbeitung';
break;
case ResubmissionTaskData::STATE_OPEN:
$dbValue = 'offen';
break;
}
return $dbValue;
}
/**
* @param string $priority
*
* @return int|null
*/
private function translatePriorityToDbValue($priority)
{
$dbValue = null;
switch ($priority) {
case ResubmissionTaskData::PRIORITY_HIGH:
$dbValue = 1;
break;
case ResubmissionTaskData::PRIORITY_LOW:
$dbValue = -1;
break;
case ResubmissionTaskData::PRIORITY_MEDIUM:
$dbValue = 0;
break;
}
return $dbValue;
}
}
@@ -0,0 +1,133 @@
<?php
namespace Xentral\Modules\Resubmission\Service;
use Xentral\Components\Database\Database;
use Xentral\Modules\Resubmission\Exception\ResubmissionNotFoundException;
use Xentral\Modules\Resubmission\Exception\TaskTemplateNotFoundException;
final class ResubmissionTaskTemplateGateway
{
/** @var Database $db */
private $db;
/** @var ResubmissionGateway $resubmissionGateway */
private $resubmissionGateway;
/**
* @param Database $database
* @param ResubmissionGateway $resubmissionGateway
*/
public function __construct(Database $database, ResubmissionGateway $resubmissionGateway)
{
$this->db = $database;
$this->resubmissionGateway = $resubmissionGateway;
}
/**
* @param int $taskTemplateId
*
* @return bool
*/
public function existsTaskTemplate($taskTemplateId)
{
$sql =
'SELECT COUNT(wav.id) AS `task_template_count`
FROM `wiedervorlage_aufgabe_vorlage` AS `wav`
WHERE wav.id = :task_template_id';
$taskTemplateCount = (int)$this->db->fetchValue($sql, ['task_template_id' => (int)$taskTemplateId]);
return $taskTemplateCount === 1;
}
/**
* @param int $taskTemplateId
*
* @throws TaskTemplateNotFoundException
*
* @return array
*/
public function getTaskTemplateById($taskTemplateId)
{
$sql =
'SELECT
wav.id,
wav.required_from_stage_id,
wav.add_task_at_stage_id,
CONCAT(adr.mitarbeiternummer, \' \', adr.name) AS `employee`,
wav.project_id,
wav.subproject_id,
wav.title,
wav.submission_date_days,
TIME_FORMAT(wav.submission_time, \'%H:%i\') AS `submission_time`,
wav.state,
wav.priority,
wav.description
FROM `wiedervorlage_aufgabe_vorlage` AS `wav`
LEFT JOIN `adresse` AS `adr` ON wav.employee_address_id = adr.id
WHERE wav.id = :task_template_id';
$result = $this->db->fetchRow($sql, ['task_template_id' => (int)$taskTemplateId]);
if (empty($result)) {
throw new TaskTemplateNotFoundException(sprintf(
'Task template not found: ID %s', $taskTemplateId
));
}
if (empty($result['required_from_stage_id'])) {
$result['required_from_stage_id'] = 0;
}
if (empty($result['add_task_at_stage_id'])) {
$result['add_task_at_stage_id'] = 0;
}
if (empty($result['employee_address_id'])) {
$result['employee_address_id'] = 0;
}
if (empty($result['project_id'])) {
$result['project_id'] = 0;
}
if (empty($result['subproject_id'])) {
$result['subproject_id'] = 0;
}
if (empty($result['submission_time']) || $result['submission_time'] === '00:00') {
$result['submission_time'] = null;
}
return $result;
}
function getTaskTemplatesByStageId($stageId){
$sql =
'SELECT
wav.id,
wav.required_from_stage_id,
wav.add_task_at_stage_id,
IFNULL(wav.employee_address_id, 0) AS employee_address_id,
wav.project_id,
wav.subproject_id,
wav.title,
wav.submission_date_days,
TIME_FORMAT(wav.submission_time, \'%H:%i\') AS `submission_time`,
wav.state,
wav.priority,
wav.description
FROM `wiedervorlage_aufgabe_vorlage` AS `wav`
LEFT JOIN `adresse` AS `adr` ON wav.employee_address_id = adr.id
WHERE wav.add_task_at_stage_id = :stage_id';
$result = $this->db->fetchAll($sql, ['stage_id' => (int)$stageId]);
if (empty($result)) {
throw new TaskTemplateNotFoundException(sprintf(
'Task template with following stage id not found: ID %s', $stageId
));
}
return $result;
}
}
@@ -0,0 +1,173 @@
<?php
namespace Xentral\Modules\Resubmission\Service;
use Xentral\Components\Database\Database;
use Xentral\Modules\Resubmission\Data\TaskTemplateData;
use Xentral\Modules\Resubmission\Exception\TaskTemplateNotFoundException;
use Xentral\Modules\Resubmission\Exception\ValidationFailedException;
final class ResubmissionTaskTemplateService
{
/** @var Database $db */
private $db;
/** @var ResubmissionTaskTemplateGateway $taskTemplateGateway */
private $taskTemplateGateway;
/** @var ResubmissionGateway $resubmissionGateway */
private $resubmissionGateway;
/**
* @param Database $database
* @param ResubmissionTaskTemplateGateway $taskTemplateGateway
* @param ResubmissionGateway $resubmissionGateway
*/
public function __construct(
Database $database,
ResubmissionTaskTemplateGateway $taskTemplateGateway,
ResubmissionGateway $resubmissionGateway
) {
$this->db = $database;
$this->taskTemplateGateway = $taskTemplateGateway;
$this->resubmissionGateway = $resubmissionGateway;
}
/**
* @param TaskTemplateData $config
*
* @throws ValidationFailedException
*
* @return int Inserted id
*/
public function createTaskTemplate(TaskTemplateData $config)
{
if ($config->id !== null) {
$errorMsg = sprintf('The "id" property must be null. Given value: "%s".', $config->id);
throw ValidationFailedException::fromErrors(['id' => [$errorMsg]]);
}
// Prüfen ob add_task_at_stage_id und required_from_stage_id im gleichen View sind
if ($config->addTaskAtStageId > 0 && $config->requiredFromStageId > 0) {
$addTaskAtViewId = $this->resubmissionGateway->getViewIdByStage($config->addTaskAtStageId);
$requiredFromViewId = $this->resubmissionGateway->getViewIdByStage($config->requiredFromStageId);
if ($addTaskAtViewId !== $requiredFromViewId) {
$errorMsg = 'The "add_task_at_stage_id" and the "required_from_stage_id" must be ';
$errorMsg .= 'on the same View.';
throw ValidationFailedException::fromErrors(['add_task_at_stage_id' => [$errorMsg]]);
}
}
$sql = 'INSERT INTO `wiedervorlage_aufgabe_vorlage`
(
`id`, `required_from_stage_id`, `add_task_at_stage_id`, `employee_address_id`,
`project_id`, `subproject_id`, `title`, `submission_date_days`, `submission_time`,
`state`, `priority`, `description`
) VALUES (
NULL, :required_from_stage_id, :add_task_at_stage_id, :employee_address_id,
:project_id, :subproject_id, :title, :submission_date_days, :submission_time,
:state, :priority, :description
)';
$bindValues = [
'required_from_stage_id' => $config->requiredFromStageId,
'add_task_at_stage_id' => $config->addTaskAtStageId,
'employee_address_id' => $config->employeeAddressId,
'project_id' => $config->projectId,
'subproject_id' => $config->subprojectId,
'title' => $config->title,
'submission_date_days' => $config->submissionDateDays,
'submission_time' => $config->submissionTime,
'state' => $config->state,
'priority' => $config->priority,
'description' => $config->description
];
$this->db->perform($sql, $bindValues);
$taskTemplateId = $this->db->lastInsertId();
return $taskTemplateId;
}
/**
* @param TaskTemplateData $config
*
* @throws TaskTemplateNotFoundException
* @throws ValidationFailedException
*
* @return void
*/
public function modifyTaskTemplate(TaskTemplateData $config)
{
if (!$this->taskTemplateGateway->existsTaskTemplate($config->id)) {
throw new TaskTemplateNotFoundException(sprintf(
'Task template not found: ID%s', $config->id
));
}
// Prüfen ob add_task_at_stage_id und required_from_stage_id im gleichen View sind
if ($config->addTaskAtStageId > 0 && $config->requiredFromStageId > 0) {
$addTaskAtViewId = $this->resubmissionGateway->getViewIdByStage($config->addTaskAtStageId);
$requiredFromViewId = $this->resubmissionGateway->getViewIdByStage($config->requiredFromStageId);
if ($addTaskAtViewId !== $requiredFromViewId) {
$errorMsg = 'The "add_task_at_stage_id" and the "required_from_stage_id" must be ';
$errorMsg .= 'on the same View.';
throw ValidationFailedException::fromErrors(['add_task_at_stage_id' => [$errorMsg]]);
}
}
$sql = 'UPDATE `wiedervorlage_aufgabe_vorlage`
SET
`required_from_stage_id` = :required_from_stage_id,
`add_task_at_stage_id` = :add_task_at_stage_id,
`employee_address_id` = :employee_address_id,
`project_id` = :project_id,
`subproject_id` = :subproject_id,
`title` = :title,
`submission_date_days` = :submission_date_days,
`submission_time` = :submission_time,
`state` = :state,
`priority` = :priority,
`description` = :description
WHERE `id` = :id
LIMIT 1';
$bindValues = [
'id' => $config->id,
'required_from_stage_id' => $config->requiredFromStageId,
'add_task_at_stage_id' => $config->addTaskAtStageId,
'employee_address_id' => $config->employeeAddressId,
'project_id' => $config->projectId,
'subproject_id' => $config->subprojectId,
'title' => $config->title,
'submission_date_days' => $config->submissionDateDays,
'submission_time' => $config->submissionTime,
'state' => $config->state,
'priority' => $config->priority,
'description' => $config->description
];
$this->db->perform($sql, $bindValues);
}
/**
* @param int $taskTemplateId
*
* @throws TaskTemplateNotFoundException
*
* @return void
*/
public function deleteTaskTemplateById($taskTemplateId)
{
if (!$this->taskTemplateGateway->existsTaskTemplate($taskTemplateId)) {
throw new TaskTemplateNotFoundException(sprintf(
'Task template not found: ID%s', $taskTemplateId
));
}
$sql = 'DELETE FROM `wiedervorlage_aufgabe_vorlage` WHERE `id` = :id LIMIT 1';
$bindValues = ['id' => (int)$taskTemplateId];
$this->db->perform($sql, $bindValues);
}
}
@@ -0,0 +1,354 @@
<?php
namespace Xentral\Modules\Resubmission\Service;
use Xentral\Components\Database\Database;
use Xentral\Modules\Resubmission\Exception\ResubmissionNotFoundException;
use Xentral\Modules\Resubmission\Exception\TextFieldConfigNotFoundException;
final class ResubmissionTextFieldGateway
{
/** @var Database $db */
private $db;
/** @var ResubmissionGateway $resubmissionGateway */
private $resubmissionGateway;
/**
* @param Database $database
* @param ResubmissionGateway $resubmissionGateway
*/
public function __construct(Database $database, ResubmissionGateway $resubmissionGateway)
{
$this->db = $database;
$this->resubmissionGateway = $resubmissionGateway;
}
/**
* @param int $configId
*
* @return bool
*/
public function existsConfig($configId)
{
$sql =
'SELECT COUNT(wfk.id) AS `config_count`
FROM `wiedervorlage_freifeld_konfiguration` AS `wfk`
WHERE wfk.id = :config_id';
$configCount = (int)$this->db->fetchValue($sql, ['config_id' => (int)$configId]);
return $configCount === 1;
}
/**
* @param int $configId
*
* @throws TextFieldConfigNotFoundException
*
* @return array
*/
public function getConfigById($configId)
{
$sql =
'SELECT
wfk.id,
wfk.title,
wfk.available_from_stage_id,
wfk.required_from_stage_id,
wfk.show_in_pipeline,
wfk.show_in_tables
FROM `wiedervorlage_freifeld_konfiguration` AS `wfk`
WHERE wfk.id = :config_id';
$result = $this->db->fetchRow($sql, ['config_id' => (int)$configId]);
if (empty($result)) {
throw new TextFieldConfigNotFoundException(sprintf(
'Text field config not found: ID%s', $configId
));
}
if (empty($result['available_from_stage_id'])) {
$result['available_from_stage_id'] = 0;
}
if (empty($result['required_from_stage_id'])) {
$result['required_from_stage_id'] = 0;
}
$result['show_in_pipeline'] = (int)$result['show_in_pipeline'] === 1;
$result['show_in_tables'] = (int)$result['show_in_tables'] === 1;
return $result;
}
/**
* @example Rückgabe: ['freifeld1' => 'Mein Freifeld 1', 'freifeld2' => 'Mein Freifeld 2']
*
* @param int $viewId
*
* @return array
*/
public function getTextFieldsForTableView($viewId)
{
$stages = $this->resubmissionGateway->getStagesByView($viewId);
$stageIds = array_column($stages, 'id');
if (empty($stageIds)) {
return [];
}
$sql =
'SELECT CONCAT(\'freifeld\', wfk.id) AS name, wfk.title
FROM `wiedervorlage_freifeld_konfiguration` AS `wfk`
WHERE wfk.show_in_tables = 1
AND (
wfk.available_from_stage_id = 0
OR wfk.available_from_stage_id IN (:stages_ids)
OR wfk.required_from_stage_id IN (:stages_ids)
)';
return $this->db->fetchPairs($sql, ['stages_ids' => $stageIds]);
}
/**
* @example Rückgabe: ['freifeld1' => 'Mein Freifeld 1', 'freifeld2' => 'Mein Freifeld 2']
*
* @param int $viewId
*
* @return array
*/
public function getTextFieldsForPipelineView($viewId)
{
$stages = $this->resubmissionGateway->getStagesByView($viewId);
$stageIds = array_column($stages, 'id');
if (empty($stageIds)) {
return [];
}
$sql =
'SELECT CONCAT(\'freifeld\', wfk.id) AS name, wfk.title
FROM `wiedervorlage_freifeld_konfiguration` AS `wfk`
WHERE wfk.show_in_pipeline = 1
AND (
wfk.available_from_stage_id = 0
OR wfk.available_from_stage_id IN (:stages_ids)
OR wfk.required_from_stage_id IN (:stages_ids)
)';
return $this->db->fetchPairs($sql, ['stages_ids' => $stageIds]);
}
/**
* Lädt alle Freitextfelder die in der übergebenen Stage ein Pflichtfeld sind
*
* @param int $stageId
*
* @return array
*/
public function getRequiredTextFieldsForStage($stageId)
{
// Schritt 1
// Alle Stage-IDs ermittlen für die wir die Freitextfelder laden sollen
// d.h. alle Stages die in der Reihenfolge vor der aktuellen Stage kommen; inklusive der aktuellen Stage
$validStages = $this->getStagesUntilStageId($stageId);
$validStageIds = array_column($validStages, 'id');
// Schritt 2
// Alle Pflicht-Freitextfelder für die Stages aus Schritt 1 ermitteln
$sql =
'SELECT
wfk.id AS `config_id`,
wfk.title AS `label`,
wfk.available_from_stage_id,
wfk.required_from_stage_id,
ws.name AS `required_from_stage_name`,
wfk.show_in_pipeline,
wfk.show_in_tables
FROM `wiedervorlage_freifeld_konfiguration` AS `wfk`
LEFT JOIN `wiedervorlage_stages` AS `ws` ON wfk.required_from_stage_id = ws.id
WHERE wfk.required_from_stage_id IN (:valid_stage_ids)';
return $this->db->fetchAll($sql, [
'valid_stage_ids' => $validStageIds,
]);
}
/**
* Lädt alle Stages die in der Reihenfolge vor der übergebenen Stage liegen
*
* @refactor ResubmissionGateway
*
* @param int $stageId
*
* @return array
*/
private function getStagesUntilStageId($stageId)
{
// Alle Stages in der gleichen View ermitteln
$stageId = (int)$stageId;
$stages = $this->resubmissionGateway->getSiblingStages($stageId);
// Rang der übergebenen Stage ermitteln
$currentRank = 0;
foreach ($stages as $stage) {
if ($stage['id'] === $stageId) {
$currentRank = $stage['rank'];
}
}
// Alle Stages filtern die VOR der übergebenen Stage liegen (inklusive der übergebenen Stage)
$validStages = [];
foreach ($stages as $stage) {
if ($stage['rank'] <= $currentRank) {
$validStages[] = $stage;
}
}
return $validStages;
}
/**
* Ermittelt alle Freifelder die das Verschieben einer Wiedervorlage blockieren
*
* @param int $resubmissionId
* @param int $targetStageId
*
* @return array Empty array if none item is blocking
*/
public function getBlockingTextFieldsForTargetStage($resubmissionId, $targetStageId)
{
$resubmissionId = (int)$resubmissionId;
$targetStageId = (int)$targetStageId;
$textfields = $this->getTextFieldsForResubmission($resubmissionId);
$blocking = [];
foreach ($textfields as $textfield) {
if (!empty($textfield['content'])) {
continue; // Textfeld ist ausgefüllt > Textfeld darf in jede Stage geschoben werden
}
if ((int)$textfield['required_from_stage_id'] === 0) {
continue; // Textfeld hat keine Fertigstellungs-Stage hinterlegt > Textfeld darf in jede Stage geschoben werden
}
$distance = $this->resubmissionGateway->getDistanceBetweenStages(
$targetStageId, $textfield['required_from_stage_id']
);
if ($distance <= 0) {
$blocking[] = [
'config_id' => $textfield['config_id'],
'content_id' => $textfield['content_id'],
'label' => $textfield['label'],
];
}
}
return $blocking;
}
/**
* Lädt alle Freitextfelder für eine Wiedervorlage
*
* @param int $resubmissionId
*
* @throws ResubmissionNotFoundException
*
* @return array
*/
public function getTextFieldsForResubmission($resubmissionId)
{
$stage = $this->resubmissionGateway->getStageByResubmission($resubmissionId);
$viewId = (int)$stage['view_id']; // View-ID `0` ist zulässig
// 1. Alle Stage-IDs ermittlen für die wir die Freitextfelder laden sollen
// d.h. alle Stages die in der Reihenfolge vor der aktuellen Stage kommen; inklusive der aktuellen Stage
$validStages = $this->getStagesUntilStageId($stage['id']);
$validStageIds = array_column($validStages, 'id');
// 2. Alle Freitextfelder für die Stages aus Schritt 1 ermitteln
$sql =
'SELECT
wfk.id AS `config_id`,
wfi.id AS `content_id`,
wfk.title AS `label`,
wfk.available_from_stage_id,
wfk.required_from_stage_id,
wfk.show_in_pipeline,
wfk.show_in_tables
FROM `wiedervorlage_freifeld_konfiguration` AS `wfk`
LEFT JOIN `wiedervorlage_freifeld_inhalt` AS `wfi`
ON wfi.resubmission_id = :resubmission_id
LEFT JOIN `wiedervorlage_stages` AS ws1
ON wfk.required_from_stage_id = ws1.id
LEFT JOIN `wiedervorlage_stages` AS ws2
ON wfk.available_from_stage_id = ws2.id
WHERE wfk.available_from_stage_id = 0
OR wfk.available_from_stage_id IN (:valid_stage_ids)
OR wfk.required_from_stage_id IN (:valid_stage_ids)
ORDER BY wfk.required_from_stage_id != 0 DESC, ws1.sort, ws2.sort, wfk.title';
// Erklärung Sortierung:
// 1. `wfk.required_from_stage_id != 0 DESC` = Felder mit Pflicht-Stage oben anzeigen
// 2. `ws1.sort, ws2.sort` = Felder nach Reihenfolge der Stages anzeigen
$textFields = $this->db->fetchAll($sql, [
'resubmission_id' => $resubmissionId,
'valid_stage_ids' => $validStageIds,
'view_id' => $viewId,
]);
// Freitextfeld-Inhalte ergänzen
$contents = $this->getTextFieldContentsForResubmission($resubmissionId);
foreach ($textFields as &$textField) {
$configId = (int)$textField['config_id'];
$textField['content'] = $contents[$configId];
}
unset($textField);
return $textFields;
}
/**
* @param int $resubmissionId
*
* @return array
*/
private function getTextFieldContentsForResubmission($resubmissionId)
{
$columnNames = $this->getTextFieldColumnNames();
$contentsAll = $this->db->fetchRow(
'SELECT wfi.* FROM `wiedervorlage_freifeld_inhalt` AS `wfi`
WHERE wfi.resubmission_id = :resubmission_id LIMIT 1',
['resubmission_id' => (int)$resubmissionId]
);
$contents = [];
foreach ($columnNames as $configId => $columnName) {
if (isset($contentsAll[$columnName])) {
$contents[$configId] = $contentsAll[$columnName];
} else {
$contents[$configId] = null;
}
}
return $contents;
}
/**
* @param string|null $tableAlias
*
* @return array
*/
private function getTextFieldColumnNames($tableAlias = null)
{
$sql = 'SELECT wfk.id FROM `wiedervorlage_freifeld_konfiguration` AS `wfk` WHERE 1';
$configIds = $this->db->fetchCol($sql);
$columnNames = [];
$columnPrefix = $tableAlias !== null ? sprintf('%s.', $tableAlias) : '';
foreach ($configIds as $configId) {
$columnNames[$configId] = sprintf('%sfreifeld%s', $columnPrefix, $configId);
}
return $columnNames;
}
}
@@ -0,0 +1,315 @@
<?php
namespace Xentral\Modules\Resubmission\Service;
use Xentral\Components\Database\Database;
use Xentral\Modules\Resubmission\Data\FreeTextFieldConfigData;
use Xentral\Modules\Resubmission\Data\FreeTextFieldContentData;
use Xentral\Modules\Resubmission\Exception\TextFieldConfigNotFoundException;
use Xentral\Modules\Resubmission\Exception\TextFieldRequiredException;
use Xentral\Modules\Resubmission\Exception\ValidationFailedException;
use Xentral\Modules\Resubmission\Exception\ResubmissionNotFoundException;
final class ResubmissionTextFieldService
{
/** @var Database $db */
private $db;
/** @var ResubmissionTextFieldGateway $textFieldGateway */
private $textFieldGateway;
/** @var ResubmissionGateway $resubmissionGateway */
private $resubmissionGateway;
/**
* @param Database $database
* @param ResubmissionTextFieldGateway $textFieldGateway
* @param ResubmissionGateway $resubmissionGateway
*/
public function __construct(
Database $database,
ResubmissionTextFieldGateway $textFieldGateway,
ResubmissionGateway $resubmissionGateway
) {
$this->db = $database;
$this->textFieldGateway = $textFieldGateway;
$this->resubmissionGateway = $resubmissionGateway;
}
/**
* @param FreeTextFieldConfigData $config
*
* @throws ValidationFailedException
*
* @return int Inserted id
*/
public function createConfig(FreeTextFieldConfigData $config)
{
if ($config->id !== null) {
$errorMsg = sprintf('The "id" property must be null. Given value: "%s".', $config->id);
throw ValidationFailedException::fromErrors(['id' => [$errorMsg]]);
}
// @todo 1. Prüfen ob available_from_stage_id vor required_from_stage_id kommt;
// @todo Nur wenn available_from_stage_id > 0 UND required_from_stage_id > 0
// Prüfen ob available_from_stage_id und required_from_stage_id im gleichen View sind
if ($config->availableFromStageId > 0 && $config->requiredFromStageId > 0) {
$availableFromViewId = $this->resubmissionGateway->getViewIdByStage($config->availableFromStageId);
$requiredFromViewId = $this->resubmissionGateway->getViewIdByStage($config->requiredFromStageId);
if ($availableFromViewId !== $requiredFromViewId) {
$errorMsg = 'The "available_from_stage_id" and the "required_from_stage_id" must be ';
$errorMsg .= 'on the same View.';
throw ValidationFailedException::fromErrors(['available_from_stage_id' => [$errorMsg]]);
}
}
$sql = 'INSERT INTO `wiedervorlage_freifeld_konfiguration`
(
`id`, `title`, `show_in_pipeline`, `show_in_tables`,
`available_from_stage_id`, `required_from_stage_id`, `created_at`, `updated_at`
) VALUES (
NULL, :title, :show_in_pipeline, :show_in_tables,
:available_from_stage_id, :required_from_stage_id, NOW(), NULL
)';
$bindValues = [
'title' => $config->title,
'show_in_pipeline' => $config->showInPipeline === true ? 1 : 0,
'show_in_tables' => $config->showInTables === true ? 1 : 0,
'available_from_stage_id' => $config->availableFromStageId,
'required_from_stage_id' => $config->requiredFromStageId,
];
$this->db->perform($sql, $bindValues);
$configId = $this->db->lastInsertId();
// Sicherstellen dass Freitext-Spalte existiert
$this->checkCreateContentColumn($configId);
return $configId;
}
/**
* @param FreeTextFieldConfigData $config
*
* @throws TextFieldConfigNotFoundException
* @throws ValidationFailedException
*
* @return void
*/
public function modifyConfig(FreeTextFieldConfigData $config)
{
if (!$this->textFieldGateway->existsConfig($config->id)) {
throw new TextFieldConfigNotFoundException(sprintf(
'Text field config not found: ID%s', $config->id
));
}
// @todo Prüfen ob available_from_stage_id vor required_from_stage_id kommt;
// @todo Nur wenn available_from_stage_id > 0 UND required_from_stage_id > 0
// Prüfen ob available_from_stage_id und required_from_stage_id im gleichen View sind
if ($config->availableFromStageId > 0 && $config->requiredFromStageId > 0) {
$availableFromViewId = $this->resubmissionGateway->getViewIdByStage($config->availableFromStageId);
$requiredFromViewId = $this->resubmissionGateway->getViewIdByStage($config->requiredFromStageId);
if ($availableFromViewId !== $requiredFromViewId) {
$errorMsg = 'The "available_from_stage_id" and the "required_from_stage_id" must be ';
$errorMsg .= 'on the same View.';
throw ValidationFailedException::fromErrors(['available_from_stage_id' => [$errorMsg]]);
}
}
// Sicherstellen dass Freitext-Spalte existiert
$this->checkCreateContentColumn($config->id);
$sql = 'UPDATE `wiedervorlage_freifeld_konfiguration`
SET
`title` = :title,
`show_in_pipeline` = :show_in_pipeline,
`show_in_tables` = :show_in_tables,
`available_from_stage_id` = :available_from_stage_id,
`required_from_stage_id` = :required_from_stage_id,
`updated_at` = NOW()
WHERE `id` = :id
LIMIT 1';
$bindValues = [
'id' => $config->id,
'title' => $config->title,
'show_in_pipeline' => $config->showInPipeline === true ? 1 : 0,
'show_in_tables' => $config->showInTables === true ? 1 : 0,
'available_from_stage_id' => $config->availableFromStageId,
'required_from_stage_id' => $config->requiredFromStageId,
];
$this->db->perform($sql, $bindValues);
}
/**
* @param int $configId
*
* @throws TextFieldConfigNotFoundException
*
* @return void
*/
public function deleteConfigById($configId)
{
if (!$this->textFieldGateway->existsConfig($configId)) {
throw new TextFieldConfigNotFoundException(sprintf(
'Text field config not found: ID%s', $configId
));
}
$sql = 'DELETE FROM `wiedervorlage_freifeld_konfiguration` WHERE `id` = :id LIMIT 1';
$bindValues = ['id' => (int)$configId];
// Inhalts-Tabelle `wiedervorlage_freifeld_inhalt` nicht anpassen, sonst Datenverlust
$this->db->perform($sql, $bindValues);
}
/**
* Alle Freifeld-Inhalte für eine Wiedervorlage speichern
*
* WICHTIG: Es müssen alle Pflicht-Freitexte mitgeschickt werden
* Optionale Felder die nicht mitgeschickt werden, werden nicht verändert.
*
* @example $contents = [123 => 'Inhalt für das Freifeld mit der Freifeld-Config-ID 123']
*
* @param int $resubmissionId
* @param array $contents
*
* @throws ResubmissionNotFoundException
* @throws TextFieldRequiredException
*
* @return void
*/
public function saveAllFieldContents($resubmissionId, array $contents)
{
if (!$this->resubmissionGateway->existsResubmission($resubmissionId)) {
throw new ResubmissionNotFoundException(sprintf('Resubmission not found: ID%s', $resubmissionId));
}
$stage = $this->resubmissionGateway->getStageByResubmission($resubmissionId);
$requiredFields = $this->textFieldGateway->getRequiredTextFieldsForStage($stage['id']);
// Prüfen ob Pflichtfeld leer
foreach ($requiredFields as $requiredField) {
$configId = (int)$requiredField['config_id'];
if (empty($contents[$configId])) {
throw TextFieldRequiredException::onEmpty(
$requiredField['label'],
$requiredField['required_from_stage_name']
);
}
}
foreach ($contents as $configId => $content) {
$textfield = new FreeTextFieldContentData();
$textfield->resubmissionId = (int)$resubmissionId;
$textfield->configId = (int)$configId;
$textfield->content = (string)$content;
$this->updateFieldContent($textfield);
}
}
/**
* Vorhandenen Freifeld-Inhalt bearbeiten
*
* @param FreeTextFieldContentData $textfield
*
* @throws ValidationFailedException
*
* @return void
*/
private function updateFieldContent(FreeTextFieldContentData $textfield)
{
$errors = $textfield->validate();
if (!empty($errors)) {
throw ValidationFailedException::fromErrors($errors);
}
// Sicherstellen dass Freitext-Zeile für Wiedervorlage existiert
$contentId = $this->getCreateContentRowId($textfield->resubmissionId);
$columnName = sprintf('freifeld%s', (int)$textfield->configId);
$sql = sprintf(
'UPDATE `wiedervorlage_freifeld_inhalt`
SET %s = :content
WHERE `id` = :content_id AND `resubmission_id` = :resubmission_id
LIMIT 1',
$this->db->escapeIdentifier($columnName)
);
$bindValues = [
'content_id' => $contentId,
'resubmission_id' => $textfield->resubmissionId,
'content' => !empty($textfield->content) ? $textfield->content : null,
];
$this->db->perform($sql, $bindValues);
}
/**
* Holt die ID einer Freifeld-Zeile; Zeile wird angelegt wenn nicht vorhanden
*
* @param int $resubmissionId
*
* @return int Primary ID aus Freifeld-Inhalts-Tabelle
*/
private function getCreateContentRowId($resubmissionId)
{
$sql = 'SELECT wfi.id FROM `wiedervorlage_freifeld_inhalt` AS `wfi` WHERE resubmission_id = :resubmission_id';
$contentId = (int)$this->db->fetchValue($sql, ['resubmission_id' => (int)$resubmissionId]);
if ($contentId === 0) {
$this->db->perform(
'INSERT INTO `wiedervorlage_freifeld_inhalt` (`id`, `resubmission_id`) VALUES (NULL, :resubmission_id)',
['resubmission_id' => (int)$resubmissionId]
);
$contentId = (int)$this->db->lastInsertId();
}
return $contentId;
}
/**
* Stellt sicher dass eine Freifeld-Spalte für eine Config-ID existiert
*
* @param int $configId
*
* @return void
*/
private function checkCreateContentColumn($configId)
{
if (!$this->textFieldGateway->existsConfig($configId)) {
return;
}
if ($this->existsContentColumn($configId)) {
return;
}
$this->db->exec(sprintf(
'ALTER TABLE `wiedervorlage_freifeld_inhalt`
ADD `freifeld%s` VARCHAR(255) NULL DEFAULT NULL; ',
(int)$configId
));
}
/**
* Prüft ob die Freifeld-Spalte für eine Config-ID existiert
*
* @param int $configId
*
* @return bool
*/
private function existsContentColumn($configId)
{
$columnName = 'freifeld' . (int)$configId;
$exists = $this->db->fetchAll(sprintf(
'SHOW COLUMNS FROM `wiedervorlage_freifeld_inhalt` LIKE %s;',
$this->db->escapeString($columnName)
));
return count($exists) === 1;
}
}
@@ -0,0 +1,118 @@
/**
* Modal zur Anzeige von blockierenden Aufgabe/Freifeldern
*
* Modal wird angezeigt wenn beim Verschieben von Wiedervorlagen (auf eine andere Stage)
* die zugeodneten Aufgaben oder Freifelder nicht die Anforderungen erfüllen.
*/
var ResubmissionBlockingItemsModal = (function ($) {
"use strict";
var me = {
storage: {
$modal: null,
data: null,
displayEditButton: true
},
/**
* @param {Object} data
* @param {Boolean} displayEditButton "Wiedervorlage bearbeiten"-Button in Modal anzeigen?
*/
show: function (data, displayEditButton) {
if (typeof displayEditButton === 'boolean') {
me.storage.displayEditButton = displayEditButton;
}
me.storage.data = data;
me.storage.$modal = me.createModal();
me.storage.$modal.dialog('open');
},
/**
*/
hide: function () {
if (me.storage.$modal === null) {
return;
}
me.storage.$modal.dialog('close');
},
/**
* @return {jQuery}
*/
createModal: function () {
var $prevModal = $('#resubmissiontask-blocking-items-modal');
if ($prevModal.length > 0) {
$prevModal.remove();
}
var data = me.storage.data;
var $modal = $('<div>').attr('id', 'resubmissiontask-blocking-items-modal').appendTo('body').hide();
var content = '';
if (data.blocking.type === 'change-stage') {
content = '<p>Die Wiedervorlage &quot;' + data.resubmission.title + '&quot; kann nicht ';
content += 'in die Stage &quot;' + data.stage.title + '&quot; verschoben werden, weil ';
content += 'folgende Element blockieren:</p>';
}
if (data.blocking.type === 'create-resubmission') {
content = '<p>Die Wiedervorlage kann nicht in der Stage &quot;' + data.stage.title + '&quot; ';
content += 'angelegt werden, weil folgende Element blockieren:</p>';
}
if (data.blocking.type === 'update-resubmission') {
content = '<p>Die Wiedervorlage kann nicht in der Stage &quot;' + data.stage.title + '&quot; ';
content += 'gespeichert werden, weil folgende Element blockieren:</p>';
}
content += '<ul>';
$.each(data.blocking.tasks, function (index, task) {
content += '<li><p>Aufgabe &quot;' + task.title + '&quot;<br><strong>nicht abgeschlossen</strong></p></li>';
});
$.each(data.blocking.textfields, function (index, textfield) {
content += '<li><p>Freitextfeld &quot;' + textfield.label + '&quot;<br><strong>ist leer</strong></p></li>';
});
content += '</ul>';
$modal.html(content);
var modalTitle = data.blocking.hasOwnProperty('title') ? data.blocking.title : 'Speichern nicht möglich';
var modalButtons = [{
text: 'OK',
click: function () {
$modal.dialog('close');
}
}];
if (
me.storage.displayEditButton === true &&
me.storage.data.resubmission.id > 0 // Neue Wiedervorlage
) {
modalButtons.unshift({
text: 'Wiedervorlage bearbeiten',
click: function () {
EditWiedervorlage(me.storage.data.resubmission.id);
$modal.dialog('close');
}
});
}
$modal.dialog({
modal: true,
bgiframe: true,
minWidth: 420,
autoOpen: false,
closeOnEscape: false,
title: modalTitle,
buttons: modalButtons
});
return $modal;
}
};
return {
show: me.show,
hide: me.hide
};
})(jQuery);
@@ -0,0 +1,500 @@
/**
* Modul zur Bedienung der Wiedervorlagen-Aufgaben
*/
var ResubmissionTasksUi = (function ($) {
var me = {
storage: {
dataTableName: 'resubmission_tasks',
resubmissionId: null,
$editDialog: null
},
/**
* @param {number} resubmissionId Wiedervorlagen-ID
*/
init: function (resubmissionId) {
me.storage.resubmissionId = parseInt(resubmissionId, 10);
if (isNaN(me.storage.resubmissionId) || me.storage.resubmissionId <= 0) {
throw 'Could not initialize ResubmissionTasksUi. Required parameter is missing: resubmissionId';
}
me.storage.$editDialog = $('#editResubmissionTask');
if (me.storage.$editDialog.length === 0) {
throw 'Could not initialize ResubmissionTasksUi. Required elements are missing: #editResubmissionTask';
}
me.registerEvents();
me.initEditDialog();
me.loadDataTable();
},
/**
* @return {void}
*/
registerEvents: function () {
// Aufgabe anlegen
var $taskCreateButton = $('#resubmissiontask-create');
$taskCreateButton.off('click');
$taskCreateButton.on('click', function (e) {
e.preventDefault();
me.createTask();
});
// Aufgabe bearbeiten
$(document).off('click', '.resubmissiontask-edit-button');
$(document).on('click', '.resubmissiontask-edit-button', function (e) {
e.preventDefault();
var taskId = $(this).data('taskId');
me.editTask(taskId);
});
// Aufgabe löschen
$(document).off('click', '.resubmissiontask-delete-button');
$(document).on('click', '.resubmissiontask-delete-button', function (e) {
e.preventDefault();
var taskId = $(this).data('taskId');
me.deleteTask(taskId);
});
// Aufgaben-Status ändern
$(document).off('click', '.resubmissiontask-state-button');
$(document).on('click', '.resubmissiontask-state-button', function (e) {
e.preventDefault();
var taskId = $(this).data('taskId');
var taskState = $(this).data('taskState');
if (taskState === 'open') {
me.setTaskState(taskId, 'completed');
}
if (taskState === 'completed') {
me.setTaskState(taskId, 'open');
}
});
},
/**
* @return {void}
*/
destroy: function () {
me.destroyDataTable();
if (me.storage.$editDialog !== null) {
me.storage.$editDialog.dialog('destroy');
}
me.storage.$editDialog = null;
me.storage.resubmissionId = null;
},
/**
* Öffnet den Dialog zum Anlegen einer Aufgabe
*
* @return {void}
*/
createTask: function () {
me.resetEditDialog();
$.ajax({
url: 'index.php?module=wiedervorlage&action=edit&cmd=taskstageslist',
data: {
resubmission_id: me.storage.resubmissionId
},
method: 'post',
dataType: 'json',
success: function (data) {
// Stages-Dropdown füllen
var $stageSelect = $('#resubmissiontask-requiredcompletionstage').html('');
if (data.hasOwnProperty('stages')) {
$('<option>').val(0).html('- Nie -').appendTo($stageSelect);
$.each(data.stages, function (index, stage) {
var stageName = stage.shortname !== '' ? stage.shortname : stage.longname;
$('<option>').val(stage.id).html(stageName).appendTo($stageSelect);
});
}
$stageSelect.val(0);
me.storage.$editDialog.find('#resubmissiontask-id').val('-1');
me.openEditDialog();
},
error: function (jqXhr) {
alert('Fehler: ' + jqXhr.responseJSON.error);
}
});
},
/**
* Öffnet den Dialog zum Bearbeiten einer Aufgabe
*
* @param {number} taskId
*
* @return {void}
*/
editTask: function (taskId) {
$.ajax({
url: 'index.php?module=wiedervorlage&action=edit&cmd=taskget',
data: {
resubmission_id: me.storage.resubmissionId,
task_id: taskId
},
method: 'post',
dataType: 'json',
success: function (data) {
$('#resubmissiontask-id').val(data.id);
$('#resubmissiontask-title').val(data.title);
$('#resubmissiontask-state').val(data.state);
$('#resubmissiontask-priority').val(data.priority);
$('#resubmissiontask-employee').val(data.employee_name);
$('#resubmissiontask-customer').val(data.customer);
$('#resubmissiontask-submissiondate').val(data.submission_date);
$('#resubmissiontask-submissiontime').val(data.submission_time);
$('#resubmissiontask-project').val(data.project_name);
$('#resubmissiontask-subproject').val(data.subproject_name);
if (CKEDITOR.instances.hasOwnProperty('resubmissiontaskdescription')
&& CKEDITOR.instances.resubmissiontaskdescription) {
CKEDITOR.instances.resubmissiontaskdescription.setData(data.description);
} else {
$('textarea#resubmissiontaskdescription').val(data.description);
}
// Stages-Dropdown füllen
var $stageSelect = $('#resubmissiontask-requiredcompletionstage').html('');
if (data.hasOwnProperty('stages')) {
$('<option>').val(0).html('- Nie -').appendTo($stageSelect);
$.each(data.stages, function (index, stage) {
$('<option>').val(stage.id).html(stage.shortname).appendTo($stageSelect);
});
}
$stageSelect.val(data.required_completion_stage_id);
me.openEditDialog();
},
error: function (jqXhr) {
alert('Fehler: ' + jqXhr.responseJSON.error);
}
});
},
/**
* Speichert das geöffnete Aufgaben-Modal;
*
* Wird verwendet für "Aufgabe bearbeiten" und "Aufgabe anlegen"
*
* @return {void}
*/
saveTask: function () {
var $description = $('textarea#resubmissiontaskdescription');
var descriptionText = $description.ckeditor().editor.getData();
var formData = {
resubmission_id: me.storage.resubmissionId,
task_id: $('#resubmissiontask-id').val(),
project: $('#resubmissiontask-project').val(),
subproject: $('#resubmissiontask-subproject').val(),
title: $('#resubmissiontask-title').val(),
state: $('#resubmissiontask-state').val(),
priority: $('#resubmissiontask-priority').val(),
employee: $('#resubmissiontask-employee').val(),
customer: $('#resubmissiontask-customer').val(),
submission_date: $('#resubmissiontask-submissiondate').val(),
submission_time: $('#resubmissiontask-submissiontime').val(),
required_completion_stage_id: $('#resubmissiontask-requiredcompletionstage').val(),
description: descriptionText
};
$.ajax({
url: 'index.php?module=wiedervorlage&action=edit&cmd=tasksave',
data: formData,
method: 'post',
dataType: 'json',
beforeSend: function () {
App.loading.open();
},
success: function (data) {
if (data.success === true) {
me.resetEditDialog();
me.reloadDataTable();
me.closeEditDialog();
}
if (data.success === false) {
alert(data.error);
}
},
error: function (jqXhr) {
alert('Fehler: ' + jqXhr.responseJSON.error);
},
complete: function () {
App.loading.close();
}
});
},
/**
* Löscht eine Aufgabe; Löschen muss vorher bestätigt werden
*
* @param {number} taskId
*
* @return {void}
*/
deleteTask: function (taskId) {
var confirmation = confirm('Möchten Sie die Aufgabe wirklich löschen?');
if (!confirmation) {
return;
}
$.ajax({
url: 'index.php?module=wiedervorlage&action=edit&cmd=taskdelete',
data: {
task_id: taskId,
resubmission_id: me.storage.resubmissionId
},
method: 'post',
dataType: 'json',
success: function () {
me.reloadDataTable();
},
error: function (jqXhr) {
alert('Fehler: ' + jqXhr.responseJSON.error);
}
});
},
/**
* Status der Aufgabe ändern
*
* @param {number} taskId
* @param {string} taskState [open|completed]
*/
setTaskState: function (taskId, taskState) {
if (taskState !== 'open' && taskState !== 'completed') {
return;
}
$.ajax({
url: 'index.php?module=wiedervorlage&action=edit&cmd=taskstatechange',
data: {
task_id: taskId,
task_state: taskState,
resubmission_id: me.storage.resubmissionId
},
method: 'post',
dataType: 'json',
success: function () {
me.reloadDataTable();
},
error: function (jqXhr) {
alert('Fehler: ' + jqXhr.responseJSON.error);
}
});
},
/**
* @return {void}
*/
initEditDialog: function () {
me.storage.$editDialog.dialog({
modal: true,
bgiframe: true,
closeOnEscape: false,
minWidth: 980,
minHeight: 400,
autoOpen: false,
buttons: [{
text: 'Abbrechen',
click: function () {
me.resetEditDialog();
me.closeEditDialog();
}
}, {
text: 'Speichern',
click: function () {
me.saveTask();
}
}],
open: function () {
me.initCkEditor();
// ANFANG Workaround Projekt-Arbeitspaket-AutoComplete
addClicklupe();
//lupeclickevent();
// ENDE Workaround Projekt-Arbeitspaket-AutoComplete
// ANFANG Workaround Projekt-Arbeitspaket-AutoComplete
$('input#resubmissiontask-subproject').autocomplete({
source: 'index.php?module=ajax&action=filter&filtername=arbeitspaket&projekt=' + 0
});
$('input#resubmissiontask-project').autocomplete({
source: 'index.php?module=ajax&action=filter&filtername=projektname',
select: function (event, ui) {
if (ui.item) {
$('input#resubmissiontask-subproject').autocomplete({
source: 'index.php?module=ajax&action=filter&filtername=arbeitspaket&projekt=' +
ui.item.value
});
}
}
});
// ENDE Workaround Projekt-Arbeitspaket-AutoComplete
},
close: function () {
me.resetEditDialog();
me.destroyCkEditor();
}
});
},
/**
* CKEditor für Beschreibungsfeld initialisieren
*
* Workaround für CKEditor
* Workaround ist notwendig weil `$this->app->YUI->CkEditor('resubmissiontaskdescription','belege');` nicht
* funktioniert
*
* @return void
*/
initCkEditor: function () {
if (CKEDITOR.instances.hasOwnProperty('resubmissiontaskdescription')) {
return; // Ist bereits initialisiert
}
if (CKEDITOR.instances.resubmissiontaskdescription) {
return;
}
var ckeditorSettings = {
toolbar:
[
['Bold', 'Italic', 'Underline', 'RemoveFormat', '-', 'Undo', 'Redo'],
['NumberedList', 'BulletedList'],
['Font', 'FontSize', 'TextColor'],
['Source']
],
allowedContent: true,
extraPlugins: 'colorbutton,font'
};
var $description = me.storage.$editDialog.find('textarea#resubmissiontaskdescription');
$description.ckeditor(ckeditorSettings);
},
/**
* @return void
*/
destroyCkEditor: function () {
if (!CKEDITOR.instances.hasOwnProperty('resubmissiontaskdescription')) {
return; // Ist nicht initialisiert
}
if (!CKEDITOR.instances.resubmissiontaskdescription) {
return;
}
CKEDITOR.instances.resubmissiontaskdescription.destroy();
},
/**
* @return void
*/
openEditDialog: function () {
me.storage.$editDialog.dialog('open');
},
/**
* @return void
*/
closeEditDialog: function () {
me.storage.$editDialog.dialog('close');
},
/**
* @return {void}
*/
resetEditDialog: function () {
me.storage.$editDialog.find('#resubmissiontask-id').val('0');
me.storage.$editDialog.find('#resubmissiontask-title').val('');
me.storage.$editDialog.find('#resubmissiontask-state').val('open');
me.storage.$editDialog.find('#resubmissiontask-priority').val('medium');
me.storage.$editDialog.find('#resubmissiontask-employee').val('');
me.storage.$editDialog.find('#resubmissiontask-customer').val('');
me.storage.$editDialog.find('#resubmissiontask-submissiondate').val('');
me.storage.$editDialog.find('#resubmissiontask-submissiontime').val('');
me.storage.$editDialog.find('#resubmissiontask-project').val('');
me.storage.$editDialog.find('#resubmissiontask-subproject').val('');
me.storage.$editDialog.find('#resubmissiontask-requiredcompletionstage').html(
'<option value="0">- Nie -</option>'
);
if (CKEDITOR.instances.hasOwnProperty('resubmissiontaskdescription')) {
CKEDITOR.instances.resubmissiontaskdescription.setData('');
}
},
/**
*
*
* @return {void}
*/
loadDataTable: function () {
me.fetchDataTableHtml()
.then(
function () {
// DataTable-Daten laden und anzeigen
me.initDataTable();
},
function () {
// Fehler beim Abrufen der DataTable-Einstellungen
alert('Fehler beim Abrufen der Datatable \'resubmission_tasks\'.');
}
);
},
/**
* DataTable-HTML-Tabelle + Settings-JSON laden
*
* @return {jqXHR} jQuery jqXHR-Objekt
*/
fetchDataTableHtml: function () {
if (me.storage.resubmissionId === null) {
throw 'Could not initialize ResubmissionTasksUi. Required settings are missing: resubmissionId';
}
return $.ajax({
url: 'index.php?module=wiedervorlage&action=edit&cmd=tasktablehtml',
data: { 'id': me.storage.resubmissionId },
type: 'GET',
dataType: 'html',
success: function (htmlResult) {
$('#resubmission-tasks-datatable').html(htmlResult);
}
});
},
/**
* @return {void}
*/
initDataTable: function () {
DataTableHelper.initDataTable(me.storage.dataTableName);
},
/**
* @return {void}
*/
reloadDataTable: function () {
DataTableHelper.refreshDataTable(me.storage.dataTableName);
},
/**
* @return {void}
*/
destroyDataTable: function () {
DataTableHelper.destroyDataTable(me.storage.dataTableName);
}
};
return {
init: me.init,
destroy: me.destroy,
editTask: me.editTask
};
})(jQuery);
@@ -0,0 +1,348 @@
/**
* Für die Bedienung der Aufgaben Vorlagen-Konfigurations-Oberfläche
*/
var ResubmissionTaskTemplateConfig = (function ($) {
'use strict';
var me = {
isInitialized: false,
storage: {
$table: null,
$editDialog: null,
dataTableApi: null
},
/**
* @return void
*/
init: function () {
if (me.isInitialized === true) {
return;
}
me.storage.$table = $('#resubmission_tasktemplate_datatable');
me.storage.$editDialog = $('#resubmission_tasktemplate_edit');
if (me.storage.$table.length === 0 || me.storage.$editDialog.length === 0) {
throw 'Could not initialize ResubmissionTaskTemplateConfig. Required elements are missing.';
}
me.initDialog();
me.registerEvents();
me.isInitialized = true;
},
/**
* @return {void}
*/
initDialog: function () {
me.storage.$editDialog.dialog({
modal: true,
bgiframe: true,
closeOnEscape: false,
minWidth: 1100,
maxHeight: 700,
autoOpen: false,
buttons: [{
text: 'ABBRECHEN',
click: function () {
me.resetEditDialog();
me.closeEditDialog();
}
}, {
text: 'SPEICHERN',
click: function () {
me.saveItem();
}
}],
open: function () {
me.initCkEditor();
// ANFANG Workaround Projekt-Arbeitspaket-AutoComplete
addClicklupe();
//lupeclickevent();
// ENDE Workaround Projekt-Arbeitspaket-AutoComplete
// ANFANG Workaround Projekt-Arbeitspaket-AutoComplete
$('input#resubmissiontasktemplate-subproject').autocomplete({
source: 'index.php?module=ajax&action=filter&filtername=arbeitspaket&projekt=' + 0
});
$('input#resubmissiontasktemplate-project').autocomplete({
source: 'index.php?module=ajax&action=filter&filtername=projektname',
select: function (event, ui) {
if (ui.item) {
$('input#resubmissiontasktemplate-subproject').autocomplete({
source: 'index.php?module=ajax&action=filter&filtername=arbeitspaket&projekt=' +
ui.item.value
});
}
}
});
// ENDE Workaround Projekt-Arbeitspaket-AutoComplete
},
close: function () {
me.resetEditDialog();
me.destroyCkEditor();
}
});
},
/**
* CKEditor für Beschreibungsfeld initialisieren
*
* Workaround für CKEditor
* Workaround ist notwendig weil `$this->app->YUI->CkEditor('resubmissiontasktemplatedescription','belege');` nicht
* funktioniert
*
* @return void
*/
initCkEditor: function () {
if (CKEDITOR.instances.hasOwnProperty('resubmissiontasktemplatedescription')) {
console.log(1);
return; // Ist bereits initialisiert
}
if (CKEDITOR.instances.resubmissiontasktemplatedescription) {
console.log(2);
return;
}
console.log(3);
var ckeditorSettings = {
toolbar:
[
['Bold', 'Italic', 'Underline', 'RemoveFormat', '-', 'Undo', 'Redo'],
['NumberedList', 'BulletedList'],
['Font', 'FontSize', 'TextColor'],
['Source']
],
allowedContent: true,
extraPlugins: 'colorbutton,font'
};
var $description = me.storage.$editDialog.find('textarea#resubmissiontasktemplatedescription');
$description.ckeditor(ckeditorSettings);
},
/**
* @return void
*/
destroyCkEditor: function () {
if (!CKEDITOR.instances.hasOwnProperty('resubmissiontasktemplatedescription')) {
return; // Ist nicht initialisiert
}
if (!CKEDITOR.instances.resubmissiontasktemplatedescription) {
return;
}
CKEDITOR.instances.resubmissiontasktemplatedescription.destroy();
},
/**
* @return {void}
*/
registerEvents: function () {
// Eintrag bearbeiten
$(document).on('click', '.resubmissiontasktemplate-edit-button', function (e) {
e.preventDefault();
var textfieldConfigId = $(this).data('tasktemplateConfigId');
me.editItem(textfieldConfigId);
});
// Eintrag löschen
$(document).on('click', '.resubmissiontasktemplate-delete-button', function (e) {
e.preventDefault();
var textfieldConfigId = $(this).data('tasktemplateConfigId');
me.deleteItem(textfieldConfigId);
});
// Neuen Eintrag anlegen
$('#resubmissiontasktemplate-create-button').on('click', function (e) {
e.preventDefault();
me.createItem();
})
},
/**
* @return {void}
*/
createItem: function () {
if (me.isInitialized === false) {
me.init();
}
me.resetEditDialog();
me.openEditDialog();
},
/**
* @param {number} tasktemplateConfigId
*
* @return {void}
*/
editItem: function (tasktemplateConfigId) {
$.ajax({
url: 'index.php?module=wiedervorlage&action=settings&cmd=tasktemplate-detail',
data: {
id: tasktemplateConfigId
},
method: 'post',
dataType: 'json',
success: function (result) {
me.storage.$editDialog.find('#resubmissiontasktemplate-id').val(result.data.id);
me.storage.$editDialog.find('#resubmissiontasktemplate-title').val(result.data.title);
me.storage.$editDialog.find('#resubmissiontasktemplate-employee').val(result.data.employee);
me.storage.$editDialog.find('#resubmissiontasktemplate-submissiondatedays').val(result.data.submission_date_days);
me.storage.$editDialog.find('#resubmissiontasktemplate-submissiontime').val(result.data.submission_time);
me.storage.$editDialog.find('#resubmissiontasktemplate-project').val(result.data.project);
me.storage.$editDialog.find('#resubmissiontasktemplate-subproject').val(result.data.subproject);
me.storage.$editDialog.find('#resubmissiontasktemplate-requiredfromstage').val(result.data.required_from_stage_id);
me.storage.$editDialog.find('#resubmissiontasktemplate-addtaskatstage').val(result.data.add_task_at_stage_id);
me.storage.$editDialog.find('#resubmissiontasktemplate-state').val(result.data.state);
me.storage.$editDialog.find('#resubmissiontasktemplate-priority').val(result.data.priority);
if (CKEDITOR.instances.hasOwnProperty('resubmissiontasktemplatedescription')
&& CKEDITOR.instances.resubmissiontasktemplatedescription) {
CKEDITOR.instances.resubmissiontasktemplatedescription.setData(result.data.description);
} else {
$('textarea#resubmissiontasktemplatedescription').val(result.data.description);
}
me.storage.$editDialog.dialog('open');
},
error: function (jqXhr) {
alert('Fehler: ' + jqXhr.responseJSON.error);
}
});
},
/**
* @return {void}
*/
saveItem: function () {
$.ajax({
url: 'index.php?module=wiedervorlage&action=settings&cmd=tasktemplate-save',
data: {
id: $('#resubmissiontasktemplate-id').val(),
title: $('#resubmissiontasktemplate-title').val(),
employee: $('#resubmissiontasktemplate-employee').val(),
submissiondatedays: $('#resubmissiontasktemplate-submissiondatedays').val(),
submissiontime: $('#resubmissiontasktemplate-submissiontime').val(),
project: $('#resubmissiontasktemplate-project').val(),
subproject:$('#resubmissiontasktemplate-subproject').val(),
requiredfromstage:$('#resubmissiontasktemplate-requiredfromstage').val(),
addtaskatstage:$('#resubmissiontasktemplate-addtaskatstage').val(),
state:$('#resubmissiontasktemplate-state').val(),
priority:$('#resubmissiontasktemplate-priority').val(),
description:$('textarea#resubmissiontasktemplatedescription').val()
},
method: 'post',
dataType: 'json',
success: function (data) {
if (data.success === true) {
me.resetEditDialog();
me.reloadDataTable();
me.closeEditDialog();
}
if (data.success === false) {
alert(data.error);
}
},
error: function (jqXhr) {
alert('Fehler: ' + jqXhr.responseJSON.error);
}
});
},
/**
* @param {number} tasktemplateConfigId
*
* @return {void}
*/
deleteItem: function (tasktemplateConfigId) {
var confirmValue = confirm('Möchten Sie die Aufgaben Vorlage wirklich löschen?');
if (confirmValue === false) {
return;
}
$.ajax({
url: 'index.php?module=wiedervorlage&action=settings&cmd=tasktemplate-delete',
data: {
id: tasktemplateConfigId
},
method: 'post',
dataType: 'json',
success: function (data) {
if (data.success === true) {
me.reloadDataTable();
}
if (data.success === false) {
alert('Unbekannter Fehler beim Löschen.');
}
},
error: function (jqXhr) {
alert('Fehler: ' + jqXhr.responseJSON.error);
}
});
},
/**
* @return void
*/
openEditDialog: function () {
me.storage.$editDialog.dialog('open');
},
/**
* @return void
*/
closeEditDialog: function () {
me.storage.$editDialog.dialog('close');
},
/**
* @return void
*/
resetEditDialog: function () {
me.storage.$editDialog.find('#resubmissiontasktemplate-id').val('');
me.storage.$editDialog.find('#resubmissiontasktemplate-title').val('');
me.storage.$editDialog.find('#resubmissiontasktemplate-employee').val('');
me.storage.$editDialog.find('#resubmissiontasktemplate-submissiondatedays').val('');
me.storage.$editDialog.find('#resubmissiontasktemplate-submissiontime').val('');
me.storage.$editDialog.find('#resubmissiontasktemplate-project').val('');
me.storage.$editDialog.find('#resubmissiontasktemplate-subproject').val('');
me.storage.$editDialog.find('#resubmissiontasktemplate-requiredfromstage').val('0');
me.storage.$editDialog.find('#resubmissiontasktemplate-addtaskatstage').prop('selectedIndex', 0);
me.storage.$editDialog.find('#resubmissiontasktemplate-state').val('open');
me.storage.$editDialog.find('#resubmissiontasktemplate-priority').val('medium');
me.storage.$editDialog.find('textarea#resubmissiontasktemplatedescription').val('');
},
/**
* Lädt die DataTable-Inhalte neu; per AJAX
*
* @return {void}
*/
reloadDataTable: function () {
if (!$.fn.DataTable.isDataTable(me.storage.$table)) {
return; // DataTable ist noch nicht initalisiert
}
var dataTableApi = $(me.storage.$table).dataTable().api();
dataTableApi.ajax.reload();
}
};
return {
init: me.init
};
})(jQuery);
$(document).ready(function () {
if ($('#resubmission_tasktemplate_datatable').length > 0) {
ResubmissionTaskTemplateConfig.init();
}
});
@@ -0,0 +1,246 @@
/**
* Für die Bedienung der Freifelder-Konfigurations-Oberfläche
*/
var ResubmissionTextFieldConfig = (function ($) {
'use strict';
var me = {
isInitialized: false,
storage: {
$table: null,
$editDialog: null,
dataTableApi: null
},
/**
* @return void
*/
init: function () {
if (me.isInitialized === true) {
return;
}
me.storage.$table = $('#resubmission_textfield_datatable');
me.storage.$editDialog = $('#resubmission_textfield_edit');
if (me.storage.$table.length === 0 || me.storage.$editDialog.length === 0) {
throw 'Could not initialize ResubmissionTextFieldConfig. Required elements are missing.';
}
me.initDialog();
me.registerEvents();
me.isInitialized = true;
},
/**
* @return {void}
*/
initDialog: function () {
me.storage.$editDialog.dialog({
modal: true,
bgiframe: true,
closeOnEscape: false,
minWidth: 550,
maxHeight: 400,
autoOpen: false,
buttons: [{
text: 'ABBRECHEN',
click: function () {
me.resetEditDialog();
me.closeEditDialog();
}
}, {
text: 'SPEICHERN',
click: function () {
me.saveItem();
}
}],
close: function () {
me.resetEditDialog();
}
});
},
/**
* @return {void}
*/
registerEvents: function () {
// Eintrag bearbeiten
$(document).on('click', '.resubmissiontextfield-edit-button', function (e) {
e.preventDefault();
var textfieldConfigId = $(this).data('textfieldConfigId');
me.editItem(textfieldConfigId);
});
// Eintrag löschen
$(document).on('click', '.resubmissiontextfield-delete-button', function (e) {
e.preventDefault();
var textfieldConfigId = $(this).data('textfieldConfigId');
me.deleteItem(textfieldConfigId);
});
// Neuen Eintrag anlegen
$('#resubmissiontextfield-create-button').on('click', function (e) {
e.preventDefault();
me.createItem();
})
},
/**
* @return {void}
*/
createItem: function () {
if (me.isInitialized === false) {
me.init();
}
me.resetEditDialog();
me.openEditDialog();
},
/**
* @param {number} textfieldConfigId
*
* @return {void}
*/
editItem: function (textfieldConfigId) {
$.ajax({
url: 'index.php?module=wiedervorlage&action=settings&cmd=textfields-detail',
data: {
id: textfieldConfigId
},
method: 'post',
dataType: 'json',
success: function (result) {
me.storage.$editDialog.find('#resubmissiontextfield-id').val(result.data.id);
me.storage.$editDialog.find('#resubmissiontextfield-title').val(result.data.title);
me.storage.$editDialog.find('#resubmissiontextfield-availablestage').val(result.data.available_from_stage_id);
me.storage.$editDialog.find('#resubmissiontextfield-requiredstage').val(result.data.required_from_stage_id);
me.storage.$editDialog.find('#resubmissiontextfield-showinpipeline').prop('checked', result.data.show_in_pipeline);
me.storage.$editDialog.find('#resubmissiontextfield-showintables').prop('checked', result.data.show_in_tables);
me.storage.$editDialog.dialog('open');
},
error: function (jqXhr) {
alert('Fehler: ' + jqXhr.responseJSON.error);
}
});
},
/**
* @return {void}
*/
saveItem: function () {
$.ajax({
url: 'index.php?module=wiedervorlage&action=settings&cmd=textfields-save',
data: {
id: $('#resubmissiontextfield-id').val(),
title: $('#resubmissiontextfield-title').val(),
available_from_stage_id: $('#resubmissiontextfield-availablestage').val(),
required_from_stage_id: $('#resubmissiontextfield-requiredstage').val(),
show_in_pipeline: $('#resubmissiontextfield-showinpipeline').prop('checked'),
show_in_tables: $('#resubmissiontextfield-showintables').prop('checked')
},
method: 'post',
dataType: 'json',
success: function (data) {
if (data.success === true) {
me.resetEditDialog();
me.reloadDataTable();
me.closeEditDialog();
}
if (data.success === false) {
alert(data.error);
}
},
error: function (jqXhr) {
alert('Fehler: ' + jqXhr.responseJSON.error);
}
});
},
/**
* @param {number} textfieldConfigId
*
* @return {void}
*/
deleteItem: function (textfieldConfigId) {
var confirmValue = confirm('Möchten Sie das Freitextfeld wirklich löschen?');
if (confirmValue === false) {
return;
}
$.ajax({
url: 'index.php?module=wiedervorlage&action=settings&cmd=textfields-delete',
data: {
id: textfieldConfigId
},
method: 'post',
dataType: 'json',
success: function (data) {
if (data.success === true) {
me.reloadDataTable();
}
if (data.success === false) {
alert('Unbekannter Fehler beim Löschen.');
}
},
error: function (jqXhr) {
alert('Fehler: ' + jqXhr.responseJSON.error);
}
});
},
/**
* @return void
*/
openEditDialog: function () {
me.storage.$editDialog.dialog('open');
},
/**
* @return void
*/
closeEditDialog: function () {
me.storage.$editDialog.dialog('close');
},
/**
* @return void
*/
resetEditDialog: function () {
me.storage.$editDialog.find('#resubmissiontextfield-id').val('');
me.storage.$editDialog.find('#resubmissiontextfield-title').val('');
me.storage.$editDialog.find('#resubmissiontextfield-availablestage').val('0');
me.storage.$editDialog.find('#resubmissiontextfield-requiredstage').val('0');
me.storage.$editDialog.find('#resubmissiontextfield-showinpipeline').prop('checked', false);
me.storage.$editDialog.find('#resubmissiontextfield-showintables').prop('checked', false);
},
/**
* Lädt die DataTable-Inhalte neu; per AJAX
*
* @return {void}
*/
reloadDataTable: function () {
if (!$.fn.DataTable.isDataTable(me.storage.$table)) {
return; // DataTable ist noch nicht initalisiert
}
var dataTableApi = $(me.storage.$table).dataTable().api();
dataTableApi.ajax.reload();
}
};
return {
init: me.init
};
})(jQuery);
$(document).ready(function () {
if ($('#resubmission_textfield_datatable').length > 0) {
ResubmissionTextFieldConfig.init();
}
});
@@ -0,0 +1,112 @@
/**
* Für die Bedienung der Freifelder innerhalb des Wiedervorlagen-Popups
*/
var ResubmissionTextFieldUi = (function ($) {
'use strict';
var me = {
storage: {
$content: null,
$errors: null
},
/**
* @param {Array} textfields
*
* @return void
*/
init: function (textfields) {
var $content = $('#resubmission-textfields-content');
if ($content.length !== 1) {
console.error('ResubmissionTextFieldUi: Benötigtes Element #resubmission-textfields-content fehlt');
return;
}
me.storage.$content = $content;
var $errors = $('#resubmission-textfields-errors');
if ($errors.length !== 1) {
console.error('ResubmissionTextFieldUi: Benötigtes Element #resubmission-textfields-errors fehlt');
return;
}
me.storage.$errors = $errors;
me.buildTextFieldForm(textfields);
},
/**
* @param {Array} textfields
*/
buildTextFieldForm: function (textfields) {
me.storage.$content.html('');
me.storage.$errors.html('');
if (textfields.length === 0) {
var content = '<div class="info">Es sind keine Freifelder für diese Stage konfiguriert. ';
content += '<a href="index.php?module=wiedervorlage&action=settings#tabs-4">Zu den Einstellungen</a>';
content += '</div>';
me.storage.$errors.html(content);
}
if (textfields.length > 0) {
var $table = $('<table class="mkTableFormular textfield-table" width="100%">');
$.each(textfields, function (index, textfield) {
var $row = me.generateTextFieldRow(textfield);
$row.appendTo($table);
});
me.storage.$content.html($table);
}
},
/**
* @param {Object} data
*
* @return {jQuery} jQuery-Element
*/
generateTextFieldRow: function (data) {
if (!data.hasOwnProperty('config_id') || !data.hasOwnProperty('label')) {
throw 'ResubmissionTextFieldUi: Can not generate textfield html. Data has wrong format.';
}
var template =
'<tr><td width="34%">' +
'<label class="resubmission-textfield-label" for="resubmission-textfield-{{configId}}"></label>' +
'</td><td width="66%">' +
'<input type="text" name="textfield[{{configId}}]" class="resubmission-textfield-content" ' +
'id="resubmission-textfield-{{configId}}">' +
'</td></tr>';
template = template.replace('{{configId}}', data.config_id);
template = template.replace('{{configId}}', data.config_id);
template = template.replace('{{configId}}', data.content_id);
var $template = $(template);
$template.find('label.resubmission-textfield-label').text(data.label);
$template
.find('input.resubmission-textfield-content')
.data('resubmissionTextfieldConfigId', data.config_id)
.data('resubmissionTextfieldContentId', data.content_id)
.val(data.content);
return $template;
},
/**
* @param {Array} errors
*/
renderErrorMessages: function (errors) {
var errorMsg = '';
$.each(errors, function (index, textfieldError) {
errorMsg += '<p>' + textfieldError.message + '</p>';
});
me.storage.$errors.html('<div class="error">' + errorMsg + '</div>');
}
};
return {
init: me.init,
renderErrorMessages: me.renderErrorMessages
};
})(jQuery);