Quelltext des aktuellen Standes
-- Migration 247: Leistungsnachweise aus der Ticket-Zeiterfassung
--
-- Ein Leistungsnachweis fasst die auf Tickets erfasste Arbeitszeit zu einem
-- Dokument zusammen: entweder zu einem einzelnen Ticket (beim Abschluss) oder
-- als Monatssammlung je Standort. Er kann rein zur Information versendet,
-- auf dem Signatur-Tablet oder über einen Link am Rechner unterschrieben
-- werden.
-- Empfänger je Standort. Leer bedeutet: Adresse des übergeordneten Standorts
-- verwenden (die Auflösung steigt die Hierarchie hinauf).
ALTER TABLE `ticket_locations`
ADD COLUMN `report_email` VARCHAR(255) NULL DEFAULT NULL
COMMENT 'Empfaenger der Leistungsnachweise; leer = Adresse des uebergeordneten Standorts';
ALTER TABLE `ticket_locations`
ADD COLUMN `report_monthly` TINYINT(1) NOT NULL DEFAULT 1
COMMENT 'Nimmt am monatlichen Sammelnachweis teil';
CREATE TABLE IF NOT EXISTS `service_reports` (
`id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`number` VARCHAR(40) NOT NULL COMMENT 'Fortlaufende Nummer, z. B. LN-2026-0042',
`kind` ENUM('ticket','monthly') NOT NULL DEFAULT 'ticket',
`location_id` INT UNSIGNED NULL,
`ticket_id` INT UNSIGNED NULL,
`period_start` DATE NULL,
`period_end` DATE NULL,
`recipient_email` VARCHAR(255) NULL,
`recipient_name` VARCHAR(255) NULL,
`mode` ENUM('info','signature_link','signature_tablet') NOT NULL DEFAULT 'info',
`status` ENUM('draft','awaiting_signature','signed','sent','failed','cancelled')
NOT NULL DEFAULT 'draft',
`total_minutes` INT UNSIGNED NOT NULL DEFAULT 0,
`entries_json` LONGTEXT NULL COMMENT 'Positionen zum Zeitpunkt der Erstellung (revisionssicher)',
`document_html` LONGTEXT NULL COMMENT 'Ausgefuellte Vorlage; ab dem Versand unveraenderlich',
`signature_token` CHAR(64) NULL,
`signature_requested_at` DATETIME NULL,
`signature_expires_at` DATETIME NULL,
`signature_data` LONGTEXT NULL COMMENT 'PNG als data-URL',
`signature_name` VARCHAR(255) NULL,
`signature_obtained_at` DATETIME NULL,
`pdf_path` VARCHAR(500) NULL,
`sent_at` DATETIME NULL,
`last_error` VARCHAR(500) NULL,
`created_by` BIGINT UNSIGNED NULL,
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY `uq_sr_number` (`number`),
UNIQUE KEY `uq_sr_token` (`signature_token`),
KEY `idx_sr_ticket` (`ticket_id`),
KEY `idx_sr_location` (`location_id`, `period_start`),
KEY `idx_sr_status` (`status`, `created_at`),
CONSTRAINT `fk_sr_ticket` FOREIGN KEY (`ticket_id`)
REFERENCES `tickets`(`id`) ON DELETE SET NULL,
CONSTRAINT `fk_sr_location` FOREIGN KEY (`location_id`)
REFERENCES `ticket_locations`(`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='Leistungsnachweise aus der Ticket-Zeiterfassung';
-- Gestaltbare Dokumentvorlagen (wie die PDF-Vorlagen der Geräteübergabe).
CREATE TABLE IF NOT EXISTS `service_report_templates` (
`id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`template_key` VARCHAR(50) NOT NULL UNIQUE COMMENT 'ticket oder monthly',
`name` VARCHAR(255) NOT NULL,
`body_html` LONGTEXT NOT NULL COMMENT 'HTML mit {{Platzhaltern}}',
`updated_by` BIGINT UNSIGNED NULL,
`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='Vorlagen der Leistungsnachweise';
INSERT IGNORE INTO `service_report_templates` (`template_key`, `name`, `body_html`) VALUES
('ticket', 'Leistungsnachweis – einzelnes Ticket',
'<div style="text-align:right;margin-bottom:6px;">{{logo}}</div>
<h2 style="text-align:center;color:#1e3a5f;border-bottom:2px solid #1e3a5f;padding-bottom:8px;">Leistungsnachweis {{number}}</h2>
<table style="width:100%;font-size:10pt;margin:14px 0;">
<tr><td style="width:28%;color:#6b7280;">Standort</td><td><strong>{{location}}</strong></td></tr>
<tr><td style="color:#6b7280;">Vorgang</td><td>#{{ticket_id}} – {{ticket_subject}}</td></tr>
<tr><td style="color:#6b7280;">Zeitraum</td><td>{{period}}</td></tr>
<tr><td style="color:#6b7280;">Gesamtaufwand</td><td><strong>{{total_time}}</strong></td></tr>
</table>
{{entries_table}}
<p style="margin-top:18px;font-size:10pt;">Die aufgeführten Leistungen wurden erbracht.</p>
{{signature_block}}'),
('monthly', 'Leistungsnachweis – Monatssammlung',
'<div style="text-align:right;margin-bottom:6px;">{{logo}}</div>
<h2 style="text-align:center;color:#1e3a5f;border-bottom:2px solid #1e3a5f;padding-bottom:8px;">Leistungsnachweis {{number}}</h2>
<table style="width:100%;font-size:10pt;margin:14px 0;">
<tr><td style="width:28%;color:#6b7280;">Standort</td><td><strong>{{location}}</strong></td></tr>
<tr><td style="color:#6b7280;">Zeitraum</td><td>{{period}}</td></tr>
<tr><td style="color:#6b7280;">Vorgänge</td><td>{{ticket_count}}</td></tr>
<tr><td style="color:#6b7280;">Gesamtaufwand</td><td><strong>{{total_time}}</strong></td></tr>
</table>
{{entries_table}}
<p style="margin-top:18px;font-size:10pt;">Die aufgeführten Leistungen wurden im genannten Zeitraum erbracht.</p>
{{signature_block}}');
-- E-Mail-Vorlagen: reine Information und Bitte um Unterschrift.
INSERT IGNORE INTO `email_templates` (`template_key`, `subject`, `body_html`, `description`) VALUES
('service_report_info',
'Leistungsnachweis {{number}} – {{location}}',
'<p>Guten Tag,</p>
<p>anbei erhalten Sie den Leistungsnachweis <strong>{{number}}</strong> für <strong>{{location}}</strong> ({{period}}).</p>
<p>Erfasster Aufwand: <strong>{{total_time}}</strong>.</p>
<p>Der Nachweis liegt dieser E-Mail als PDF bei. Eine Rückmeldung ist nicht erforderlich.</p>
<p>Freundliche Grüße<br>{{app_name}}</p>',
'Leistungsnachweis zur Information (mit PDF im Anhang)'),
('service_report_signature',
'Bitte um Bestätigung: Leistungsnachweis {{number}} – {{location}}',
'<p>Guten Tag,</p>
<p>für <strong>{{location}}</strong> liegt der Leistungsnachweis <strong>{{number}}</strong> ({{period}}) vor – erfasster Aufwand: <strong>{{total_time}}</strong>.</p>
<p>Bitte bestätigen Sie die Leistungen über den folgenden Link:</p>
<p><a href="{{signature_url}}" style="background:#2563eb;color:#ffffff;padding:10px 18px;border-radius:6px;text-decoration:none;display:inline-block;">Leistungsnachweis ansehen und unterschreiben</a></p>
<p style="font-size:12px;color:#6b7280;">Der Link ist bis zum {{expires_at}} gültig.</p>
<p>Freundliche Grüße<br>{{app_name}}</p>',
'Leistungsnachweis mit Link zur Unterschrift am Rechner');
-- Monatslauf als Scheduler-Task. Läuft stündlich an und prüft selbst, ob der
-- eingestellte Stichtag erreicht ist; so genügt eine Zeile in der Crontab.
INSERT IGNORE INTO `cron_tasks`
(`name`, `label`, `description`, `handler_class`, `frequency_minutes`, `enabled`, `run_in_background`, `max_runtime_seconds`)
VALUES
('service_report_monthly', 'Leistungsnachweise (Monat)',
'Erstellt zu Monatsbeginn je Standort den Leistungsnachweis des Vormonats und versendet ihn gemaess Einstellung (Information oder Unterschrift per Link). Standorte ohne erfasste Zeit werden uebersprungen.',
'OpenNIT\\Cron\\Handlers\\ServiceReportMonthlyHandler', 60, 1, 1, 900);