Files
omsorg/omsorgWeb/mitarbeiter-app/pages/stundenerfassung.php
T
Felix KemmlerandClaude Sonnet 5 598dfcd38a
Docker-Images bauen und veröffentlichen / build (, omsorgCore/Dockerfile, omsorgcore) (push) Successful in 3s
Docker-Images bauen und veröffentlichen / build (, omsorgWeb/Dockerfile, omsorgweb) (push) Failing after 4s
Docker-Images bauen und veröffentlichen / build (VITE_OMSORG_CORE_URL=${{ vars.OMSORG_CORE_PUBLIC_URL }}, omsorgapp/Dockerfile, omsorgapp) (push) Successful in 3s
Migrate omsorgapp to browser SPA, add Docker/CI build setup
- omsorgapp: drop Electron, run as a plain Vite/React browser app; refresh
  token moves to an HttpOnly cookie (omsorgCore), CORS added for the new
  browser origin, document download/preview switched to Blob-based browser
  APIs.
- Add Dockerfiles for omsorgCore, omsorgapp, and omsorgWeb, a docker-compose.yml
  wiring Postgres/MySQL/all three apps together, and a Gitea Actions workflow
  that builds and pushes images to the repo's container registry on push to
  main and on version tags.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-10 17:42:45 +02:00

293 lines
12 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
require_once __DIR__ . '/../lib/auth.php';
require_once __DIR__ . '/../lib/layout.php';
require_login();
if (!has_permission('TimeEntries', 'View')) {
header('Location: dashboard.php');
exit;
}
$canCreate = has_permission('TimeEntries', 'Create');
$canEdit = has_permission('TimeEntries', 'Edit');
$error = '';
$success = false;
$editId = null;
$ordersResult = omsorgcore_orders_list(_omsorgcore_config(), $_SESSION['omsorgcore_access_token']);
$orders = $ordersResult['ok'] ? ($ordersResult['data']['items'] ?? []) : [];
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
verify_csrf();
$mode = $_POST['mode'] ?? 'create';
if ($mode === 'submit') {
if (!$canEdit) {
$error = 'Keine Berechtigung, diese Zeiterfassung einzureichen.';
} else {
$timeEntryId = $_POST['time_entry_id'] ?? '';
$result = omsorgcore_time_entries_submit(_omsorgcore_config(), $_SESSION['omsorgcore_access_token'], $timeEntryId);
if ($result['ok']) {
header('Location: stundenerfassung.php?submitted=1');
exit;
}
$error = $result['status'] === 403
? 'Keine Berechtigung, diese Zeiterfassung einzureichen.'
: (is_string($result['data'] ?? null) ? $result['data'] : 'Zeiterfassung konnte nicht eingereicht werden.');
}
}
if ($mode !== 'submit') {
$payload = [
'orderId' => $_POST['order_id'] ?? '',
'date' => $_POST['date'] ?? '',
'start' => ($_POST['start'] ?? '') !== '' ? $_POST['start'] . ':00' : '',
'end' => ($_POST['end'] ?? '') !== '' ? $_POST['end'] . ':00' : '',
'breakDuration' => sprintf('%02d:%02d:00', intdiv((int)($_POST['break_minutes'] ?? 0), 60), (int)($_POST['break_minutes'] ?? 0) % 60),
'nightHours' => (float)($_POST['night_hours'] ?? 0),
'saturdayHours' => (float)($_POST['saturday_hours'] ?? 0),
'sundayHours' => (float)($_POST['sunday_hours'] ?? 0),
'holidayHours' => (float)($_POST['holiday_hours'] ?? 0),
];
if ($payload['orderId'] === '' || $payload['date'] === '' || $payload['start'] === '' || $payload['end'] === '') {
$error = 'Auftrag, Datum, Beginn und Ende sind erforderlich.';
$editId = $mode === 'edit' ? ($_POST['time_entry_id'] ?? null) : null;
} elseif ($mode === 'edit') {
if (!$canEdit) {
// Server lehnt das ohnehin über [RequirePermission(TimeEntries, Edit)] ab - dieser Zweig
// greift nur, wenn jemand das Formular manuell postet, obwohl es clientseitig gar nicht
// angezeigt wurde.
$error = 'Keine Berechtigung, diese Zeiterfassung zu bearbeiten.';
} else {
$timeEntryId = $_POST['time_entry_id'] ?? '';
$result = omsorgcore_time_entries_update(_omsorgcore_config(), $_SESSION['omsorgcore_access_token'], $timeEntryId, $payload);
if ($result['ok']) {
header('Location: stundenerfassung.php?updated=1');
exit;
}
$editId = $timeEntryId;
$error = $result['status'] === 403
? 'Keine Berechtigung, diese Zeiterfassung zu bearbeiten.'
: (is_string($result['data'] ?? null) ? $result['data'] : 'Zeiterfassung konnte nicht gespeichert werden.');
}
} elseif (!$canCreate) {
$error = 'Keine Berechtigung, eine Zeiterfassung anzulegen.';
} else {
$result = omsorgcore_time_entries_create(_omsorgcore_config(), $_SESSION['omsorgcore_access_token'], $payload);
if ($result['ok']) {
$success = true;
} elseif ($result['status'] === 403) {
$error = 'Keine Berechtigung, eine Zeiterfassung anzulegen.';
} else {
$error = is_string($result['data'] ?? null) ? $result['data'] : 'Zeiterfassung konnte nicht gespeichert werden.';
}
}
}
} elseif (isset($_GET['edit'])) {
$editId = $_GET['edit'];
}
$listResult = omsorgcore_time_entries_list(_omsorgcore_config(), $_SESSION['omsorgcore_access_token']);
$timeEntries = $listResult['ok'] ? ($listResult['data']['items'] ?? []) : [];
// Statuswerte mit einer Selbst-Einreichungs-Kante (requiresApproval=false, siehe
// TimeEntryService.GetSelfServiceTransitionAsync in omsorgCore) - nur für diese Einträge zeigt die
// Liste den "Einreichen"-Button, damit er nicht auch bei bereits eingereichten Einträgen (die zwar
// noch isEditableByOwner sind, aber keine ausgehende Selbst-Einreichungs-Kante mehr haben) erscheint.
$transitionsResult = omsorgcore_value_list_transitions(_omsorgcore_config(), $_SESSION['omsorgcore_access_token'], 'TimeEntryStatus');
$transitions = $transitionsResult['ok'] ? ($transitionsResult['data'] ?? []) : [];
$submittableStatusIds = array_column(array_filter($transitions, fn($t) => empty($t['requiresApproval'])), 'fromItemId');
$editTimeEntry = null;
if ($editId !== null) {
foreach ($timeEntries as $timeEntry) {
if ($timeEntry['id'] === $editId) {
$editTimeEntry = $timeEntry;
break;
}
}
// Fremder/ungültiger/nicht mehr bearbeitbarer Eintrag - Own-Scope-Filterung passiert serverseitig
// in omsorgcore_time_entries_list, hier nur zusätzlich: nicht (mehr) bearbeitbar zurück zu "Neuer Eintrag".
if ($editTimeEntry === null || empty($editTimeEntry['isEditableByOwner'])) {
$editId = null;
$editTimeEntry = null;
}
}
function order_label(array $order): string
{
return substr($order['id'], 0, 8) . ' (' . ($order['requiredQualification'] ?? 'Auftrag') . ')';
}
layout_start('Zeiterfassung Mitarbeiter-App', 'stundenerfassung');
?>
<h1 class="main-title">Zeiterfassung</h1>
<div class="split-layout">
<div class="glass" style="padding:24px">
<h2 style="font-family:'KindelSerif',Georgia,serif;font-size:1.3rem;margin:0 0 18px">Meine Einträge</h2>
<?php if (empty($timeEntries)): ?>
<p>Noch keine Zeiterfassung angelegt.</p>
<?php else: ?>
<?php foreach ($timeEntries as $timeEntry): ?>
<div style="padding:12px 0;border-bottom:1px solid rgba(0,0,0,.08)">
<b><?= e(substr($timeEntry['date'], 0, 10)) ?></b> — <?= e($timeEntry['statusName']) ?>
<div class="hint">
<?= e(substr($timeEntry['start'], 0, 5)) ?> <?= e(substr($timeEntry['end'], 0, 5)) ?> Uhr, <?= e($timeEntry['facilityName']) ?>
</div>
<?php if (!empty($timeEntry['adminNote'])): ?>
<div class="hint">Kommentar: <?= e($timeEntry['adminNote']) ?></div>
<?php endif; ?>
<div style="margin-top:6px;display:flex;gap:12px">
<?php if ($canEdit && !empty($timeEntry['isEditableByOwner'])): ?>
<a href="stundenerfassung.php?edit=<?= e($timeEntry['id']) ?>">Bearbeiten</a>
<?php endif; ?>
<?php if ($canEdit && in_array($timeEntry['statusId'], $submittableStatusIds, true)): ?>
<form method="post" style="display:inline">
<?= csrf_field() ?>
<input type="hidden" name="mode" value="submit">
<input type="hidden" name="time_entry_id" value="<?= e($timeEntry['id']) ?>">
<button type="submit" style="background:none;border:none;padding:0;color:inherit;text-decoration:underline;cursor:pointer;font:inherit">Einreichen</button>
</form>
<?php endif; ?>
</div>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
<div class="glass" style="padding:24px">
<h2 style="font-family:'KindelSerif',Georgia,serif;font-size:1.3rem;margin:0 0 18px">
<?= $editTimeEntry ? 'Eintrag bearbeiten' : 'Neuer Eintrag' ?>
</h2>
<?php if ($success): ?>
<div class="hint" style="margin-bottom:16px">Eintrag wurde angelegt.</div>
<?php endif; ?>
<?php if (isset($_GET['updated'])): ?>
<div class="hint" style="margin-bottom:16px">Eintrag wurde aktualisiert.</div>
<?php endif; ?>
<?php if (isset($_GET['submitted'])): ?>
<div class="hint" style="margin-bottom:16px">Eintrag wurde eingereicht.</div>
<?php endif; ?>
<?php if ($error): ?>
<div class="error" style="margin-bottom:16px"><?= e($error) ?></div>
<?php endif; ?>
<?php if ($editTimeEntry): ?>
<form method="post">
<?= csrf_field() ?>
<input type="hidden" name="mode" value="edit">
<input type="hidden" name="time_entry_id" value="<?= e($editTimeEntry['id']) ?>">
<label>
Auftrag
<select name="order_id" required>
<option value="">— auswählen —</option>
<?php foreach ($orders as $order): ?>
<option value="<?= e($order['id']) ?>" <?= $order['id'] === $editTimeEntry['orderId'] ? 'selected' : '' ?>><?= e(order_label($order)) ?></option>
<?php endforeach; ?>
</select>
</label>
<label>
Datum
<input type="date" name="date" value="<?= e(substr($editTimeEntry['date'], 0, 10)) ?>" required>
</label>
<label>
Beginn
<input type="time" name="start" value="<?= e(substr($editTimeEntry['start'], 0, 5)) ?>" required>
</label>
<label>
Ende
<input type="time" name="end" value="<?= e(substr($editTimeEntry['end'], 0, 5)) ?>" required>
</label>
<label>
Pause (Minuten)
<input type="number" min="0" name="break_minutes" value="<?= e((string)((int)substr($editTimeEntry['breakDuration'], 0, 2) * 60 + (int)substr($editTimeEntry['breakDuration'], 3, 2))) ?>">
</label>
<label>
Nachtstunden
<input type="number" min="0" step="0.25" name="night_hours" value="<?= e((string)$editTimeEntry['nightHours']) ?>">
</label>
<label>
Samstagsstunden
<input type="number" min="0" step="0.25" name="saturday_hours" value="<?= e((string)$editTimeEntry['saturdayHours']) ?>">
</label>
<label>
Sonntagsstunden
<input type="number" min="0" step="0.25" name="sunday_hours" value="<?= e((string)$editTimeEntry['sundayHours']) ?>">
</label>
<label>
Feiertagsstunden
<input type="number" min="0" step="0.25" name="holiday_hours" value="<?= e((string)$editTimeEntry['holidayHours']) ?>">
</label>
<button type="submit" class="btn" style="width:100%;margin-top:6px">Änderungen speichern</button>
</form>
<p class="hint" style="text-align:center;margin-top:14px">
<a href="stundenerfassung.php">Abbrechen</a>
</p>
<?php elseif ($canCreate): ?>
<form method="post">
<?= csrf_field() ?>
<input type="hidden" name="mode" value="create">
<label>
Auftrag
<select name="order_id" required>
<option value="">— auswählen —</option>
<?php foreach ($orders as $order): ?>
<option value="<?= e($order['id']) ?>"><?= e(order_label($order)) ?></option>
<?php endforeach; ?>
</select>
</label>
<label>
Datum
<input type="date" name="date" required>
</label>
<label>
Beginn
<input type="time" name="start" required>
</label>
<label>
Ende
<input type="time" name="end" required>
</label>
<label>
Pause (Minuten)
<input type="number" min="0" name="break_minutes" value="0">
</label>
<label>
Nachtstunden
<input type="number" min="0" step="0.25" name="night_hours" value="0">
</label>
<label>
Samstagsstunden
<input type="number" min="0" step="0.25" name="saturday_hours" value="0">
</label>
<label>
Sonntagsstunden
<input type="number" min="0" step="0.25" name="sunday_hours" value="0">
</label>
<label>
Feiertagsstunden
<input type="number" min="0" step="0.25" name="holiday_hours" value="0">
</label>
<button type="submit" class="btn" style="width:100%;margin-top:6px">Eintrag anlegen</button>
</form>
<?php else: ?>
<p class="hint">Keine Berechtigung, eine Zeiterfassung anzulegen.</p>
<?php endif; ?>
</div>
</div>
<?php
layout_end();