Files
awo-hamburg-intranet/module/tasks/service/CertificateService.php
Moritz Weinmann 743476f64a 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>
2026-06-22 09:20:02 +02:00

140 lines
4.6 KiB
PHP

<?php
declare(strict_types=1);
class CertificateService
{
private TaskCertificateRepository $certificateRepo;
private TaskAssignmentRepository $assignmentRepo;
private TaskDefinitionRepository $definitionRepo;
private CycleCalculatorService $cycleCalculator;
public function __construct(
TaskCertificateRepository $certificateRepo,
TaskAssignmentRepository $assignmentRepo,
TaskDefinitionRepository $definitionRepo,
CycleCalculatorService $cycleCalculator
) {
$this->certificateRepo = $certificateRepo;
$this->assignmentRepo = $assignmentRepo;
$this->definitionRepo = $definitionRepo;
$this->cycleCalculator = $cycleCalculator;
}
public function issueForAssignment(int $assignmentId, int $submissionId): int
{
$existing = $this->certificateRepo->findByAssignmentId($assignmentId);
if ($existing !== null) {
return (int) $existing['id'];
}
$assignment = $this->assignmentRepo->findById($assignmentId);
if ($assignment === null) {
throw new RuntimeException('Assignment nicht gefunden');
}
$task = $this->definitionRepo->findById((int) $assignment['task_id']);
if ($task === null) {
throw new RuntimeException('Aufgabe nicht gefunden');
}
if ((int) $task['certificate_enabled'] !== 1) {
return 0;
}
$validFrom = new DateTimeImmutable();
$validUntil = null;
$validityMode = $this->resolveValidityMode($task);
if ($validityMode === 'days') {
$validityDays = (int) ($task['certificate_validity_days'] ?? 0);
if ($validityDays > 0) {
$validUntil = $validFrom->modify('+' . $validityDays . ' days')->format('Y-m-d H:i:s');
}
} elseif ($validityMode === 'cycle') {
$validUntil = $this->resolveCycleValidUntil($task, $assignment, $validFrom);
}
return $this->certificateRepo->create([
'task_id' => (int) $assignment['task_id'],
'assignment_id' => $assignmentId,
'submission_id' => $submissionId,
'main_contact_id' => (int) $assignment['main_contact_id'],
'certificate_code' => $this->buildCertificateCode(),
'valid_from' => $validFrom->format('Y-m-d H:i:s'),
'valid_until' => $validUntil,
'status' => 'valid',
]);
}
public function listUserCertificates(int $mainContactId): array
{
return $this->certificateRepo->listForUser($mainContactId);
}
public function listUserTaskCertificates(int $mainContactId, int $taskId): array
{
if ($taskId <= 0) {
return [];
}
return $this->certificateRepo->listForUserTask($mainContactId, $taskId);
}
public function getUserTaskCertificateSummary(int $mainContactId, array $taskIds): array
{
return $this->certificateRepo->summarizeForUserTasks($mainContactId, $taskIds);
}
public function refreshExpiredCertificates(): int
{
return $this->certificateRepo->markExpiredByDate((new DateTimeImmutable())->format('Y-m-d H:i:s'));
}
private function buildCertificateCode(): string
{
return strtoupper(bin2hex(random_bytes(8)));
}
private function resolveCycleValidUntil(array $task, array $assignment, DateTimeImmutable $reference): ?string
{
$assignmentCycleEnd = trim((string) ($assignment['cycle_end_at'] ?? ''));
if ($assignmentCycleEnd !== '') {
try {
return (new DateTimeImmutable($assignmentCycleEnd))->format('Y-m-d H:i:s');
} catch (Throwable $throwable) {
// fallback to calculated cycle
}
}
if ((string) ($task['frequency_type'] ?? 'once') !== 'recurring') {
return null;
}
$cycle = $this->cycleCalculator->resolveCurrentCycle($task, $reference);
if ($cycle === [] || !($cycle['cycle_end_at'] ?? null) instanceof DateTimeImmutable) {
return null;
}
return $cycle['cycle_end_at']->format('Y-m-d H:i:s');
}
private function resolveValidityMode(array $task): string
{
$mode = (string) ($task['certificate_validity_mode'] ?? '');
if (in_array($mode, ['cycle', 'days', 'permanent'], true)) {
return $mode;
}
$days = (int) ($task['certificate_validity_days'] ?? 0);
if ($days > 0) {
return 'days';
}
if ((string) ($task['frequency_type'] ?? 'once') === 'recurring') {
return 'cycle';
}
return 'permanent';
}
}