Migrate omsorgapp to browser SPA, add Docker/CI build setup
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
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
- 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>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
e9e96a57dc
commit
598dfcd38a
@@ -0,0 +1,292 @@
|
||||
<?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();
|
||||
Reference in New Issue
Block a user