replaceMethods($taskId, $dto['methods'] ?? []); $this->replaceTargetRules($taskId, $dto['target_rules'] ?? []); $this->replaceTargetOverrides($taskId, $dto['target_overrides'] ?? [], $actorId); $this->replaceReminderPolicy($taskId, $dto['reminder_policy'] ?? []); $this->replaceResponsibles($taskId, $dto['responsible_contact_ids'] ?? []); } public function getTaskRuleBundle(int $taskId): array { return [ 'methods' => $this->getMethodsByTask($taskId, true), 'target_rules' => $this->getTargetRulesByTask($taskId), 'target_overrides' => $this->getTargetOverridesByTask($taskId), 'reminder_policy' => $this->getReminderPolicyByTask($taskId), 'responsible_contact_ids' => $this->getResponsibleContactIds($taskId), ]; } public function findMethodById(int $methodId): ?array { return TaskCertDb::fetchOne('SELECT * FROM task_method WHERE id = ? AND active = 1', 'i', [$methodId]); } public function getMethodsByTask(int $taskId, bool $includeInactive = false): array { $sql = 'SELECT * FROM task_method WHERE task_id = ?'; if (!$includeInactive) { $sql .= ' AND active = 1'; } $sql .= ' ORDER BY sort_order ASC, id ASC'; $methods = TaskCertDb::fetchAll($sql, 'i', [$taskId]); foreach ($methods as &$method) { $methodId = (int) $method['id']; $method['settings'] = $this->decodeJson($method['settings_json'] ?? null); $method['knowledge_posts'] = $this->getKnowledgePostsForMethod($methodId); $method['form_fields'] = $this->getFormFieldsForMethod($methodId); $method['quiz_questions'] = $this->getQuizQuestionsWithOptions($methodId); unset($method['settings_json']); } return $methods; } public function getTargetRulesByTask(int $taskId): array { return TaskCertDb::fetchAll( 'SELECT * FROM task_target_rule WHERE task_id = ? ORDER BY id ASC', 'i', [$taskId] ); } public function getTargetOverridesByTask(int $taskId): array { return TaskCertDb::fetchAll( 'SELECT * FROM task_target_override WHERE task_id = ? ORDER BY id ASC', 'i', [$taskId] ); } public function getReminderPolicyByTask(int $taskId): ?array { return TaskCertDb::fetchOne( 'SELECT * FROM task_reminder_policy WHERE task_id = ? LIMIT 1', 'i', [$taskId] ); } private function replaceMethods(int $taskId, array $methods): void { $existingRows = TaskCertDb::fetchAll( 'SELECT id, method_type FROM task_method WHERE task_id = ?', 'i', [$taskId] ); $existingByType = []; foreach ($existingRows as $row) { $type = (string) ($row['method_type'] ?? ''); $id = (int) ($row['id'] ?? 0); if ($type !== '' && $id > 0) { $existingByType[$type] = $id; } } $sort = 0; $seenTypes = []; foreach ($methods as $method) { $methodType = (string) ($method['method_type'] ?? ''); if (!TaskCertConstants::isValidMethodType($methodType)) { continue; } $isRequired = (int) ($method['is_required'] ?? 1); $sortOrder = (int) ($method['sort_order'] ?? $sort); $settingsJson = json_encode($method['settings'] ?? [], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); $active = (int) ($method['active'] ?? 1); $seenTypes[$methodType] = true; if (isset($existingByType[$methodType])) { $methodId = (int) $existingByType[$methodType]; TaskCertDb::execute( 'UPDATE task_method SET is_required = ?, sort_order = ?, settings_json = ?, active = ? WHERE id = ?', 'iisii', [$isRequired, $sortOrder, $settingsJson, $active, $methodId] ); } else { $methodId = TaskCertDb::insert( 'INSERT INTO task_method (task_id, method_type, is_required, sort_order, settings_json, active) VALUES (?, ?, ?, ?, ?, ?)', 'isiisi', [$taskId, $methodType, $isRequired, $sortOrder, $settingsJson, $active] ); } $this->clearMethodChildren($methodId); if ($methodType === 'knowledge_read') { $this->insertKnowledgePosts($methodId, $method['knowledge_posts'] ?? []); } if ($methodType === 'form_submit') { $this->insertFormFields($methodId, $method['form_fields'] ?? []); } if ($methodType === 'quiz') { $this->insertQuizQuestions($methodId, $method['quiz_questions'] ?? []); } $sort++; } foreach ($existingByType as $methodType => $methodId) { if (isset($seenTypes[$methodType])) { continue; } TaskCertDb::execute( 'UPDATE task_method SET active = 0, sort_order = ? WHERE id = ?', 'ii', [$sort, (int) $methodId] ); $sort++; } } private function clearMethodChildren(int $methodId): void { TaskCertDb::execute('DELETE FROM task_method_knowledge_post WHERE method_id = ?', 'i', [$methodId]); TaskCertDb::execute('DELETE FROM task_form_field WHERE method_id = ?', 'i', [$methodId]); $questionRows = TaskCertDb::fetchAll( 'SELECT id FROM task_quiz_question WHERE method_id = ?', 'i', [$methodId] ); $questionIds = []; foreach ($questionRows as $row) { $questionId = (int) ($row['id'] ?? 0); if ($questionId > 0) { $questionIds[] = $questionId; } } if ($questionIds !== []) { $placeholders = implode(',', array_fill(0, count($questionIds), '?')); TaskCertDb::execute( 'DELETE FROM task_quiz_option WHERE question_id IN (' . $placeholders . ')', str_repeat('i', count($questionIds)), $questionIds ); } TaskCertDb::execute('DELETE FROM task_quiz_question WHERE method_id = ?', 'i', [$methodId]); } private function replaceTargetRules(int $taskId, array $targetRules): void { TaskCertDb::execute('DELETE FROM task_target_rule WHERE task_id = ?', 'i', [$taskId]); foreach ($targetRules as $rule) { TaskCertDb::insert( 'INSERT INTO task_target_rule (task_id, main_mandant_id, main_department_id, main_role_id, active) VALUES (?, ?, ?, ?, ?)', 'iiiii', [ $taskId, (int) ($rule['main_mandant_id'] ?? 0) ?: null, (int) ($rule['main_department_id'] ?? 0) ?: null, (int) ($rule['main_role_id'] ?? 0) ?: null, (int) ($rule['active'] ?? 1), ] ); } } private function replaceTargetOverrides(int $taskId, array $overrides, int $actorId): void { TaskCertDb::execute('DELETE FROM task_target_override WHERE task_id = ?', 'i', [$taskId]); foreach ($overrides as $override) { $type = (string) ($override['override_type'] ?? ''); $contactId = (int) ($override['main_contact_id'] ?? 0); if (!in_array($type, ['include', 'exclude'], true) || $contactId <= 0) { continue; } TaskCertDb::insert( 'INSERT INTO task_target_override (task_id, main_contact_id, override_type, reason, created_by) VALUES (?, ?, ?, ?, ?)', 'iissi', [ $taskId, $contactId, $type, (string) ($override['reason'] ?? ''), $actorId, ] ); } } private function replaceReminderPolicy(int $taskId, array $policy): void { TaskCertDb::execute('DELETE FROM task_reminder_policy WHERE task_id = ?', 'i', [$taskId]); TaskCertDb::insert( 'INSERT INTO task_reminder_policy (task_id, base_mode, first_escalation_days, step_days, max_level, grace_days, active) VALUES (?, ?, ?, ?, ?, ?, ?)', 'isiiiii', [ $taskId, 'due_date', (int) ($policy['first_escalation_days'] ?? 0), max(1, (int) ($policy['step_days'] ?? 7)), max(1, (int) ($policy['max_level'] ?? 3)), max(0, (int) ($policy['grace_days'] ?? 7)), (int) ($policy['active'] ?? 1), ] ); } private function insertKnowledgePosts(int $methodId, array $knowledgePosts): void { $sortOrder = 0; foreach ($knowledgePosts as $item) { $postId = is_array($item) ? (int) ($item['knowledge_post_id'] ?? 0) : (int) $item; if ($postId <= 0) { continue; } $required = is_array($item) ? (int) ($item['required'] ?? 1) : 1; $sort = is_array($item) ? (int) ($item['sort_order'] ?? $sortOrder) : $sortOrder; TaskCertDb::insert( 'INSERT INTO task_method_knowledge_post (method_id, knowledge_post_id, required, sort_order) VALUES (?, ?, ?, ?)', 'iiii', [$methodId, $postId, $required, $sort] ); $sortOrder++; } } private function insertFormFields(int $methodId, array $fields): void { $sortOrder = 0; foreach ($fields as $field) { if (!is_array($field)) { continue; } $fieldKey = trim((string) ($field['field_key'] ?? '')); $label = trim((string) ($field['label'] ?? '')); $fieldType = (string) ($field['field_type'] ?? 'text'); if ($fieldKey === '' || $label === '') { continue; } if (!TaskCertConstants::isValidFieldType($fieldType)) { $fieldType = 'text'; } $prefillColumn = ContactPrefillColumns::sanitize((string) ($field['prefill_column'] ?? '')); TaskCertDb::insert( 'INSERT INTO task_form_field (method_id, field_key, label, field_type, is_required, options_json, placeholder, prefill_column, sort_order, active) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', 'isssisssii', [ $methodId, $fieldKey, $label, $fieldType, (int) ($field['is_required'] ?? 0), json_encode($field['options'] ?? [], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), (string) ($field['placeholder'] ?? ''), $prefillColumn, (int) ($field['sort_order'] ?? $sortOrder), (int) ($field['active'] ?? 1), ] ); $sortOrder++; } } private function insertQuizQuestions(int $methodId, array $questions): void { $questionSort = 0; foreach ($questions as $question) { if (!is_array($question)) { continue; } $questionText = trim((string) ($question['question_text'] ?? '')); if ($questionText === '') { continue; } $questionId = TaskCertDb::insert( 'INSERT INTO task_quiz_question (method_id, question_text, sort_order, active) VALUES (?, ?, ?, ?)', 'isii', [ $methodId, $questionText, (int) ($question['sort_order'] ?? $questionSort), (int) ($question['active'] ?? 1), ] ); $options = is_array($question['options'] ?? null) ? $question['options'] : []; $optionSort = 0; foreach ($options as $option) { if (!is_array($option)) { continue; } $optionText = trim((string) ($option['option_text'] ?? '')); if ($optionText === '') { continue; } TaskCertDb::insert( 'INSERT INTO task_quiz_option (question_id, option_text, is_correct, sort_order) VALUES (?, ?, ?, ?)', 'isii', [ $questionId, $optionText, (int) ($option['is_correct'] ?? 0), (int) ($option['sort_order'] ?? $optionSort), ] ); $optionSort++; } $questionSort++; } } private function getKnowledgePostsForMethod(int $methodId): array { return TaskCertDb::fetchAll( 'SELECT * FROM task_method_knowledge_post WHERE method_id = ? ORDER BY sort_order ASC, id ASC', 'i', [$methodId] ); } private function getFormFieldsForMethod(int $methodId): array { $rows = TaskCertDb::fetchAll( 'SELECT * FROM task_form_field WHERE method_id = ? ORDER BY sort_order ASC, id ASC', 'i', [$methodId] ); foreach ($rows as &$row) { $row['options'] = $this->decodeJson($row['options_json'] ?? null); unset($row['options_json']); } return $rows; } private function getQuizQuestionsWithOptions(int $methodId): array { $questions = TaskCertDb::fetchAll( 'SELECT * FROM task_quiz_question WHERE method_id = ? ORDER BY sort_order ASC, id ASC', 'i', [$methodId] ); foreach ($questions as &$question) { $question['options'] = TaskCertDb::fetchAll( 'SELECT * FROM task_quiz_option WHERE question_id = ? ORDER BY sort_order ASC, id ASC', 'i', [(int) $question['id']] ); } return $questions; } private function decodeJson(?string $json): array { if ($json === null || trim($json) === '') { return []; } $decoded = json_decode($json, true); return is_array($decoded) ? $decoded : []; } public function getResponsibleContactIds(int $taskId): array { $rows = TaskCertDb::fetchAll( 'SELECT main_contact_id FROM task_responsible WHERE task_id = ? ORDER BY id ASC', 'i', [$taskId] ); return array_map(static fn(array $row): int => (int) $row['main_contact_id'], $rows); } public function getResponsiblesWithContactData(int $taskId): array { return TaskCertDb::fetchAll( 'SELECT r.main_contact_id, c.name, c.email FROM task_responsible r INNER JOIN main_contact c ON c.id = r.main_contact_id WHERE r.task_id = ? ORDER BY c.name ASC', 'i', [$taskId] ); } private function replaceResponsibles(int $taskId, array $contactIds): void { TaskCertDb::execute('DELETE FROM task_responsible WHERE task_id = ?', 'i', [$taskId]); $contactIds = array_values(array_unique(array_filter(array_map('intval', $contactIds), static fn(int $id): bool => $id > 0))); foreach ($contactIds as $contactId) { TaskCertDb::insert( 'INSERT INTO task_responsible (task_id, main_contact_id) VALUES (?, ?)', 'ii', [$taskId, $contactId] ); } } }