Initial xentral_oss_20.3.c9ffacf
This commit is contained in:
@@ -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 "' + data.resubmission.title + '" kann nicht ';
|
||||
content += 'in die Stage "' + data.stage.title + '" verschoben werden, weil ';
|
||||
content += 'folgende Element blockieren:</p>';
|
||||
}
|
||||
if (data.blocking.type === 'create-resubmission') {
|
||||
content = '<p>Die Wiedervorlage kann nicht in der Stage "' + data.stage.title + '" ';
|
||||
content += 'angelegt werden, weil folgende Element blockieren:</p>';
|
||||
}
|
||||
if (data.blocking.type === 'update-resubmission') {
|
||||
content = '<p>Die Wiedervorlage kann nicht in der Stage "' + data.stage.title + '" ';
|
||||
content += 'gespeichert werden, weil folgende Element blockieren:</p>';
|
||||
}
|
||||
|
||||
content += '<ul>';
|
||||
$.each(data.blocking.tasks, function (index, task) {
|
||||
content += '<li><p>Aufgabe "' + task.title + '"<br><strong>nicht abgeschlossen</strong></p></li>';
|
||||
});
|
||||
$.each(data.blocking.textfields, function (index, textfield) {
|
||||
content += '<li><p>Freitextfeld "' + textfield.label + '"<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);
|
||||
Reference in New Issue
Block a user