Add employee-to-order assignments with FR-EM-3 conflict validation
Docker-Images bauen und veröffentlichen / build (, omsorgCore/Dockerfile, omsorgcore) (push) Successful in 17s
Docker-Images bauen und veröffentlichen / build (, omsorgWeb/Dockerfile, omsorgweb) (push) Successful in 6s
Docker-Images bauen und veröffentlichen / build (, omsorgapp/Dockerfile, omsorgapp) (push) Successful in 17s

Adds the Assignment core object (Order x Employee, date range) with full
CRUD, soft-delete/trash integration, and generated API clients.

Layers FR-EM-3 conflict checks onto assignment creation: qualification,
absence/availability, working-hours approximation, cross-order overlap,
and active-contract coverage. Each check's severity (Warning vs. Error)
is configurable at runtime via a new AssignmentValidationSettings
singleton and admin settings panel, instead of being hardcoded - lets
the business tune strictness per check without a redeploy.

Adds a live GET /api/assignments/check endpoint so the create-assignment
dialog can preview conflicts as the user picks employee/dates, before
they hit save, rather than only finding out after submitting.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Felix Kemmler
2026-08-11 00:44:14 +02:00
co-authored by Claude Sonnet 5
parent fcbf27db3c
commit dfeb37cf33
104 changed files with 15846 additions and 7 deletions
@@ -5,6 +5,8 @@ package.json
src/apis/AbsencesApi.ts
src/apis/AdminEmailApi.ts
src/apis/AdminSessionsApi.ts
src/apis/AssignmentValidationSettingsApi.ts
src/apis/AssignmentsApi.ts
src/apis/AuditLogApi.ts
src/apis/AuthApi.ts
src/apis/ContractsApi.ts
@@ -28,6 +30,10 @@ src/models/AbsenceDecisionRequest.ts
src/models/AbsenceResponse.ts
src/models/AbsenceResponsePagedResponse.ts
src/models/AddUserPermissionOverrideRequest.ts
src/models/AssignmentConflictResponse.ts
src/models/AssignmentResponse.ts
src/models/AssignmentResponsePagedResponse.ts
src/models/AssignmentValidationSettingsResponse.ts
src/models/AuditEventCategory.ts
src/models/AuditLogEntryResponse.ts
src/models/AuditLogEntryResponsePagedResponse.ts
@@ -35,6 +41,7 @@ src/models/ChangePasswordRequest.ts
src/models/ContractResponse.ts
src/models/ContractResponsePagedResponse.ts
src/models/CreateAbsenceRequest.ts
src/models/CreateAssignmentRequest.ts
src/models/CreateContractRequest.ts
src/models/CreateEmployeeFacilityDistanceRequest.ts
src/models/CreateEmployeeRequest.ts
@@ -85,6 +92,7 @@ src/models/TimeEntryDecisionRequest.ts
src/models/TimeEntryResponse.ts
src/models/TimeEntryResponsePagedResponse.ts
src/models/TrashAbsenceResponse.ts
src/models/TrashAssignmentResponse.ts
src/models/TrashContractResponse.ts
src/models/TrashEmployeeFacilityDistanceResponse.ts
src/models/TrashEmployeeResponse.ts
@@ -94,6 +102,8 @@ src/models/TrashFacilityResponse.ts
src/models/TrashOrderResponse.ts
src/models/TrashTimeEntryResponse.ts
src/models/UpdateAbsenceRequest.ts
src/models/UpdateAssignmentRequest.ts
src/models/UpdateAssignmentValidationSettingsRequest.ts
src/models/UpdateContractRequest.ts
src/models/UpdateDocumentRequest.ts
src/models/UpdateEmployeeFacilityDistanceRequest.ts
@@ -0,0 +1,110 @@
/* tslint:disable */
/* eslint-disable */
/**
* OmsorgCore.Api
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import * as runtime from '../runtime';
import type {
AssignmentValidationSettingsResponse,
UpdateAssignmentValidationSettingsRequest,
} from '../models/index';
import {
AssignmentValidationSettingsResponseFromJSON,
AssignmentValidationSettingsResponseToJSON,
UpdateAssignmentValidationSettingsRequestFromJSON,
UpdateAssignmentValidationSettingsRequestToJSON,
} from '../models/index';
export interface ApiSettingsAssignmentValidationPutRequest {
updateAssignmentValidationSettingsRequest?: UpdateAssignmentValidationSettingsRequest;
}
/**
*
*/
export class AssignmentValidationSettingsApi extends runtime.BaseAPI {
/**
*/
async apiSettingsAssignmentValidationGetRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<AssignmentValidationSettingsResponse>> {
const queryParameters: any = {};
const headerParameters: runtime.HTTPHeaders = {};
if (this.configuration && this.configuration.accessToken) {
const token = this.configuration.accessToken;
const tokenString = await token("Bearer", []);
if (tokenString) {
headerParameters["Authorization"] = `Bearer ${tokenString}`;
}
}
let urlPath = `/api/settings/assignment-validation`;
const response = await this.request({
path: urlPath,
method: 'GET',
headers: headerParameters,
query: queryParameters,
}, initOverrides);
return new runtime.JSONApiResponse(response, (jsonValue) => AssignmentValidationSettingsResponseFromJSON(jsonValue));
}
/**
*/
async apiSettingsAssignmentValidationGet(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<AssignmentValidationSettingsResponse> {
const response = await this.apiSettingsAssignmentValidationGetRaw(initOverrides);
return await response.value();
}
/**
*/
async apiSettingsAssignmentValidationPutRaw(requestParameters: ApiSettingsAssignmentValidationPutRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<AssignmentValidationSettingsResponse>> {
const queryParameters: any = {};
const headerParameters: runtime.HTTPHeaders = {};
headerParameters['Content-Type'] = 'application/json';
if (this.configuration && this.configuration.accessToken) {
const token = this.configuration.accessToken;
const tokenString = await token("Bearer", []);
if (tokenString) {
headerParameters["Authorization"] = `Bearer ${tokenString}`;
}
}
let urlPath = `/api/settings/assignment-validation`;
const response = await this.request({
path: urlPath,
method: 'PUT',
headers: headerParameters,
query: queryParameters,
body: UpdateAssignmentValidationSettingsRequestToJSON(requestParameters['updateAssignmentValidationSettingsRequest']),
}, initOverrides);
return new runtime.JSONApiResponse(response, (jsonValue) => AssignmentValidationSettingsResponseFromJSON(jsonValue));
}
/**
*/
async apiSettingsAssignmentValidationPut(requestParameters: ApiSettingsAssignmentValidationPutRequest = {}, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<AssignmentValidationSettingsResponse> {
const response = await this.apiSettingsAssignmentValidationPutRaw(requestParameters, initOverrides);
return await response.value();
}
}
@@ -0,0 +1,359 @@
/* tslint:disable */
/* eslint-disable */
/**
* OmsorgCore.Api
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import * as runtime from '../runtime';
import type {
AssignmentConflictResponse,
AssignmentResponse,
AssignmentResponsePagedResponse,
CreateAssignmentRequest,
UpdateAssignmentRequest,
} from '../models/index';
import {
AssignmentConflictResponseFromJSON,
AssignmentConflictResponseToJSON,
AssignmentResponseFromJSON,
AssignmentResponseToJSON,
AssignmentResponsePagedResponseFromJSON,
AssignmentResponsePagedResponseToJSON,
CreateAssignmentRequestFromJSON,
CreateAssignmentRequestToJSON,
UpdateAssignmentRequestFromJSON,
UpdateAssignmentRequestToJSON,
} from '../models/index';
export interface ApiAssignmentsCheckGetRequest {
orderId?: string;
employeeId?: string;
startDate?: Date;
endDate?: Date;
excludeAssignmentId?: string;
}
export interface ApiAssignmentsGetRequest {
orderId?: string;
employeeId?: string;
fromDate?: Date;
toDate?: Date;
page?: number;
pageSize?: number;
}
export interface ApiAssignmentsIdDeleteRequest {
id: string;
}
export interface ApiAssignmentsIdGetRequest {
id: string;
}
export interface ApiAssignmentsIdPutRequest {
id: string;
updateAssignmentRequest?: UpdateAssignmentRequest;
}
export interface ApiAssignmentsPostRequest {
createAssignmentRequest?: CreateAssignmentRequest;
}
/**
*
*/
export class AssignmentsApi extends runtime.BaseAPI {
/**
*/
async apiAssignmentsCheckGetRaw(requestParameters: ApiAssignmentsCheckGetRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<Array<AssignmentConflictResponse>>> {
const queryParameters: any = {};
if (requestParameters['orderId'] != null) {
queryParameters['orderId'] = requestParameters['orderId'];
}
if (requestParameters['employeeId'] != null) {
queryParameters['employeeId'] = requestParameters['employeeId'];
}
if (requestParameters['startDate'] != null) {
queryParameters['startDate'] = (requestParameters['startDate'] as any).toISOString().substring(0,10);
}
if (requestParameters['endDate'] != null) {
queryParameters['endDate'] = (requestParameters['endDate'] as any).toISOString().substring(0,10);
}
if (requestParameters['excludeAssignmentId'] != null) {
queryParameters['excludeAssignmentId'] = requestParameters['excludeAssignmentId'];
}
const headerParameters: runtime.HTTPHeaders = {};
if (this.configuration && this.configuration.accessToken) {
const token = this.configuration.accessToken;
const tokenString = await token("Bearer", []);
if (tokenString) {
headerParameters["Authorization"] = `Bearer ${tokenString}`;
}
}
let urlPath = `/api/assignments/check`;
const response = await this.request({
path: urlPath,
method: 'GET',
headers: headerParameters,
query: queryParameters,
}, initOverrides);
return new runtime.JSONApiResponse(response, (jsonValue) => jsonValue.map(AssignmentConflictResponseFromJSON));
}
/**
*/
async apiAssignmentsCheckGet(requestParameters: ApiAssignmentsCheckGetRequest = {}, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<Array<AssignmentConflictResponse>> {
const response = await this.apiAssignmentsCheckGetRaw(requestParameters, initOverrides);
return await response.value();
}
/**
*/
async apiAssignmentsGetRaw(requestParameters: ApiAssignmentsGetRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<AssignmentResponsePagedResponse>> {
const queryParameters: any = {};
if (requestParameters['orderId'] != null) {
queryParameters['orderId'] = requestParameters['orderId'];
}
if (requestParameters['employeeId'] != null) {
queryParameters['employeeId'] = requestParameters['employeeId'];
}
if (requestParameters['fromDate'] != null) {
queryParameters['fromDate'] = (requestParameters['fromDate'] as any).toISOString().substring(0,10);
}
if (requestParameters['toDate'] != null) {
queryParameters['toDate'] = (requestParameters['toDate'] as any).toISOString().substring(0,10);
}
if (requestParameters['page'] != null) {
queryParameters['page'] = requestParameters['page'];
}
if (requestParameters['pageSize'] != null) {
queryParameters['pageSize'] = requestParameters['pageSize'];
}
const headerParameters: runtime.HTTPHeaders = {};
if (this.configuration && this.configuration.accessToken) {
const token = this.configuration.accessToken;
const tokenString = await token("Bearer", []);
if (tokenString) {
headerParameters["Authorization"] = `Bearer ${tokenString}`;
}
}
let urlPath = `/api/assignments`;
const response = await this.request({
path: urlPath,
method: 'GET',
headers: headerParameters,
query: queryParameters,
}, initOverrides);
return new runtime.JSONApiResponse(response, (jsonValue) => AssignmentResponsePagedResponseFromJSON(jsonValue));
}
/**
*/
async apiAssignmentsGet(requestParameters: ApiAssignmentsGetRequest = {}, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<AssignmentResponsePagedResponse> {
const response = await this.apiAssignmentsGetRaw(requestParameters, initOverrides);
return await response.value();
}
/**
*/
async apiAssignmentsIdDeleteRaw(requestParameters: ApiAssignmentsIdDeleteRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<void>> {
if (requestParameters['id'] == null) {
throw new runtime.RequiredError(
'id',
'Required parameter "id" was null or undefined when calling apiAssignmentsIdDelete().'
);
}
const queryParameters: any = {};
const headerParameters: runtime.HTTPHeaders = {};
if (this.configuration && this.configuration.accessToken) {
const token = this.configuration.accessToken;
const tokenString = await token("Bearer", []);
if (tokenString) {
headerParameters["Authorization"] = `Bearer ${tokenString}`;
}
}
let urlPath = `/api/assignments/{id}`;
urlPath = urlPath.replace(`{${"id"}}`, encodeURIComponent(String(requestParameters['id'])));
const response = await this.request({
path: urlPath,
method: 'DELETE',
headers: headerParameters,
query: queryParameters,
}, initOverrides);
return new runtime.VoidApiResponse(response);
}
/**
*/
async apiAssignmentsIdDelete(requestParameters: ApiAssignmentsIdDeleteRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<void> {
await this.apiAssignmentsIdDeleteRaw(requestParameters, initOverrides);
}
/**
*/
async apiAssignmentsIdGetRaw(requestParameters: ApiAssignmentsIdGetRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<AssignmentResponse>> {
if (requestParameters['id'] == null) {
throw new runtime.RequiredError(
'id',
'Required parameter "id" was null or undefined when calling apiAssignmentsIdGet().'
);
}
const queryParameters: any = {};
const headerParameters: runtime.HTTPHeaders = {};
if (this.configuration && this.configuration.accessToken) {
const token = this.configuration.accessToken;
const tokenString = await token("Bearer", []);
if (tokenString) {
headerParameters["Authorization"] = `Bearer ${tokenString}`;
}
}
let urlPath = `/api/assignments/{id}`;
urlPath = urlPath.replace(`{${"id"}}`, encodeURIComponent(String(requestParameters['id'])));
const response = await this.request({
path: urlPath,
method: 'GET',
headers: headerParameters,
query: queryParameters,
}, initOverrides);
return new runtime.JSONApiResponse(response, (jsonValue) => AssignmentResponseFromJSON(jsonValue));
}
/**
*/
async apiAssignmentsIdGet(requestParameters: ApiAssignmentsIdGetRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<AssignmentResponse> {
const response = await this.apiAssignmentsIdGetRaw(requestParameters, initOverrides);
return await response.value();
}
/**
*/
async apiAssignmentsIdPutRaw(requestParameters: ApiAssignmentsIdPutRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<AssignmentResponse>> {
if (requestParameters['id'] == null) {
throw new runtime.RequiredError(
'id',
'Required parameter "id" was null or undefined when calling apiAssignmentsIdPut().'
);
}
const queryParameters: any = {};
const headerParameters: runtime.HTTPHeaders = {};
headerParameters['Content-Type'] = 'application/json';
if (this.configuration && this.configuration.accessToken) {
const token = this.configuration.accessToken;
const tokenString = await token("Bearer", []);
if (tokenString) {
headerParameters["Authorization"] = `Bearer ${tokenString}`;
}
}
let urlPath = `/api/assignments/{id}`;
urlPath = urlPath.replace(`{${"id"}}`, encodeURIComponent(String(requestParameters['id'])));
const response = await this.request({
path: urlPath,
method: 'PUT',
headers: headerParameters,
query: queryParameters,
body: UpdateAssignmentRequestToJSON(requestParameters['updateAssignmentRequest']),
}, initOverrides);
return new runtime.JSONApiResponse(response, (jsonValue) => AssignmentResponseFromJSON(jsonValue));
}
/**
*/
async apiAssignmentsIdPut(requestParameters: ApiAssignmentsIdPutRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<AssignmentResponse> {
const response = await this.apiAssignmentsIdPutRaw(requestParameters, initOverrides);
return await response.value();
}
/**
*/
async apiAssignmentsPostRaw(requestParameters: ApiAssignmentsPostRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<AssignmentResponse>> {
const queryParameters: any = {};
const headerParameters: runtime.HTTPHeaders = {};
headerParameters['Content-Type'] = 'application/json';
if (this.configuration && this.configuration.accessToken) {
const token = this.configuration.accessToken;
const tokenString = await token("Bearer", []);
if (tokenString) {
headerParameters["Authorization"] = `Bearer ${tokenString}`;
}
}
let urlPath = `/api/assignments`;
const response = await this.request({
path: urlPath,
method: 'POST',
headers: headerParameters,
query: queryParameters,
body: CreateAssignmentRequestToJSON(requestParameters['createAssignmentRequest']),
}, initOverrides);
return new runtime.JSONApiResponse(response, (jsonValue) => AssignmentResponseFromJSON(jsonValue));
}
/**
*/
async apiAssignmentsPost(requestParameters: ApiAssignmentsPostRequest = {}, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<AssignmentResponse> {
const response = await this.apiAssignmentsPostRaw(requestParameters, initOverrides);
return await response.value();
}
}
@@ -16,6 +16,7 @@
import * as runtime from '../runtime';
import type {
TrashAbsenceResponse,
TrashAssignmentResponse,
TrashContractResponse,
TrashEmployeeFacilityDistanceResponse,
TrashEmployeeResponse,
@@ -28,6 +29,8 @@ import type {
import {
TrashAbsenceResponseFromJSON,
TrashAbsenceResponseToJSON,
TrashAssignmentResponseFromJSON,
TrashAssignmentResponseToJSON,
TrashContractResponseFromJSON,
TrashContractResponseToJSON,
TrashEmployeeFacilityDistanceResponseFromJSON,
@@ -54,6 +57,14 @@ export interface ApiTrashAbsencesIdRestorePostRequest {
id: string;
}
export interface ApiTrashAssignmentsGetRequest {
search?: string;
}
export interface ApiTrashAssignmentsIdRestorePostRequest {
id: string;
}
export interface ApiTrashContractsGetRequest {
search?: string;
}
@@ -204,6 +215,87 @@ export class TrashApi extends runtime.BaseAPI {
await this.apiTrashAbsencesIdRestorePostRaw(requestParameters, initOverrides);
}
/**
*/
async apiTrashAssignmentsGetRaw(requestParameters: ApiTrashAssignmentsGetRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<Array<TrashAssignmentResponse>>> {
const queryParameters: any = {};
if (requestParameters['search'] != null) {
queryParameters['search'] = requestParameters['search'];
}
const headerParameters: runtime.HTTPHeaders = {};
if (this.configuration && this.configuration.accessToken) {
const token = this.configuration.accessToken;
const tokenString = await token("Bearer", []);
if (tokenString) {
headerParameters["Authorization"] = `Bearer ${tokenString}`;
}
}
let urlPath = `/api/trash/assignments`;
const response = await this.request({
path: urlPath,
method: 'GET',
headers: headerParameters,
query: queryParameters,
}, initOverrides);
return new runtime.JSONApiResponse(response, (jsonValue) => jsonValue.map(TrashAssignmentResponseFromJSON));
}
/**
*/
async apiTrashAssignmentsGet(requestParameters: ApiTrashAssignmentsGetRequest = {}, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<Array<TrashAssignmentResponse>> {
const response = await this.apiTrashAssignmentsGetRaw(requestParameters, initOverrides);
return await response.value();
}
/**
*/
async apiTrashAssignmentsIdRestorePostRaw(requestParameters: ApiTrashAssignmentsIdRestorePostRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<void>> {
if (requestParameters['id'] == null) {
throw new runtime.RequiredError(
'id',
'Required parameter "id" was null or undefined when calling apiTrashAssignmentsIdRestorePost().'
);
}
const queryParameters: any = {};
const headerParameters: runtime.HTTPHeaders = {};
if (this.configuration && this.configuration.accessToken) {
const token = this.configuration.accessToken;
const tokenString = await token("Bearer", []);
if (tokenString) {
headerParameters["Authorization"] = `Bearer ${tokenString}`;
}
}
let urlPath = `/api/trash/assignments/{id}/restore`;
urlPath = urlPath.replace(`{${"id"}}`, encodeURIComponent(String(requestParameters['id'])));
const response = await this.request({
path: urlPath,
method: 'POST',
headers: headerParameters,
query: queryParameters,
}, initOverrides);
return new runtime.VoidApiResponse(response);
}
/**
*/
async apiTrashAssignmentsIdRestorePost(requestParameters: ApiTrashAssignmentsIdRestorePostRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<void> {
await this.apiTrashAssignmentsIdRestorePostRaw(requestParameters, initOverrides);
}
/**
*/
async apiTrashContractsGetRaw(requestParameters: ApiTrashContractsGetRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<Array<TrashContractResponse>>> {
@@ -3,6 +3,8 @@
export * from './AbsencesApi';
export * from './AdminEmailApi';
export * from './AdminSessionsApi';
export * from './AssignmentValidationSettingsApi';
export * from './AssignmentsApi';
export * from './AuditLogApi';
export * from './AuthApi';
export * from './ContractsApi';
@@ -0,0 +1,81 @@
/* tslint:disable */
/* eslint-disable */
/**
* OmsorgCore.Api
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { mapValues } from '../runtime';
/**
*
* @export
* @interface AssignmentConflictResponse
*/
export interface AssignmentConflictResponse {
/**
*
* @type {string}
* @memberof AssignmentConflictResponse
*/
type?: string | null;
/**
*
* @type {string}
* @memberof AssignmentConflictResponse
*/
severity?: string | null;
/**
*
* @type {string}
* @memberof AssignmentConflictResponse
*/
message?: string | null;
}
/**
* Check if a given object implements the AssignmentConflictResponse interface.
*/
export function instanceOfAssignmentConflictResponse(value: object): value is AssignmentConflictResponse {
return true;
}
export function AssignmentConflictResponseFromJSON(json: any): AssignmentConflictResponse {
return AssignmentConflictResponseFromJSONTyped(json, false);
}
export function AssignmentConflictResponseFromJSONTyped(json: any, ignoreDiscriminator: boolean): AssignmentConflictResponse {
if (json == null) {
return json;
}
return {
'type': json['type'] == null ? undefined : json['type'],
'severity': json['severity'] == null ? undefined : json['severity'],
'message': json['message'] == null ? undefined : json['message'],
};
}
export function AssignmentConflictResponseToJSON(json: any): AssignmentConflictResponse {
return AssignmentConflictResponseToJSONTyped(json, false);
}
export function AssignmentConflictResponseToJSONTyped(value?: AssignmentConflictResponse | null, ignoreDiscriminator: boolean = false): any {
if (value == null) {
return value;
}
return {
'type': value['type'],
'severity': value['severity'],
'message': value['message'],
};
}
@@ -0,0 +1,137 @@
/* tslint:disable */
/* eslint-disable */
/**
* OmsorgCore.Api
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { mapValues } from '../runtime';
import type { AssignmentConflictResponse } from './AssignmentConflictResponse';
import {
AssignmentConflictResponseFromJSON,
AssignmentConflictResponseFromJSONTyped,
AssignmentConflictResponseToJSON,
AssignmentConflictResponseToJSONTyped,
} from './AssignmentConflictResponse';
/**
*
* @export
* @interface AssignmentResponse
*/
export interface AssignmentResponse {
/**
*
* @type {string}
* @memberof AssignmentResponse
*/
id?: string;
/**
*
* @type {string}
* @memberof AssignmentResponse
*/
orderId?: string;
/**
*
* @type {string}
* @memberof AssignmentResponse
*/
employeeId?: string;
/**
*
* @type {string}
* @memberof AssignmentResponse
*/
employeeFirstName?: string | null;
/**
*
* @type {string}
* @memberof AssignmentResponse
*/
employeeLastName?: string | null;
/**
*
* @type {Date}
* @memberof AssignmentResponse
*/
startDate?: Date;
/**
*
* @type {Date}
* @memberof AssignmentResponse
*/
endDate?: Date;
/**
*
* @type {string}
* @memberof AssignmentResponse
*/
note?: string | null;
/**
*
* @type {Array<AssignmentConflictResponse>}
* @memberof AssignmentResponse
*/
conflicts?: Array<AssignmentConflictResponse> | null;
}
/**
* Check if a given object implements the AssignmentResponse interface.
*/
export function instanceOfAssignmentResponse(value: object): value is AssignmentResponse {
return true;
}
export function AssignmentResponseFromJSON(json: any): AssignmentResponse {
return AssignmentResponseFromJSONTyped(json, false);
}
export function AssignmentResponseFromJSONTyped(json: any, ignoreDiscriminator: boolean): AssignmentResponse {
if (json == null) {
return json;
}
return {
'id': json['id'] == null ? undefined : json['id'],
'orderId': json['orderId'] == null ? undefined : json['orderId'],
'employeeId': json['employeeId'] == null ? undefined : json['employeeId'],
'employeeFirstName': json['employeeFirstName'] == null ? undefined : json['employeeFirstName'],
'employeeLastName': json['employeeLastName'] == null ? undefined : json['employeeLastName'],
'startDate': json['startDate'] == null ? undefined : (new Date(json['startDate'])),
'endDate': json['endDate'] == null ? undefined : (new Date(json['endDate'])),
'note': json['note'] == null ? undefined : json['note'],
'conflicts': json['conflicts'] == null ? undefined : ((json['conflicts'] as Array<any>).map(AssignmentConflictResponseFromJSON)),
};
}
export function AssignmentResponseToJSON(json: any): AssignmentResponse {
return AssignmentResponseToJSONTyped(json, false);
}
export function AssignmentResponseToJSONTyped(value?: AssignmentResponse | null, ignoreDiscriminator: boolean = false): any {
if (value == null) {
return value;
}
return {
'id': value['id'],
'orderId': value['orderId'],
'employeeId': value['employeeId'],
'employeeFirstName': value['employeeFirstName'],
'employeeLastName': value['employeeLastName'],
'startDate': value['startDate'] == null ? undefined : ((value['startDate']).toISOString().substring(0,10)),
'endDate': value['endDate'] == null ? undefined : ((value['endDate']).toISOString().substring(0,10)),
'note': value['note'],
'conflicts': value['conflicts'] == null ? undefined : ((value['conflicts'] as Array<any>).map(AssignmentConflictResponseToJSON)),
};
}
@@ -0,0 +1,97 @@
/* tslint:disable */
/* eslint-disable */
/**
* OmsorgCore.Api
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { mapValues } from '../runtime';
import type { AssignmentResponse } from './AssignmentResponse';
import {
AssignmentResponseFromJSON,
AssignmentResponseFromJSONTyped,
AssignmentResponseToJSON,
AssignmentResponseToJSONTyped,
} from './AssignmentResponse';
/**
*
* @export
* @interface AssignmentResponsePagedResponse
*/
export interface AssignmentResponsePagedResponse {
/**
*
* @type {Array<AssignmentResponse>}
* @memberof AssignmentResponsePagedResponse
*/
items?: Array<AssignmentResponse> | null;
/**
*
* @type {number}
* @memberof AssignmentResponsePagedResponse
*/
totalCount?: number;
/**
*
* @type {number}
* @memberof AssignmentResponsePagedResponse
*/
page?: number;
/**
*
* @type {number}
* @memberof AssignmentResponsePagedResponse
*/
pageSize?: number;
}
/**
* Check if a given object implements the AssignmentResponsePagedResponse interface.
*/
export function instanceOfAssignmentResponsePagedResponse(value: object): value is AssignmentResponsePagedResponse {
return true;
}
export function AssignmentResponsePagedResponseFromJSON(json: any): AssignmentResponsePagedResponse {
return AssignmentResponsePagedResponseFromJSONTyped(json, false);
}
export function AssignmentResponsePagedResponseFromJSONTyped(json: any, ignoreDiscriminator: boolean): AssignmentResponsePagedResponse {
if (json == null) {
return json;
}
return {
'items': json['items'] == null ? undefined : ((json['items'] as Array<any>).map(AssignmentResponseFromJSON)),
'totalCount': json['totalCount'] == null ? undefined : json['totalCount'],
'page': json['page'] == null ? undefined : json['page'],
'pageSize': json['pageSize'] == null ? undefined : json['pageSize'],
};
}
export function AssignmentResponsePagedResponseToJSON(json: any): AssignmentResponsePagedResponse {
return AssignmentResponsePagedResponseToJSONTyped(json, false);
}
export function AssignmentResponsePagedResponseToJSONTyped(value?: AssignmentResponsePagedResponse | null, ignoreDiscriminator: boolean = false): any {
if (value == null) {
return value;
}
return {
'items': value['items'] == null ? undefined : ((value['items'] as Array<any>).map(AssignmentResponseToJSON)),
'totalCount': value['totalCount'],
'page': value['page'],
'pageSize': value['pageSize'],
};
}
@@ -0,0 +1,97 @@
/* tslint:disable */
/* eslint-disable */
/**
* OmsorgCore.Api
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { mapValues } from '../runtime';
/**
*
* @export
* @interface AssignmentValidationSettingsResponse
*/
export interface AssignmentValidationSettingsResponse {
/**
*
* @type {string}
* @memberof AssignmentValidationSettingsResponse
*/
qualificationMode?: string | null;
/**
*
* @type {string}
* @memberof AssignmentValidationSettingsResponse
*/
absenceMode?: string | null;
/**
*
* @type {string}
* @memberof AssignmentValidationSettingsResponse
*/
workingHoursMode?: string | null;
/**
*
* @type {string}
* @memberof AssignmentValidationSettingsResponse
*/
overlapMode?: string | null;
/**
*
* @type {string}
* @memberof AssignmentValidationSettingsResponse
*/
contractMode?: string | null;
}
/**
* Check if a given object implements the AssignmentValidationSettingsResponse interface.
*/
export function instanceOfAssignmentValidationSettingsResponse(value: object): value is AssignmentValidationSettingsResponse {
return true;
}
export function AssignmentValidationSettingsResponseFromJSON(json: any): AssignmentValidationSettingsResponse {
return AssignmentValidationSettingsResponseFromJSONTyped(json, false);
}
export function AssignmentValidationSettingsResponseFromJSONTyped(json: any, ignoreDiscriminator: boolean): AssignmentValidationSettingsResponse {
if (json == null) {
return json;
}
return {
'qualificationMode': json['qualificationMode'] == null ? undefined : json['qualificationMode'],
'absenceMode': json['absenceMode'] == null ? undefined : json['absenceMode'],
'workingHoursMode': json['workingHoursMode'] == null ? undefined : json['workingHoursMode'],
'overlapMode': json['overlapMode'] == null ? undefined : json['overlapMode'],
'contractMode': json['contractMode'] == null ? undefined : json['contractMode'],
};
}
export function AssignmentValidationSettingsResponseToJSON(json: any): AssignmentValidationSettingsResponse {
return AssignmentValidationSettingsResponseToJSONTyped(json, false);
}
export function AssignmentValidationSettingsResponseToJSONTyped(value?: AssignmentValidationSettingsResponse | null, ignoreDiscriminator: boolean = false): any {
if (value == null) {
return value;
}
return {
'qualificationMode': value['qualificationMode'],
'absenceMode': value['absenceMode'],
'workingHoursMode': value['workingHoursMode'],
'overlapMode': value['overlapMode'],
'contractMode': value['contractMode'],
};
}
@@ -0,0 +1,97 @@
/* tslint:disable */
/* eslint-disable */
/**
* OmsorgCore.Api
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { mapValues } from '../runtime';
/**
*
* @export
* @interface CreateAssignmentRequest
*/
export interface CreateAssignmentRequest {
/**
*
* @type {string}
* @memberof CreateAssignmentRequest
*/
orderId?: string;
/**
*
* @type {string}
* @memberof CreateAssignmentRequest
*/
employeeId?: string;
/**
*
* @type {Date}
* @memberof CreateAssignmentRequest
*/
startDate?: Date;
/**
*
* @type {Date}
* @memberof CreateAssignmentRequest
*/
endDate?: Date;
/**
*
* @type {string}
* @memberof CreateAssignmentRequest
*/
note?: string | null;
}
/**
* Check if a given object implements the CreateAssignmentRequest interface.
*/
export function instanceOfCreateAssignmentRequest(value: object): value is CreateAssignmentRequest {
return true;
}
export function CreateAssignmentRequestFromJSON(json: any): CreateAssignmentRequest {
return CreateAssignmentRequestFromJSONTyped(json, false);
}
export function CreateAssignmentRequestFromJSONTyped(json: any, ignoreDiscriminator: boolean): CreateAssignmentRequest {
if (json == null) {
return json;
}
return {
'orderId': json['orderId'] == null ? undefined : json['orderId'],
'employeeId': json['employeeId'] == null ? undefined : json['employeeId'],
'startDate': json['startDate'] == null ? undefined : (new Date(json['startDate'])),
'endDate': json['endDate'] == null ? undefined : (new Date(json['endDate'])),
'note': json['note'] == null ? undefined : json['note'],
};
}
export function CreateAssignmentRequestToJSON(json: any): CreateAssignmentRequest {
return CreateAssignmentRequestToJSONTyped(json, false);
}
export function CreateAssignmentRequestToJSONTyped(value?: CreateAssignmentRequest | null, ignoreDiscriminator: boolean = false): any {
if (value == null) {
return value;
}
return {
'orderId': value['orderId'],
'employeeId': value['employeeId'],
'startDate': value['startDate'] == null ? undefined : ((value['startDate']).toISOString().substring(0,10)),
'endDate': value['endDate'] == null ? undefined : ((value['endDate']).toISOString().substring(0,10)),
'note': value['note'],
};
}
@@ -25,6 +25,12 @@ export interface CreateFacilityRequest {
* @memberof CreateFacilityRequest
*/
name?: string | null;
/**
*
* @type {string}
* @memberof CreateFacilityRequest
*/
crmStatus?: string | null;
/**
*
* @type {string}
@@ -85,6 +91,90 @@ export interface CreateFacilityRequest {
* @memberof CreateFacilityRequest
*/
billingCountry?: string | null;
/**
*
* @type {number}
* @memberof CreateFacilityRequest
*/
followUpDays?: number | null;
/**
*
* @type {number}
* @memberof CreateFacilityRequest
*/
billingRate?: number | null;
/**
*
* @type {number}
* @memberof CreateFacilityRequest
*/
nightSurchargePercent?: number | null;
/**
*
* @type {number}
* @memberof CreateFacilityRequest
*/
saturdaySurchargePercent?: number | null;
/**
*
* @type {number}
* @memberof CreateFacilityRequest
*/
sundaySurchargePercent?: number | null;
/**
*
* @type {number}
* @memberof CreateFacilityRequest
*/
holidaySurchargePercent?: number | null;
/**
*
* @type {number}
* @memberof CreateFacilityRequest
*/
travelCostRate?: number | null;
/**
*
* @type {string}
* @memberof CreateFacilityRequest
*/
travelCostMode?: string | null;
/**
*
* @type {number}
* @memberof CreateFacilityRequest
*/
travelCostPerKm?: number | null;
/**
*
* @type {number}
* @memberof CreateFacilityRequest
*/
mealAllowanceRate?: number | null;
/**
*
* @type {number}
* @memberof CreateFacilityRequest
*/
minimumHours?: number | null;
/**
*
* @type {string}
* @memberof CreateFacilityRequest
*/
billingInterval?: string | null;
/**
*
* @type {number}
* @memberof CreateFacilityRequest
*/
paymentTermDays?: number | null;
/**
*
* @type {string}
* @memberof CreateFacilityRequest
*/
individualAgreements?: string | null;
}
/**
@@ -105,6 +195,7 @@ export function CreateFacilityRequestFromJSONTyped(json: any, ignoreDiscriminato
return {
'name': json['name'] == null ? undefined : json['name'],
'crmStatus': json['crmStatus'] == null ? undefined : json['crmStatus'],
'facilityType': json['facilityType'] == null ? undefined : json['facilityType'],
'website': json['website'] == null ? undefined : json['website'],
'street': json['street'] == null ? undefined : json['street'],
@@ -115,6 +206,20 @@ export function CreateFacilityRequestFromJSONTyped(json: any, ignoreDiscriminato
'billingPostalCode': json['billingPostalCode'] == null ? undefined : json['billingPostalCode'],
'billingCity': json['billingCity'] == null ? undefined : json['billingCity'],
'billingCountry': json['billingCountry'] == null ? undefined : json['billingCountry'],
'followUpDays': json['followUpDays'] == null ? undefined : json['followUpDays'],
'billingRate': json['billingRate'] == null ? undefined : json['billingRate'],
'nightSurchargePercent': json['nightSurchargePercent'] == null ? undefined : json['nightSurchargePercent'],
'saturdaySurchargePercent': json['saturdaySurchargePercent'] == null ? undefined : json['saturdaySurchargePercent'],
'sundaySurchargePercent': json['sundaySurchargePercent'] == null ? undefined : json['sundaySurchargePercent'],
'holidaySurchargePercent': json['holidaySurchargePercent'] == null ? undefined : json['holidaySurchargePercent'],
'travelCostRate': json['travelCostRate'] == null ? undefined : json['travelCostRate'],
'travelCostMode': json['travelCostMode'] == null ? undefined : json['travelCostMode'],
'travelCostPerKm': json['travelCostPerKm'] == null ? undefined : json['travelCostPerKm'],
'mealAllowanceRate': json['mealAllowanceRate'] == null ? undefined : json['mealAllowanceRate'],
'minimumHours': json['minimumHours'] == null ? undefined : json['minimumHours'],
'billingInterval': json['billingInterval'] == null ? undefined : json['billingInterval'],
'paymentTermDays': json['paymentTermDays'] == null ? undefined : json['paymentTermDays'],
'individualAgreements': json['individualAgreements'] == null ? undefined : json['individualAgreements'],
};
}
@@ -130,6 +235,7 @@ export function CreateFacilityRequestToJSONTyped(value?: CreateFacilityRequest |
return {
'name': value['name'],
'crmStatus': value['crmStatus'],
'facilityType': value['facilityType'],
'website': value['website'],
'street': value['street'],
@@ -140,6 +246,20 @@ export function CreateFacilityRequestToJSONTyped(value?: CreateFacilityRequest |
'billingPostalCode': value['billingPostalCode'],
'billingCity': value['billingCity'],
'billingCountry': value['billingCountry'],
'followUpDays': value['followUpDays'],
'billingRate': value['billingRate'],
'nightSurchargePercent': value['nightSurchargePercent'],
'saturdaySurchargePercent': value['saturdaySurchargePercent'],
'sundaySurchargePercent': value['sundaySurchargePercent'],
'holidaySurchargePercent': value['holidaySurchargePercent'],
'travelCostRate': value['travelCostRate'],
'travelCostMode': value['travelCostMode'],
'travelCostPerKm': value['travelCostPerKm'],
'mealAllowanceRate': value['mealAllowanceRate'],
'minimumHours': value['minimumHours'],
'billingInterval': value['billingInterval'],
'paymentTermDays': value['paymentTermDays'],
'individualAgreements': value['individualAgreements'],
};
}
@@ -32,7 +32,8 @@ export const ModuleType = {
Users: 'Users',
Configuration: 'Configuration',
Absences: 'Absences',
EmployeeFacilityDistances: 'EmployeeFacilityDistances'
EmployeeFacilityDistances: 'EmployeeFacilityDistances',
Assignments: 'Assignments'
} as const;
export type ModuleType = typeof ModuleType[keyof typeof ModuleType];
@@ -0,0 +1,89 @@
/* tslint:disable */
/* eslint-disable */
/**
* OmsorgCore.Api
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { mapValues } from '../runtime';
/**
*
* @export
* @interface TrashAssignmentResponse
*/
export interface TrashAssignmentResponse {
/**
*
* @type {string}
* @memberof TrashAssignmentResponse
*/
id?: string;
/**
*
* @type {string}
* @memberof TrashAssignmentResponse
*/
employeeFirstName?: string | null;
/**
*
* @type {string}
* @memberof TrashAssignmentResponse
*/
employeeLastName?: string | null;
/**
*
* @type {Date}
* @memberof TrashAssignmentResponse
*/
deletedAt?: Date | null;
}
/**
* Check if a given object implements the TrashAssignmentResponse interface.
*/
export function instanceOfTrashAssignmentResponse(value: object): value is TrashAssignmentResponse {
return true;
}
export function TrashAssignmentResponseFromJSON(json: any): TrashAssignmentResponse {
return TrashAssignmentResponseFromJSONTyped(json, false);
}
export function TrashAssignmentResponseFromJSONTyped(json: any, ignoreDiscriminator: boolean): TrashAssignmentResponse {
if (json == null) {
return json;
}
return {
'id': json['id'] == null ? undefined : json['id'],
'employeeFirstName': json['employeeFirstName'] == null ? undefined : json['employeeFirstName'],
'employeeLastName': json['employeeLastName'] == null ? undefined : json['employeeLastName'],
'deletedAt': json['deletedAt'] == null ? undefined : (new Date(json['deletedAt'])),
};
}
export function TrashAssignmentResponseToJSON(json: any): TrashAssignmentResponse {
return TrashAssignmentResponseToJSONTyped(json, false);
}
export function TrashAssignmentResponseToJSONTyped(value?: TrashAssignmentResponse | null, ignoreDiscriminator: boolean = false): any {
if (value == null) {
return value;
}
return {
'id': value['id'],
'employeeFirstName': value['employeeFirstName'],
'employeeLastName': value['employeeLastName'],
'deletedAt': value['deletedAt'] === null ? null : ((value['deletedAt'] as any)?.toISOString()),
};
}
@@ -0,0 +1,65 @@
/* tslint:disable */
/* eslint-disable */
/**
* OmsorgCore.Api
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { mapValues } from '../runtime';
/**
*
* @export
* @interface UpdateAssignmentRequest
*/
export interface UpdateAssignmentRequest {
/**
*
* @type {string}
* @memberof UpdateAssignmentRequest
*/
note?: string | null;
}
/**
* Check if a given object implements the UpdateAssignmentRequest interface.
*/
export function instanceOfUpdateAssignmentRequest(value: object): value is UpdateAssignmentRequest {
return true;
}
export function UpdateAssignmentRequestFromJSON(json: any): UpdateAssignmentRequest {
return UpdateAssignmentRequestFromJSONTyped(json, false);
}
export function UpdateAssignmentRequestFromJSONTyped(json: any, ignoreDiscriminator: boolean): UpdateAssignmentRequest {
if (json == null) {
return json;
}
return {
'note': json['note'] == null ? undefined : json['note'],
};
}
export function UpdateAssignmentRequestToJSON(json: any): UpdateAssignmentRequest {
return UpdateAssignmentRequestToJSONTyped(json, false);
}
export function UpdateAssignmentRequestToJSONTyped(value?: UpdateAssignmentRequest | null, ignoreDiscriminator: boolean = false): any {
if (value == null) {
return value;
}
return {
'note': value['note'],
};
}
@@ -0,0 +1,97 @@
/* tslint:disable */
/* eslint-disable */
/**
* OmsorgCore.Api
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { mapValues } from '../runtime';
/**
*
* @export
* @interface UpdateAssignmentValidationSettingsRequest
*/
export interface UpdateAssignmentValidationSettingsRequest {
/**
*
* @type {string}
* @memberof UpdateAssignmentValidationSettingsRequest
*/
qualificationMode?: string | null;
/**
*
* @type {string}
* @memberof UpdateAssignmentValidationSettingsRequest
*/
absenceMode?: string | null;
/**
*
* @type {string}
* @memberof UpdateAssignmentValidationSettingsRequest
*/
workingHoursMode?: string | null;
/**
*
* @type {string}
* @memberof UpdateAssignmentValidationSettingsRequest
*/
overlapMode?: string | null;
/**
*
* @type {string}
* @memberof UpdateAssignmentValidationSettingsRequest
*/
contractMode?: string | null;
}
/**
* Check if a given object implements the UpdateAssignmentValidationSettingsRequest interface.
*/
export function instanceOfUpdateAssignmentValidationSettingsRequest(value: object): value is UpdateAssignmentValidationSettingsRequest {
return true;
}
export function UpdateAssignmentValidationSettingsRequestFromJSON(json: any): UpdateAssignmentValidationSettingsRequest {
return UpdateAssignmentValidationSettingsRequestFromJSONTyped(json, false);
}
export function UpdateAssignmentValidationSettingsRequestFromJSONTyped(json: any, ignoreDiscriminator: boolean): UpdateAssignmentValidationSettingsRequest {
if (json == null) {
return json;
}
return {
'qualificationMode': json['qualificationMode'] == null ? undefined : json['qualificationMode'],
'absenceMode': json['absenceMode'] == null ? undefined : json['absenceMode'],
'workingHoursMode': json['workingHoursMode'] == null ? undefined : json['workingHoursMode'],
'overlapMode': json['overlapMode'] == null ? undefined : json['overlapMode'],
'contractMode': json['contractMode'] == null ? undefined : json['contractMode'],
};
}
export function UpdateAssignmentValidationSettingsRequestToJSON(json: any): UpdateAssignmentValidationSettingsRequest {
return UpdateAssignmentValidationSettingsRequestToJSONTyped(json, false);
}
export function UpdateAssignmentValidationSettingsRequestToJSONTyped(value?: UpdateAssignmentValidationSettingsRequest | null, ignoreDiscriminator: boolean = false): any {
if (value == null) {
return value;
}
return {
'qualificationMode': value['qualificationMode'],
'absenceMode': value['absenceMode'],
'workingHoursMode': value['workingHoursMode'],
'overlapMode': value['overlapMode'],
'contractMode': value['contractMode'],
};
}
@@ -4,6 +4,10 @@ export * from './AbsenceDecisionRequest';
export * from './AbsenceResponse';
export * from './AbsenceResponsePagedResponse';
export * from './AddUserPermissionOverrideRequest';
export * from './AssignmentConflictResponse';
export * from './AssignmentResponse';
export * from './AssignmentResponsePagedResponse';
export * from './AssignmentValidationSettingsResponse';
export * from './AuditEventCategory';
export * from './AuditLogEntryResponse';
export * from './AuditLogEntryResponsePagedResponse';
@@ -11,6 +15,7 @@ export * from './ChangePasswordRequest';
export * from './ContractResponse';
export * from './ContractResponsePagedResponse';
export * from './CreateAbsenceRequest';
export * from './CreateAssignmentRequest';
export * from './CreateContractRequest';
export * from './CreateEmployeeFacilityDistanceRequest';
export * from './CreateEmployeeRequest';
@@ -61,6 +66,7 @@ export * from './TimeEntryDecisionRequest';
export * from './TimeEntryResponse';
export * from './TimeEntryResponsePagedResponse';
export * from './TrashAbsenceResponse';
export * from './TrashAssignmentResponse';
export * from './TrashContractResponse';
export * from './TrashEmployeeFacilityDistanceResponse';
export * from './TrashEmployeeResponse';
@@ -70,6 +76,8 @@ export * from './TrashFacilityResponse';
export * from './TrashOrderResponse';
export * from './TrashTimeEntryResponse';
export * from './UpdateAbsenceRequest';
export * from './UpdateAssignmentRequest';
export * from './UpdateAssignmentValidationSettingsRequest';
export * from './UpdateContractRequest';
export * from './UpdateDocumentRequest';
export * from './UpdateEmployeeFacilityDistanceRequest';
@@ -0,0 +1,14 @@
import { AssignmentValidationSettingsApi } from "omsorgcore-client-ts";
import { configFor, callApi } from "./apiClientHelpers";
// Kapselt /api/settings/assignment-validation (FR-EM-3: Warning/Error je Konfliktprüfung bei
// der Mitarbeiterzuweisung, siehe omsorgCore/CLAUDE.md "Prüf-Logik in AssignmentService").
export function getAssignmentValidationSettings(accessToken) {
const api = new AssignmentValidationSettingsApi(configFor(accessToken));
return callApi(api.apiSettingsAssignmentValidationGetRaw());
}
export function updateAssignmentValidationSettings(accessToken, payload) {
const api = new AssignmentValidationSettingsApi(configFor(accessToken));
return callApi(api.apiSettingsAssignmentValidationPutRaw({ updateAssignmentValidationSettingsRequest: payload }));
}
+48
View File
@@ -0,0 +1,48 @@
import { configFor, callApi } from "./apiClientHelpers";
import { AssignmentsApi } from "omsorgcore-client-ts";
const API_BASE_URL = configFor(null).basePath;
export function listAssignments(accessToken, { orderId, employeeId, fromDate, toDate, page = 1, pageSize = 20 } = {}) {
const api = new AssignmentsApi(configFor(accessToken));
return callApi(api.apiAssignmentsGetRaw({ orderId, employeeId, fromDate, toDate, page, pageSize }));
}
export function getAssignment(accessToken, id) {
const api = new AssignmentsApi(configFor(accessToken));
return callApi(api.apiAssignmentsIdGetRaw({ id }));
}
export function createAssignment(accessToken, payload) {
const api = new AssignmentsApi(configFor(accessToken));
return callApi(api.apiAssignmentsPostRaw({
createAssignmentRequest: {
...payload,
startDate: payload.startDate ? new Date(payload.startDate) : payload.startDate,
endDate: payload.endDate ? new Date(payload.endDate) : payload.endDate
}
}));
}
export function updateAssignment(accessToken, id, payload) {
const api = new AssignmentsApi(configFor(accessToken));
return callApi(api.apiAssignmentsIdPutRaw({ id, updateAssignmentRequest: payload }));
}
export function deleteAssignment(accessToken, id) {
const api = new AssignmentsApi(configFor(accessToken));
return callApi(api.apiAssignmentsIdDeleteRaw({ id }));
}
// Live-Vorschau der FR-EM-3-Konfliktprüfungen (Qualifikation/Abwesenheit/Arbeitszeit/Überschneidung/
// Vertrag), ohne etwas anzulegen - für den Anlegen-Dialog, sobald Mitarbeiter + Zeitraum feststehen.
export function checkAssignmentConflicts(accessToken, { orderId, employeeId, startDate, endDate, excludeAssignmentId }) {
const api = new AssignmentsApi(configFor(accessToken));
return callApi(api.apiAssignmentsCheckGetRaw({
orderId,
employeeId,
startDate: startDate ? new Date(startDate) : startDate,
endDate: endDate ? new Date(endDate) : endDate,
excludeAssignmentId
}));
}
+17 -1
View File
@@ -7,6 +7,8 @@ import * as facilityContactsApi from "./facilityContactsApi.js";
import * as facilityQualificationRatesApi from "./facilityQualificationRatesApi.js";
import * as employeeFacilityDistancesApi from "./employeeFacilityDistancesApi.js";
import * as ordersApi from "./ordersApi.js";
import * as assignmentsApi from "./assignmentsApi.js";
import * as assignmentValidationSettingsApi from "./assignmentValidationSettingsApi.js";
import * as absencesApi from "./absencesApi.js";
import * as timeEntriesApi from "./timeEntriesApi.js";
import * as usersApi from "./usersApi.js";
@@ -141,6 +143,18 @@ export function buildOmsorgApi() {
update: withAuth(ordersApi.updateOrder),
delete: withAuth(ordersApi.deleteOrder)
},
assignments: {
list: withAuth(assignmentsApi.listAssignments),
create: withAuth(assignmentsApi.createAssignment),
get: withAuth(assignmentsApi.getAssignment),
update: withAuth(assignmentsApi.updateAssignment),
delete: withAuth(assignmentsApi.deleteAssignment),
checkConflicts: withAuth(assignmentsApi.checkAssignmentConflicts)
},
assignmentValidationSettings: {
get: withAuth(assignmentValidationSettingsApi.getAssignmentValidationSettings),
update: withAuth(assignmentValidationSettingsApi.updateAssignmentValidationSettings)
},
absences: {
list: withAuth(absencesApi.listAbsences),
get: withAuth(absencesApi.getAbsence),
@@ -203,7 +217,9 @@ export function buildOmsorgApi() {
listAbsences: withAuth(trashApi.listDeletedAbsences),
restoreAbsence: withAuth(trashApi.restoreAbsence),
listTimeEntries: withAuth(trashApi.listDeletedTimeEntries),
restoreTimeEntry: withAuth(trashApi.restoreTimeEntry)
restoreTimeEntry: withAuth(trashApi.restoreTimeEntry),
listAssignments: withAuth(trashApi.listDeletedAssignments),
restoreAssignment: withAuth(trashApi.restoreAssignment)
},
contracts: {
list: withAuth(contractsApi.listContracts),
+10
View File
@@ -93,3 +93,13 @@ export async function restoreTimeEntry(accessToken, id) {
const api = new TrashApi(configFor(accessToken));
return callApi(api.apiTrashTimeEntriesIdRestorePostRaw({ id }));
}
export async function listDeletedAssignments(accessToken, search) {
const api = new TrashApi(configFor(accessToken));
return callApi(api.apiTrashAssignmentsGetRaw({ search }));
}
export async function restoreAssignment(accessToken, id) {
const api = new TrashApi(configFor(accessToken));
return callApi(api.apiTrashAssignmentsIdRestorePostRaw({ id }));
}
+2 -1
View File
@@ -20,7 +20,8 @@ export const TRASH_MODULES = [
ModuleType.Contracts,
ModuleType.Orders,
ModuleType.Absences,
ModuleType.TimeEntries
ModuleType.TimeEntries,
ModuleType.Assignments
];
// Einstellungen bündelt drei unabhängige Rechte (siehe SettingsPage.jsx, die jeden Tab einzeln
@@ -0,0 +1,244 @@
import { useCallback, useEffect, useState } from "react";
import { Plus, Trash2 } from "lucide-react";
import OmsorgButton from "../../components/ui/OmsorgButton";
import { useAuth } from "../../app/AuthContext";
import CreateAssignmentDialog from "./CreateAssignmentDialog";
const CONFLICT_LABELS = {
Qualification: "Qualifikation",
Absence: "Abwesenheit",
WorkingHours: "Arbeitszeit",
Overlap: "Überschneidung",
Contract: "Vertrag"
};
function formatDate(value) {
if (!value) return "—";
return new Date(value).toLocaleDateString("de-DE");
}
export default function AssignmentsList({ orderId, order, requiredHeadcount }) {
const { hasPermission } = useAuth();
const canCreate = hasPermission("Assignments", "Create");
const canDelete = hasPermission("Assignments", "Delete");
const [assignments, setAssignments] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false);
const [deletingId, setDeletingId] = useState(null);
const [warnings, setWarnings] = useState([]);
const loadAssignments = useCallback(async () => {
setIsLoading(true);
const result = await window.omsorg.assignments.list({ orderId, pageSize: 100 });
if (result.ok) {
setAssignments(result.data?.items ?? []);
}
setIsLoading(false);
}, [orderId]);
useEffect(() => {
loadAssignments();
}, [loadAssignments]);
async function handleDeleteAssignment(assignmentId) {
if (!window.confirm("Diese Zuweisung wirklich löschen?")) {
return;
}
setDeletingId(assignmentId);
const result = await window.omsorg.assignments.delete(assignmentId);
setDeletingId(null);
if (result.ok) {
await loadAssignments();
}
}
const formatDateRange = (startDate, endDate) => {
if (startDate === endDate) {
return formatDate(startDate);
}
return `${formatDate(startDate)} ${formatDate(endDate)}`;
};
return (
<div className="assignments-section">
<div className="assignments-header">
<div>
<h3>Zuweisungen</h3>
<p className="assignments-count">
{assignments.length} / {requiredHeadcount} Mitarbeiter zugewiesen
</p>
</div>
{canCreate && (
<OmsorgButton variant="primary" size="sm" icon={Plus} onClick={() => setIsCreateDialogOpen(true)}>
Zuweisung hinzufügen
</OmsorgButton>
)}
</div>
{warnings.length > 0 && (
<div className="assignments-warnings">
{warnings.map((w, i) => (
<p key={i}> {CONFLICT_LABELS[w.type] ?? w.type}: {w.message}</p>
))}
</div>
)}
{isLoading ? (
<p>Lädt...</p>
) : assignments.length === 0 ? (
<p className="assignments-empty">Noch keine Zuweisungen. {canCreate && "Klicke oben, um einen Mitarbeiter zuzuweisen."}</p>
) : (
<div className="assignments-table">
<div className="assignments-table-header">
<div>Mitarbeiter</div>
<div>Zeitraum</div>
<div>Notiz</div>
{canDelete && <div></div>}
</div>
{assignments.map((assignment) => (
<div key={assignment.id} className="assignments-table-row">
<div className="assignments-cell">
{assignment.employeeFirstName} {assignment.employeeLastName}
</div>
<div className="assignments-cell">
{formatDateRange(assignment.startDate, assignment.endDate)}
</div>
<div className="assignments-cell">{assignment.note ?? "—"}</div>
{canDelete && (
<div className="assignments-cell assignments-cell-action">
<button
className="assignments-delete-btn"
onClick={() => handleDeleteAssignment(assignment.id)}
disabled={deletingId === assignment.id}
title="Zuweisung löschen"
>
<Trash2 size={16} />
</button>
</div>
)}
</div>
))}
</div>
)}
{isCreateDialogOpen && (
<CreateAssignmentDialog
orderId={orderId}
order={order}
onClose={() => setIsCreateDialogOpen(false)}
onCreated={(conflicts) => {
setIsCreateDialogOpen(false);
setWarnings(conflicts ?? []);
loadAssignments();
}}
/>
)}
<style>{`
.assignments-section {
margin-top: 2rem;
padding-top: 2rem;
border-top: 1px solid var(--color-border);
}
.assignments-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 1rem;
}
.assignments-header h3 {
margin: 0 0 0.25rem 0;
font-size: 1.1rem;
}
.assignments-count {
margin: 0;
font-size: 0.9rem;
color: var(--color-text-secondary);
}
.assignments-warnings {
background-color: rgba(240, 195, 109, 0.12);
border: 1px solid rgba(240, 195, 109, 0.45);
color: #f0c36d;
border-radius: 4px;
padding: 0.75rem;
margin-bottom: 1rem;
}
.assignments-warnings p {
margin: 0.25rem 0;
font-size: 0.9rem;
}
.assignments-empty {
color: var(--color-text-secondary);
font-style: italic;
margin: 1rem 0 0 0;
}
.assignments-table {
border: 1px solid var(--color-border);
border-radius: 4px;
overflow: hidden;
margin-top: 1rem;
}
.assignments-table-header {
display: grid;
grid-template-columns: 1fr 1.5fr 1.5fr ${canDelete ? "40px" : ""};
background-color: var(--color-bg-secondary);
padding: 0.75rem;
font-weight: 600;
font-size: 0.9rem;
gap: 1rem;
}
.assignments-table-row {
display: grid;
grid-template-columns: 1fr 1.5fr 1.5fr ${canDelete ? "40px" : ""};
padding: 0.75rem;
border-top: 1px solid var(--color-border);
gap: 1rem;
align-items: center;
}
.assignments-cell {
font-size: 0.9rem;
}
.assignments-cell-action {
display: flex;
justify-content: center;
}
.assignments-delete-btn {
background: none;
border: none;
color: var(--color-text-secondary);
cursor: pointer;
padding: 0.25rem;
display: flex;
align-items: center;
justify-content: center;
border-radius: 3px;
transition: all 0.2s;
}
.assignments-delete-btn:hover:not(:disabled) {
background-color: var(--color-bg-secondary);
color: var(--color-error);
}
.assignments-delete-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
`}</style>
</div>
);
}
@@ -0,0 +1,278 @@
import { useEffect, useState } from "react";
import ModalPortal from "../../components/ui/ModalPortal";
import OmsorgButton from "../../components/ui/OmsorgButton";
const CONFLICT_LABELS = {
Qualification: "Qualifikation",
Absence: "Abwesenheit",
WorkingHours: "Arbeitszeit",
Overlap: "Überschneidung",
Contract: "Vertrag"
};
function conflictLabel(conflict) {
return `${CONFLICT_LABELS[conflict.type] ?? conflict.type}: ${conflict.message}`;
}
function errorMessage(result) {
if (result.status === 403) {
return "Keine Berechtigung, Zuweisungen zu erstellen.";
}
if (result.status === 404) {
return typeof result.data === "string" ? result.data : "Mitarbeiter oder Auftrag nicht gefunden.";
}
if (result.status === 400) {
const msg = typeof result.data === "string" ? result.data : result.data?.error;
if (msg === "invalid_date_range") {
return "Das Enddatum muss nach oder gleich dem Startdatum sein.";
}
if (msg === "date_outside_order_range") {
return "Der Zeitraum liegt außerhalb der Auftragsspanne.";
}
return msg || "Eingaben prüfen.";
}
if (result.status === 409) {
const msg = typeof result.data === "string" ? result.data : result.data?.error;
if (msg === "overlapping_assignment") {
return "Dieser Mitarbeiter ist bereits in diesem Zeitraum diesem Auftrag zugewiesen.";
}
if (msg === "validation_conflict") {
const conflicts = result.data?.conflicts ?? [];
return conflicts.length > 0
? conflicts.map(conflictLabel).join(" | ")
: "Zuweisung durch eine Konfliktprüfung blockiert.";
}
return "Zeitraum-Konflikt: Überprüfe die Verfügbarkeit.";
}
return "Zuweisung konnte nicht erstellt werden.";
}
export default function CreateAssignmentDialog({ orderId, order, onClose, onCreated }) {
const [employees, setEmployees] = useState([]);
const [selectedEmployeeId, setSelectedEmployeeId] = useState("");
const [startDate, setStartDate] = useState("");
const [endDate, setEndDate] = useState("");
const [note, setNote] = useState("");
const [error, setError] = useState("");
const [isSubmitting, setIsSubmitting] = useState(false);
const [isLoadingEmployees, setIsLoadingEmployees] = useState(true);
const [liveConflicts, setLiveConflicts] = useState([]);
const [isChecking, setIsChecking] = useState(false);
useEffect(() => {
async function loadEmployees() {
const result = await window.omsorg.employees.list({ status: "Aktiv", pageSize: 200 });
if (result.ok) {
setEmployees(result.data?.items ?? []);
}
setIsLoadingEmployees(false);
}
loadEmployees();
}, []);
// Live-Vorschau: sobald Mitarbeiter + gültiger Zeitraum feststehen, dieselben FR-EM-3-Prüfungen
// wie beim Speichern laufen lassen (debounced), damit Warnings/Errors schon vor dem Klick auf
// "Speichern" sichtbar sind, nicht erst danach.
useEffect(() => {
setError("");
if (!selectedEmployeeId || !startDate || !endDate || endDate < startDate) {
setLiveConflicts([]);
return;
}
let cancelled = false;
setIsChecking(true);
const timer = setTimeout(async () => {
const result = await window.omsorg.assignments.checkConflicts({
orderId,
employeeId: selectedEmployeeId,
startDate,
endDate
});
if (!cancelled) {
setLiveConflicts(result.ok ? result.data ?? [] : []);
setIsChecking(false);
}
}, 400);
return () => {
cancelled = true;
clearTimeout(timer);
};
}, [orderId, selectedEmployeeId, startDate, endDate]);
const hasBlockingConflict = liveConflicts.some((c) => c.severity === "Error");
const orderStartDate = order?.startDate ? new Date(order.startDate).toISOString().split("T")[0] : "";
const orderEndDate = order?.endDate ? new Date(order.endDate).toISOString().split("T")[0] : "";
const handleSubmit = async (e) => {
e.preventDefault();
setError("");
if (!selectedEmployeeId) {
setError("Bitte wähle einen Mitarbeiter.");
return;
}
if (!startDate) {
setError("Bitte wähle ein Startdatum.");
return;
}
if (!endDate) {
setError("Bitte wähle ein Enddatum.");
return;
}
if (endDate < startDate) {
setError("Das Enddatum muss nach oder gleich dem Startdatum sein.");
return;
}
setIsSubmitting(true);
const result = await window.omsorg.assignments.create({
orderId,
employeeId: selectedEmployeeId,
startDate,
endDate,
note: note || null
});
if (result.ok) {
onCreated(result.data?.conflicts ?? []);
} else {
setError(errorMessage(result));
}
setIsSubmitting(false);
};
return (
<ModalPortal>
<div className="modal-overlay" role="dialog" aria-modal="true" aria-label="Zuweisung hinzufügen">
<div className="modal-panel">
<h2>Zuweisung hinzufügen</h2>
<form onSubmit={handleSubmit}>
{error && <p className="login-error form-error">{error}</p>}
<div className="form-group">
<label htmlFor="employee-select">Mitarbeiter *</label>
<select
id="employee-select"
value={selectedEmployeeId}
onChange={(e) => setSelectedEmployeeId(e.target.value)}
disabled={isLoadingEmployees || isSubmitting}
required
>
<option value=""> Bitte wähle einen Mitarbeiter </option>
{employees.map((emp) => (
<option key={emp.id} value={emp.id}>
{emp.firstName} {emp.lastName}
</option>
))}
</select>
</div>
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "1rem" }}>
<div className="form-group">
<label htmlFor="start-date">Startdatum *</label>
<input
id="start-date"
type="date"
value={startDate}
onChange={(e) => {
setStartDate(e.target.value);
if (endDate && e.target.value > endDate) {
setEndDate(e.target.value);
}
}}
min={orderStartDate}
max={orderEndDate || undefined}
disabled={isSubmitting}
required
/>
</div>
<div className="form-group">
<label htmlFor="end-date">Enddatum *</label>
<input
id="end-date"
type="date"
value={endDate}
onChange={(e) => setEndDate(e.target.value)}
min={startDate || orderStartDate}
max={orderEndDate || undefined}
disabled={isSubmitting}
required
/>
</div>
</div>
{isChecking && <p style={{ color: "var(--omsorg-text-secondary)", fontSize: "0.85rem" }}>Prüfe Konflikte...</p>}
{!isChecking && liveConflicts.length > 0 && (
<div className={hasBlockingConflict ? "assignment-conflicts assignment-conflicts--error" : "assignment-conflicts assignment-conflicts--warning"}>
{liveConflicts.map((c, i) => (
<p key={i}>
{c.severity === "Error" ? "⛔" : "⚠"} {CONFLICT_LABELS[c.type] ?? c.type}: {c.message}
</p>
))}
</div>
)}
<div className="form-group">
<label htmlFor="note">Notiz (optional)</label>
<textarea
id="note"
value={note}
onChange={(e) => setNote(e.target.value)}
placeholder="z.B. Springer, spezielle Anforderungen..."
disabled={isSubmitting}
rows={3}
/>
</div>
<div style={{ display: "flex", gap: "0.75rem", justifyContent: "flex-end", marginTop: "1.5rem" }}>
<button
type="button"
onClick={onClose}
disabled={isSubmitting}
className="button-link"
>
Abbrechen
</button>
<OmsorgButton type="submit" variant="primary" disabled={isSubmitting || hasBlockingConflict}>
{isSubmitting ? "Wird gespeichert..." : "Speichern"}
</OmsorgButton>
</div>
</form>
</div>
<style>{`
.assignment-conflicts {
border-radius: 4px;
padding: 0.75rem;
margin: 0.5rem 0 1rem 0;
}
.assignment-conflicts p {
margin: 0.25rem 0;
font-size: 0.9rem;
}
.assignment-conflicts--warning {
background-color: rgba(240, 195, 109, 0.12);
border: 1px solid rgba(240, 195, 109, 0.45);
color: #f0c36d;
}
.assignment-conflicts--error {
background-color: rgba(242, 139, 139, 0.12);
border: 1px solid rgba(242, 139, 139, 0.45);
color: #f28b8b;
}
`}</style>
</div>
</ModalPortal>
);
}
@@ -5,6 +5,7 @@ import OmsorgCard from "../../components/OmsorgCard";
import OmsorgButton from "../../components/ui/OmsorgButton";
import { useAuth } from "../../app/AuthContext";
import EditOrderDialog from "./EditOrderDialog";
import AssignmentsList from "./AssignmentsList";
function formatDate(value) {
if (!value) {
@@ -100,6 +101,8 @@ export default function OrderDetailPanel({ order, facilityName, facilities, onUp
<p>{order.conditions ?? "—"}</p>
</div>
</div>
<AssignmentsList orderId={order.id} order={order} requiredHeadcount={order.requiredHeadcount} />
</div>
{isEditDialogOpen && (
@@ -0,0 +1,115 @@
import { useCallback, useEffect, useState } from "react";
import { Save } from "lucide-react";
import OmsorgButton from "../../components/ui/OmsorgButton";
import { useAuth } from "../../app/AuthContext";
const CHECKS = [
{ key: "qualificationMode", label: "Qualifikation" },
{ key: "absenceMode", label: "Abwesenheit (Verfügbarkeit)" },
{ key: "workingHoursMode", label: "Arbeitszeit" },
{ key: "overlapMode", label: "Überschneidung" },
{ key: "contractMode", label: "Vertrag" }
];
export default function AssignmentValidationSettingsPanel() {
const { hasPermission } = useAuth();
const canEdit = hasPermission("Configuration", "Edit");
const [settings, setSettings] = useState(null);
const [isLoading, setIsLoading] = useState(true);
const [isSaving, setIsSaving] = useState(false);
const [error, setError] = useState(null);
const [saved, setSaved] = useState(false);
const load = useCallback(async () => {
setIsLoading(true);
setError(null);
const result = await window.omsorg.assignmentValidationSettings.get();
if (result.ok) {
setSettings(result.data);
} else {
setError("Einstellungen konnten nicht geladen werden.");
}
setIsLoading(false);
}, []);
useEffect(() => {
load();
}, [load]);
function handleChange(key, value) {
setSaved(false);
setSettings((current) => ({ ...current, [key]: value }));
}
async function handleSave() {
setIsSaving(true);
setError(null);
setSaved(false);
const result = await window.omsorg.assignmentValidationSettings.update(settings);
if (result.ok) {
setSettings(result.data);
setSaved(true);
} else {
setError("Speichern fehlgeschlagen.");
}
setIsSaving(false);
}
return (
<div className="settings-roles-panel">
<div className="settings-panel-heading">
<h4>Zuweisungsprüfung (FR-EM-3)</h4>
</div>
<p style={{ color: "var(--omsorg-text-secondary)", fontSize: "0.9rem", marginTop: 0 }}>
Legt je Prüfung fest, ob eine Mitarbeiterzuweisung bei einem Konflikt blockiert wird
("Error") oder nur ein Hinweis erscheint, die Zuweisung aber trotzdem angelegt wird
("Warning").
</p>
{isLoading && <p>Lädt...</p>}
{error && <p className="login-error">{error}</p>}
{!isLoading && settings && (
<>
<div className="settings-permission-matrix settings-permission-matrix-scroll">
<table>
<thead>
<tr>
<th>Prüfung</th>
<th>Modus</th>
</tr>
</thead>
<tbody>
{CHECKS.map(({ key, label }) => (
<tr key={key}>
<td>{label}</td>
<td>
<select
value={settings[key]}
onChange={(e) => handleChange(key, e.target.value)}
disabled={!canEdit || isSaving}
>
<option value="Warning">Warning (nur Hinweis)</option>
<option value="Error">Error (blockiert)</option>
</select>
</td>
</tr>
))}
</tbody>
</table>
</div>
{canEdit && (
<div style={{ marginTop: "1rem", display: "flex", alignItems: "center", gap: "0.75rem" }}>
<OmsorgButton variant="primary" icon={Save} onClick={handleSave} disabled={isSaving}>
{isSaving ? "Speichert..." : "Speichern"}
</OmsorgButton>
{saved && <span style={{ color: "var(--omsorg-turquoise)" }}>Gespeichert.</span>}
</div>
)}
</>
)}
</div>
);
}
@@ -3,6 +3,7 @@ import OmsorgCard from "../../components/OmsorgCard";
import RolesPanel from "./RolesPanel";
import UserOverridesPanel from "./UserOverridesPanel";
import StatusManagementPanel from "./StatusManagementPanel";
import AssignmentValidationSettingsPanel from "./AssignmentValidationSettingsPanel";
import UsersPanel from "./UsersPanel";
import { useAuth } from "../../app/AuthContext";
@@ -11,6 +12,7 @@ const ALL_TABS = [
{ id: "roles", label: "Rollen", module: "UserManagement" },
{ id: "overrides", label: "Benutzerrechte", module: "UserManagement" },
{ id: "statuses", label: "Status-Verwaltung", module: "Configuration" },
{ id: "assignmentValidation", label: "Zuweisungsprüfung", module: "Configuration" },
];
export default function SettingsPage() {
@@ -42,6 +44,7 @@ export default function SettingsPage() {
{currentTab === "roles" && <RolesPanel />}
{currentTab === "overrides" && <UserOverridesPanel />}
{currentTab === "statuses" && <StatusManagementPanel />}
{currentTab === "assignmentValidation" && <AssignmentValidationSettingsPanel />}
</>
)}
</OmsorgCard>
@@ -83,6 +83,14 @@ const TABS = [
restore: (id) => window.omsorg.trash.restoreTimeEntry(id),
displayName: (item) => new Date(item.date).toLocaleDateString("de-DE"),
},
{
key: "assignments",
label: "Zuweisungen",
module: "Assignments",
list: (search) => window.omsorg.trash.listAssignments(search),
restore: (id) => window.omsorg.trash.restoreAssignment(id),
displayName: (item) => `${item.employeeFirstName} ${item.employeeLastName}`.trim(),
},
];
export default function TrashPage() {