Add facilities, contracts, orders, value lists, audit log, and desktop app modules

Extends omsorgCore with full CRUD for Facility/Contract/Order plus
configurable value lists and an audit trail, and wires the omsorgapp
frontend up to the new facilities, settings, and audit-log modules;
includes a sidebar active-nav-item highlight.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Felix Kemmler
2026-08-08 23:29:16 +02:00
co-authored by Claude Sonnet 5
parent ee74ed65f5
commit e9e96a57dc
528 changed files with 96814 additions and 456 deletions
+7 -4
View File
@@ -1,7 +1,7 @@
<?php
// Minimale Hülle für dieses Milestone - nur Dashboard im Nav, keine Admin-Sichtbarkeit
// (Connect hat kategorisch keine Admin-Oberfläche, siehe CLAUDE.md).
function layout_start(string $title): void {
// Minimale Hülle für dieses Milestone - nur Dashboard/Einstellungen im Nav, keine
// Admin-Sichtbarkeit (Connect hat kategorisch keine Admin-Oberfläche, siehe CLAUDE.md).
function layout_start(string $title, string $currentPage = 'dashboard'): void {
$name = current_name();
?>
<!DOCTYPE html>
@@ -32,9 +32,12 @@ function layout_start(string $title): void {
</div>
<nav class="sidebar-nav">
<a href="dashboard.php" class="active">
<a href="dashboard.php" class="<?= $currentPage === 'dashboard' ? 'active' : '' ?>">
<span class="nav-icon">⊞</span><span class="nav-label"> Dashboard</span>
</a>
<a href="settings.php" class="<?= $currentPage === 'settings' ? 'active' : '' ?>">
<span class="nav-icon">⚙</span><span class="nav-label"> Einstellungen</span>
</a>
</nav>
<div class="sidebar-footer">
@@ -5,62 +5,108 @@
//
// Nur Auth-Endpunkte - Connect verwaltet keine anderen Nutzer/Rollen/Mitarbeiter (das bleibt
// OMSORG Desktop vorbehalten), deshalb fehlen hier bewusst omsorgcore_users_*/roles_*/employees_*.
//
// Transport läuft über den generierten Client in ../api-client-php/ (openapi-generator, siehe
// ../api-client-php/ANLEITUNG.md) statt über rohes cURL.
function omsorgcore_request(array $config, string $method, string $path, ?array $body = null, ?string $accessToken = null): array
require_once __DIR__ . '/../api-client-php/vendor/autoload.php';
// Führt einen generierten Api-Aufruf aus und wandelt das Ergebnis in den einheitlichen
// Vertrag ['ok', 'status', 'data'] um. $fn bekommt die fertige Configuration und muss
// eine ...WithHttpInfo(...)-Methode aufrufen (liefert [$model, $status, $headers]).
function omsorgcore_call(array $config, callable $fn): array
{
$url = rtrim($config['omsorg_core_url'], '/') . $path;
$configuration = \OmsorgCoreClient\Configuration::getDefaultConfiguration()
->setHost(rtrim($config['omsorg_core_url'], '/'));
$headers = ['Content-Type: application/json'];
if ($accessToken !== null) {
$headers[] = 'Authorization: Bearer ' . $accessToken;
try {
[$model, $status] = $fn($configuration);
$data = $model !== null ? json_decode(json_encode($model), true) : null;
return ['ok' => true, 'status' => $status, 'data' => $data];
} catch (\OmsorgCoreClient\ApiException $e) {
$status = $e->getCode();
$body = $e->getResponseBody();
$decoded = $body !== null ? json_decode($body, true) : null;
$data = $decoded !== null ? $decoded : $body;
$result = ['ok' => false, 'status' => $status, 'data' => $data];
if ($status === 0) {
$result['error'] = $e->getMessage();
}
return $result;
}
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 15,
CURLOPT_POSTFIELDS => $body !== null ? json_encode($body) : null,
]);
$responseBody = curl_exec($ch);
if ($responseBody === false) {
$error = curl_error($ch);
curl_close($ch);
return ['ok' => false, 'status' => 0, 'data' => null, 'error' => $error];
}
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$data = null;
if ($responseBody !== '') {
$decoded = json_decode($responseBody, true);
$data = json_last_error() === JSON_ERROR_NONE ? $decoded : null;
}
return ['ok' => $status >= 200 && $status < 300, 'status' => $status, 'data' => $data];
}
// --- Auth ---
function omsorgcore_login(array $config, string $username, string $password): array
{
return omsorgcore_request($config, 'POST', '/api/auth/login', ['username' => $username, 'password' => $password]);
$loginRequest = new \OmsorgCoreClient\Model\LoginRequest(['username' => $username, 'password' => $password]);
return omsorgcore_call($config, fn($cfg) => (new \OmsorgCoreClient\Api\AuthApi(new \GuzzleHttp\Client(), $cfg))
->apiAuthLoginPostWithHttpInfo($loginRequest));
}
function omsorgcore_refresh(array $config, string $refreshToken): array
{
return omsorgcore_request($config, 'POST', '/api/auth/refresh', ['refreshToken' => $refreshToken]);
$refreshRequest = new \OmsorgCoreClient\Model\RefreshRequest(['refresh_token' => $refreshToken]);
return omsorgcore_call($config, fn($cfg) => (new \OmsorgCoreClient\Api\AuthApi(new \GuzzleHttp\Client(), $cfg))
->apiAuthRefreshPostWithHttpInfo($refreshRequest));
}
function omsorgcore_logout(array $config, string $refreshToken): array
{
return omsorgcore_request($config, 'POST', '/api/auth/logout', ['refreshToken' => $refreshToken]);
$logoutRequest = new \OmsorgCoreClient\Model\LogoutRequest(['refresh_token' => $refreshToken]);
return omsorgcore_call($config, fn($cfg) => (new \OmsorgCoreClient\Api\AuthApi(new \GuzzleHttp\Client(), $cfg))
->apiAuthLogoutPostWithHttpInfo($logoutRequest));
}
function omsorgcore_me(array $config, string $accessToken): array
{
return omsorgcore_request($config, 'GET', '/api/auth/me', null, $accessToken);
return omsorgcore_call($config, function ($cfg) use ($accessToken) {
$cfg->setAccessToken($accessToken);
return (new \OmsorgCoreClient\Api\AuthApi(new \GuzzleHttp\Client(), $cfg))->apiAuthMeGetWithHttpInfo();
});
}
function omsorgcore_change_password(array $config, string $accessToken, string $currentPassword, string $newPassword): array
{
$changePasswordRequest = new \OmsorgCoreClient\Model\ChangePasswordRequest([
'current_password' => $currentPassword,
'new_password' => $newPassword,
]);
return omsorgcore_call($config, function ($cfg) use ($accessToken, $changePasswordRequest) {
$cfg->setAccessToken($accessToken);
return (new \OmsorgCoreClient\Api\AuthApi(new \GuzzleHttp\Client(), $cfg))
->apiAuthChangePasswordPostWithHttpInfo($changePasswordRequest);
});
}
function omsorgcore_forgot_password_request(array $config, string $username): array
{
$forgotPasswordRequestRequest = new \OmsorgCoreClient\Model\ForgotPasswordRequestRequest(['username' => $username]);
return omsorgcore_call($config, fn($cfg) => (new \OmsorgCoreClient\Api\AuthApi(new \GuzzleHttp\Client(), $cfg))
->apiAuthForgotPasswordRequestPostWithHttpInfo($forgotPasswordRequestRequest));
}
function omsorgcore_forgot_password_verify(array $config, string $username, string $pin): array
{
$forgotPasswordVerifyRequest = new \OmsorgCoreClient\Model\ForgotPasswordVerifyRequest(['username' => $username, 'pin' => $pin]);
return omsorgcore_call($config, fn($cfg) => (new \OmsorgCoreClient\Api\AuthApi(new \GuzzleHttp\Client(), $cfg))
->apiAuthForgotPasswordVerifyPostWithHttpInfo($forgotPasswordVerifyRequest));
}
function omsorgcore_forgot_password_reset(array $config, string $resetToken, string $newPassword): array
{
$forgotPasswordResetRequest = new \OmsorgCoreClient\Model\ForgotPasswordResetRequest([
'reset_token' => $resetToken,
'new_password' => $newPassword,
]);
return omsorgcore_call($config, fn($cfg) => (new \OmsorgCoreClient\Api\AuthApi(new \GuzzleHttp\Client(), $cfg))
->apiAuthForgotPasswordResetPostWithHttpInfo($forgotPasswordResetRequest));
}
function omsorgcore_password_policy(array $config): array
{
return omsorgcore_call($config, fn($cfg) => (new \OmsorgCoreClient\Api\AuthApi(new \GuzzleHttp\Client(), $cfg))
->apiAuthPasswordPolicyGetWithHttpInfo());
}