Module tasks + knowledgecenter_update aus Kundenprojekt übernehmen (bereinigt)
Beide Module wurden aus einem anderen Kundenprojekt kopiert und bereinigt: Knowledgecenter: - Bild-Cache (1.985 Dateien) geleert, Ordnerstruktur mit .gitkeep behalten - Files-Gallery-Lizenzschlüssel entfernt (config.php) - Kundenspezifische Kategorien gelöscht: Teil I/II/III QM-Struktur, nummerierte QM-Kapitel, Gremien (Präsidium/Finanzausschuss/Landesausschuss), Sitzungsservice-Serie, Protokoll-Abteilungen, Testeinträge - 56 generische Kategorien bleiben (Downloads, Onboarding, Personalthemen …) Tasks: - Alle Betriebsdaten gelöscht (114 Tasks, 914 Submissions, 579 Assignments) - Task-Definitionen und Verlinkungen geleert - Kategoriestruktur bleibt (Allgemein, IT, Personal, Buchhaltung …) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
514
module/tasks/repo/TaskAssignmentRepository.php
Normal file
514
module/tasks/repo/TaskAssignmentRepository.php
Normal file
@@ -0,0 +1,514 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
class TaskAssignmentRepository
|
||||
{
|
||||
public function createIfNotExists(
|
||||
int $taskId,
|
||||
int $contactId,
|
||||
string $cycleKey,
|
||||
string $assignedSource,
|
||||
?string $cycleStartAt,
|
||||
?string $cycleEndAt,
|
||||
?string $dueAt,
|
||||
?string $graceUntil = null
|
||||
): int {
|
||||
$existing = TaskCertDb::fetchOne(
|
||||
'SELECT id FROM task_assignment WHERE task_id = ? AND main_contact_id = ? AND cycle_key = ? LIMIT 1',
|
||||
'iis',
|
||||
[$taskId, $contactId, $cycleKey]
|
||||
);
|
||||
|
||||
if ($existing !== null) {
|
||||
$this->syncAssignmentCycleData((int) $existing['id'], $cycleStartAt, $cycleEndAt, $dueAt);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ($cycleEndAt !== null && trim($cycleEndAt) !== '') {
|
||||
$cycleEndMatch = TaskCertDb::fetchOne(
|
||||
'SELECT id
|
||||
FROM task_assignment
|
||||
WHERE task_id = ?
|
||||
AND main_contact_id = ?
|
||||
AND cycle_end_at = ?
|
||||
ORDER BY id DESC
|
||||
LIMIT 1',
|
||||
'iis',
|
||||
[$taskId, $contactId, $cycleEndAt]
|
||||
);
|
||||
|
||||
if ($cycleEndMatch !== null) {
|
||||
$this->rekeyAndSyncAssignment(
|
||||
(int) $cycleEndMatch['id'],
|
||||
$cycleKey,
|
||||
$cycleStartAt,
|
||||
$cycleEndAt,
|
||||
$dueAt
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return TaskCertDb::insert(
|
||||
'INSERT INTO task_assignment (task_id, main_contact_id, cycle_key, status, assigned_source, cycle_start_at, cycle_end_at, due_at, grace_until)
|
||||
VALUES (?, ?, ?, "open", ?, ?, ?, ?, ?)',
|
||||
'iissssss',
|
||||
[$taskId, $contactId, $cycleKey, $assignedSource, $cycleStartAt, $cycleEndAt, $dueAt, $graceUntil]
|
||||
);
|
||||
}
|
||||
|
||||
private function syncAssignmentCycleData(
|
||||
int $assignmentId,
|
||||
?string $cycleStartAt,
|
||||
?string $cycleEndAt,
|
||||
?string $dueAt
|
||||
): void {
|
||||
TaskCertDb::execute(
|
||||
'UPDATE task_assignment
|
||||
SET cycle_start_at = ?,
|
||||
cycle_end_at = ?,
|
||||
due_at = ?,
|
||||
modified_at = NOW()
|
||||
WHERE id = ?',
|
||||
'sssi',
|
||||
[$cycleStartAt, $cycleEndAt, $dueAt, $assignmentId]
|
||||
);
|
||||
}
|
||||
|
||||
private function rekeyAndSyncAssignment(
|
||||
int $assignmentId,
|
||||
string $cycleKey,
|
||||
?string $cycleStartAt,
|
||||
?string $cycleEndAt,
|
||||
?string $dueAt
|
||||
): void {
|
||||
TaskCertDb::execute(
|
||||
'UPDATE task_assignment
|
||||
SET cycle_key = ?,
|
||||
cycle_start_at = ?,
|
||||
cycle_end_at = ?,
|
||||
due_at = ?,
|
||||
modified_at = NOW()
|
||||
WHERE id = ?',
|
||||
'ssssi',
|
||||
[$cycleKey, $cycleStartAt, $cycleEndAt, $dueAt, $assignmentId]
|
||||
);
|
||||
}
|
||||
|
||||
public function findById(int $assignmentId): ?array
|
||||
{
|
||||
return TaskCertDb::fetchOne('SELECT * FROM task_assignment WHERE id = ?', 'i', [$assignmentId]);
|
||||
}
|
||||
|
||||
public function listForUser(int $contactId): array
|
||||
{
|
||||
return TaskCertDb::fetchAll(
|
||||
'SELECT
|
||||
a.*,
|
||||
d.title,
|
||||
d.description,
|
||||
d.task_category_id,
|
||||
cat.name AS task_category_name,
|
||||
cat.slug AS task_category_slug,
|
||||
d.fulfillment_mode,
|
||||
d.show_cycle_period,
|
||||
d.certificate_enabled,
|
||||
d.certificate_title,
|
||||
cert.valid_until AS certificate_valid_until,
|
||||
cert.status AS certificate_status
|
||||
FROM task_assignment a
|
||||
INNER JOIN task_definition d ON d.id = a.task_id
|
||||
LEFT JOIN task_category cat ON cat.id = d.task_category_id
|
||||
LEFT JOIN task_certificate cert ON cert.assignment_id = a.id
|
||||
WHERE a.main_contact_id = ?
|
||||
ORDER BY
|
||||
CASE a.status
|
||||
WHEN "overdue" THEN 1
|
||||
WHEN "rejected" THEN 2
|
||||
WHEN "open" THEN 3
|
||||
WHEN "in_progress" THEN 4
|
||||
WHEN "approval_pending" THEN 5
|
||||
WHEN "completed" THEN 6
|
||||
WHEN "expired" THEN 7
|
||||
ELSE 7
|
||||
END,
|
||||
a.due_at IS NULL,
|
||||
a.due_at ASC,
|
||||
a.id DESC',
|
||||
'i',
|
||||
[$contactId]
|
||||
);
|
||||
}
|
||||
|
||||
public function listForAdmin(int $taskId = 0, string $status = '', string $searchQuery = ''): array
|
||||
{
|
||||
$searchQuery = trim($searchQuery);
|
||||
|
||||
$sql = 'SELECT
|
||||
a.*,
|
||||
d.title AS task_title,
|
||||
c.name AS contact_name,
|
||||
c.email AS contact_email
|
||||
FROM task_assignment a
|
||||
INNER JOIN task_definition d ON d.id = a.task_id
|
||||
LEFT JOIN main_contact c ON c.id = a.main_contact_id
|
||||
WHERE 1=1';
|
||||
|
||||
$types = '';
|
||||
$params = [];
|
||||
|
||||
if ($taskId > 0) {
|
||||
$sql .= ' AND a.task_id = ?';
|
||||
$types .= 'i';
|
||||
$params[] = $taskId;
|
||||
}
|
||||
|
||||
if ($status !== '') {
|
||||
$sql .= ' AND a.status = ?';
|
||||
$types .= 's';
|
||||
$params[] = $status;
|
||||
}
|
||||
|
||||
if ($searchQuery !== '') {
|
||||
$searchLike = '%' . $searchQuery . '%';
|
||||
$sql .= ' AND (
|
||||
CAST(a.id AS CHAR) LIKE ?
|
||||
OR d.title LIKE ?
|
||||
OR COALESCE(c.name, "") LIKE ?
|
||||
OR COALESCE(c.email, "") LIKE ?
|
||||
OR a.status LIKE ?
|
||||
)';
|
||||
$types .= 'sssss';
|
||||
$params[] = $searchLike;
|
||||
$params[] = $searchLike;
|
||||
$params[] = $searchLike;
|
||||
$params[] = $searchLike;
|
||||
$params[] = $searchLike;
|
||||
}
|
||||
|
||||
$sql .= ' ORDER BY a.escalation_level DESC, a.due_at ASC, a.id DESC';
|
||||
|
||||
return TaskCertDb::fetchAll($sql, $types, $params);
|
||||
}
|
||||
|
||||
public function listByTaskAndCycleKey(int $taskId, string $cycleKey): array
|
||||
{
|
||||
return TaskCertDb::fetchAll(
|
||||
'SELECT *
|
||||
FROM task_assignment
|
||||
WHERE task_id = ?
|
||||
AND cycle_key = ?
|
||||
ORDER BY id ASC',
|
||||
'is',
|
||||
[$taskId, $cycleKey]
|
||||
);
|
||||
}
|
||||
|
||||
public function listByTask(int $taskId): array
|
||||
{
|
||||
return TaskCertDb::fetchAll(
|
||||
'SELECT *
|
||||
FROM task_assignment
|
||||
WHERE task_id = ?
|
||||
ORDER BY id ASC',
|
||||
'i',
|
||||
[$taskId]
|
||||
);
|
||||
}
|
||||
|
||||
public function listByContactId(int $contactId): array
|
||||
{
|
||||
return TaskCertDb::fetchAll(
|
||||
'SELECT
|
||||
a.id, a.task_id, a.status, a.due_at, a.grace_until,
|
||||
a.created_at, a.cycle_key, a.escalation_level,
|
||||
d.title AS task_title,
|
||||
d.active AS task_active,
|
||||
tc.name AS task_category_name,
|
||||
ls.last_submitted_at
|
||||
FROM task_assignment a
|
||||
INNER JOIN task_definition d ON d.id = a.task_id
|
||||
LEFT JOIN task_category tc ON tc.id = d.task_category_id
|
||||
LEFT JOIN (
|
||||
SELECT assignment_id, MAX(submitted_at) AS last_submitted_at
|
||||
FROM task_submission
|
||||
GROUP BY assignment_id
|
||||
) ls ON ls.assignment_id = a.id
|
||||
WHERE a.main_contact_id = ?
|
||||
ORDER BY
|
||||
CASE a.status
|
||||
WHEN "overdue" THEN 1
|
||||
WHEN "open" THEN 2
|
||||
WHEN "in_progress" THEN 3
|
||||
WHEN "approval_pending" THEN 4
|
||||
WHEN "rejected" THEN 5
|
||||
WHEN "completed" THEN 6
|
||||
WHEN "expired" THEN 7
|
||||
ELSE 8
|
||||
END,
|
||||
a.due_at ASC',
|
||||
'i',
|
||||
[$contactId]
|
||||
);
|
||||
}
|
||||
|
||||
public function findLatestByTaskAndContact(int $taskId, int $contactId): ?array
|
||||
{
|
||||
return TaskCertDb::fetchOne(
|
||||
'SELECT *
|
||||
FROM task_assignment
|
||||
WHERE task_id = ?
|
||||
AND main_contact_id = ?
|
||||
ORDER BY id DESC
|
||||
LIMIT 1',
|
||||
'ii',
|
||||
[$taskId, $contactId]
|
||||
);
|
||||
}
|
||||
|
||||
public function listLatestByTaskForContacts(int $taskId, array $contactIds): array
|
||||
{
|
||||
$contactIds = array_values(array_unique(array_map('intval', $contactIds)));
|
||||
$contactIds = array_values(array_filter($contactIds, static function (int $contactId): bool {
|
||||
return $contactId > 0;
|
||||
}));
|
||||
|
||||
if ($taskId <= 0 || $contactIds === []) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$placeholders = implode(',', array_fill(0, count($contactIds), '?'));
|
||||
$types = 'i' . str_repeat('i', count($contactIds));
|
||||
$params = array_merge([$taskId], $contactIds);
|
||||
|
||||
return TaskCertDb::fetchAll(
|
||||
'SELECT a.*
|
||||
FROM task_assignment a
|
||||
INNER JOIN (
|
||||
SELECT main_contact_id, MAX(id) AS latest_id
|
||||
FROM task_assignment
|
||||
WHERE task_id = ?
|
||||
AND main_contact_id IN (' . $placeholders . ')
|
||||
GROUP BY main_contact_id
|
||||
) latest ON latest.latest_id = a.id
|
||||
ORDER BY a.main_contact_id ASC',
|
||||
$types,
|
||||
$params
|
||||
);
|
||||
}
|
||||
|
||||
public function listRemovableOpenByTaskAndContacts(int $taskId, array $contactIds): array
|
||||
{
|
||||
$contactIds = array_values(array_unique(array_map('intval', $contactIds)));
|
||||
$contactIds = array_values(array_filter($contactIds, static function (int $contactId): bool {
|
||||
return $contactId > 0;
|
||||
}));
|
||||
|
||||
if ($taskId <= 0 || $contactIds === []) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$placeholders = implode(',', array_fill(0, count($contactIds), '?'));
|
||||
$types = 'i' . str_repeat('i', count($contactIds));
|
||||
$params = array_merge([$taskId], $contactIds);
|
||||
|
||||
return TaskCertDb::fetchAll(
|
||||
'SELECT *
|
||||
FROM task_assignment
|
||||
WHERE task_id = ?
|
||||
AND main_contact_id IN (' . $placeholders . ')
|
||||
AND status IN ("open", "in_progress", "approval_pending", "overdue", "rejected", "completed", "expired")
|
||||
ORDER BY id ASC',
|
||||
$types,
|
||||
$params
|
||||
);
|
||||
}
|
||||
|
||||
public function updateStatus(int $assignmentId, string $status, ?string $completedAt = null): void
|
||||
{
|
||||
TaskCertDb::execute(
|
||||
'UPDATE task_assignment
|
||||
SET status = ?, completed_at = ?, modified_at = NOW()
|
||||
WHERE id = ?',
|
||||
'ssi',
|
||||
[$status, $completedAt, $assignmentId]
|
||||
);
|
||||
}
|
||||
|
||||
public function markInProgressIfOpen(int $assignmentId): void
|
||||
{
|
||||
TaskCertDb::execute(
|
||||
'UPDATE task_assignment
|
||||
SET status = "in_progress", modified_at = NOW()
|
||||
WHERE id = ? AND status = "open"',
|
||||
'i',
|
||||
[$assignmentId]
|
||||
);
|
||||
}
|
||||
|
||||
public function setEscalationLevel(int $assignmentId, int $level): void
|
||||
{
|
||||
TaskCertDb::execute(
|
||||
'UPDATE task_assignment
|
||||
SET escalation_level = ?, last_escalated_at = NOW(), modified_at = NOW()
|
||||
WHERE id = ?',
|
||||
'ii',
|
||||
[$level, $assignmentId]
|
||||
);
|
||||
}
|
||||
|
||||
public function clearEscalation(int $assignmentId): void
|
||||
{
|
||||
TaskCertDb::execute(
|
||||
'UPDATE task_assignment
|
||||
SET escalation_level = 0, last_escalated_at = NULL, modified_at = NOW()
|
||||
WHERE id = ?',
|
||||
'i',
|
||||
[$assignmentId]
|
||||
);
|
||||
}
|
||||
|
||||
public function addEscalationEvent(int $assignmentId, int $level, string $eventType, array $snapshot): int
|
||||
{
|
||||
return TaskCertDb::insert(
|
||||
'INSERT INTO task_escalation_event (assignment_id, escalation_level, event_type, snapshot_json)
|
||||
VALUES (?, ?, ?, ?)',
|
||||
'iiss',
|
||||
[
|
||||
$assignmentId,
|
||||
$level,
|
||||
$eventType,
|
||||
json_encode($snapshot, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function findEscalationCandidates(?int $taskId = null): array
|
||||
{
|
||||
$sql = 'SELECT a.*, d.title AS task_title
|
||||
FROM task_assignment a
|
||||
INNER JOIN task_definition d ON d.id = a.task_id
|
||||
WHERE d.active = 1
|
||||
AND a.status IN ("open", "in_progress", "approval_pending", "overdue", "rejected")
|
||||
AND a.due_at IS NOT NULL';
|
||||
$types = '';
|
||||
$params = [];
|
||||
|
||||
if ($taskId !== null && $taskId > 0) {
|
||||
$sql .= ' AND a.task_id = ?';
|
||||
$types = 'i';
|
||||
$params[] = $taskId;
|
||||
}
|
||||
|
||||
return TaskCertDb::fetchAll($sql, $types, $params);
|
||||
}
|
||||
|
||||
public function markOverdue(int $assignmentId, string $overdueAt): void
|
||||
{
|
||||
TaskCertDb::execute(
|
||||
'UPDATE task_assignment
|
||||
SET status = "overdue",
|
||||
overdue_at = COALESCE(overdue_at, ?),
|
||||
modified_at = NOW()
|
||||
WHERE id = ?
|
||||
AND status IN ("open", "in_progress", "approval_pending", "overdue", "rejected")',
|
||||
'si',
|
||||
[$overdueAt, $assignmentId]
|
||||
);
|
||||
}
|
||||
|
||||
public function clearOverdue(int $assignmentId): void
|
||||
{
|
||||
TaskCertDb::execute(
|
||||
'UPDATE task_assignment
|
||||
SET status = "in_progress",
|
||||
overdue_at = NULL,
|
||||
modified_at = NOW()
|
||||
WHERE id = ?
|
||||
AND status = "overdue"',
|
||||
'i',
|
||||
[$assignmentId]
|
||||
);
|
||||
}
|
||||
|
||||
public function resetByIds(array $assignmentIds): int
|
||||
{
|
||||
$assignmentIds = array_values(array_unique(array_map('intval', $assignmentIds)));
|
||||
$assignmentIds = array_values(array_filter($assignmentIds, static function (int $assignmentId): bool {
|
||||
return $assignmentId > 0;
|
||||
}));
|
||||
|
||||
if ($assignmentIds === []) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$placeholders = implode(',', array_fill(0, count($assignmentIds), '?'));
|
||||
|
||||
return TaskCertDb::execute(
|
||||
'UPDATE task_assignment
|
||||
SET status = "open",
|
||||
completed_at = NULL,
|
||||
overdue_at = NULL,
|
||||
escalation_level = 0,
|
||||
last_escalated_at = NULL,
|
||||
modified_at = NOW()
|
||||
WHERE id IN (' . $placeholders . ')',
|
||||
str_repeat('i', count($assignmentIds)),
|
||||
$assignmentIds
|
||||
);
|
||||
}
|
||||
|
||||
public function deleteByIds(array $assignmentIds): int
|
||||
{
|
||||
$assignmentIds = array_values(array_unique(array_map('intval', $assignmentIds)));
|
||||
$assignmentIds = array_values(array_filter($assignmentIds, static function (int $assignmentId): bool {
|
||||
return $assignmentId > 0;
|
||||
}));
|
||||
|
||||
if ($assignmentIds === []) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$placeholders = implode(',', array_fill(0, count($assignmentIds), '?'));
|
||||
|
||||
return TaskCertDb::execute(
|
||||
'DELETE FROM task_assignment
|
||||
WHERE id IN (' . $placeholders . ')',
|
||||
str_repeat('i', count($assignmentIds)),
|
||||
$assignmentIds
|
||||
);
|
||||
}
|
||||
|
||||
public function listTaskAssignmentSummary(int $taskId): array
|
||||
{
|
||||
$rows = TaskCertDb::fetchAll(
|
||||
'SELECT status, COUNT(*) AS cnt
|
||||
FROM task_assignment
|
||||
WHERE task_id = ?
|
||||
GROUP BY status',
|
||||
'i',
|
||||
[$taskId]
|
||||
);
|
||||
|
||||
$summary = [
|
||||
'open' => 0,
|
||||
'in_progress' => 0,
|
||||
'approval_pending' => 0,
|
||||
'overdue' => 0,
|
||||
'completed' => 0,
|
||||
'expired' => 0,
|
||||
'rejected' => 0,
|
||||
];
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$status = (string) $row['status'];
|
||||
if (!array_key_exists($status, $summary)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$summary[$status] = (int) $row['cnt'];
|
||||
}
|
||||
|
||||
return $summary;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user