phaller222/Prioritize

IoT-ready, neutral platform for project & resource management with NFC, MQTT and BPMN process orchestration (Flowable) — Spring Boot REST backend + Vaadin admin GUI.

Java

1

315 commits

updated Sep 18, 2026

See the code
bpmn
flowable
iot
java
mqtt
nfc
project-management
resource-management
rest-api
self-hosted
spring-boot
vaadin

See what people are saying (1)

SourceMessageScoreDate

Self-hosted task tracker where you clock in by tapping an NFC sticker on the thing you're working on (r/selfhosted)

I've been building this for a while and it's at a point where other people could actually run it, so here it is. Apache-2.0, self-hosted, no cloud anything. **The idea:** stick an NFC tag on a thing — a machine, a toolbox, a job folder, a door — and tap it with your phone. The tag is bound to a…

0

Sep 19, 2026

README

Prioritize

Open-source framework for organizing companies, employees, devices (IoT), and their tasks — Spring Boot 4 / Java 21.

Find the right people — and things — for the right job. Prioritize models organizational structures (companies, departments, users, roles), manages documents with versioning, represents skills for people and devices, controls IoT resources over MQTT and REST, and organizes work as projects, tasks and goals with NFC-driven time tracking. It exposes a REST API throughout, against which arbitrary clients can be built.


Status

Released and self-hostable — a Spring Boot platform you can get running in one command (see Quickstart). The runnable Spring Boot core covers: company/user management, documents with versioning, skills for people and devices, resource control (MQTT/REST), telemetry state-transition rules, recurring (cron) task schedules, BPMN orchestration via Flowable, and the project subsystem — projects, blackboards, tasks, goal-driven progress, task time tracking, and NFC tags as physical triggers (including broadcasting scans over MQTT). A Vaadin admin GUI covering the org/security, resource, document, skill, scheduling and process subsystems is merged and functional. A few concepts (action board, message inbox) are planned but not yet implemented.

Prioritize — scan an NFC tag to start and stop task time tracking

Scan a TIMETRACKER NFC tag to start and stop a task's time tracking — one physical tap, no screens, no logins. Driven entirely through the REST API.

Prioritize is the backend — you bring the reader. A scan is just one REST call, POST /api/v1/nfc/scan/{uuid}. Whatever reads the tag and fires that call — an NFC-capable phone app, a fixed reader, an ESP32/Raspberry Pi, or a shell script — is up to you. Prioritize provides the endpoint (and can broadcast scans over MQTT); it does not ship a reader app.


Technology stack

AreaTechnology
RuntimeJava 21
FrameworkSpring Boot 4.0.5 (Web, Data JPA, Security, Integration)
PersistencePostgreSQL (production), H2 (local/tests)
AuthenticationHTTP Basic Auth or OAuth2 Resource Server (Keycloak, JWT)
IoT transportMQTT (Spring Integration + Eclipse Paho v3) and REST
Process engineFlowable (BPMN)
Admin GUIVaadin 25 (Flow)
Document parsingApache Tika
API docsspringdoc-openapi (Swagger UI)
BuildMaven
BoilerplateLombok

Architecture

Layers

The application follows a clear layering with a fixed convention for authorization:

  • Controllers accept Authentication, resolve the PUser from it, and pass it explicitly to the service layer. Controllers contain no authorization logic.
  • Services hold all business logic including authorization. Permission checks happen exclusively here and are enforced via exceptions (not via return values).
  • Repositories (Spring Data JPA) encapsulate data access.

Further conventions: constructor injection via Lombok @RequiredArgsConstructor; IDs consistently typed as Long; CurrentUserResolver as the central bridge between Spring Security's Authentication and the PUser model.

Resource control (hexagonal)

Control of IoT resources is modeled as a hexagonal port. The rest of the system only knows the ResourceControlAdapter interface, not the concrete transport:

  • REST (RestResourceControlAdapter) is the always-active base transport. Any resource with an IP set is controllable via REST (POST http://<ip>:<port>/command).
  • MQTT (MqttResourceControlAdapter) is an optional, additional capability. The entire MQTT branch is active only when prioritize.mqtt.enabled=true (@ConditionalOnProperty); otherwise it stays dormant.

The ResourceControlService selects the transport per command following a capability-set strategy with fallback:

  1. MQTT capability present and online → MQTT
  2. MQTT capability present but offline + REST endpoint (IP) set → REST fallback
  3. No MQTT capability → REST
  4. No reachable transport → ResourceOfflineException (HTTP 503)

The inbound and outbound directions are deliberately separated: outbound commands go through the port; inbound device events (status, discovery, telemetry) are handled by a separate inbound path (InboundResourceEventHandler). The wire format is JSON (ResourceCommandMessage with command / param / slot).

Slot-bound control via reservations

Resources can have multiple slots (maxSlots). A control command always addresses a specific slot — this slot is not supplied by the client but derived server-side from the calling user's active reservation:

  • Exactly one active reservation by the user → its slot is used.
  • No active reservation → SlotNotReservedException (HTTP 409). A command requires an ongoing reservation.
  • Multiple active reservations → slot is ambiguous → SlotNotReservedException (HTTP 409).

"Active" means the current point in time lies within the reserved window (dateFrom <= now < dateUntil). An expired reservation implicitly releases the slot; a command sent afterwards runs into the 409 case.

Projects, tasks and goal-driven progress

Projects own a blackboard carrying tasks. Authorization in this subsystem is membership-based (project manager or member), orthogonal to the role/permission system used elsewhere; a task's assignee/manager is a PActor (either a PUser or a Resource). Progress is goal-driven, not task-driven: a Task carries no percentage, only an optional link to a ProjectGoal. A goal's completion is the share of its non-cancelled tasks that reached a done status; a project's progress is the average over its counting goals, and null (n/a) when undefined. Progress is always computed, never stored.

Task time tracking and NFC

Time tracking lives on the Task (a running span plus a history of completed spans), so it works with or without NFC. GET /tasks/{id}/tracking returns the aggregated total (the running span counted live up to now); GET /tasks/{id}/tracking/sessions lists the individual work sessions.

An NfcUnit is a physical NFC tag mounted on a resource (a resource may carry several tags of different types: COUNTER, CHECKPOINT, TIMETRACKER, INFOPOINT, OTHER). Scanning a tag resolves it by UUID and triggers a type-specific action — a TIMETRACKER tag toggles the time tracking of the single task it is bound to. When the MQTT profile is active, each scan is additionally broadcast on the topic nfc/scan/<tag-uuid> so devices and dashboards can observe it live; without MQTT, scanning works unchanged.

Telemetry state-transition rules

A resource can carry telemetry rules that turn a stream of numeric readings into a persisted OK/ALARM state. A rule defines an operator (GT, LT, RANGE), one or two thresholds, a hysteresis dead-band, and a severity. Both ingest paths (REST POST /resources/{id}/values and MQTT) evaluate the resource's enabled rules after each save; the breach uses the raw threshold, the clear only triggers once the value crosses back past threshold ± hysteresis (edge logic, so a value hovering at the boundary does not flap). On a state change the rule is persisted and a TelemetryThresholdEvent is fired; with the MQTT profile a telemetry/alarm/<resourceId> message is broadcast (type: "TELEMETRY_ALARM"). Resources without any rule cost zero extra database work (an in-memory guard is checked before any query).

Recurring task schedules

A TaskSchedule fires a task template onto a project's blackboard on a cron cadence. Each schedule carries the target project, the task template (name/description/priority), a cronExpression, a zoneId, an enabled flag, and nextFireAt/lastFiredAt. A gated poller (@Scheduled, enabled by default, disable with prioritize.scheduling.enabled=false) runs due schedules through a trusted user-less create path, stamps lastFiredAt, and advances nextFireAt. The cron is evaluated in the schedule's own zone but nextFireAt is stored normalized to the server zone, so the poller compares every schedule against a single wall clock. Failures are isolated per schedule.

BPMN orchestration (Flowable)

Flowable is used for orchestration, not lifecycle ownership: it describes order, waiting and responsibility, never computation. BPMN definitions are managed as versioned documents that require an explicit activation step before they can run (the classpath stays as a trusted root/break-glass source). Running processes link generically to a project or task (business key project:<id> / task:<id>). Two bridges connect processes to the platform: an inbound trusted facade (PlatformGateway) lets a process create tasks, store documents or control resources under a seeded system principal, and an outbound event bridge wakes waiting processes from platform events (NFC scans, telemetry thresholds) correlated by a single rule — the pair of message name and an awaitedResourceId process variable.


Configuration (profiles)

Behavior is controlled via Spring profiles. The default profile is h2 (see application.yaml), so a fresh checkout runs with no external setup.

ProfilePurpose
h2Local H2 file database including H2 console at /h2-console. Default.
postgresPostgreSQL data source (shared/production/NAS setup).
keycloakSwitches security from Basic Auth to OAuth2 Resource Server (JWT).
mqttEnables the MQTT transport (prioritize.mqtt.enabled=true).

Profiles are combinable, e.g. spring.profiles.active=postgres,keycloak,mqtt.

Authentication

The security configuration is profile-dependent and mutually exclusive:

  • Without the keycloak profile, SecurityConfig (@Profile("!keycloak")) applies with HTTP Basic Auth for the REST API plus form login for the admin GUI.
  • With the keycloak profile, KeycloakSecurityConfig (@Profile("keycloak")) applies as an OAuth2 Resource Server (JWT / Bearer). The issuer-uri is set in application-keycloak.yaml; it must exactly match the iss claim of the tokens.

Bearer auth is a REST-API story only. Under the keycloak profile the app is a pure resource server with no login page — the Vaadin admin GUI is not reachable (every browser page returns 401, because the browser sends no Authorization: Bearer header). Use the admin GUI without the keycloak profile (Basic/form login); use Keycloak/Bearer tokens against /api/v1/**. Running the GUI under Keycloak (OAuth2 login + JIT user provisioning) is a planned post-1.0 feature.

A default administrator (admin / p@ssword) is seeded on first start by InitializationService (BCrypt-hashed in the database). Change it before any non-local deployment.

Database credentials

The PostgreSQL data source reads its password from the DB_PASSWORD environment variable, defaulting to prioritize for local development (see application-postgres.yaml). Set DB_PASSWORD in any shared or production environment.

MQTT

Bound to the prefix prioritize.mqtt (MqttProperties):

prioritize:
  mqtt:
    enabled: true
    broker-url: tcp://memoryalpha:1883   # TLS later: ssl://...:8883
    client-id: prioritize-backend
    # username: prioritize
    # password: ${MQTT_PASSWORD:}
    qos: 1
    subscribe-topics:
      - DISCOVERY
      - "+/status"

Quickstart

The fastest way to try Prioritize is Docker. You can also run it from source with a JDK.

Requires Docker with the Compose plugin. This builds the app image and starts it together with a PostgreSQL database:

docker compose up --build

Then open http://localhost:8080 and log in with admin / p@ssword. The REST API lives under /api/v1 and Swagger UI is at /swagger-ui.html.

Just want a quick look with no database to manage? A single self-contained container backed by an embedded H2 file DB is enough:

docker build -t prioritize .
docker run --rm -m 1g -p 8080:8080 prioritize

-m 1g is not decoration: without a limit the JVM sizes its heap against the host's RAM, so on a large machine the ceiling ends up in the tens of gigabytes. Compose sets the limit for you; a plain docker run does not. See Memory.

The optional stacks are opt-in via Compose profiles (copy .env.example to .env and add the profile to SPRING_PROFILES_ACTIVE, e.g. postgres,mqtt):

docker compose --profile mqtt up        # + Mosquitto broker (device telemetry / NFC)
docker compose --profile keycloak up    # + Keycloak (OIDC bearer-token auth)

Data persists in the db-data volume (PostgreSQL) or the container's /app/data volume (H2). Stop with docker compose down to keep the data, or docker compose down -v to drop it.

Memory

Give the app container 1 GB. Compose does that by default (mem_limit, override with MEM_LIMIT in .env); for a plain docker run, pass -m 1g. Measured on 1.4.0: about 480 MiB idle at that limit, plus roughly 90 MiB for the PostgreSQL container. It starts in around 30 seconds.

The limit matters more than it looks. The JVM is container-aware and sizes its heap from the container's limit — but with no limit it falls back to a share of the host's RAM, which on a 32 GB machine means a heap ceiling of 8.4 GB. The image sets -XX:MaxRAMPercentage=60 rather than a fixed -Xmx, so it adapts to whatever limit you give it. 60 and not the usual 75 because this application's non-heap footprint — metaspace, code cache, threads, natives — measures around 310 MiB: at a 1 GB limit, 75% would place the heap ceiling beyond the limit and a heap that filled up would be OOM-killed instead of collected. If you want 75%, give the container 2 GB:

docker run -m 2g -e JDK_JAVA_OPTIONS="-XX:MaxRAMPercentage=75" -p 8080:8080 prioritize

It does fit in 512 MB — verified, no OOM kill under a few hundred API calls — but at ~92% of the limit there is no headroom, so that is a demo size rather than one to run on.

From source (JDK 21 + Maven)

Prerequisites: JDK 21 and Maven. No database setup is needed for the default h2 profile.

# Default: local H2 file DB, no external services (just run it)
mvn spring-boot:run

# Against a shared/production PostgreSQL
mvn spring-boot:run -Dspring-boot.run.profiles=postgres

# PostgreSQL with Keycloak and MQTT
mvn spring-boot:run -Dspring-boot.run.profiles=postgres,keycloak,mqtt

Tests:

mvn test

With the h2 profile, the H2 console is available at http://localhost:8080/h2-console.

API documentation

While the application is running, interactive OpenAPI documentation is served via springdoc (Swagger UI, typically at /swagger-ui.html). The basicAuth and bearerAuth security schemes are registered, so endpoints can be tested authenticated directly from the UI.

Swagger UI — the REST API


Admin GUI (Vaadin)

A Vaadin admin GUI ships in-process and is served from the application root (http://localhost:8080/). Log in with a local user (default admin / p@ssword); it uses form login and is available only without the keycloak profile (see Authentication). It is an operator tool for the platform, not the primary API — arbitrary clients are expected to build on the REST API instead.

Covered subsystems (one navigation entry each): Dashboard, Companies, Departments, Users, Roles, Groups, Resources (with live online status and reservations), Documents (list/download/delete), Skills, Skill Categories, Task Schedules, Process Definitions, and Process Instances. GUI routes are an implementation detail and are not part of the public API contract (see API stability).

📸  Screenshots  (click to expand)

Dashboard — the Vaadin admin GUI home:

Prioritize admin GUI — dashboard

Resources — networked machines and sensors with live online-status indicators:

Resources view with online-status indicators

Users — org-wide user administration:

Users view

Skills — competencies for people and devices:

Skills view

Login:

Login screen


REST API (overview)

All core endpoints live under /api/v1. The table below is an overview, not a complete reference — the authoritative, always-current description is provided by the OpenAPI docs.

Resources & control

MethodPathPurpose
GET/api/v1/resourcegroups/{groupId}/resourcesResources of a group
POST/api/v1/resourcegroups/{groupId}/resourcesCreate resource
GET/api/v1/resources/{id}Get resource
PATCH/api/v1/resources/{id}Partial update (null = unchanged)
DELETE/api/v1/resources/{id}Delete resource
POST/api/v1/resources/{id}/commandSend control command (slot derived from reservation)
POST/api/v1/resources/{id}/reserveReserve resource for a time window
GET/api/v1/resources/{id}/reservationsAll reservations of the resource
GET/api/v1/resources/{id}/reservations/mineOwn active reservations (slot preview)
DELETE/api/v1/reservations/{reservationId}Cancel reservation / release slot
POST/api/v1/resources/{id}/valuesIngest a telemetry reading
GET/api/v1/resources/{resourceId}/skillsSkills of a resource
POST/api/v1/resources/{resourceId}/skillsAssign skill

Projects, tasks & goals

MethodPathPurpose
POST/api/v1/projectsCreate project
GET/api/v1/projects/mineProjects I manage or am a member of
GET / PUT / DELETE/api/v1/projects/{id}Get / update / delete project
POST / DELETE/api/v1/projects/{id}/membersAdd / remove member
GET/api/v1/projects/{id}/tasksTasks on the project's blackboard
GET / POST/api/v1/projects/{id}/goalsList / create goals
GET/api/v1/projects/{id}/progressComputed goal-driven progress
POST/api/v1/projects/{projectId}/tasksCreate task
GET / PUT / DELETE/api/v1/tasks/{id}Get / update / delete task
POST/api/v1/tasks/{id}/assignAssign a PActor
PUT/api/v1/tasks/{id}/statusChange task status
PUT / DELETE/api/v1/tasks/{id}/goalAssign / unassign a goal

Time tracking & NFC

MethodPathPurpose
POST/api/v1/tasks/{id}/tracking/{start|stop|toggle}Start / stop / toggle time tracking
GET/api/v1/tasks/{id}/trackingAggregated tracked total (running span live)
GET/api/v1/tasks/{id}/tracking/sessionsIndividual tracked work sessions
GET / POST/api/v1/resources/{id}/nfc-unitsList / register NFC tags on a resource
PUT / DELETE/api/v1/nfc-units/{id}/task/{taskId}Bind / unbind a TIMETRACKER tag to a task
POST/api/v1/nfc/scan/{uuid}Process a tag scan (type-specific action)

Companies & departments

MethodPathPurpose
GET/api/v1/companies/{id}Get company
POST/api/v1/companies/filterFilter companies
PUT/api/v1/companies/{id}Update company
DELETE/api/v1/companies/{id}Delete company
POST/api/v1/companies/{companyId}/departmentsCreate department
GET/api/v1/companies/{companyId}/departmentsDepartments of a company
GET/api/v1/departments/{id}Get department
PUT/api/v1/departments/{id}Update department
DELETE/api/v1/departments/{id}Delete department

Users

MethodPathPurpose
GET/api/v1/users/{id}Get user
PUT/api/v1/users/{id}Update user
PATCH/api/v1/users/{id}Partial update
DELETE/api/v1/users/{id}Delete user
GET/api/v1/users/{userId}/skillsSkills of a user
POST/api/v1/users/{userId}/skillsAssign skill

Skills

MethodPathPurpose
GET / POST/api/v1/skillsList / create skills
GET / PUT / DELETE/api/v1/skills/{skillId}Get / update / delete skill
GET / POST/api/v1/skills/categoriesList / create categories
GET / PUT / DELETE/api/v1/skills/categories/{categoryId}Get / update / delete category

Documents

MethodPathPurpose
GET/api/v1/documents/download/{documentInfoId}Download document
GET/api/v1/documents/{id}/version/{versionNumber}Get a specific version
GET/api/v1/documents/{id}/historyVersion history
POST/api/v1/documents/{id}/check-outCheck out (lock)
POST/api/v1/documents/{id}/check-inCheck in (new version)
POST/api/v1/documents/{id}/cancel-check-outCancel check-out
GET/api/v1/documents/searchFull-text / metadata search
GET/api/v1/documents/recentRecently changed documents
DELETE/api/v1/documents/{id}Delete document
GET/api/v1/document-groups/{groupId}/documentsDocuments of a group
DELETE/api/v1/document-groups/{groupId}Delete group

Telemetry rules

MethodPathPurpose
GET / POST/api/v1/resources/{resourceId}/telemetry-rulesList / create rules for a resource
GET / PATCH / DELETE/api/v1/telemetry-rules/{id}Get / update / delete a rule

Task schedules

MethodPathPurpose
GET / POST/api/v1/projects/{projectId}/task-schedulesList / create schedules for a project
GET / PATCH / DELETE/api/v1/task-schedules/{id}Get / update / delete a schedule

Processes (Flowable)

MethodPathPurpose
GET/api/v1/process-definitionsList registered process definitions
POST/api/v1/documents/{documentInfoId}/process-definitionRegister a BPMN document as a definition
GET/api/v1/process-definitions/{id}Get a definition
POST/api/v1/process-definitions/{id}/activateActivate (deploy to the engine)
POST/api/v1/process-definitions/{id}/deactivateDeactivate
DELETE/api/v1/process-definitions/{id}Remove a definition (?force=true to also drop a deployment)
GET / POST/api/v1/projects/{projectId}/process-instancesList / start instances linked to a project
POST/api/v1/tasks/{taskId}/process-instancesStart an instance linked to a task
GET/api/v1/tasks/{taskId}/process-instanceThe instance linked to a task
GET/api/v1/process-instances/{id}Get an instance
POST/api/v1/process-instances/{id}/cancelCancel an instance (manager only)

API stability

From 1.0.0 onward the project follows semantic versioning: the version number communicates what a consumer can rely on.

  • The REST API under /api/v1 is the stable contract. These endpoints — their paths, methods, request/response shapes and documented status codes — are the public surface that external clients build against. Backward-incompatible changes to them will not happen within the 1.x line; they would come with a new major version (and, where practical, a new path prefix such as /api/v2). Additive changes (new endpoints, new optional fields) are minor releases and are safe to adopt. The authoritative, always-current description is the OpenAPI document served by the running application (see API documentation); the tables above are only an overview.

  • The Vaadin admin GUI and its routes are an implementation detail — not part of the contract. The @Route URLs (/process-definitions, /task-schedules, …) exist for the browser UI and may change at any time without a major bump. Do not script or link against them; drive automation through /api/v1 instead.

  • SkillProperty is experimental. The skill property model (typed key/value attributes on a skill) is exposed for early feedback and is marked EXPERIMENTAL in the OpenAPI schema. It has no admin GUI yet and its shape may change in a minor release without the usual stability guarantee. Everything else in the skill subsystem (skills, categories, assignments) is stable.

  • POST /api/v1/users creates users without a password, by design. In production the identity provider (Keycloak) owns credentials, so the local account is provisioned password-less; PUser.password carries @JsonIgnore and is never accepted or returned over REST. A REST-created user therefore cannot log in via Basic auth — that is intended for the Keycloak deployment model, not a missing feature. To create a login-capable local user (for a Basic-auth / development setup), use the admin GUI's user view, which has a password field. Just-in-time provisioning of local users on first Keycloak login is a planned post-1.0 addition.


Client libraries

Official REST clients build against the stable /api/v1 contract. They are spec-first: every API and model class is generated from the released OpenAPI document (see API documentation) and never hand-written, so the client version tracks the API version (a 1.x client targets the 1.x API).

LanguageRepositoryInstall
Javaprioritize-java-clientMaven Central: de.hallerweb:prioritize-java-client:1.3.1
PHPprioritize-php-clientComposer: composer require hallerweb/prioritize-php-client
Pythonprioritize-python-clientPyPI: pip install prioritize-client
TypeScriptprioritize-typescript-clientnpm: npm install prioritize-client
<dependency>
    <groupId>de.hallerweb</groupId>
    <artifactId>prioritize-java-client</artifactId>
    <version>1.3.1</version>
</dependency>

All four support HTTP Basic (default profile) and Bearer/Keycloak authentication.

Java only: build the client with new PrioritizeApiClient(), not new ApiClient(). The generated client transports over HttpURLConnection, which rejects PATCH, so every partial-update call would fail before leaving the JVM. PrioritizeApiClient is the same client on a PATCH-capable transport.


Error semantics (HTTP status)

Centralized in GlobalExceptionHandler:

StatusTrigger
400 Bad RequestIllegalArgumentException (e.g. invalid date format, end before start date); HttpMessageNotReadableException (malformed / unreadable request body)
403 ForbiddenAccessDeniedException (missing permission)
404 Not FoundNoSuchElementException / EntityNotFoundException
409 ConflictIllegalStateException, SlotNotReservedException (no / ambiguous active reservation), SlotOccupiedException (slot already taken), DataIntegrityViolationException (constraint violation)
500 Internal Server ErrorIncorrectResultSizeDataAccessException and any otherwise unmapped exception
502 Bad GatewayResourceCommandFailedException (device rejected the command)
503 Service UnavailableResourceOfflineException (no reachable control channel)

Domain model (brief overview)

  • Company / Department — organizational base structure. The hierarchy provides a foundation for tenant separation, but enforced multi-tenant isolation is not yet implemented (projects are membership-scoped, admin is a global superuser).
  • PUser / Role / PermissionRecord — users, roles, and the fine-grained permission model.
  • Resource — a device / resource; can represent an IoT device and communicate externally (REST/MQTT). Has slots and reservations.
  • ResourceReservation — time-bound occupancy of a resource slot by a user.
  • Document / DocumentInfo / DocumentGroup — documents with versioning and check-in/check-out.
  • Skill / SkillCategory / SkillRecord — capabilities; assignable to users and resources.
  • Project / Blackboard / Task / ProjectGoal — projects own a blackboard of tasks; goals drive computed progress; a task's assignee/manager is a PActor.
  • NfcUnit — a physical NFC tag mounted on a resource; a scan triggers a type-specific action (e.g. toggling a task's time tracking).
  • TelemetryRule — a per-resource rule turning numeric readings into a persisted OK/ALARM state with hysteresis.
  • TaskSchedule — a cron-driven schedule that fires a task template onto a project's blackboard.
  • ProcessDefinition / ProcessInstance — a registered, explicitly activated BPMN definition and its running orchestration, linked generically to a project or task.
  • Address — embedded value object, managed exclusively through its owners (Company, Department, PUser).

🗂️  Class diagrams per subsystem  (click to expand)

Each subsystem is documented as a focused UML class diagram — curated, entities only, with cross-package neighbours shown as «external» for context. PlantUML sources live under docs/diagrams/uml; the rendered PNGs under docs/diagrams/images.

Organization & security

Companies & departments

Company and Department subsystem class diagram

Users, roles & permissions

Security subsystem class diagram

Content & knowledge

Documents (versioning, check-in/out)

Document subsystem class diagram

Skills (for people and devices)

Skill subsystem class diagram

Work & operations

Projects, blackboards, tasks & goals

Project subsystem class diagram

Resources, reservations & control

Resource subsystem class diagram

Time spans (reservations & time tracking)

Calendar subsystem class diagram

Platform, IoT & automation

Telemetry monitoring rules

Telemetry subsystem class diagram

Recurring task schedules

Scheduling subsystem class diagram

BPMN process orchestration (Flowable)

Process subsystem class diagram

NFC tags as physical triggers

NFC subsystem class diagram


Contributing

Pull requests are welcome. For major changes, please open an issue first to discuss the direction.

See CONTRIBUTING.md for how to build, test and submit changes (branch model, commit conventions, code style, API-stability rules). Please also read our Code of Conduct. For security issues, follow the Security Policy — do not open a public issue.

License

Apache License 2.0 — see LICENSE. Source files carry the corresponding Apache 2.0 headers.

Contributors

phaller222

315 commits

phaller222/Prioritize

IoT-ready, neutral platform for project & resource management with NFC, MQTT and BPMN process orchestration (Flowable) — Spring Boot REST backend + Vaadin admin GUI.

Java

1

315 commits

updated Sep 18, 2026

See the code
bpmn
flowable
iot
java
mqtt
nfc
project-management
resource-management
rest-api
self-hosted
spring-boot
vaadin

See what people are saying (1)

SourceMessageScoreDate

Self-hosted task tracker where you clock in by tapping an NFC sticker on the thing you're working on (r/selfhosted)

I've been building this for a while and it's at a point where other people could actually run it, so here it is. Apache-2.0, self-hosted, no cloud anything. **The idea:** stick an NFC tag on a thing — a machine, a toolbox, a job folder, a door — and tap it with your phone. The tag is bound to a…

0

Sep 19, 2026

README

Prioritize

Open-source framework for organizing companies, employees, devices (IoT), and their tasks — Spring Boot 4 / Java 21.

Find the right people — and things — for the right job. Prioritize models organizational structures (companies, departments, users, roles), manages documents with versioning, represents skills for people and devices, controls IoT resources over MQTT and REST, and organizes work as projects, tasks and goals with NFC-driven time tracking. It exposes a REST API throughout, against which arbitrary clients can be built.


Status

Released and self-hostable — a Spring Boot platform you can get running in one command (see Quickstart). The runnable Spring Boot core covers: company/user management, documents with versioning, skills for people and devices, resource control (MQTT/REST), telemetry state-transition rules, recurring (cron) task schedules, BPMN orchestration via Flowable, and the project subsystem — projects, blackboards, tasks, goal-driven progress, task time tracking, and NFC tags as physical triggers (including broadcasting scans over MQTT). A Vaadin admin GUI covering the org/security, resource, document, skill, scheduling and process subsystems is merged and functional. A few concepts (action board, message inbox) are planned but not yet implemented.

Prioritize — scan an NFC tag to start and stop task time tracking

Scan a TIMETRACKER NFC tag to start and stop a task's time tracking — one physical tap, no screens, no logins. Driven entirely through the REST API.

Prioritize is the backend — you bring the reader. A scan is just one REST call, POST /api/v1/nfc/scan/{uuid}. Whatever reads the tag and fires that call — an NFC-capable phone app, a fixed reader, an ESP32/Raspberry Pi, or a shell script — is up to you. Prioritize provides the endpoint (and can broadcast scans over MQTT); it does not ship a reader app.


Technology stack

AreaTechnology
RuntimeJava 21
FrameworkSpring Boot 4.0.5 (Web, Data JPA, Security, Integration)
PersistencePostgreSQL (production), H2 (local/tests)
AuthenticationHTTP Basic Auth or OAuth2 Resource Server (Keycloak, JWT)
IoT transportMQTT (Spring Integration + Eclipse Paho v3) and REST
Process engineFlowable (BPMN)
Admin GUIVaadin 25 (Flow)
Document parsingApache Tika
API docsspringdoc-openapi (Swagger UI)
BuildMaven
BoilerplateLombok

Architecture

Layers

The application follows a clear layering with a fixed convention for authorization:

  • Controllers accept Authentication, resolve the PUser from it, and pass it explicitly to the service layer. Controllers contain no authorization logic.
  • Services hold all business logic including authorization. Permission checks happen exclusively here and are enforced via exceptions (not via return values).
  • Repositories (Spring Data JPA) encapsulate data access.

Further conventions: constructor injection via Lombok @RequiredArgsConstructor; IDs consistently typed as Long; CurrentUserResolver as the central bridge between Spring Security's Authentication and the PUser model.

Resource control (hexagonal)

Control of IoT resources is modeled as a hexagonal port. The rest of the system only knows the ResourceControlAdapter interface, not the concrete transport:

  • REST (RestResourceControlAdapter) is the always-active base transport. Any resource with an IP set is controllable via REST (POST http://<ip>:<port>/command).
  • MQTT (MqttResourceControlAdapter) is an optional, additional capability. The entire MQTT branch is active only when prioritize.mqtt.enabled=true (@ConditionalOnProperty); otherwise it stays dormant.

The ResourceControlService selects the transport per command following a capability-set strategy with fallback:

  1. MQTT capability present and online → MQTT
  2. MQTT capability present but offline + REST endpoint (IP) set → REST fallback
  3. No MQTT capability → REST
  4. No reachable transport → ResourceOfflineException (HTTP 503)

The inbound and outbound directions are deliberately separated: outbound commands go through the port; inbound device events (status, discovery, telemetry) are handled by a separate inbound path (InboundResourceEventHandler). The wire format is JSON (ResourceCommandMessage with command / param / slot).

Slot-bound control via reservations

Resources can have multiple slots (maxSlots). A control command always addresses a specific slot — this slot is not supplied by the client but derived server-side from the calling user's active reservation:

  • Exactly one active reservation by the user → its slot is used.
  • No active reservation → SlotNotReservedException (HTTP 409). A command requires an ongoing reservation.
  • Multiple active reservations → slot is ambiguous → SlotNotReservedException (HTTP 409).

"Active" means the current point in time lies within the reserved window (dateFrom <= now < dateUntil). An expired reservation implicitly releases the slot; a command sent afterwards runs into the 409 case.

Projects, tasks and goal-driven progress

Projects own a blackboard carrying tasks. Authorization in this subsystem is membership-based (project manager or member), orthogonal to the role/permission system used elsewhere; a task's assignee/manager is a PActor (either a PUser or a Resource). Progress is goal-driven, not task-driven: a Task carries no percentage, only an optional link to a ProjectGoal. A goal's completion is the share of its non-cancelled tasks that reached a done status; a project's progress is the average over its counting goals, and null (n/a) when undefined. Progress is always computed, never stored.

Task time tracking and NFC

Time tracking lives on the Task (a running span plus a history of completed spans), so it works with or without NFC. GET /tasks/{id}/tracking returns the aggregated total (the running span counted live up to now); GET /tasks/{id}/tracking/sessions lists the individual work sessions.

An NfcUnit is a physical NFC tag mounted on a resource (a resource may carry several tags of different types: COUNTER, CHECKPOINT, TIMETRACKER, INFOPOINT, OTHER). Scanning a tag resolves it by UUID and triggers a type-specific action — a TIMETRACKER tag toggles the time tracking of the single task it is bound to. When the MQTT profile is active, each scan is additionally broadcast on the topic nfc/scan/<tag-uuid> so devices and dashboards can observe it live; without MQTT, scanning works unchanged.

Telemetry state-transition rules

A resource can carry telemetry rules that turn a stream of numeric readings into a persisted OK/ALARM state. A rule defines an operator (GT, LT, RANGE), one or two thresholds, a hysteresis dead-band, and a severity. Both ingest paths (REST POST /resources/{id}/values and MQTT) evaluate the resource's enabled rules after each save; the breach uses the raw threshold, the clear only triggers once the value crosses back past threshold ± hysteresis (edge logic, so a value hovering at the boundary does not flap). On a state change the rule is persisted and a TelemetryThresholdEvent is fired; with the MQTT profile a telemetry/alarm/<resourceId> message is broadcast (type: "TELEMETRY_ALARM"). Resources without any rule cost zero extra database work (an in-memory guard is checked before any query).

Recurring task schedules

A TaskSchedule fires a task template onto a project's blackboard on a cron cadence. Each schedule carries the target project, the task template (name/description/priority), a cronExpression, a zoneId, an enabled flag, and nextFireAt/lastFiredAt. A gated poller (@Scheduled, enabled by default, disable with prioritize.scheduling.enabled=false) runs due schedules through a trusted user-less create path, stamps lastFiredAt, and advances nextFireAt. The cron is evaluated in the schedule's own zone but nextFireAt is stored normalized to the server zone, so the poller compares every schedule against a single wall clock. Failures are isolated per schedule.

BPMN orchestration (Flowable)

Flowable is used for orchestration, not lifecycle ownership: it describes order, waiting and responsibility, never computation. BPMN definitions are managed as versioned documents that require an explicit activation step before they can run (the classpath stays as a trusted root/break-glass source). Running processes link generically to a project or task (business key project:<id> / task:<id>). Two bridges connect processes to the platform: an inbound trusted facade (PlatformGateway) lets a process create tasks, store documents or control resources under a seeded system principal, and an outbound event bridge wakes waiting processes from platform events (NFC scans, telemetry thresholds) correlated by a single rule — the pair of message name and an awaitedResourceId process variable.


Configuration (profiles)

Behavior is controlled via Spring profiles. The default profile is h2 (see application.yaml), so a fresh checkout runs with no external setup.

ProfilePurpose
h2Local H2 file database including H2 console at /h2-console. Default.
postgresPostgreSQL data source (shared/production/NAS setup).
keycloakSwitches security from Basic Auth to OAuth2 Resource Server (JWT).
mqttEnables the MQTT transport (prioritize.mqtt.enabled=true).

Profiles are combinable, e.g. spring.profiles.active=postgres,keycloak,mqtt.

Authentication

The security configuration is profile-dependent and mutually exclusive:

  • Without the keycloak profile, SecurityConfig (@Profile("!keycloak")) applies with HTTP Basic Auth for the REST API plus form login for the admin GUI.
  • With the keycloak profile, KeycloakSecurityConfig (@Profile("keycloak")) applies as an OAuth2 Resource Server (JWT / Bearer). The issuer-uri is set in application-keycloak.yaml; it must exactly match the iss claim of the tokens.

Bearer auth is a REST-API story only. Under the keycloak profile the app is a pure resource server with no login page — the Vaadin admin GUI is not reachable (every browser page returns 401, because the browser sends no Authorization: Bearer header). Use the admin GUI without the keycloak profile (Basic/form login); use Keycloak/Bearer tokens against /api/v1/**. Running the GUI under Keycloak (OAuth2 login + JIT user provisioning) is a planned post-1.0 feature.

A default administrator (admin / p@ssword) is seeded on first start by InitializationService (BCrypt-hashed in the database). Change it before any non-local deployment.

Database credentials

The PostgreSQL data source reads its password from the DB_PASSWORD environment variable, defaulting to prioritize for local development (see application-postgres.yaml). Set DB_PASSWORD in any shared or production environment.

MQTT

Bound to the prefix prioritize.mqtt (MqttProperties):

prioritize:
  mqtt:
    enabled: true
    broker-url: tcp://memoryalpha:1883   # TLS later: ssl://...:8883
    client-id: prioritize-backend
    # username: prioritize
    # password: ${MQTT_PASSWORD:}
    qos: 1
    subscribe-topics:
      - DISCOVERY
      - "+/status"

Quickstart

The fastest way to try Prioritize is Docker. You can also run it from source with a JDK.

Requires Docker with the Compose plugin. This builds the app image and starts it together with a PostgreSQL database:

docker compose up --build

Then open http://localhost:8080 and log in with admin / p@ssword. The REST API lives under /api/v1 and Swagger UI is at /swagger-ui.html.

Just want a quick look with no database to manage? A single self-contained container backed by an embedded H2 file DB is enough:

docker build -t prioritize .
docker run --rm -m 1g -p 8080:8080 prioritize

-m 1g is not decoration: without a limit the JVM sizes its heap against the host's RAM, so on a large machine the ceiling ends up in the tens of gigabytes. Compose sets the limit for you; a plain docker run does not. See Memory.

The optional stacks are opt-in via Compose profiles (copy .env.example to .env and add the profile to SPRING_PROFILES_ACTIVE, e.g. postgres,mqtt):

docker compose --profile mqtt up        # + Mosquitto broker (device telemetry / NFC)
docker compose --profile keycloak up    # + Keycloak (OIDC bearer-token auth)

Data persists in the db-data volume (PostgreSQL) or the container's /app/data volume (H2). Stop with docker compose down to keep the data, or docker compose down -v to drop it.

Memory

Give the app container 1 GB. Compose does that by default (mem_limit, override with MEM_LIMIT in .env); for a plain docker run, pass -m 1g. Measured on 1.4.0: about 480 MiB idle at that limit, plus roughly 90 MiB for the PostgreSQL container. It starts in around 30 seconds.

The limit matters more than it looks. The JVM is container-aware and sizes its heap from the container's limit — but with no limit it falls back to a share of the host's RAM, which on a 32 GB machine means a heap ceiling of 8.4 GB. The image sets -XX:MaxRAMPercentage=60 rather than a fixed -Xmx, so it adapts to whatever limit you give it. 60 and not the usual 75 because this application's non-heap footprint — metaspace, code cache, threads, natives — measures around 310 MiB: at a 1 GB limit, 75% would place the heap ceiling beyond the limit and a heap that filled up would be OOM-killed instead of collected. If you want 75%, give the container 2 GB:

docker run -m 2g -e JDK_JAVA_OPTIONS="-XX:MaxRAMPercentage=75" -p 8080:8080 prioritize

It does fit in 512 MB — verified, no OOM kill under a few hundred API calls — but at ~92% of the limit there is no headroom, so that is a demo size rather than one to run on.

From source (JDK 21 + Maven)

Prerequisites: JDK 21 and Maven. No database setup is needed for the default h2 profile.

# Default: local H2 file DB, no external services (just run it)
mvn spring-boot:run

# Against a shared/production PostgreSQL
mvn spring-boot:run -Dspring-boot.run.profiles=postgres

# PostgreSQL with Keycloak and MQTT
mvn spring-boot:run -Dspring-boot.run.profiles=postgres,keycloak,mqtt

Tests:

mvn test

With the h2 profile, the H2 console is available at http://localhost:8080/h2-console.

API documentation

While the application is running, interactive OpenAPI documentation is served via springdoc (Swagger UI, typically at /swagger-ui.html). The basicAuth and bearerAuth security schemes are registered, so endpoints can be tested authenticated directly from the UI.

Swagger UI — the REST API


Admin GUI (Vaadin)

A Vaadin admin GUI ships in-process and is served from the application root (http://localhost:8080/). Log in with a local user (default admin / p@ssword); it uses form login and is available only without the keycloak profile (see Authentication). It is an operator tool for the platform, not the primary API — arbitrary clients are expected to build on the REST API instead.

Covered subsystems (one navigation entry each): Dashboard, Companies, Departments, Users, Roles, Groups, Resources (with live online status and reservations), Documents (list/download/delete), Skills, Skill Categories, Task Schedules, Process Definitions, and Process Instances. GUI routes are an implementation detail and are not part of the public API contract (see API stability).

📸  Screenshots  (click to expand)

Dashboard — the Vaadin admin GUI home:

Prioritize admin GUI — dashboard

Resources — networked machines and sensors with live online-status indicators:

Resources view with online-status indicators

Users — org-wide user administration:

Users view

Skills — competencies for people and devices:

Skills view

Login:

Login screen


REST API (overview)

All core endpoints live under /api/v1. The table below is an overview, not a complete reference — the authoritative, always-current description is provided by the OpenAPI docs.

Resources & control

MethodPathPurpose
GET/api/v1/resourcegroups/{groupId}/resourcesResources of a group
POST/api/v1/resourcegroups/{groupId}/resourcesCreate resource
GET/api/v1/resources/{id}Get resource
PATCH/api/v1/resources/{id}Partial update (null = unchanged)
DELETE/api/v1/resources/{id}Delete resource
POST/api/v1/resources/{id}/commandSend control command (slot derived from reservation)
POST/api/v1/resources/{id}/reserveReserve resource for a time window
GET/api/v1/resources/{id}/reservationsAll reservations of the resource
GET/api/v1/resources/{id}/reservations/mineOwn active reservations (slot preview)
DELETE/api/v1/reservations/{reservationId}Cancel reservation / release slot
POST/api/v1/resources/{id}/valuesIngest a telemetry reading
GET/api/v1/resources/{resourceId}/skillsSkills of a resource
POST/api/v1/resources/{resourceId}/skillsAssign skill

Projects, tasks & goals

MethodPathPurpose
POST/api/v1/projectsCreate project
GET/api/v1/projects/mineProjects I manage or am a member of
GET / PUT / DELETE/api/v1/projects/{id}Get / update / delete project
POST / DELETE/api/v1/projects/{id}/membersAdd / remove member
GET/api/v1/projects/{id}/tasksTasks on the project's blackboard
GET / POST/api/v1/projects/{id}/goalsList / create goals
GET/api/v1/projects/{id}/progressComputed goal-driven progress
POST/api/v1/projects/{projectId}/tasksCreate task
GET / PUT / DELETE/api/v1/tasks/{id}Get / update / delete task
POST/api/v1/tasks/{id}/assignAssign a PActor
PUT/api/v1/tasks/{id}/statusChange task status
PUT / DELETE/api/v1/tasks/{id}/goalAssign / unassign a goal

Time tracking & NFC

MethodPathPurpose
POST/api/v1/tasks/{id}/tracking/{start|stop|toggle}Start / stop / toggle time tracking
GET/api/v1/tasks/{id}/trackingAggregated tracked total (running span live)
GET/api/v1/tasks/{id}/tracking/sessionsIndividual tracked work sessions
GET / POST/api/v1/resources/{id}/nfc-unitsList / register NFC tags on a resource
PUT / DELETE/api/v1/nfc-units/{id}/task/{taskId}Bind / unbind a TIMETRACKER tag to a task
POST/api/v1/nfc/scan/{uuid}Process a tag scan (type-specific action)

Companies & departments

MethodPathPurpose
GET/api/v1/companies/{id}Get company
POST/api/v1/companies/filterFilter companies
PUT/api/v1/companies/{id}Update company
DELETE/api/v1/companies/{id}Delete company
POST/api/v1/companies/{companyId}/departmentsCreate department
GET/api/v1/companies/{companyId}/departmentsDepartments of a company
GET/api/v1/departments/{id}Get department
PUT/api/v1/departments/{id}Update department
DELETE/api/v1/departments/{id}Delete department

Users

MethodPathPurpose
GET/api/v1/users/{id}Get user
PUT/api/v1/users/{id}Update user
PATCH/api/v1/users/{id}Partial update
DELETE/api/v1/users/{id}Delete user
GET/api/v1/users/{userId}/skillsSkills of a user
POST/api/v1/users/{userId}/skillsAssign skill

Skills

MethodPathPurpose
GET / POST/api/v1/skillsList / create skills
GET / PUT / DELETE/api/v1/skills/{skillId}Get / update / delete skill
GET / POST/api/v1/skills/categoriesList / create categories
GET / PUT / DELETE/api/v1/skills/categories/{categoryId}Get / update / delete category

Documents

MethodPathPurpose
GET/api/v1/documents/download/{documentInfoId}Download document
GET/api/v1/documents/{id}/version/{versionNumber}Get a specific version
GET/api/v1/documents/{id}/historyVersion history
POST/api/v1/documents/{id}/check-outCheck out (lock)
POST/api/v1/documents/{id}/check-inCheck in (new version)
POST/api/v1/documents/{id}/cancel-check-outCancel check-out
GET/api/v1/documents/searchFull-text / metadata search
GET/api/v1/documents/recentRecently changed documents
DELETE/api/v1/documents/{id}Delete document
GET/api/v1/document-groups/{groupId}/documentsDocuments of a group
DELETE/api/v1/document-groups/{groupId}Delete group

Telemetry rules

MethodPathPurpose
GET / POST/api/v1/resources/{resourceId}/telemetry-rulesList / create rules for a resource
GET / PATCH / DELETE/api/v1/telemetry-rules/{id}Get / update / delete a rule

Task schedules

MethodPathPurpose
GET / POST/api/v1/projects/{projectId}/task-schedulesList / create schedules for a project
GET / PATCH / DELETE/api/v1/task-schedules/{id}Get / update / delete a schedule

Processes (Flowable)

MethodPathPurpose
GET/api/v1/process-definitionsList registered process definitions
POST/api/v1/documents/{documentInfoId}/process-definitionRegister a BPMN document as a definition
GET/api/v1/process-definitions/{id}Get a definition
POST/api/v1/process-definitions/{id}/activateActivate (deploy to the engine)
POST/api/v1/process-definitions/{id}/deactivateDeactivate
DELETE/api/v1/process-definitions/{id}Remove a definition (?force=true to also drop a deployment)
GET / POST/api/v1/projects/{projectId}/process-instancesList / start instances linked to a project
POST/api/v1/tasks/{taskId}/process-instancesStart an instance linked to a task
GET/api/v1/tasks/{taskId}/process-instanceThe instance linked to a task
GET/api/v1/process-instances/{id}Get an instance
POST/api/v1/process-instances/{id}/cancelCancel an instance (manager only)

API stability

From 1.0.0 onward the project follows semantic versioning: the version number communicates what a consumer can rely on.

  • The REST API under /api/v1 is the stable contract. These endpoints — their paths, methods, request/response shapes and documented status codes — are the public surface that external clients build against. Backward-incompatible changes to them will not happen within the 1.x line; they would come with a new major version (and, where practical, a new path prefix such as /api/v2). Additive changes (new endpoints, new optional fields) are minor releases and are safe to adopt. The authoritative, always-current description is the OpenAPI document served by the running application (see API documentation); the tables above are only an overview.

  • The Vaadin admin GUI and its routes are an implementation detail — not part of the contract. The @Route URLs (/process-definitions, /task-schedules, …) exist for the browser UI and may change at any time without a major bump. Do not script or link against them; drive automation through /api/v1 instead.

  • SkillProperty is experimental. The skill property model (typed key/value attributes on a skill) is exposed for early feedback and is marked EXPERIMENTAL in the OpenAPI schema. It has no admin GUI yet and its shape may change in a minor release without the usual stability guarantee. Everything else in the skill subsystem (skills, categories, assignments) is stable.

  • POST /api/v1/users creates users without a password, by design. In production the identity provider (Keycloak) owns credentials, so the local account is provisioned password-less; PUser.password carries @JsonIgnore and is never accepted or returned over REST. A REST-created user therefore cannot log in via Basic auth — that is intended for the Keycloak deployment model, not a missing feature. To create a login-capable local user (for a Basic-auth / development setup), use the admin GUI's user view, which has a password field. Just-in-time provisioning of local users on first Keycloak login is a planned post-1.0 addition.


Client libraries

Official REST clients build against the stable /api/v1 contract. They are spec-first: every API and model class is generated from the released OpenAPI document (see API documentation) and never hand-written, so the client version tracks the API version (a 1.x client targets the 1.x API).

LanguageRepositoryInstall
Javaprioritize-java-clientMaven Central: de.hallerweb:prioritize-java-client:1.3.1
PHPprioritize-php-clientComposer: composer require hallerweb/prioritize-php-client
Pythonprioritize-python-clientPyPI: pip install prioritize-client
TypeScriptprioritize-typescript-clientnpm: npm install prioritize-client
<dependency>
    <groupId>de.hallerweb</groupId>
    <artifactId>prioritize-java-client</artifactId>
    <version>1.3.1</version>
</dependency>

All four support HTTP Basic (default profile) and Bearer/Keycloak authentication.

Java only: build the client with new PrioritizeApiClient(), not new ApiClient(). The generated client transports over HttpURLConnection, which rejects PATCH, so every partial-update call would fail before leaving the JVM. PrioritizeApiClient is the same client on a PATCH-capable transport.


Error semantics (HTTP status)

Centralized in GlobalExceptionHandler:

StatusTrigger
400 Bad RequestIllegalArgumentException (e.g. invalid date format, end before start date); HttpMessageNotReadableException (malformed / unreadable request body)
403 ForbiddenAccessDeniedException (missing permission)
404 Not FoundNoSuchElementException / EntityNotFoundException
409 ConflictIllegalStateException, SlotNotReservedException (no / ambiguous active reservation), SlotOccupiedException (slot already taken), DataIntegrityViolationException (constraint violation)
500 Internal Server ErrorIncorrectResultSizeDataAccessException and any otherwise unmapped exception
502 Bad GatewayResourceCommandFailedException (device rejected the command)
503 Service UnavailableResourceOfflineException (no reachable control channel)

Domain model (brief overview)

  • Company / Department — organizational base structure. The hierarchy provides a foundation for tenant separation, but enforced multi-tenant isolation is not yet implemented (projects are membership-scoped, admin is a global superuser).
  • PUser / Role / PermissionRecord — users, roles, and the fine-grained permission model.
  • Resource — a device / resource; can represent an IoT device and communicate externally (REST/MQTT). Has slots and reservations.
  • ResourceReservation — time-bound occupancy of a resource slot by a user.
  • Document / DocumentInfo / DocumentGroup — documents with versioning and check-in/check-out.
  • Skill / SkillCategory / SkillRecord — capabilities; assignable to users and resources.
  • Project / Blackboard / Task / ProjectGoal — projects own a blackboard of tasks; goals drive computed progress; a task's assignee/manager is a PActor.
  • NfcUnit — a physical NFC tag mounted on a resource; a scan triggers a type-specific action (e.g. toggling a task's time tracking).
  • TelemetryRule — a per-resource rule turning numeric readings into a persisted OK/ALARM state with hysteresis.
  • TaskSchedule — a cron-driven schedule that fires a task template onto a project's blackboard.
  • ProcessDefinition / ProcessInstance — a registered, explicitly activated BPMN definition and its running orchestration, linked generically to a project or task.
  • Address — embedded value object, managed exclusively through its owners (Company, Department, PUser).

🗂️  Class diagrams per subsystem  (click to expand)

Each subsystem is documented as a focused UML class diagram — curated, entities only, with cross-package neighbours shown as «external» for context. PlantUML sources live under docs/diagrams/uml; the rendered PNGs under docs/diagrams/images.

Organization & security

Companies & departments

Company and Department subsystem class diagram

Users, roles & permissions

Security subsystem class diagram

Content & knowledge

Documents (versioning, check-in/out)

Document subsystem class diagram

Skills (for people and devices)

Skill subsystem class diagram

Work & operations

Projects, blackboards, tasks & goals

Project subsystem class diagram

Resources, reservations & control

Resource subsystem class diagram

Time spans (reservations & time tracking)

Calendar subsystem class diagram

Platform, IoT & automation

Telemetry monitoring rules

Telemetry subsystem class diagram

Recurring task schedules

Scheduling subsystem class diagram

BPMN process orchestration (Flowable)

Process subsystem class diagram

NFC tags as physical triggers

NFC subsystem class diagram


Contributing

Pull requests are welcome. For major changes, please open an issue first to discuss the direction.

See CONTRIBUTING.md for how to build, test and submit changes (branch model, commit conventions, code style, API-stability rules). Please also read our Code of Conduct. For security issues, follow the Security Policy — do not open a public issue.

License

Apache License 2.0 — see LICENSE. Source files carry the corresponding Apache 2.0 headers.

Contributors

phaller222

315 commits

Languages

Java

99.8%