Initial xentral_oss_20.3.c9ffacf

This commit is contained in:
Alex
2021-05-21 08:49:41 +02:00
parent 5406e4a551
commit 34e5ac43d9
18884 changed files with 2109867 additions and 0 deletions
@@ -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;
}
}