- Create knowledgecenter_category_einricht, _fachbereich, knowledgecenter_post_einricht, knowledgecenter_post_fachbereich tables; extend task_target_rule with main_einricht_id and main_bereich_id columns - Middleware: load main_einricht_id/main_bereich_id from main_contact_department, expose $allowedEinrichts/$allowedFachbereiche globals (same pattern as existing 3 types) - Ajax: add update_category/post_einrichts/fachbereiche functions, extend update_category_links() and search filter query - Views: add einricht/fachbereich LEFT JOINs and WHERE filters in all 5 query sites (categories_posts_listform, category_posts_widget, post_announcement_listform, post_cardform userHasAccessForPost, Ajax search) - post_cardform_settings: add Einrichtungen/Fachbereiche tagify pickers with save/load - Tasks: add listEinrichts/listFachbereiche to repo+service, add UI selects in task_form, extend target-rule normalization and upsert builder, extend AssignmentResolverService scope loading and matchesAnyRule to include einricht/fachbereich matching Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1159 lines
67 KiB
PHP
1159 lines
67 KiB
PHP
<?php
|
||
declare(strict_types=1);
|
||
|
||
if (!isset($task) && !isset($rules) && !isset($error)) {
|
||
require_once dirname(dirname(__DIR__)) . '/actions/admin/task_form_action.php';
|
||
$viewData = task_cert_admin_task_form_action();
|
||
if (is_array($viewData)) {
|
||
extract($viewData, EXTR_SKIP);
|
||
}
|
||
}
|
||
|
||
$task = $task ?? null;
|
||
$rules = $rules ?? [];
|
||
$error = $error ?? null;
|
||
$lookup = $lookup ?? [];
|
||
$methodConfig = $method_config ?? [];
|
||
$targetSelection = $target_selection ?? [];
|
||
|
||
$taskId = (int) ($task['id'] ?? 0);
|
||
$savedSuccessfully = TaskCertRequest::queryInt('saved', 0) === 1;
|
||
$resetApplied = TaskCertRequest::queryInt('reset', 0) === 1;
|
||
$resetAssignments = TaskCertRequest::queryInt('reset_assignments', 0);
|
||
$resetSubmissions = TaskCertRequest::queryInt('reset_submissions', 0);
|
||
$resetCertificates = TaskCertRequest::queryInt('reset_certificates', 0);
|
||
$syncDone = TaskCertRequest::queryInt('sync_done', 0) === 1;
|
||
$syncError = TaskCertRequest::queryString('sync_error', '');
|
||
$syncMode = TaskCertRequest::queryString('sync_mode', '');
|
||
$syncApplied = TaskCertRequest::queryInt('sync_applied', 0) === 1;
|
||
$syncScope = TaskCertRequest::queryString('sync_scope', '');
|
||
$syncCycleKey = TaskCertRequest::queryString('sync_cycle_key', '');
|
||
$syncTargetCount = TaskCertRequest::queryInt('sync_target_count', 0);
|
||
$syncExistingCount = TaskCertRequest::queryInt('sync_existing_count', 0);
|
||
$syncCreateCount = TaskCertRequest::queryInt('sync_create_count', 0);
|
||
$syncUpdateCount = TaskCertRequest::queryInt('sync_update_count', 0);
|
||
$syncStaleCount = TaskCertRequest::queryInt('sync_stale_count', 0);
|
||
$syncProtectedCompletedCount = TaskCertRequest::queryInt('sync_protected_completed_count', 0);
|
||
$syncProtectedCertificateCount = TaskCertRequest::queryInt('sync_protected_certificate_count', 0);
|
||
$syncRemoveCount = TaskCertRequest::queryInt('sync_remove_count', 0);
|
||
$syncRemoveWithSubmissionsCount = TaskCertRequest::queryInt('sync_remove_with_submissions_count', 0);
|
||
$activeTab = TaskCertRequest::queryInt('tab', 0);
|
||
if ($activeTab < 0) {
|
||
$activeTab = 0;
|
||
}
|
||
$syncProtectedTotalCount = $syncProtectedCompletedCount + $syncProtectedCertificateCount;
|
||
$syncCanApply = $syncDone && $syncMode === 'dry_run';
|
||
|
||
$methodConfig = array_merge([
|
||
'enabled' => [
|
||
'knowledge_read' => 0,
|
||
'form_submit' => 0,
|
||
'quiz' => 0,
|
||
'manual_approval' => 0,
|
||
],
|
||
'knowledge_post_ids' => [],
|
||
'form_fields' => [],
|
||
'quiz_questions' => [],
|
||
], $methodConfig);
|
||
|
||
$targetSelection = array_merge([
|
||
'mandant_ids' => [],
|
||
'department_ids' => [],
|
||
'role_ids' => [],
|
||
'einricht_ids' => [],
|
||
'fachbereich_ids' => [],
|
||
'override_include_contact_ids' => [],
|
||
'override_exclude_contact_ids' => [],
|
||
'override_include_reason' => '',
|
||
'override_exclude_reason' => '',
|
||
], $targetSelection);
|
||
|
||
$reminderPolicy = $rules['reminder_policy'] ?? [
|
||
'base_mode' => 'due_date',
|
||
'first_escalation_days' => 0,
|
||
'step_days' => 7,
|
||
'max_level' => 3,
|
||
'grace_days' => 7,
|
||
'active' => 1,
|
||
];
|
||
$isReminderActive = (int) ($reminderPolicy['active'] ?? 1) === 1;
|
||
|
||
$formFields = is_array($methodConfig['form_fields']) ? $methodConfig['form_fields'] : [];
|
||
if ($formFields === []) {
|
||
$formFields = [[
|
||
'field_key' => '',
|
||
'label' => '',
|
||
'field_type' => 'text',
|
||
'is_required' => 0,
|
||
'placeholder' => '',
|
||
'options_text' => '',
|
||
]];
|
||
}
|
||
|
||
$quizQuestions = is_array($methodConfig['quiz_questions']) ? $methodConfig['quiz_questions'] : [];
|
||
if ($quizQuestions === []) {
|
||
$quizQuestions = [[
|
||
'question_text' => '',
|
||
'option_a' => '',
|
||
'option_b' => '',
|
||
'option_c' => '',
|
||
'option_d' => '',
|
||
'correct_option' => 'a',
|
||
]];
|
||
}
|
||
|
||
$lookupMandants = is_array($lookup['mandants'] ?? null) ? $lookup['mandants'] : [];
|
||
$lookupDepartments = is_array($lookup['departments'] ?? null) ? $lookup['departments'] : [];
|
||
$lookupRoles = is_array($lookup['roles'] ?? null) ? $lookup['roles'] : [];
|
||
$lookupEinrichts = is_array($lookup['einrichts'] ?? null) ? $lookup['einrichts'] : [];
|
||
$lookupFachbereiche = is_array($lookup['fachbereiche'] ?? null) ? $lookup['fachbereiche'] : [];
|
||
$lookupContacts = is_array($lookup['contacts'] ?? null) ? $lookup['contacts'] : [];
|
||
$lookupKnowledgePosts = is_array($lookup['knowledge_posts'] ?? null) ? $lookup['knowledge_posts'] : [];
|
||
$lookupTaskCategories = is_array($lookup['task_categories'] ?? null) ? $lookup['task_categories'] : [];
|
||
$hasLookupGaps = ($lookupMandants === [] || $lookupDepartments === [] || $lookupRoles === [] || $lookupContacts === []);
|
||
$hasTaskCategoryLookupGap = ($lookupTaskCategories === []);
|
||
|
||
$deadlineRuleType = (string) ($task['deadline_rule_type'] ?? 'cycle_end');
|
||
if (!in_array($deadlineRuleType, ['cycle_end', 'offset_days', 'weekday', 'monthday'], true)) {
|
||
$deadlineRuleType = 'cycle_end';
|
||
}
|
||
$deadlineOffsetDays = (int) ($task['deadline_offset_days'] ?? 0);
|
||
$deadlineWeekday = (int) ($task['deadline_weekday'] ?? 7);
|
||
if ($deadlineWeekday < 1 || $deadlineWeekday > 7) {
|
||
$deadlineWeekday = 7;
|
||
}
|
||
$deadlineMonthday = (int) ($task['deadline_monthday'] ?? 0);
|
||
$isRecurringFrequency = (($task['frequency_type'] ?? 'once') === 'recurring');
|
||
$recurrenceBasis = (string) ($task['recurrence_basis'] ?? 'calendar');
|
||
if (!in_array($recurrenceBasis, ['calendar', 'completion'], true)) {
|
||
$recurrenceBasis = 'calendar';
|
||
}
|
||
$startDateValueRaw = trim((string) ($task['start_date'] ?? ''));
|
||
if ($startDateValueRaw === '') {
|
||
$startDateValue = date('Y-m-d');
|
||
} else {
|
||
try {
|
||
$startDateValue = (new DateTimeImmutable($startDateValueRaw))->format('Y-m-d');
|
||
} catch (Throwable $throwable) {
|
||
$startDateValue = preg_match('/^\d{4}-\d{2}-\d{2}$/', $startDateValueRaw)
|
||
? $startDateValueRaw
|
||
: date('Y-m-d');
|
||
}
|
||
}
|
||
$certificateValidityMode = (string) ($task['certificate_validity_mode'] ?? '');
|
||
if (!in_array($certificateValidityMode, ['cycle', 'days', 'permanent'], true)) {
|
||
$certificateDays = (int) ($task['certificate_validity_days'] ?? 0);
|
||
if ($certificateDays > 0) {
|
||
$certificateValidityMode = 'days';
|
||
} elseif ($isRecurringFrequency) {
|
||
$certificateValidityMode = 'cycle';
|
||
} else {
|
||
$certificateValidityMode = 'permanent';
|
||
}
|
||
}
|
||
|
||
$ensureLookupRows = static function (array $rows, array $selectedIds, string $labelKey, string $fallbackPrefix): array {
|
||
$existingIds = [];
|
||
foreach ($rows as $row) {
|
||
$rowId = (int) ($row['id'] ?? 0);
|
||
if ($rowId > 0) {
|
||
$existingIds[$rowId] = true;
|
||
}
|
||
}
|
||
|
||
foreach (array_values(array_unique(array_map('intval', $selectedIds))) as $selectedId) {
|
||
if ($selectedId <= 0 || isset($existingIds[$selectedId])) {
|
||
continue;
|
||
}
|
||
|
||
$rows[] = [
|
||
'id' => $selectedId,
|
||
$labelKey => $fallbackPrefix . ' #' . $selectedId . ' (nicht gefunden)',
|
||
];
|
||
}
|
||
|
||
return $rows;
|
||
};
|
||
|
||
$lookupMandants = $ensureLookupRows($lookupMandants, (array) ($targetSelection['mandant_ids'] ?? []), 'description', 'Mandant');
|
||
$lookupDepartments = $ensureLookupRows($lookupDepartments, (array) ($targetSelection['department_ids'] ?? []), 'description', 'Abteilung');
|
||
$lookupRoles = $ensureLookupRows($lookupRoles, (array) ($targetSelection['role_ids'] ?? []), 'description', 'Rolle');
|
||
$lookupEinrichts = $ensureLookupRows($lookupEinrichts, (array) ($targetSelection['einricht_ids'] ?? []), 'description', 'Einrichtung');
|
||
$lookupFachbereiche = $ensureLookupRows($lookupFachbereiche, (array) ($targetSelection['fachbereich_ids'] ?? []), 'description', 'Fachbereich');
|
||
$lookupKnowledgePosts = $ensureLookupRows($lookupKnowledgePosts, (array) ($methodConfig['knowledge_post_ids'] ?? []), 'title', 'Beitrag');
|
||
$lookupTaskCategories = $ensureLookupRows($lookupTaskCategories, [(int) ($task['task_category_id'] ?? 0)], 'name', 'Kategorie');
|
||
$lookupContacts = $ensureLookupRows(
|
||
$lookupContacts,
|
||
array_merge(
|
||
(array) ($targetSelection['override_include_contact_ids'] ?? []),
|
||
(array) ($targetSelection['override_exclude_contact_ids'] ?? []),
|
||
(array) ($targetSelection['responsible_contact_ids'] ?? [])
|
||
),
|
||
'name',
|
||
'Kontakt'
|
||
);
|
||
|
||
$contactLabel = static function (array $contact): string {
|
||
$name = trim((string) ($contact['name'] ?? ''));
|
||
$email = trim((string) ($contact['email'] ?? ''));
|
||
|
||
if ($name !== '' && $email !== '') {
|
||
return $name . ' (' . $email . ')';
|
||
}
|
||
|
||
if ($name !== '') {
|
||
return $name;
|
||
}
|
||
|
||
if ($email !== '') {
|
||
return $email;
|
||
}
|
||
|
||
return 'Kontakt #' . (int) ($contact['id'] ?? 0);
|
||
};
|
||
|
||
if (!function_exists('task_cert_render_styles')) {
|
||
require_once dirname(dirname(__DIR__)) . '/bootstrap.php';
|
||
}
|
||
|
||
task_cert_render_styles(['admin'], ['vendor/multiselect/MultiSelect.css']);
|
||
task_cert_render_scripts([
|
||
'vendor/multiselect/MultiSelect.js',
|
||
'js/task_cert_admin_form_wizard.js',
|
||
]);
|
||
?>
|
||
<!-- Quill auf Goldstandard-Stack (wie Wiki/Forum/Products): CDN 2.0.0-dev.3 +
|
||
quill-better-table + shared_components (resize, presets). Die alte
|
||
vendored Quill (assets/vendor/quill/) wird hier bewusst nicht mehr geladen. -->
|
||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/quill@2.0.0-dev.3/dist/quill.snow.css">
|
||
<script src="https://cdn.jsdelivr.net/npm/quill@2.0.0-dev.3/dist/quill.min.js"></script>
|
||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/quill-better-table@1.2.10/dist/quill-better-table.css">
|
||
<script src="https://cdn.jsdelivr.net/npm/quill-better-table@1.2.10/dist/quill-better-table.min.js"></script>
|
||
<link rel="stylesheet" href="/module/shared_components/quill/quill_resize.css">
|
||
<script src="/module/shared_components/quill/quill_resize.js"></script>
|
||
<script src="/module/shared_components/quill/toolbar_presets.js"></script>
|
||
<?php
|
||
task_cert_render_scripts(['js/task_cert_quill_editor.js']);
|
||
|
||
$taskCertBaseUrl = task_cert_base_url();
|
||
$taskFormViewUrl = task_cert_view_url('admin.task_form');
|
||
$taskListViewUrl = task_cert_view_url('admin.task_list');
|
||
$taskDetailViewUrl = $taskId > 0
|
||
? task_cert_view_url('admin.task_detail', ['id' => $taskId])
|
||
: '';
|
||
$taskSyncAssignmentsEndpoint = $taskCertBaseUrl . '/endpoints/admin/sync_assignments.php';
|
||
$taskSyncRedirectUrl = $taskId > 0
|
||
? task_cert_view_url('admin.task_form', ['id' => $taskId])
|
||
: $taskFormViewUrl;
|
||
?>
|
||
<div class="task-cert task-cert-admin-form" data-task-cert-admin-wizard>
|
||
<form action="<?php echo TaskCertView::e($taskCertBaseUrl . '/endpoints/admin/task_upsert.php'); ?>" method="post">
|
||
<input type="hidden" name="csrf_token" value="<?php echo TaskCertView::e($csrf_token ?? ''); ?>">
|
||
<input type="hidden" name="task_id" value="<?php echo $taskId; ?>">
|
||
<input
|
||
type="hidden"
|
||
name="redirect_to_stay"
|
||
value="<?php echo TaskCertView::e($taskFormViewUrl . '?id={task_id}&saved=1'); ?>"
|
||
data-wizard-redirect-stay
|
||
>
|
||
<input type="hidden" name="redirect_to_close" value="<?php echo TaskCertView::e($taskListViewUrl); ?>">
|
||
|
||
<div class="task-cert-form-header">
|
||
<div class="task-cert-title-wrap">
|
||
<a
|
||
href="<?php echo TaskCertView::e($taskListViewUrl); ?>"
|
||
class="task-cert-back-link"
|
||
aria-label="Zur Aufgabenliste"
|
||
title="Zur Aufgabenliste"
|
||
>←</a>
|
||
<h2><?php echo $taskId > 0 ? 'Aufgabe bearbeiten' : 'Aufgabe erstellen'; ?></h2>
|
||
</div>
|
||
<div class="task-cert-form-header-actions">
|
||
<?php if ($taskId > 0): ?>
|
||
<a href="<?php echo TaskCertView::e($taskDetailViewUrl); ?>" class="btn">Aufgabenübersicht</a>
|
||
<?php endif; ?>
|
||
<button type="submit" class="btn task-cert-btn-success" name="after_save" value="stay">Speichern</button>
|
||
<button type="submit" class="btn task-cert-btn-success" name="after_save" value="close">Speichern & schließen</button>
|
||
</div>
|
||
</div>
|
||
|
||
<?php if ($error): ?>
|
||
<div class="task-cert-error"><?php echo TaskCertView::e($error); ?></div>
|
||
<?php elseif ($syncError !== ''): ?>
|
||
<div class="task-cert-error"><?php echo TaskCertView::e($syncError); ?></div>
|
||
<?php elseif ($savedSuccessfully): ?>
|
||
<div class="task-cert-success">Aufgabe wurde erfolgreich gespeichert.</div>
|
||
<?php if ($resetApplied): ?>
|
||
<div class="task-cert-warning">
|
||
Bestehender Fortschritt im aktuellen Zyklus wurde zurückgesetzt.
|
||
Zuweisungen: <?php echo (int) $resetAssignments; ?>,
|
||
Einreichungen: <?php echo (int) $resetSubmissions; ?>,
|
||
Zertifikate widerrufen: <?php echo (int) $resetCertificates; ?>.
|
||
</div>
|
||
<?php endif; ?>
|
||
<?php endif; ?>
|
||
|
||
<div class="task-cert-wizard-tabs" role="tablist" aria-label="Aufgaben-Assistent">
|
||
<button type="button" class="task-cert-wizard-tab" data-wizard-tab="0" role="tab">Stammdaten</button>
|
||
<button type="button" class="task-cert-wizard-tab" data-wizard-tab="1" role="tab">Zeitplan</button>
|
||
<button type="button" class="task-cert-wizard-tab" data-wizard-tab="2" role="tab">Zertifikat</button>
|
||
<button type="button" class="task-cert-wizard-tab" data-wizard-tab="3" role="tab">Bestätigungsvoraussetzungen</button>
|
||
<button type="button" class="task-cert-wizard-tab" data-wizard-tab="4" role="tab">Zielgruppe</button>
|
||
<button type="button" class="task-cert-wizard-tab" data-wizard-tab="5" role="tab">Ausnahmeregelungen</button>
|
||
<button type="button" class="task-cert-wizard-tab" data-wizard-tab="6" role="tab">Eskalation</button>
|
||
<button type="button" class="task-cert-wizard-tab" data-wizard-tab="7" role="tab">Gefahrenbereich</button>
|
||
<button type="button" class="task-cert-wizard-tab" data-wizard-tab="8" role="tab">Vorschau</button>
|
||
</div>
|
||
|
||
<section class="task-cert-wizard-step" data-wizard-step="0">
|
||
<aside class="task-cert-step-intro">
|
||
<strong>Grunddaten der Aufgabe</strong>
|
||
<span>Titel, Beschreibung und grundlegender Status.</span>
|
||
</aside>
|
||
|
||
<div class="task-cert-step-main">
|
||
<div class="task-cert-block">
|
||
<h3>Stammdaten</h3>
|
||
|
||
<div>
|
||
<label for="title">Titel</label>
|
||
<input type="text" id="title" name="title" value="<?php echo TaskCertView::e($task['title'] ?? ''); ?>" required>
|
||
</div>
|
||
|
||
<div>
|
||
<label for="description">Beschreibung</label>
|
||
<textarea id="description" name="description" rows="4"><?php echo TaskCertView::e($task['description'] ?? ''); ?></textarea>
|
||
</div>
|
||
|
||
<div>
|
||
<label for="task_category_id">Kategorie</label>
|
||
<select id="task_category_id" name="task_category_id" required>
|
||
<option value="">Bitte wählen</option>
|
||
<?php foreach ($lookupTaskCategories as $category): ?>
|
||
<?php $id = (int) ($category['id'] ?? 0); ?>
|
||
<?php if ($id <= 0) { continue; } ?>
|
||
<?php
|
||
$isSelectedCategory = ((int) ($task['task_category_id'] ?? 0) === $id);
|
||
$isActiveCategory = (int) ($category['active'] ?? 1) === 1;
|
||
$isDisabledCategory = !$isActiveCategory && !$isSelectedCategory;
|
||
$optionLabel = (string) ($category['name'] ?? ('Kategorie #' . $id));
|
||
if (!$isActiveCategory) {
|
||
$optionLabel .= ' (inaktiv)';
|
||
}
|
||
?>
|
||
<option
|
||
value="<?php echo $id; ?>"
|
||
<?php echo $isSelectedCategory ? 'selected' : ''; ?>
|
||
<?php echo $isDisabledCategory ? 'disabled' : ''; ?>
|
||
>
|
||
<?php echo TaskCertView::e($optionLabel); ?>
|
||
</option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
<?php if ($hasTaskCategoryLookupGap): ?>
|
||
<div class="task-cert-warning">Kategorien konnten nicht geladen werden. Bitte DB-Struktur/Migration prüfen.</div>
|
||
<?php endif; ?>
|
||
</div>
|
||
|
||
<div>
|
||
<label for="active">Aktiv</label>
|
||
<select id="active" name="active">
|
||
<option value="1" <?php echo ((int) ($task['active'] ?? 1) === 1) ? 'selected' : ''; ?>>Ja</option>
|
||
<option value="0" <?php echo ((int) ($task['active'] ?? 1) === 0) ? 'selected' : ''; ?>>Nein</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div>
|
||
<label for="responsible_contact_ids">Verantwortliche</label>
|
||
<select id="responsible_contact_ids" class="tc-multiselect" name="responsible_contact_ids[]" multiple data-placeholder="Verantwortliche auswählen">
|
||
<?php foreach ($lookupContacts as $contact): ?>
|
||
<?php $id = (int) ($contact['id'] ?? 0); ?>
|
||
<?php if ($id <= 0) { continue; } ?>
|
||
<?php $label = $contactLabel($contact); ?>
|
||
<option value="<?php echo $id; ?>" <?php echo in_array($id, (array) ($targetSelection['responsible_contact_ids'] ?? []), true) ? 'selected' : ''; ?>>
|
||
<?php echo TaskCertView::e($label); ?>
|
||
</option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
<div class="task-cert-hint">Ansprechpartner, die den Mitarbeitenden als Kontakt für diese Aufgabe angezeigt werden.</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="task-cert-wizard-step" data-wizard-step="1" hidden>
|
||
<aside class="task-cert-step-intro">
|
||
<strong>Zeitplan und Fälligkeit</strong>
|
||
<span>Lege Startdatum, Wiederholung und Fälligkeit in einem Schritt fest.</span>
|
||
</aside>
|
||
|
||
<div class="task-cert-step-main">
|
||
<div class="task-cert-block">
|
||
<h3>Start & Wiederholung</h3>
|
||
|
||
<div>
|
||
<label for="start_date">Startdatum</label>
|
||
<input type="date" id="start_date" name="start_date" value="<?php echo TaskCertView::e($startDateValue); ?>" required>
|
||
<div class="task-cert-hint">
|
||
Die Aufgabe wird den zugewiesenen Benutzern erst am Startdatum angezeigt.
|
||
</div>
|
||
</div>
|
||
|
||
<div>
|
||
<label for="frequency_type">Frequenz</label>
|
||
<select id="frequency_type" name="frequency_type" data-frequency-toggle>
|
||
<option value="once" <?php echo (($task['frequency_type'] ?? 'once') === 'once') ? 'selected' : ''; ?>>Einmalig</option>
|
||
<option value="recurring" <?php echo (($task['frequency_type'] ?? '') === 'recurring') ? 'selected' : ''; ?>>Wiederkehrend</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div data-frequency-group="recurring" <?php echo $isRecurringFrequency ? '' : 'hidden'; ?>>
|
||
<label for="recurrence_basis">Rhythmus-Basis</label>
|
||
<select id="recurrence_basis" name="recurrence_basis" <?php echo $isRecurringFrequency ? '' : 'disabled'; ?>>
|
||
<option value="calendar" <?php echo $recurrenceBasis === 'calendar' ? 'selected' : ''; ?>>Kalenderbasiert</option>
|
||
<option value="completion" <?php echo $recurrenceBasis === 'completion' ? 'selected' : ''; ?>>Erledigungsbasiert (pro Mitarbeiter)</option>
|
||
</select>
|
||
<div class="task-cert-hint">
|
||
Kalenderbasiert folgt festen Intervallen ab Startdatum. Erledigungsbasiert startet die nächste Periode erst nach Abschluss.
|
||
</div>
|
||
</div>
|
||
|
||
<div class="task-cert-recurrence-inline" data-frequency-group="recurring" <?php echo $isRecurringFrequency ? '' : 'hidden'; ?>>
|
||
<div>
|
||
<label for="recurrence_unit">Intervall-Einheit</label>
|
||
<select id="recurrence_unit" name="recurrence_unit" <?php echo $isRecurringFrequency ? '' : 'disabled'; ?>>
|
||
<option value="">-</option>
|
||
<option value="day" <?php echo (($task['recurrence_unit'] ?? '') === 'day') ? 'selected' : ''; ?>>Tag</option>
|
||
<option value="week" <?php echo (($task['recurrence_unit'] ?? '') === 'week') ? 'selected' : ''; ?>>Woche</option>
|
||
<option value="month" <?php echo (($task['recurrence_unit'] ?? '') === 'month') ? 'selected' : ''; ?>>Monat</option>
|
||
<option value="year" <?php echo (($task['recurrence_unit'] ?? '') === 'year') ? 'selected' : ''; ?>>Jahr</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div>
|
||
<label for="recurrence_interval">Intervall</label>
|
||
<input type="number" id="recurrence_interval" name="recurrence_interval" min="1" value="<?php echo (int) ($task['recurrence_interval'] ?? 1); ?>" <?php echo $isRecurringFrequency ? '' : 'disabled'; ?>>
|
||
</div>
|
||
</div>
|
||
|
||
<div data-frequency-group="recurring" <?php echo $isRecurringFrequency ? '' : 'hidden'; ?>>
|
||
<label>
|
||
<input type="checkbox" name="show_cycle_period" value="1" <?php echo ((int) ($task['show_cycle_period'] ?? 0) === 1) ? 'checked' : ''; ?>>
|
||
Zeitraum in „Meine Aufgaben" anzeigen
|
||
</label>
|
||
<div class="task-cert-hint">
|
||
Zeigt den Erledigungs-Zeitraum (z. B. 01.06.–30.06.) hinter dem Fälligkeitsdatum. Standardmäßig aus.
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="task-cert-block">
|
||
<h3>Fälligkeit</h3>
|
||
|
||
<div class="task-cert-deadline-mode">
|
||
<label class="task-cert-deadline-choice">
|
||
<input type="radio" name="deadline_rule_type" value="cycle_end" data-deadline-rule <?php echo $deadlineRuleType === 'cycle_end' ? 'checked' : ''; ?>>
|
||
<span class="task-cert-deadline-choice-title">Bis Zyklusende</span>
|
||
<span class="task-cert-deadline-choice-text">Fälligkeit liegt immer am Ende des aktuellen Zyklus.</span>
|
||
</label>
|
||
|
||
<label class="task-cert-deadline-choice">
|
||
<input type="radio" name="deadline_rule_type" value="offset_days" data-deadline-rule <?php echo $deadlineRuleType === 'offset_days' ? 'checked' : ''; ?>>
|
||
<span class="task-cert-deadline-choice-title">X Tage nach Startdatum</span>
|
||
<span class="task-cert-deadline-choice-text">Frist wird relativ zum Startdatum gesetzt.</span>
|
||
</label>
|
||
|
||
<label class="task-cert-deadline-choice">
|
||
<input type="radio" name="deadline_rule_type" value="weekday" data-deadline-rule <?php echo $deadlineRuleType === 'weekday' ? 'checked' : ''; ?>>
|
||
<span class="task-cert-deadline-choice-title">Fester Wochentag</span>
|
||
<span class="task-cert-deadline-choice-text">Nur bei wöchentlichen Aufgaben. Beispiel: bis Sonntag.</span>
|
||
</label>
|
||
|
||
<label class="task-cert-deadline-choice">
|
||
<input type="radio" name="deadline_rule_type" value="monthday" data-deadline-rule <?php echo $deadlineRuleType === 'monthday' ? 'checked' : ''; ?>>
|
||
<span class="task-cert-deadline-choice-title">Fester Monatstag</span>
|
||
<span class="task-cert-deadline-choice-text">Nur für monatliche/jährliche Aufgaben. Kürzere Monate werden gekappt.</span>
|
||
</label>
|
||
</div>
|
||
|
||
<div class="task-cert-deadline-group" data-deadline-group="cycle_end" hidden>
|
||
<div class="task-cert-hint">Die Frist entspricht exakt dem Ende des aktuellen Zyklus.</div>
|
||
</div>
|
||
|
||
<div class="task-cert-deadline-group" data-deadline-group="offset_days" hidden>
|
||
<label for="deadline_offset_days">Fällig nach (Tagen)</label>
|
||
<input type="number" id="deadline_offset_days" name="deadline_offset_days" min="1" value="<?php echo TaskCertView::e($deadlineOffsetDays > 0 ? (string) $deadlineOffsetDays : ''); ?>">
|
||
</div>
|
||
|
||
<div class="task-cert-deadline-group" data-deadline-group="weekday" hidden>
|
||
<label for="deadline_weekday">Wochentag</label>
|
||
<select id="deadline_weekday" name="deadline_weekday">
|
||
<option value="1" <?php echo $deadlineWeekday === 1 ? 'selected' : ''; ?>>Montag</option>
|
||
<option value="2" <?php echo $deadlineWeekday === 2 ? 'selected' : ''; ?>>Dienstag</option>
|
||
<option value="3" <?php echo $deadlineWeekday === 3 ? 'selected' : ''; ?>>Mittwoch</option>
|
||
<option value="4" <?php echo $deadlineWeekday === 4 ? 'selected' : ''; ?>>Donnerstag</option>
|
||
<option value="5" <?php echo $deadlineWeekday === 5 ? 'selected' : ''; ?>>Freitag</option>
|
||
<option value="6" <?php echo $deadlineWeekday === 6 ? 'selected' : ''; ?>>Samstag</option>
|
||
<option value="7" <?php echo $deadlineWeekday === 7 ? 'selected' : ''; ?>>Sonntag</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="task-cert-deadline-group" data-deadline-group="monthday" hidden>
|
||
<div>
|
||
<label for="deadline_monthday">Monatstag</label>
|
||
<input type="number" id="deadline_monthday" name="deadline_monthday" min="1" max="31" value="<?php echo TaskCertView::e($deadlineMonthday > 0 ? (string) $deadlineMonthday : ''); ?>">
|
||
</div>
|
||
</div>
|
||
|
||
<div class="task-cert-hint task-cert-deadline-preview" data-deadline-preview></div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="task-cert-wizard-step" data-wizard-step="2" hidden>
|
||
<aside class="task-cert-step-intro">
|
||
<strong>Zertifikatslogik</strong>
|
||
<span>Aktiviere Zertifikate und steuere deren Gültigkeit.</span>
|
||
</aside>
|
||
|
||
<div class="task-cert-step-main">
|
||
<div class="task-cert-block">
|
||
<h3>Zertifikat</h3>
|
||
|
||
<div>
|
||
<label for="certificate_enabled">Zertifikat aktiv</label>
|
||
<select id="certificate_enabled" name="certificate_enabled">
|
||
<option value="1" <?php echo ((int) ($task['certificate_enabled'] ?? 1) === 1) ? 'selected' : ''; ?>>Ja</option>
|
||
<option value="0" <?php echo ((int) ($task['certificate_enabled'] ?? 1) === 0) ? 'selected' : ''; ?>>Nein</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div>
|
||
<label for="certificate_title">Zertifikat Titel</label>
|
||
<input type="text" id="certificate_title" name="certificate_title" value="<?php echo TaskCertView::e($task['certificate_title'] ?? ''); ?>">
|
||
</div>
|
||
|
||
<div>
|
||
<label for="certificate_validity_mode">Gültigkeitsmodus</label>
|
||
<select id="certificate_validity_mode" name="certificate_validity_mode" data-certificate-validity-mode>
|
||
<option value="cycle" <?php echo $certificateValidityMode === 'cycle' ? 'selected' : ''; ?>>Zyklusbasiert (bis zum nächsten Zyklus)</option>
|
||
<option value="days" <?php echo $certificateValidityMode === 'days' ? 'selected' : ''; ?>>Tagebasiert</option>
|
||
<option value="permanent" <?php echo $certificateValidityMode === 'permanent' ? 'selected' : ''; ?>>Dauerhaft</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div data-certificate-validity-group="days" <?php echo $certificateValidityMode === 'days' ? '' : 'hidden'; ?>>
|
||
<label for="certificate_validity_days">Gültigkeit in Tagen</label>
|
||
<input type="number" id="certificate_validity_days" name="certificate_validity_days" min="1" value="<?php echo TaskCertView::e((string) ($task['certificate_validity_days'] ?? '')); ?>">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="task-cert-wizard-step" data-wizard-step="3" hidden>
|
||
<aside class="task-cert-step-intro">
|
||
<strong>Bestätigungsvoraussetzungen</strong>
|
||
<span>Alle Methoden in einem Reiter. Details werden nur bei aktivierter Methode angezeigt.</span>
|
||
</aside>
|
||
|
||
<div class="task-cert-step-main">
|
||
<div class="task-cert-block">
|
||
<h3>Methoden</h3>
|
||
|
||
<div class="task-cert-method-section">
|
||
<div class="task-cert-method-header">
|
||
<label>
|
||
<input type="checkbox" data-method-toggle="knowledge_read" name="method_enabled[knowledge_read]" value="1" <?php echo ((int) ($methodConfig['enabled']['knowledge_read'] ?? 0) === 1) ? 'checked' : ''; ?>>
|
||
Wissensbeiträge lesen
|
||
</label>
|
||
</div>
|
||
<div class="task-cert-method-details" data-method-details="knowledge_read" hidden>
|
||
<label for="knowledge_post_ids">Verknüpfte Beiträge</label>
|
||
<select id="knowledge_post_ids" class="tc-multiselect" name="knowledge_post_ids[]" multiple data-placeholder="Beiträge auswählen">
|
||
<?php foreach ($lookupKnowledgePosts as $post): ?>
|
||
<?php $postId = (int) ($post['id'] ?? 0); ?>
|
||
<?php if ($postId <= 0) { continue; } ?>
|
||
<option value="<?php echo $postId; ?>" <?php echo in_array($postId, (array) $methodConfig['knowledge_post_ids'], true) ? 'selected' : ''; ?>>
|
||
<?php echo TaskCertView::e((string) ($post['title'] ?? ('Beitrag #' . $postId))); ?>
|
||
</option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="task-cert-method-section">
|
||
<div class="task-cert-method-header">
|
||
<label>
|
||
<input type="checkbox" data-method-toggle="form_submit" name="method_enabled[form_submit]" value="1" <?php echo ((int) ($methodConfig['enabled']['form_submit'] ?? 0) === 1) ? 'checked' : ''; ?>>
|
||
Formular ausfüllen
|
||
</label>
|
||
</div>
|
||
<div class="task-cert-method-details" data-method-details="form_submit" hidden>
|
||
<strong>Formularfelder</strong>
|
||
<?php
|
||
$prefillOptionsJson = [];
|
||
foreach (ContactPrefillColumns::MAP as $colKey => $colLabel) {
|
||
$prefillOptionsJson[] = ['value' => $colKey, 'label' => $colLabel];
|
||
}
|
||
?>
|
||
<div id="task-cert-form-fields" data-next-index="<?php echo count($formFields); ?>" data-prefill-options="<?php echo TaskCertView::e(json_encode($prefillOptionsJson, JSON_UNESCAPED_UNICODE)); ?>">
|
||
<?php foreach ($formFields as $idx => $field): ?>
|
||
<?php $fieldTypeCurrent = (string) ($field['field_type'] ?? 'text'); ?>
|
||
<?php $showOptions = in_array($fieldTypeCurrent, ['select', 'checkbox', 'radio'], true); ?>
|
||
<div class="task-cert-form-row" data-form-row>
|
||
<div class="task-cert-form-row-head">
|
||
<strong class="task-cert-form-row-title">Formularfeld</strong>
|
||
<button type="button" class="btn task-cert-btn-danger" data-remove-form-row>Entfernen</button>
|
||
</div>
|
||
|
||
<input type="hidden" name="form_fields[<?php echo (int) $idx; ?>][field_key]" value="<?php echo TaskCertView::e((string) ($field['field_key'] ?? '')); ?>">
|
||
|
||
<div class="task-cert-form-row-grid">
|
||
<div class="task-cert-form-cell">
|
||
<label>Label</label>
|
||
<input
|
||
type="text"
|
||
name="form_fields[<?php echo (int) $idx; ?>][label]"
|
||
placeholder="z. B. Kilometerstand"
|
||
value="<?php echo TaskCertView::e((string) ($field['label'] ?? '')); ?>"
|
||
>
|
||
</div>
|
||
|
||
<div class="task-cert-form-cell">
|
||
<label>Typ</label>
|
||
<select name="form_fields[<?php echo (int) $idx; ?>][field_type]" data-form-field-type>
|
||
<?php foreach (TaskCertConstants::FIELD_TYPES as $fieldType): ?>
|
||
<option value="<?php echo TaskCertView::e($fieldType); ?>" <?php echo ($fieldTypeCurrent === $fieldType) ? 'selected' : ''; ?>>
|
||
<?php echo TaskCertView::e($fieldType); ?>
|
||
</option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="task-cert-form-cell task-cert-form-required">
|
||
<label>
|
||
<input type="checkbox" name="form_fields[<?php echo (int) $idx; ?>][is_required]" value="1" <?php echo ((int) ($field['is_required'] ?? 0) === 1) ? 'checked' : ''; ?>>
|
||
Pflicht
|
||
</label>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="task-cert-form-row-grid task-cert-form-row-grid-secondary">
|
||
<div class="task-cert-form-cell">
|
||
<label>Placeholder</label>
|
||
<input
|
||
type="text"
|
||
name="form_fields[<?php echo (int) $idx; ?>][placeholder]"
|
||
placeholder="Optionaler Hinweistext"
|
||
value="<?php echo TaskCertView::e((string) ($field['placeholder'] ?? '')); ?>"
|
||
>
|
||
</div>
|
||
|
||
<div class="task-cert-form-cell" data-form-field-prefill <?php echo $fieldTypeCurrent !== 'text' ? 'hidden' : ''; ?>>
|
||
<label>Vorbefüllung</label>
|
||
<?php $currentPrefill = (string) ($field['prefill_column'] ?? ''); ?>
|
||
<select name="form_fields[<?php echo (int) $idx; ?>][prefill_column]">
|
||
<?php foreach (ContactPrefillColumns::MAP as $colKey => $colLabel): ?>
|
||
<option value="<?php echo TaskCertView::e($colKey); ?>" <?php echo $currentPrefill === $colKey ? 'selected' : ''; ?>>
|
||
<?php echo TaskCertView::e($colLabel); ?>
|
||
</option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="task-cert-form-field-options" data-form-field-options <?php echo $showOptions ? '' : 'hidden'; ?>>
|
||
<label>Optionen (Komma getrennt)</label>
|
||
<input
|
||
type="text"
|
||
name="form_fields[<?php echo (int) $idx; ?>][options_text]"
|
||
placeholder="z. B. Ja, Nein"
|
||
value="<?php echo TaskCertView::e((string) ($field['options_text'] ?? '')); ?>"
|
||
>
|
||
</div>
|
||
</div>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
<button type="button" class="btn task-cert-btn-success" id="task-cert-add-form-row">Feld hinzufügen</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="task-cert-method-section">
|
||
<div class="task-cert-method-header">
|
||
<label>
|
||
<input type="checkbox" data-method-toggle="quiz" name="method_enabled[quiz]" value="1" <?php echo ((int) ($methodConfig['enabled']['quiz'] ?? 0) === 1) ? 'checked' : ''; ?>>
|
||
Quiz
|
||
</label>
|
||
</div>
|
||
<div class="task-cert-method-details" data-method-details="quiz" hidden>
|
||
<strong>Quizfragen</strong>
|
||
<div id="task-cert-quiz-questions" data-next-index="<?php echo count($quizQuestions); ?>">
|
||
<?php foreach ($quizQuestions as $idx => $question): ?>
|
||
<div class="task-cert-quiz-row" data-quiz-row>
|
||
<div class="task-cert-quiz-question">
|
||
<label>Frage</label>
|
||
<input type="text" name="quiz_questions[<?php echo (int) $idx; ?>][question_text]" placeholder="Frage" value="<?php echo TaskCertView::e((string) ($question['question_text'] ?? '')); ?>">
|
||
</div>
|
||
|
||
<div class="task-cert-quiz-answers">
|
||
<div class="task-cert-quiz-answer">
|
||
<label>Antwort A</label>
|
||
<input type="text" name="quiz_questions[<?php echo (int) $idx; ?>][option_a]" placeholder="Antwort A" value="<?php echo TaskCertView::e((string) ($question['option_a'] ?? '')); ?>">
|
||
</div>
|
||
<div class="task-cert-quiz-answer">
|
||
<label>Antwort B</label>
|
||
<input type="text" name="quiz_questions[<?php echo (int) $idx; ?>][option_b]" placeholder="Antwort B" value="<?php echo TaskCertView::e((string) ($question['option_b'] ?? '')); ?>">
|
||
</div>
|
||
<div class="task-cert-quiz-answer">
|
||
<label>Antwort C</label>
|
||
<input type="text" name="quiz_questions[<?php echo (int) $idx; ?>][option_c]" placeholder="Antwort C" value="<?php echo TaskCertView::e((string) ($question['option_c'] ?? '')); ?>">
|
||
</div>
|
||
<div class="task-cert-quiz-answer">
|
||
<label>Antwort D</label>
|
||
<input type="text" name="quiz_questions[<?php echo (int) $idx; ?>][option_d]" placeholder="Antwort D" value="<?php echo TaskCertView::e((string) ($question['option_d'] ?? '')); ?>">
|
||
</div>
|
||
</div>
|
||
|
||
<div class="task-cert-quiz-footer">
|
||
<div class="task-cert-quiz-correct">
|
||
<label>Richtige Antwort</label>
|
||
<select name="quiz_questions[<?php echo (int) $idx; ?>][correct_option]">
|
||
<?php foreach (['a', 'b', 'c', 'd'] as $letter): ?>
|
||
<option value="<?php echo $letter; ?>" <?php echo (($question['correct_option'] ?? 'a') === $letter) ? 'selected' : ''; ?>>
|
||
Antwort <?php echo strtoupper($letter); ?>
|
||
</option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
</div>
|
||
<div class="task-cert-quiz-remove">
|
||
<button type="button" class="btn task-cert-btn-danger" data-remove-quiz-row>Entfernen</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
<button type="button" class="btn task-cert-btn-success" id="task-cert-add-quiz-row">Frage hinzufügen</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="task-cert-method-section">
|
||
<div class="task-cert-method-header">
|
||
<label>
|
||
<input type="checkbox" data-method-toggle="manual_approval" name="method_enabled[manual_approval]" value="1" <?php echo ((int) ($methodConfig['enabled']['manual_approval'] ?? 0) === 1) ? 'checked' : ''; ?>>
|
||
Manuelle Freigabe
|
||
</label>
|
||
</div>
|
||
<div class="task-cert-method-details" data-method-details="manual_approval" hidden>
|
||
<div class="task-cert-hint">Mitarbeiter stellen Antrag, Admin entscheidet in der Freigabe-Warteschlange.</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="task-cert-hint">Empfehlung: Mindestens eine Methode aktivieren.</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="task-cert-wizard-step" data-wizard-step="4" hidden>
|
||
<aside class="task-cert-step-intro">
|
||
<strong>Zielgruppe</strong>
|
||
<span>Ordne die Aufgabe über Mandanten, Abteilungen und Rollen zu.</span>
|
||
</aside>
|
||
|
||
<div class="task-cert-step-main">
|
||
<?php if ($hasLookupGaps): ?>
|
||
<div class="task-cert-error">
|
||
Einige Auswahl-Listen sind leer. Bitte DB-Zugriff und Legacy-Tabellen prüfen (`main_mandant`, `main_department`, `main_role`, `main_contact`).
|
||
</div>
|
||
<?php endif; ?>
|
||
|
||
<div class="task-cert-block">
|
||
<h3>Zielgruppe</h3>
|
||
|
||
<div>
|
||
<label for="target_mandant_ids">Mandanten</label>
|
||
<select id="target_mandant_ids" class="tc-multiselect" name="target_mandant_ids[]" multiple data-placeholder="Mandanten auswählen">
|
||
<?php foreach ($lookupMandants as $mandant): ?>
|
||
<?php $id = (int) ($mandant['id'] ?? 0); ?>
|
||
<?php if ($id <= 0) { continue; } ?>
|
||
<option value="<?php echo $id; ?>" <?php echo in_array($id, (array) $targetSelection['mandant_ids'], true) ? 'selected' : ''; ?>>
|
||
<?php echo TaskCertView::e((string) ($mandant['description'] ?? ('Mandant #' . $id))); ?>
|
||
</option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
</div>
|
||
|
||
<div>
|
||
<label for="target_department_ids">Abteilungen</label>
|
||
<select id="target_department_ids" class="tc-multiselect" name="target_department_ids[]" multiple data-placeholder="Abteilungen auswählen">
|
||
<?php foreach ($lookupDepartments as $department): ?>
|
||
<?php $id = (int) ($department['id'] ?? 0); ?>
|
||
<?php if ($id <= 0) { continue; } ?>
|
||
<option value="<?php echo $id; ?>" <?php echo in_array($id, (array) $targetSelection['department_ids'], true) ? 'selected' : ''; ?>>
|
||
<?php echo TaskCertView::e((string) ($department['description'] ?? ('Abteilung #' . $id))); ?>
|
||
</option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
</div>
|
||
|
||
<div>
|
||
<label for="target_role_ids">Rollen</label>
|
||
<select id="target_role_ids" class="tc-multiselect" name="target_role_ids[]" multiple data-placeholder="Rollen auswählen">
|
||
<?php foreach ($lookupRoles as $role): ?>
|
||
<?php $id = (int) ($role['id'] ?? 0); ?>
|
||
<?php if ($id <= 0) { continue; } ?>
|
||
<option value="<?php echo $id; ?>" <?php echo in_array($id, (array) $targetSelection['role_ids'], true) ? 'selected' : ''; ?>>
|
||
<?php echo TaskCertView::e((string) ($role['description'] ?? ('Rolle #' . $id))); ?>
|
||
</option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
</div>
|
||
|
||
<div>
|
||
<label for="target_einricht_ids">Einrichtungen</label>
|
||
<select id="target_einricht_ids" class="tc-multiselect" name="target_einricht_ids[]" multiple data-placeholder="Einrichtungen auswählen">
|
||
<?php foreach ($lookupEinrichts as $einricht): ?>
|
||
<?php $id = (int) ($einricht['id'] ?? 0); ?>
|
||
<?php if ($id <= 0) { continue; } ?>
|
||
<option value="<?php echo $id; ?>" <?php echo in_array($id, (array) $targetSelection['einricht_ids'], true) ? 'selected' : ''; ?>>
|
||
<?php echo TaskCertView::e((string) ($einricht['description'] ?? ('Einrichtung #' . $id))); ?>
|
||
</option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
</div>
|
||
|
||
<div>
|
||
<label for="target_fachbereich_ids">Fachbereiche</label>
|
||
<select id="target_fachbereich_ids" class="tc-multiselect" name="target_fachbereich_ids[]" multiple data-placeholder="Fachbereiche auswählen">
|
||
<?php foreach ($lookupFachbereiche as $fachbereich): ?>
|
||
<?php $id = (int) ($fachbereich['id'] ?? 0); ?>
|
||
<?php if ($id <= 0) { continue; } ?>
|
||
<option value="<?php echo $id; ?>" <?php echo in_array($id, (array) $targetSelection['fachbereich_ids'], true) ? 'selected' : ''; ?>>
|
||
<?php echo TaskCertView::e((string) ($fachbereich['description'] ?? ('Fachbereich #' . $id))); ?>
|
||
</option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="task-cert-wizard-step" data-wizard-step="5" hidden>
|
||
<aside class="task-cert-step-intro">
|
||
<strong>Ausnahmeregelungen</strong>
|
||
<span>Einzelne Mitarbeiter explizit ein- oder ausschließen.</span>
|
||
</aside>
|
||
|
||
<div class="task-cert-step-main">
|
||
<?php if ($hasLookupGaps): ?>
|
||
<div class="task-cert-error">
|
||
Kontaktlisten konnten nicht vollständig geladen werden. Bitte Berechtigungen und Datenbasis prüfen.
|
||
</div>
|
||
<?php endif; ?>
|
||
|
||
<div class="task-cert-block">
|
||
<h3>Ausnahmeregelungen</h3>
|
||
|
||
<div>
|
||
<label for="override_include_contact_ids">Mitarbeiter zusätzlich einschließen</label>
|
||
<select id="override_include_contact_ids" class="tc-multiselect" name="override_include_contact_ids[]" multiple data-placeholder="Mitarbeiter hinzufügen">
|
||
<?php foreach ($lookupContacts as $contact): ?>
|
||
<?php $id = (int) ($contact['id'] ?? 0); ?>
|
||
<?php if ($id <= 0) { continue; } ?>
|
||
<?php $label = $contactLabel($contact); ?>
|
||
<option value="<?php echo $id; ?>" <?php echo in_array($id, (array) $targetSelection['override_include_contact_ids'], true) ? 'selected' : ''; ?>>
|
||
<?php echo TaskCertView::e($label); ?>
|
||
</option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
</div>
|
||
|
||
<div>
|
||
<label for="override_include_reason">Grund (Include)</label>
|
||
<input type="text" id="override_include_reason" name="override_include_reason" value="<?php echo TaskCertView::e((string) ($targetSelection['override_include_reason'] ?? '')); ?>">
|
||
</div>
|
||
|
||
<div>
|
||
<label for="override_exclude_contact_ids">Mitarbeiter ausschließen</label>
|
||
<select id="override_exclude_contact_ids" class="tc-multiselect" name="override_exclude_contact_ids[]" multiple data-placeholder="Mitarbeiter ausschließen">
|
||
<?php foreach ($lookupContacts as $contact): ?>
|
||
<?php $id = (int) ($contact['id'] ?? 0); ?>
|
||
<?php if ($id <= 0) { continue; } ?>
|
||
<?php $label = $contactLabel($contact); ?>
|
||
<option value="<?php echo $id; ?>" <?php echo in_array($id, (array) $targetSelection['override_exclude_contact_ids'], true) ? 'selected' : ''; ?>>
|
||
<?php echo TaskCertView::e($label); ?>
|
||
</option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
</div>
|
||
|
||
<div>
|
||
<label for="override_exclude_reason">Grund (Exclude)</label>
|
||
<input type="text" id="override_exclude_reason" name="override_exclude_reason" value="<?php echo TaskCertView::e((string) ($targetSelection['override_exclude_reason'] ?? '')); ?>">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="task-cert-wizard-step" data-wizard-step="6" hidden>
|
||
<aside class="task-cert-step-intro">
|
||
<strong>Eskalation nach Fälligkeit</strong>
|
||
<span>Lege fest, wann und in welchen Stufen nach Fälligkeit eskaliert wird.</span>
|
||
</aside>
|
||
|
||
<div class="task-cert-step-main">
|
||
|
||
<div class="task-cert-block">
|
||
<div class="task-cert-escalation-toggle">
|
||
<label>
|
||
<input
|
||
type="checkbox"
|
||
id="reminder_active"
|
||
name="reminder_active"
|
||
value="1"
|
||
<?php echo $isReminderActive ? 'checked' : ''; ?>
|
||
>
|
||
Eskalation aktiv
|
||
</label>
|
||
<input type="hidden" name="reminder_base_mode" value="due_date">
|
||
</div>
|
||
|
||
<div data-reminder-settings <?php echo $isReminderActive ? '' : 'hidden'; ?>>
|
||
|
||
<h3>Eskalationsstufen</h3>
|
||
<div class="task-cert-escalation-fields">
|
||
<div>
|
||
<label for="reminder_first_escalation_days">Wartezeit</label>
|
||
<div class="task-cert-input-with-suffix">
|
||
<input type="number" id="reminder_first_escalation_days" name="reminder_first_escalation_days" min="0" value="<?php echo (int) ($reminderPolicy['first_escalation_days'] ?? 0); ?>" data-escalation-input>
|
||
<span>Tage nach Fälligkeit</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div>
|
||
<label for="reminder_step_days">Stufenintervall</label>
|
||
<div class="task-cert-input-with-suffix">
|
||
<input type="number" id="reminder_step_days" name="reminder_step_days" min="1" value="<?php echo (int) ($reminderPolicy['step_days'] ?? 7); ?>" data-escalation-input>
|
||
<span>Tage pro Stufe</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div>
|
||
<label for="reminder_max_level">Maximum</label>
|
||
<div class="task-cert-input-with-suffix">
|
||
<input type="number" id="reminder_max_level" name="reminder_max_level" min="1" max="5" value="<?php echo min(5, (int) ($reminderPolicy['max_level'] ?? 3)); ?>" data-escalation-input>
|
||
<span>Stufen</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<hr class="task-cert-escalation-divider">
|
||
|
||
<h3>Karenzzeit</h3>
|
||
<div class="task-cert-escalation-fields">
|
||
<div>
|
||
<label for="reminder_grace_days">Mindestbearbeitungszeit</label>
|
||
<div class="task-cert-input-with-suffix">
|
||
<input type="number" id="reminder_grace_days" name="reminder_grace_days" min="0" value="<?php echo (int) ($reminderPolicy['grace_days'] ?? 7); ?>" data-escalation-input>
|
||
<span>Tage</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="task-cert-hint">
|
||
Wenn ein Mitarbeiter mitten im Zyklus zugewiesen wird, greift die Eskalation erst nach dieser Frist.
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</section>
|
||
|
||
<section class="task-cert-wizard-step" data-wizard-step="7" hidden>
|
||
<aside class="task-cert-step-intro">
|
||
<strong>Gefahrenbereich</strong>
|
||
<span>Aktionen mit direkter Auswirkung auf bestehende Daten und Aufgabenstatus.</span>
|
||
</aside>
|
||
|
||
<div class="task-cert-step-main">
|
||
<?php if ($taskId > 0): ?>
|
||
<div class="task-cert-block task-cert-danger-block">
|
||
<h3>Aufgabe deaktivieren</h3>
|
||
<button
|
||
type="submit"
|
||
class="btn task-cert-btn-danger"
|
||
form="task-cert-delete-form"
|
||
onclick="return confirm('Aufgabe wirklich deaktivieren?');"
|
||
>Nur deaktivieren</button>
|
||
<div class="task-cert-hint">
|
||
Die Aufgabe wird auf inaktiv gesetzt. Einreichungen, Zertifikate und Historie bleiben erhalten.
|
||
</div>
|
||
</div>
|
||
|
||
<div class="task-cert-block task-cert-danger-block">
|
||
<h3>Aufgabe endgültig löschen</h3>
|
||
<div class="task-cert-warning">
|
||
Diese Aktion ist endgültig und entfernt die Aufgabe inklusive aller abhängigen Daten.
|
||
</div>
|
||
<button
|
||
type="submit"
|
||
class="btn task-cert-btn-danger"
|
||
form="task-cert-hard-delete-form"
|
||
onclick="return confirm('Aufgabe UND alle zugehörigen Daten endgültig löschen? Dieser Vorgang kann nicht rückgängig gemacht werden.');"
|
||
>Endgültig löschen</button>
|
||
<div class="task-cert-hint">
|
||
Es werden auch Zuweisungen, Einreichungen, Zertifikate, Methoden, Zielgruppenregeln und Eskalationsereignisse entfernt.
|
||
</div>
|
||
</div>
|
||
|
||
<div class="task-cert-block">
|
||
<h3>Bestandsdaten bei Änderungen</h3>
|
||
<label>
|
||
<input type="checkbox" name="reset_existing_records" value="1">
|
||
Bestehende Einreichungen im aktuellen Zyklus zurücksetzen
|
||
</label>
|
||
<div class="task-cert-hint">
|
||
Setzt den Fortschritt im aktuellen Zyklus auf offen, entfernt Einreichungen dieses Zyklus
|
||
und widerruft bereits ausgestellte Zertifikate dieses Zyklus.
|
||
Historische Zyklen bleiben unverändert.
|
||
</div>
|
||
</div>
|
||
|
||
<div class="task-cert-block">
|
||
<h3>Zuweisungsregeln anwenden</h3>
|
||
<div class="task-cert-hint">
|
||
Prüft die Zuweisungen im aktuellen Zyklus gegen Mandant/Abteilung/Rolle und Overrides.
|
||
Nutzer ohne Treffer werden entfernt, außer bei abgeschlossenen Einträgen oder vorhandenem Zertifikat.
|
||
</div>
|
||
|
||
<button
|
||
type="button"
|
||
class="btn"
|
||
onclick="document.getElementById('task-cert-sync-preview-form').submit();"
|
||
>Vorschau berechnen</button>
|
||
|
||
<?php if ($syncDone): ?>
|
||
<div class="task-cert-block task-cert-block-inset">
|
||
<h4>Vorschau-Ergebnis</h4>
|
||
<div class="task-cert-detail-grid">
|
||
<div><strong>Zyklus:</strong> <?php echo TaskCertView::e($syncCycleKey !== '' ? $syncCycleKey : '-'); ?></div>
|
||
<div><strong>Zielkontakte:</strong> <?php echo $syncTargetCount; ?></div>
|
||
<div><strong>Bestehende Zuweisungen:</strong> <?php echo $syncExistingCount; ?></div>
|
||
<div><strong>Neu:</strong> <?php echo $syncCreateCount; ?></div>
|
||
<div><strong>Aktualisiert:</strong> <?php echo $syncUpdateCount; ?></div>
|
||
<div><strong>Nicht mehr passend:</strong> <?php echo $syncStaleCount; ?></div>
|
||
<div><strong>Geschützt (Completed):</strong> <?php echo $syncProtectedCompletedCount; ?></div>
|
||
<div><strong>Geschützt (Zertifikat):</strong> <?php echo $syncProtectedCertificateCount; ?></div>
|
||
<div><strong>Geschützt gesamt:</strong> <?php echo $syncProtectedTotalCount; ?></div>
|
||
<div><strong>Zu entfernen:</strong> <?php echo $syncRemoveCount; ?></div>
|
||
<div><strong>davon mit Einreichungen:</strong> <?php echo $syncRemoveWithSubmissionsCount; ?></div>
|
||
<div><strong>Modus:</strong> <?php echo TaskCertView::e($syncApplied ? 'Angewendet' : 'Vorschau'); ?></div>
|
||
</div>
|
||
|
||
<?php if ($syncCanApply): ?>
|
||
<div class="task-cert-warning">
|
||
Beim Anwenden werden <?php echo $syncRemoveCount; ?> nicht mehr passende Zuweisungen im aktuellen Zyklus entfernt.
|
||
</div>
|
||
<button
|
||
type="button"
|
||
class="btn task-cert-btn-success"
|
||
onclick="if (confirm('Zuweisungsregeln jetzt anwenden? Es werden ' + <?php echo $syncRemoveCount; ?> + ' Zuweisungen entfernt.')) { document.getElementById('task-cert-sync-apply-form').submit(); }"
|
||
>Jetzt anwenden</button>
|
||
<?php elseif ($syncApplied): ?>
|
||
<div class="task-cert-success">
|
||
Zuweisungsregeln wurden angewendet. Neu: <?php echo $syncCreateCount; ?>, entfernt: <?php echo $syncRemoveCount; ?>, geschützt: <?php echo $syncProtectedTotalCount; ?>.
|
||
</div>
|
||
<?php endif; ?>
|
||
</div>
|
||
<?php endif; ?>
|
||
</div>
|
||
<?php else: ?>
|
||
<div class="task-cert-block">
|
||
<div class="task-cert-hint">
|
||
Dieser Bereich ist verfügbar, sobald die Aufgabe einmal gespeichert wurde.
|
||
</div>
|
||
</div>
|
||
<?php endif; ?>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="task-cert-wizard-step" data-wizard-step="8" hidden>
|
||
<aside class="task-cert-step-intro">
|
||
<strong>Vorschau</strong>
|
||
<span>So sieht die Detailseite für Mitarbeiter aus (zuletzt gespeicherter Stand).</span>
|
||
</aside>
|
||
<div class="task-cert-block">
|
||
<?php if ($taskId > 0): ?>
|
||
<?php $previewUrl = $taskCertBaseUrl . '/pages/admin/task_preview.php?' . http_build_query(['id' => $taskId, 'csrf_token' => (string) ($csrf_token ?? '')]); ?>
|
||
<div class="task-cert-preview-toolbar">
|
||
<button type="button" class="btn task-cert-btn-success" data-preview-reload>Aktualisieren</button>
|
||
<a href="<?php echo TaskCertView::e($previewUrl); ?>" target="_blank" rel="noopener">In neuem Tab öffnen</a>
|
||
</div>
|
||
<iframe
|
||
class="task-cert-preview-frame"
|
||
data-preview-frame
|
||
data-preview-src="<?php echo TaskCertView::e($previewUrl); ?>"
|
||
title="Vorschau der Detailseite"
|
||
></iframe>
|
||
<?php else: ?>
|
||
<div class="task-cert-hint">
|
||
Bitte zuerst speichern – danach erscheint hier die Vorschau der Detailseite.
|
||
</div>
|
||
<?php endif; ?>
|
||
</div>
|
||
</section>
|
||
</form>
|
||
|
||
<?php if ($taskId > 0): ?>
|
||
<form
|
||
id="task-cert-sync-preview-form"
|
||
action="<?php echo TaskCertView::e($taskSyncAssignmentsEndpoint); ?>"
|
||
method="post"
|
||
style="display:none;"
|
||
>
|
||
<input type="hidden" name="csrf_token" value="<?php echo TaskCertView::e($csrf_token ?? ''); ?>">
|
||
<input type="hidden" name="task_id" value="<?php echo $taskId; ?>">
|
||
<input type="hidden" name="mode" value="dry_run">
|
||
<input type="hidden" name="scope" value="current_cycle">
|
||
<input type="hidden" name="tab" value="<?php echo $activeTab; ?>" data-sync-tab>
|
||
<input type="hidden" name="redirect_to" value="<?php echo TaskCertView::e($taskSyncRedirectUrl); ?>" data-sync-redirect>
|
||
</form>
|
||
|
||
<form
|
||
id="task-cert-sync-apply-form"
|
||
action="<?php echo TaskCertView::e($taskSyncAssignmentsEndpoint); ?>"
|
||
method="post"
|
||
style="display:none;"
|
||
>
|
||
<input type="hidden" name="csrf_token" value="<?php echo TaskCertView::e($csrf_token ?? ''); ?>">
|
||
<input type="hidden" name="task_id" value="<?php echo $taskId; ?>">
|
||
<input type="hidden" name="mode" value="apply">
|
||
<input type="hidden" name="scope" value="current_cycle">
|
||
<input type="hidden" name="tab" value="<?php echo $activeTab; ?>" data-sync-tab>
|
||
<input type="hidden" name="redirect_to" value="<?php echo TaskCertView::e($taskSyncRedirectUrl); ?>" data-sync-redirect>
|
||
</form>
|
||
|
||
<form
|
||
id="task-cert-delete-form"
|
||
action="<?php echo TaskCertView::e($taskCertBaseUrl . '/endpoints/admin/task_delete.php'); ?>"
|
||
method="post"
|
||
style="display:none;"
|
||
>
|
||
<input type="hidden" name="csrf_token" value="<?php echo TaskCertView::e($csrf_token ?? ''); ?>">
|
||
<input type="hidden" name="task_id" value="<?php echo $taskId; ?>">
|
||
<input type="hidden" name="redirect_to" value="<?php echo TaskCertView::e($taskListViewUrl); ?>">
|
||
</form>
|
||
|
||
<form
|
||
id="task-cert-hard-delete-form"
|
||
action="<?php echo TaskCertView::e($taskCertBaseUrl . '/endpoints/admin/task_delete_hard.php'); ?>"
|
||
method="post"
|
||
style="display:none;"
|
||
>
|
||
<input type="hidden" name="csrf_token" value="<?php echo TaskCertView::e($csrf_token ?? ''); ?>">
|
||
<input type="hidden" name="task_id" value="<?php echo $taskId; ?>">
|
||
<input type="hidden" name="confirm_hard_delete" value="1">
|
||
<input type="hidden" name="redirect_to" value="<?php echo TaskCertView::e($taskListViewUrl); ?>">
|
||
</form>
|
||
<?php endif; ?>
|
||
</div>
|