feat: complete tOS project with HR, LEAN, Dashboard and Integrations modules

Full enterprise web operating system including:
- Next.js 14 frontend with App Router, i18n (DE/EN), shadcn/ui
- NestJS 10 backend with Prisma, JWT auth, Swagger docs
- Keycloak 24 SSO with role-based access control
- HR module (employees, time tracking, absences, org chart)
- LEAN module (3S planning, morning meeting SQCDM, skill matrix)
- Integrations module (PlentyONE, Zulip, Todoist, FreeScout, Nextcloud, ecoDMS, GembaDocs)
- Dashboard with customizable drag & drop widget grid
- Docker Compose infrastructure (PostgreSQL 16, Redis 7, Keycloak 24)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-06 19:37:55 +01:00
parent a2b6612e9e
commit fe305f6fc8
509 changed files with 81111 additions and 1 deletions

View File

@@ -0,0 +1,36 @@
{
"name": "@tos/shared",
"version": "0.1.0",
"private": true,
"description": "Shared types, utilities, and constants for tOS",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.js",
"types": "./dist/index.d.ts"
},
"./types": {
"import": "./dist/types/index.mjs",
"require": "./dist/types/index.js",
"types": "./dist/types/index.d.ts"
},
"./utils": {
"import": "./dist/utils/index.mjs",
"require": "./dist/utils/index.js",
"types": "./dist/utils/index.d.ts"
}
},
"scripts": {
"build": "tsup",
"dev": "tsup --watch",
"typecheck": "tsc --noEmit",
"clean": "rm -rf dist .turbo"
},
"devDependencies": {
"tsup": "^8.0.0",
"typescript": "^5.3.0"
}
}

View File

@@ -0,0 +1,6 @@
/**
* @tos/shared - Shared types, utilities, and constants for tOS
*/
export * from './types';
export * from './utils';

View File

@@ -0,0 +1,111 @@
/**
* Authentication and authorization types
*/
export interface AuthUser {
id: string;
email: string;
firstName: string;
lastName: string;
roles: string[];
accessToken: string;
refreshToken?: string;
expiresAt: number;
}
export interface TokenPayload {
sub: string;
email: string;
given_name: string;
family_name: string;
realm_access?: {
roles: string[];
};
resource_access?: {
[clientId: string]: {
roles: string[];
};
};
iat: number;
exp: number;
iss: string;
aud: string | string[];
}
export interface LoginRequest {
username: string;
password: string;
}
export interface RefreshTokenRequest {
refreshToken: string;
}
export interface AuthResponse {
accessToken: string;
refreshToken: string;
expiresIn: number;
tokenType: 'Bearer';
}
export const PERMISSIONS = {
USERS_READ: 'users:read',
USERS_CREATE: 'users:create',
USERS_UPDATE: 'users:update',
USERS_DELETE: 'users:delete',
DEPARTMENTS_READ: 'departments:read',
DEPARTMENTS_MANAGE: 'departments:manage',
EMPLOYEES_READ_ALL: 'employees:read:all',
EMPLOYEES_READ_DEPARTMENT: 'employees:read:department',
EMPLOYEES_READ_OWN: 'employees:read:own',
EMPLOYEES_MANAGE: 'employees:manage',
TIME_READ_ALL: 'time:read:all',
TIME_READ_DEPARTMENT: 'time:read:department',
TIME_READ_OWN: 'time:read:own',
TIME_MANAGE: 'time:manage',
ABSENCES_READ_ALL: 'absences:read:all',
ABSENCES_READ_DEPARTMENT: 'absences:read:department',
ABSENCES_READ_OWN: 'absences:read:own',
ABSENCES_APPROVE: 'absences:approve',
ABSENCES_CREATE: 'absences:create',
LEAN_READ: 'lean:read',
LEAN_MANAGE: 'lean:manage',
INTEGRATIONS_READ: 'integrations:read',
INTEGRATIONS_MANAGE: 'integrations:manage',
ADMIN_ACCESS: 'admin:access',
ADMIN_SYSTEM: 'admin:system',
} as const;
export type Permission = (typeof PERMISSIONS)[keyof typeof PERMISSIONS];
export const ROLE_PERMISSIONS: Record<string, Permission[]> = {
admin: Object.values(PERMISSIONS),
manager: [
PERMISSIONS.USERS_READ,
PERMISSIONS.DEPARTMENTS_READ,
PERMISSIONS.EMPLOYEES_READ_ALL,
PERMISSIONS.TIME_READ_ALL,
PERMISSIONS.ABSENCES_READ_ALL,
PERMISSIONS.ABSENCES_APPROVE,
PERMISSIONS.LEAN_READ,
PERMISSIONS.LEAN_MANAGE,
PERMISSIONS.INTEGRATIONS_READ,
],
department_head: [
PERMISSIONS.USERS_READ,
PERMISSIONS.DEPARTMENTS_READ,
PERMISSIONS.EMPLOYEES_READ_DEPARTMENT,
PERMISSIONS.TIME_READ_DEPARTMENT,
PERMISSIONS.ABSENCES_READ_DEPARTMENT,
PERMISSIONS.ABSENCES_APPROVE,
PERMISSIONS.LEAN_READ,
PERMISSIONS.LEAN_MANAGE,
],
employee: [
PERMISSIONS.EMPLOYEES_READ_OWN,
PERMISSIONS.TIME_READ_OWN,
PERMISSIONS.ABSENCES_READ_OWN,
PERMISSIONS.ABSENCES_CREATE,
PERMISSIONS.LEAN_READ,
],
};

View File

@@ -0,0 +1,55 @@
/**
* Department-related types
*/
export interface Department {
id: string;
name: string;
description?: string;
code: string;
parentId?: string;
managerId?: string;
isActive: boolean;
createdAt: Date;
updatedAt: Date;
}
export const DEPARTMENTS = [
'sales',
'accounting',
'warehouse',
'logistics',
'engineering',
'it',
'executive',
'executive_assistant',
'hr',
'procurement',
] as const;
export type DepartmentCode = (typeof DEPARTMENTS)[number];
export interface DepartmentWithStats extends Department {
employeeCount: number;
manager?: {
id: string;
firstName: string;
lastName: string;
};
}
export interface CreateDepartmentDto {
name: string;
description?: string;
code: string;
parentId?: string;
managerId?: string;
}
export interface UpdateDepartmentDto {
name?: string;
description?: string;
parentId?: string;
managerId?: string;
isActive?: boolean;
}

View File

@@ -0,0 +1,133 @@
/**
* Employee and HR-related types
*/
export interface Employee {
id: string;
userId: string;
employeeNumber: string;
position: string;
entryDate: Date;
exitDate?: Date;
contractType: ContractType;
workingHours: number;
departmentId: string;
managerId?: string;
createdAt: Date;
updatedAt: Date;
}
export type ContractType =
| 'FULL_TIME'
| 'PART_TIME'
| 'MINI_JOB'
| 'INTERN'
| 'WORKING_STUDENT'
| 'FREELANCE'
| 'TEMPORARY';
export interface TimeEntry {
id: string;
employeeId: string;
date: Date;
clockIn: Date;
clockOut?: Date;
breakMinutes: number;
type: TimeEntryType;
note?: string;
correctedById?: string;
createdAt: Date;
updatedAt: Date;
}
export type TimeEntryType =
| 'REGULAR'
| 'OVERTIME'
| 'HOLIDAY'
| 'SICK'
| 'VACATION'
| 'CORRECTION';
export interface Absence {
id: string;
employeeId: string;
type: AbsenceType;
startDate: Date;
endDate: Date;
days: number;
status: ApprovalStatus;
approvedById?: string;
approvedAt?: Date;
note?: string;
createdAt: Date;
updatedAt: Date;
}
export type AbsenceType =
| 'VACATION'
| 'SICK'
| 'SICK_CHILD'
| 'SPECIAL_LEAVE'
| 'UNPAID_LEAVE'
| 'PARENTAL_LEAVE'
| 'HOME_OFFICE'
| 'BUSINESS_TRIP'
| 'TRAINING'
| 'COMPENSATION';
export type ApprovalStatus = 'PENDING' | 'APPROVED' | 'REJECTED' | 'CANCELLED';
export interface EmployeeReview {
id: string;
employeeId: string;
date: Date;
type: ReviewType;
notes?: string;
goals?: ReviewGoal[];
reviewerId: string;
createdAt: Date;
updatedAt: Date;
}
export type ReviewType = 'PROBATION' | 'ANNUAL' | 'GOAL_SETTING' | 'FEEDBACK';
export interface ReviewGoal {
title: string;
description?: string;
dueDate?: Date;
status: 'open' | 'in_progress' | 'completed' | 'cancelled';
}
export interface OnboardingTask {
id: string;
title: string;
description?: string;
dueOffset: number;
assignedTo?: string;
departmentId?: string;
isActive: boolean;
createdAt: Date;
updatedAt: Date;
}
export interface CreateTimeEntryDto {
date: Date;
clockIn: Date;
clockOut?: Date;
breakMinutes?: number;
type?: TimeEntryType;
note?: string;
}
export interface CreateAbsenceDto {
type: AbsenceType;
startDate: Date;
endDate: Date;
days: number;
note?: string;
}
export interface UpdateAbsenceStatusDto {
status: ApprovalStatus;
note?: string;
}

View File

@@ -0,0 +1,8 @@
/**
* Shared TypeScript types for tOS
*/
export * from './user';
export * from './department';
export * from './employee';
export * from './auth';

View File

@@ -0,0 +1,286 @@
/**
* Skill Matrix types for tOS LEAN module
*/
/**
* Skill level numeric values (0-4)
* Based on LEAN competency matrix standards
*/
export type SkillLevelValue = 0 | 1 | 2 | 3 | 4;
/**
* Skill level labels
*/
export const SKILL_LEVEL_LABELS: Record<SkillLevelValue, string> = {
0: 'Keine Kenntnisse',
1: 'Grundlagen',
2: 'Selbststaendig',
3: 'Experte',
4: 'Kann schulen',
};
/**
* Skill level labels in English
*/
export const SKILL_LEVEL_LABELS_EN: Record<SkillLevelValue, string> = {
0: 'No knowledge',
1: 'Basic understanding',
2: 'Independent work',
3: 'Expert',
4: 'Can train others',
};
/**
* Skill level colors for visual display
* Following LEAN traffic light convention
*/
export const SKILL_LEVEL_COLORS: Record<SkillLevelValue, string> = {
0: 'bg-gray-200 text-gray-700',
1: 'bg-red-500 text-white',
2: 'bg-yellow-500 text-black',
3: 'bg-green-500 text-white',
4: 'bg-blue-500 text-white',
};
/**
* Skill level badge variants
*/
export const SKILL_LEVEL_VARIANTS: Record<SkillLevelValue, 'default' | 'secondary' | 'destructive' | 'warning' | 'success'> = {
0: 'secondary',
1: 'destructive',
2: 'warning',
3: 'success',
4: 'default',
};
/**
* Skill categories
*/
export type SkillCategory = 'technical' | 'softSkills' | 'processes' | 'safety' | 'quality' | 'other';
/**
* Skill category labels
*/
export const SKILL_CATEGORY_LABELS: Record<SkillCategory, string> = {
technical: 'Technisch',
softSkills: 'Soft Skills',
processes: 'Prozesse',
safety: 'Sicherheit',
quality: 'Qualitaet',
other: 'Sonstige',
};
/**
* Skill interface
*/
export interface Skill {
id: string;
name: string;
description?: string | null;
category?: string | null;
departmentId?: string | null;
isActive: boolean;
createdAt: Date;
updatedAt: Date;
department?: {
id: string;
name: string;
code?: string | null;
} | null;
_count?: {
entries: number;
};
}
/**
* Skill matrix entry (assessment)
*/
export interface SkillMatrixEntry {
id: string;
employeeId: string;
skillId: string;
level: SkillLevelValue;
assessedAt: Date;
assessedById?: string | null;
notes?: string | null;
nextReview?: Date | null;
employee?: {
id: string;
employeeNumber: string;
position: string;
user: {
id: string;
firstName: string;
lastName: string;
email: string;
};
};
skill?: Skill;
assessedBy?: {
id: string;
firstName: string;
lastName: string;
} | null;
}
/**
* Employee skill data in matrix view
*/
export interface EmployeeSkillData {
level: number;
assessedAt: Date | null;
assessedBy: {
id: string;
firstName: string;
lastName: string;
} | null;
notes: string | null;
}
/**
* Matrix row (employee with skills)
*/
export interface MatrixRow {
employee: {
id: string;
employeeNumber: string;
position: string;
user: {
id: string;
firstName: string;
lastName: string;
email: string;
};
};
skills: Record<string, EmployeeSkillData>;
}
/**
* Skill matrix data structure
*/
export interface SkillMatrix {
department: {
id: string;
name: string;
code?: string | null;
};
skills: Array<{
id: string;
name: string;
description?: string | null;
category?: string | null;
isGlobal: boolean;
}>;
matrix: MatrixRow[];
}
/**
* Skill gap analysis result
*/
export interface SkillGapAnalysis {
skillId: string;
skillName: string;
category: string | null;
targetLevel: number;
averageLevel: number;
gap: number;
employeesBelow: number;
employeesAtOrAbove: number;
totalEmployees: number;
}
/**
* Skill coverage result
*/
export interface SkillCoverage {
skillId: string;
skillName: string;
levelDistribution: Record<number, number>;
totalAssessed: number;
coverage: number;
averageLevel: number;
}
/**
* Trainer info for a skill
*/
export interface SkillTrainer {
employeeId: string;
employeeNumber: string;
position: string;
user: {
id: string;
firstName: string;
lastName: string;
email: string;
department?: {
id: string;
name: string;
} | null;
};
assessedAt: Date;
}
/**
* Trainers for a skill result
*/
export interface SkillTrainers {
skill: {
id: string;
name: string;
category: string | null;
};
trainers: SkillTrainer[];
totalTrainers: number;
}
/**
* Create skill DTO
*/
export interface CreateSkillDto {
name: string;
description?: string;
category?: SkillCategory;
departmentId?: string;
targetLevel?: SkillLevelValue;
isActive?: boolean;
}
/**
* Update skill DTO
*/
export interface UpdateSkillDto {
name?: string;
description?: string;
category?: SkillCategory;
departmentId?: string;
targetLevel?: SkillLevelValue;
isActive?: boolean;
}
/**
* Create skill entry DTO
*/
export interface CreateSkillEntryDto {
employeeId: string;
skillId: string;
level: SkillLevelValue;
notes?: string;
nextReview?: string;
}
/**
* Update skill entry DTO
*/
export interface UpdateSkillEntryDto {
level?: SkillLevelValue;
notes?: string;
nextReview?: string;
}
/**
* Bulk skill entry DTO
*/
export interface BulkSkillEntryDto {
entries: CreateSkillEntryDto[];
}

View File

@@ -0,0 +1,57 @@
/**
* User-related types
*/
export interface User {
id: string;
email: string;
firstName: string;
lastName: string;
avatarUrl?: string;
departmentId?: string;
roles: UserRole[];
isActive: boolean;
createdAt: Date;
updatedAt: Date;
}
export type UserRole = 'admin' | 'manager' | 'department_head' | 'employee';
export interface UserPreferences {
theme: 'light' | 'dark' | 'system';
language: 'de' | 'en';
dashboardLayout?: DashboardWidgetConfig[];
notifications: NotificationPreferences;
}
export interface DashboardWidgetConfig {
id: string;
widgetType: string;
position: { x: number; y: number };
size: { width: number; height: number };
settings?: Record<string, unknown>;
}
export interface NotificationPreferences {
email: boolean;
push: boolean;
inApp: boolean;
digest: 'none' | 'daily' | 'weekly';
}
export interface CreateUserDto {
email: string;
firstName: string;
lastName: string;
departmentId?: string;
roles: UserRole[];
}
export interface UpdateUserDto {
firstName?: string;
lastName?: string;
avatarUrl?: string;
departmentId?: string;
roles?: UserRole[];
isActive?: boolean;
}

View File

@@ -0,0 +1,82 @@
/**
* Shared constants for tOS
*/
export const APP_NAME = 'tOS';
export const APP_DESCRIPTION = 'Enterprise Web Operating System';
export const APP_VERSION = '0.1.0';
export const API_VERSION = 'v1';
export const API_PREFIX = `/api/${API_VERSION}`;
export const DEFAULT_PAGE_SIZE = 20;
export const MAX_PAGE_SIZE = 100;
// Time tracking configuration
export const DEFAULT_BREAK_MINUTES = 30;
export const MIN_BREAK_AFTER_HOURS = 6;
export const REQUIRED_BREAK_MINUTES = 30;
export const MAX_BREAK_AFTER_HOURS = 9;
export const EXTENDED_BREAK_MINUTES = 45;
// Vacation configuration (German law defaults)
export const DEFAULT_VACATION_DAYS = 30;
export const MIN_VACATION_DAYS = 20;
// Working hours configuration
export const STANDARD_WORKING_HOURS_PER_WEEK = 40;
export const STANDARD_WORKING_HOURS_PER_DAY = 8;
export const MAX_WORKING_HOURS_PER_DAY = 10;
// Skill matrix levels
export const SKILL_LEVELS = {
0: 'No knowledge',
1: 'Basic understanding',
2: 'Independent work',
3: 'Expert',
4: 'Can train others',
} as const;
export type SkillLevel = keyof typeof SKILL_LEVELS;
// 3S categories (Seiri, Seiton, Seiso)
export const S3_CATEGORIES = {
SEIRI: 'Sort',
SEITON: 'Set in Order',
SEISO: 'Shine',
} as const;
export type S3Category = keyof typeof S3_CATEGORIES;
// Morning meeting SQCDM categories
export const SQCDM_CATEGORIES = {
S: 'Safety',
Q: 'Quality',
C: 'Cost',
D: 'Delivery',
M: 'Morale',
} as const;
export type SQCDMCategory = keyof typeof SQCDM_CATEGORIES;
export const HTTP_STATUS = {
OK: 200,
CREATED: 201,
NO_CONTENT: 204,
BAD_REQUEST: 400,
UNAUTHORIZED: 401,
FORBIDDEN: 403,
NOT_FOUND: 404,
CONFLICT: 409,
UNPROCESSABLE_ENTITY: 422,
INTERNAL_SERVER_ERROR: 500,
SERVICE_UNAVAILABLE: 503,
} as const;
export const DATE_FORMATS = {
ISO: 'yyyy-MM-dd',
DE_SHORT: 'dd.MM.yyyy',
DE_LONG: 'dd. MMMM yyyy',
TIME_24H: 'HH:mm',
DATETIME_DE: 'dd.MM.yyyy HH:mm',
} as const;

View File

@@ -0,0 +1,115 @@
/**
* Shared utility functions for tOS
*/
export * from './constants';
/**
* Format a date to German locale string
*/
export function formatDate(
date: Date | string,
options?: Intl.DateTimeFormatOptions
): string {
const d = typeof date === 'string' ? new Date(date) : date;
return d.toLocaleDateString(
'de-DE',
options ?? { day: '2-digit', month: '2-digit', year: 'numeric' }
);
}
/**
* Format a time to German locale string
*/
export function formatTime(
date: Date | string,
options?: Intl.DateTimeFormatOptions
): string {
const d = typeof date === 'string' ? new Date(date) : date;
return d.toLocaleTimeString('de-DE', options ?? { hour: '2-digit', minute: '2-digit' });
}
/**
* Format a date and time to German locale string
*/
export function formatDateTime(date: Date | string): string {
return `${formatDate(date)} ${formatTime(date)}`;
}
/**
* Calculate working days between two dates (excluding weekends)
*/
export function calculateWorkingDays(startDate: Date, endDate: Date): number {
let count = 0;
const current = new Date(startDate);
while (current <= endDate) {
const dayOfWeek = current.getDay();
if (dayOfWeek !== 0 && dayOfWeek !== 6) {
count++;
}
current.setDate(current.getDate() + 1);
}
return count;
}
/**
* Calculate hours and minutes from minutes
*/
export function minutesToHoursAndMinutes(totalMinutes: number): {
hours: number;
minutes: number;
} {
const hours = Math.floor(totalMinutes / 60);
const minutes = totalMinutes % 60;
return { hours, minutes };
}
/**
* Format minutes to "HH:MM" string
*/
export function formatMinutesToTime(totalMinutes: number): string {
const { hours, minutes } = minutesToHoursAndMinutes(totalMinutes);
return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}`;
}
/**
* Get initials from first and last name
*/
export function getInitials(firstName: string, lastName: string): string {
return `${firstName.charAt(0)}${lastName.charAt(0)}`.toUpperCase();
}
/**
* Get full name from first and last name
*/
export function getFullName(firstName: string, lastName: string): string {
return `${firstName} ${lastName}`;
}
/**
* Slugify a string
*/
export function slugify(str: string): string {
return str
.toLowerCase()
.trim()
.replace(/[^\w\s-]/g, '')
.replace(/[\s_-]+/g, '-')
.replace(/^-+|-+$/g, '');
}
/**
* Check if a value is not null or undefined
*/
export function isDefined<T>(value: T | null | undefined): value is T {
return value !== null && value !== undefined;
}
/**
* Sleep for a specified number of milliseconds
*/
export function sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}

View File

@@ -0,0 +1,22 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"lib": ["ES2022"],
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": "./dist",
"rootDir": "./src",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}

View File

@@ -0,0 +1,11 @@
import { defineConfig } from 'tsup';
export default defineConfig({
entry: ['src/index.ts', 'src/types/index.ts', 'src/utils/index.ts'],
format: ['cjs', 'esm'],
dts: true,
splitting: false,
sourcemap: true,
clean: true,
treeshake: true,
});