Add kilometer-based Fahrtkosten billing, self-service distance entry, role deletion
Docker-Images bauen und veröffentlichen / build (, omsorgCore/Dockerfile, omsorgcore) (push) Successful in 16s
Docker-Images bauen und veröffentlichen / build (, omsorgWeb/Dockerfile, omsorgweb) (push) Successful in 5s
Docker-Images bauen und veröffentlichen / build (, omsorgapp/Dockerfile, omsorgapp) (push) Successful in 19s

- Facility: TravelCostMode (Pauschale/ProKilometer) + TravelCostPerKm, alongside
  the existing flat rate; fixes FacilityService.UpdateAsync silently dropping all
  Konditionen fields on update.
- New EmployeeFacilityDistance (Mitarbeiter x Einrichtung -> km) with full
  office-side CRUD in omsorgapp, plus a self-service endpoint/UI so field staff
  can maintain their own commute distance via OMSORG Connect (new
  ModuleType.EmployeeFacilityDistances, Own-scope, no Facilities access needed).
- Roles can now be deleted (blocked with a 409 while still assigned to a user).
- Fix employeesApi.js missing the Date-object conversion for dateOfBirth/entryDate/
  exitDate that crashed employee creation whenever a date was filled in; add a
  clear/remove control for those date fields.
- Requirements: flesh out FR-REC-3 with a staged Akquise cadence, add FR-REC-5/6
  for CRM feature scope and success metrics.
- Regenerate api-client-ts and api-client-php for all of the above.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Felix Kemmler
2026-08-10 20:15:42 +02:00
co-authored by Claude Sonnet 5
parent 09dce2ab98
commit ffa2c4a9e7
120 changed files with 13433 additions and 36 deletions
@@ -86,6 +86,12 @@ class TrashApi
'apiTrashContractsIdRestorePost' => [
'application/json',
],
'apiTrashEmployeeFacilityDistancesGet' => [
'application/json',
],
'apiTrashEmployeeFacilityDistancesIdRestorePost' => [
'application/json',
],
'apiTrashEmployeesGet' => [
'application/json',
],
@@ -1049,6 +1055,479 @@ class TrashApi
// path params
if ($id !== null) {
$resourcePath = str_replace(
'{' . 'id' . '}',
ObjectSerializer::toPathValue($id),
$resourcePath
);
}
$headers = $this->headerSelector->selectHeaders(
[],
$contentType,
$multipart
);
// for model (json/xml)
if (count($formParams) > 0) {
if ($multipart) {
$multipartContents = [];
foreach ($formParams as $formParamName => $formParamValue) {
$formParamValueItems = is_array($formParamValue) ? $formParamValue : [$formParamValue];
foreach ($formParamValueItems as $formParamValueItem) {
$multipartContents[] = [
'name' => $formParamName,
'contents' => $formParamValueItem
];
}
}
// for HTTP post (form)
$httpBody = new MultipartStream($multipartContents);
} elseif (stripos($headers['Content-Type'], 'application/json') !== false) {
# if Content-Type contains "application/json", json_encode the form parameters
$httpBody = \GuzzleHttp\Utils::jsonEncode($formParams);
} else {
// for HTTP post (form)
$httpBody = ObjectSerializer::buildQuery($formParams);
}
}
// this endpoint requires Bearer (JWT) authentication (access token)
if (!empty($this->config->getAccessToken())) {
$headers['Authorization'] = 'Bearer ' . $this->config->getAccessToken();
}
$defaultHeaders = [];
if ($this->config->getUserAgent()) {
$defaultHeaders['User-Agent'] = $this->config->getUserAgent();
}
$headers = array_merge(
$defaultHeaders,
$headerParams,
$headers
);
$operationHost = $this->config->getHost();
$query = ObjectSerializer::buildQuery($queryParams);
return new Request(
'POST',
$operationHost . $resourcePath . ($query ? "?{$query}" : ''),
$headers,
$httpBody
);
}
/**
* Operation apiTrashEmployeeFacilityDistancesGet
*
* @param string|null $search search (optional)
* @param string $contentType The value for the Content-Type header. Check self::contentTypes['apiTrashEmployeeFacilityDistancesGet'] to see the possible values for this operation
*
* @throws \OmsorgCoreClient\ApiException on non-2xx response or if the response body is not in the expected format
* @throws \InvalidArgumentException
* @return \OmsorgCoreClient\Model\TrashEmployeeFacilityDistanceResponse[]
*/
public function apiTrashEmployeeFacilityDistancesGet($search = null, string $contentType = self::contentTypes['apiTrashEmployeeFacilityDistancesGet'][0])
{
list($response) = $this->apiTrashEmployeeFacilityDistancesGetWithHttpInfo($search, $contentType);
return $response;
}
/**
* Operation apiTrashEmployeeFacilityDistancesGetWithHttpInfo
*
* @param string|null $search (optional)
* @param string $contentType The value for the Content-Type header. Check self::contentTypes['apiTrashEmployeeFacilityDistancesGet'] to see the possible values for this operation
*
* @throws \OmsorgCoreClient\ApiException on non-2xx response or if the response body is not in the expected format
* @throws \InvalidArgumentException
* @return array of \OmsorgCoreClient\Model\TrashEmployeeFacilityDistanceResponse[], HTTP status code, HTTP response headers (array of strings)
*/
public function apiTrashEmployeeFacilityDistancesGetWithHttpInfo($search = null, string $contentType = self::contentTypes['apiTrashEmployeeFacilityDistancesGet'][0])
{
$request = $this->apiTrashEmployeeFacilityDistancesGetRequest($search, $contentType);
try {
$options = $this->createHttpClientOption();
try {
$response = $this->client->send($request, $options);
} catch (RequestException $e) {
throw new ApiException(
"[{$e->getCode()}] {$e->getMessage()}",
(int) $e->getCode(),
$e->getResponse() ? $e->getResponse()->getHeaders() : null,
$e->getResponse() ? (string) $e->getResponse()->getBody() : null
);
} catch (ConnectException $e) {
throw new ApiException(
"[{$e->getCode()}] {$e->getMessage()}",
(int) $e->getCode(),
null,
null
);
}
$statusCode = $response->getStatusCode();
switch($statusCode) {
case 200:
return $this->handleResponseWithDataType(
'\OmsorgCoreClient\Model\TrashEmployeeFacilityDistanceResponse[]',
$request,
$response,
);
}
if ($statusCode < 200 || $statusCode > 299) {
throw new ApiException(
sprintf(
'[%d] Error connecting to the API (%s)',
$statusCode,
(string) $request->getUri()
),
$statusCode,
$response->getHeaders(),
(string) $response->getBody()
);
}
return $this->handleResponseWithDataType(
'\OmsorgCoreClient\Model\TrashEmployeeFacilityDistanceResponse[]',
$request,
$response,
);
} catch (ApiException $e) {
switch ($e->getCode()) {
case 200:
$data = ObjectSerializer::deserialize(
$e->getResponseBody(),
'\OmsorgCoreClient\Model\TrashEmployeeFacilityDistanceResponse[]',
$e->getResponseHeaders()
);
$e->setResponseObject($data);
throw $e;
}
throw $e;
}
}
/**
* Operation apiTrashEmployeeFacilityDistancesGetAsync
*
* @param string|null $search (optional)
* @param string $contentType The value for the Content-Type header. Check self::contentTypes['apiTrashEmployeeFacilityDistancesGet'] to see the possible values for this operation
*
* @throws \InvalidArgumentException
* @return \GuzzleHttp\Promise\PromiseInterface
*/
public function apiTrashEmployeeFacilityDistancesGetAsync($search = null, string $contentType = self::contentTypes['apiTrashEmployeeFacilityDistancesGet'][0])
{
return $this->apiTrashEmployeeFacilityDistancesGetAsyncWithHttpInfo($search, $contentType)
->then(
function ($response) {
return $response[0];
}
);
}
/**
* Operation apiTrashEmployeeFacilityDistancesGetAsyncWithHttpInfo
*
* @param string|null $search (optional)
* @param string $contentType The value for the Content-Type header. Check self::contentTypes['apiTrashEmployeeFacilityDistancesGet'] to see the possible values for this operation
*
* @throws \InvalidArgumentException
* @return \GuzzleHttp\Promise\PromiseInterface
*/
public function apiTrashEmployeeFacilityDistancesGetAsyncWithHttpInfo($search = null, string $contentType = self::contentTypes['apiTrashEmployeeFacilityDistancesGet'][0])
{
$returnType = '\OmsorgCoreClient\Model\TrashEmployeeFacilityDistanceResponse[]';
$request = $this->apiTrashEmployeeFacilityDistancesGetRequest($search, $contentType);
return $this->client
->sendAsync($request, $this->createHttpClientOption())
->then(
function ($response) use ($returnType) {
if ($returnType === '\SplFileObject') {
$content = $response->getBody(); //stream goes to serializer
} else {
$content = (string) $response->getBody();
if ($returnType !== 'string') {
$content = json_decode($content);
}
}
return [
ObjectSerializer::deserialize($content, $returnType, []),
$response->getStatusCode(),
$response->getHeaders()
];
},
function ($exception) {
$response = $exception->getResponse();
$statusCode = $response->getStatusCode();
throw new ApiException(
sprintf(
'[%d] Error connecting to the API (%s)',
$statusCode,
$exception->getRequest()->getUri()
),
$statusCode,
$response->getHeaders(),
(string) $response->getBody()
);
}
);
}
/**
* Create request for operation 'apiTrashEmployeeFacilityDistancesGet'
*
* @param string|null $search (optional)
* @param string $contentType The value for the Content-Type header. Check self::contentTypes['apiTrashEmployeeFacilityDistancesGet'] to see the possible values for this operation
*
* @throws \InvalidArgumentException
* @return \GuzzleHttp\Psr7\Request
*/
public function apiTrashEmployeeFacilityDistancesGetRequest($search = null, string $contentType = self::contentTypes['apiTrashEmployeeFacilityDistancesGet'][0])
{
$resourcePath = '/api/trash/employee-facility-distances';
$formParams = [];
$queryParams = [];
$headerParams = [];
$httpBody = '';
$multipart = false;
// query params
$queryParams = array_merge($queryParams, ObjectSerializer::toQueryValue(
$search,
'search', // param base name
'string', // openApiType
'form', // style
true, // explode
false // required
) ?? []);
$headers = $this->headerSelector->selectHeaders(
['text/plain', 'application/json', 'text/json', ],
$contentType,
$multipart
);
// for model (json/xml)
if (count($formParams) > 0) {
if ($multipart) {
$multipartContents = [];
foreach ($formParams as $formParamName => $formParamValue) {
$formParamValueItems = is_array($formParamValue) ? $formParamValue : [$formParamValue];
foreach ($formParamValueItems as $formParamValueItem) {
$multipartContents[] = [
'name' => $formParamName,
'contents' => $formParamValueItem
];
}
}
// for HTTP post (form)
$httpBody = new MultipartStream($multipartContents);
} elseif (stripos($headers['Content-Type'], 'application/json') !== false) {
# if Content-Type contains "application/json", json_encode the form parameters
$httpBody = \GuzzleHttp\Utils::jsonEncode($formParams);
} else {
// for HTTP post (form)
$httpBody = ObjectSerializer::buildQuery($formParams);
}
}
// this endpoint requires Bearer (JWT) authentication (access token)
if (!empty($this->config->getAccessToken())) {
$headers['Authorization'] = 'Bearer ' . $this->config->getAccessToken();
}
$defaultHeaders = [];
if ($this->config->getUserAgent()) {
$defaultHeaders['User-Agent'] = $this->config->getUserAgent();
}
$headers = array_merge(
$defaultHeaders,
$headerParams,
$headers
);
$operationHost = $this->config->getHost();
$query = ObjectSerializer::buildQuery($queryParams);
return new Request(
'GET',
$operationHost . $resourcePath . ($query ? "?{$query}" : ''),
$headers,
$httpBody
);
}
/**
* Operation apiTrashEmployeeFacilityDistancesIdRestorePost
*
* @param string $id id (required)
* @param string $contentType The value for the Content-Type header. Check self::contentTypes['apiTrashEmployeeFacilityDistancesIdRestorePost'] to see the possible values for this operation
*
* @throws \OmsorgCoreClient\ApiException on non-2xx response or if the response body is not in the expected format
* @throws \InvalidArgumentException
* @return void
*/
public function apiTrashEmployeeFacilityDistancesIdRestorePost($id, string $contentType = self::contentTypes['apiTrashEmployeeFacilityDistancesIdRestorePost'][0])
{
$this->apiTrashEmployeeFacilityDistancesIdRestorePostWithHttpInfo($id, $contentType);
}
/**
* Operation apiTrashEmployeeFacilityDistancesIdRestorePostWithHttpInfo
*
* @param string $id (required)
* @param string $contentType The value for the Content-Type header. Check self::contentTypes['apiTrashEmployeeFacilityDistancesIdRestorePost'] to see the possible values for this operation
*
* @throws \OmsorgCoreClient\ApiException on non-2xx response or if the response body is not in the expected format
* @throws \InvalidArgumentException
* @return array of null, HTTP status code, HTTP response headers (array of strings)
*/
public function apiTrashEmployeeFacilityDistancesIdRestorePostWithHttpInfo($id, string $contentType = self::contentTypes['apiTrashEmployeeFacilityDistancesIdRestorePost'][0])
{
$request = $this->apiTrashEmployeeFacilityDistancesIdRestorePostRequest($id, $contentType);
try {
$options = $this->createHttpClientOption();
try {
$response = $this->client->send($request, $options);
} catch (RequestException $e) {
throw new ApiException(
"[{$e->getCode()}] {$e->getMessage()}",
(int) $e->getCode(),
$e->getResponse() ? $e->getResponse()->getHeaders() : null,
$e->getResponse() ? (string) $e->getResponse()->getBody() : null
);
} catch (ConnectException $e) {
throw new ApiException(
"[{$e->getCode()}] {$e->getMessage()}",
(int) $e->getCode(),
null,
null
);
}
$statusCode = $response->getStatusCode();
return [null, $statusCode, $response->getHeaders()];
} catch (ApiException $e) {
switch ($e->getCode()) {
}
throw $e;
}
}
/**
* Operation apiTrashEmployeeFacilityDistancesIdRestorePostAsync
*
* @param string $id (required)
* @param string $contentType The value for the Content-Type header. Check self::contentTypes['apiTrashEmployeeFacilityDistancesIdRestorePost'] to see the possible values for this operation
*
* @throws \InvalidArgumentException
* @return \GuzzleHttp\Promise\PromiseInterface
*/
public function apiTrashEmployeeFacilityDistancesIdRestorePostAsync($id, string $contentType = self::contentTypes['apiTrashEmployeeFacilityDistancesIdRestorePost'][0])
{
return $this->apiTrashEmployeeFacilityDistancesIdRestorePostAsyncWithHttpInfo($id, $contentType)
->then(
function ($response) {
return $response[0];
}
);
}
/**
* Operation apiTrashEmployeeFacilityDistancesIdRestorePostAsyncWithHttpInfo
*
* @param string $id (required)
* @param string $contentType The value for the Content-Type header. Check self::contentTypes['apiTrashEmployeeFacilityDistancesIdRestorePost'] to see the possible values for this operation
*
* @throws \InvalidArgumentException
* @return \GuzzleHttp\Promise\PromiseInterface
*/
public function apiTrashEmployeeFacilityDistancesIdRestorePostAsyncWithHttpInfo($id, string $contentType = self::contentTypes['apiTrashEmployeeFacilityDistancesIdRestorePost'][0])
{
$returnType = '';
$request = $this->apiTrashEmployeeFacilityDistancesIdRestorePostRequest($id, $contentType);
return $this->client
->sendAsync($request, $this->createHttpClientOption())
->then(
function ($response) use ($returnType) {
return [null, $response->getStatusCode(), $response->getHeaders()];
},
function ($exception) {
$response = $exception->getResponse();
$statusCode = $response->getStatusCode();
throw new ApiException(
sprintf(
'[%d] Error connecting to the API (%s)',
$statusCode,
$exception->getRequest()->getUri()
),
$statusCode,
$response->getHeaders(),
(string) $response->getBody()
);
}
);
}
/**
* Create request for operation 'apiTrashEmployeeFacilityDistancesIdRestorePost'
*
* @param string $id (required)
* @param string $contentType The value for the Content-Type header. Check self::contentTypes['apiTrashEmployeeFacilityDistancesIdRestorePost'] to see the possible values for this operation
*
* @throws \InvalidArgumentException
* @return \GuzzleHttp\Psr7\Request
*/
public function apiTrashEmployeeFacilityDistancesIdRestorePostRequest($id, string $contentType = self::contentTypes['apiTrashEmployeeFacilityDistancesIdRestorePost'][0])
{
// verify the required parameter 'id' is set
if ($id === null || (is_array($id) && count($id) === 0)) {
throw new \InvalidArgumentException(
'Missing the required parameter $id when calling apiTrashEmployeeFacilityDistancesIdRestorePost'
);
}
$resourcePath = '/api/trash/employee-facility-distances/{id}/restore';
$formParams = [];
$queryParams = [];
$headerParams = [];
$httpBody = '';
$multipart = false;
// path params
if ($id !== null) {
$resourcePath = str_replace(