DMN/Software

ProjectsTime tracking

Time tracking

Working time, leave, flextime and invoices for small businesses and freelancers, following the German Working Hours Act, the Federal Leave Act and § 14 of the VAT Act. Every figure can be expanded to show what it is made of.

Week view in the made-up sample company, calendar week 40: one bar per day from 6 a.m. to 10 p.m. with clock-in, clock-out, break, actual, target and difference. Thursday is expanded and shows the bookings, the calculation actual 7:56 hours minus target 7:48 hours equals plus 8 minutes, and a finding under section 5 of the Working Hours Act: rest period only 9:34 hours instead of 11
An employee's week in the demo, here with the German interface. An expanded day shows bookings, calculation and findings.

Key facts

As of

Tests
1703 unit tests, 1000 integration tests against a real PostgreSQL, 20 end-to-end runs on desktop and mobile
E-invoicing
KoSIT validator accepted 16 of 16 files, veraPDF passed 7 of 7
Operations
live and demo since 2 October 2026, two separate instances on a self-managed server

Every figure shows what it is made of

Ten days of remaining leave only become believable once you can see how they came about: which entitlement from which contract, which requests, which cancellation. The same holds for the day's time in the screenshot above, the flextime account and an invoice total.

The business rules live in a package of their own, without a database and without a clock. Each rule returns its reasoning alongside the result, and the interface only turns it into sentences.

Labour law as a finding, not a lock

Maximum working time, breaks, rest periods and Sunday work under sections 3, 4, 5 and 9 of the Working Hours Act are checked and shown as a finding on the day. The booking still goes through, because the time was worked. The rules are versioned, and every finding names the version it came from.

Leave follows the Federal Leave Act: waiting period, partial leave in the years of joining and leaving, carry-over until 31 March, and expiry only after a documented notice to the employee. Public holidays cover all 16 federal states.

Anyone who wants to change a time files a request with a reason. HR can correct immediately, but only with a mandatory reason, an audit entry and a mail to the person concerned. At month end a close with a checklist freezes the state.

Month-end close for September 2026 in the HR role: a checklist with the blocking item open absence requests and a notice about unacknowledged Working Hours Act findings. The button to close the month stays disabled until the blocking items are resolved
Month-end close in the HR role, German interface. An open request blocks, a notice does not.

Invoices under § 14 UStG

Tracked project time turns into an invoice with a PDF laid out to DIN 5008, in German or English depending on the client. It supports standard taxation, small businesses under § 19, EU reverse charge, third countries and small-amount invoices, plus e-invoices as XRechnung or ZUGFeRD.

Invoice numbers run without gaps per year. A Postgres sequence would burn numbers on a rollback, so a counter row does the counting and stays locked until the transaction ends. A test issues 20 invoices at once and expects every number from 1 to 20 exactly once. Issued invoices never change, corrections go through a cancellation.

Issued specimen invoice MUSTER-2026-0009 from a made-up freelancer to the made-up Stadtwerke Lindental: on the left date, due date, net amount, 19 percent VAT and invoice total, below it two line items from tracked hours, on the right the PDF preview with address field, info block, line item table and the specimen watermark
An invoice issued as XRechnung in the freelancer workspace. In the demo invoices carry a watermark and a number starting with MUSTER-.

What the database guarantees itself

Two bookings of the same person must not overlap. The API does not check that, an exclusion constraint in Postgres does, even for two requests at the same moment.

Several businesses share one database. Each request sets business, role and person for its own transaction only, and row level security with FORCE then filters every table. Without that context the database returns no row at all, not even to the owner of the tables.

Excerpts from the real code, shortened.

Real code from the time tracking

db/migrations/20260924000100_time.sql

alter table app.time_entries  add constraint time_entries_no_overlap  exclude using gist (    employee_id with =,    tstzrange(started_at, ended_at, '[)') with &&  )  where (voided_at is null);

packages/rules/src/arbzg/index.ts

const { version, limits } = ruleSetFor(shift.date);const flag = (code, severity, facts = {}) => {  findings.push({ code, severity, date: shift.date,    ruleVersion: version, facts });};if (restMinutes !== null && restMinutes < limits.minRestMinutes) {  flag('ARBZG_5_REST_UNDER_11H', 'warning', { restMinutes });}

apps/api/src/db/database.ts

withTenant<T>(ctx: TenantContext, work: (trx: Trx) => Promise<T>): Promise<T> {  return this.transaction().execute(async (trx) => {    await sql`select set_config('app.tenant_id', ${ctx.tenantId}, true),      set_config('app.user_id', ${ctx.userId ?? ''}, true),      set_config('app.role', ${ctx.role}, true),      …`.execute(trx);    return work(trx);  });}

db/migrations/20260926000200_invoices.sql

insert into app.invoice_counters as c  (tenant_id, year, last_seq)values (app.tenant_id(), year_num, 1)on conflict (tenant_id, year)  do update set last_seq = c.last_seq + 1returning c.last_seq into seq;

A demo for every visit

One click on “Start demo” creates a sample company of your own: a made-up tax office in North Rhine-Westphalia with twelve people in three teams, bookings since January, open requests and findings under the Working Hours Act. Next to it sits the workspace of a made-up freelancer with clients, projects and invoices.

Security

Two tests hold the permissions together: one reads every API route and compares it with a table of minimum roles. The other checks that every table tied to a business is covered by row level security. A new route or table without an entry fails the test.

Technology

Frontend
React 19 with Vite, TanStack Router and Query, React Aria, i18next for German and English
Backend
NestJS 11 with Kysely and zod, PDF with pdfkit in worker threads
Data
PostgreSQL 17 with row level security, migrations with dbmate
Tests
Vitest with fast-check, Testcontainers, Playwright with axe, KoSIT validator and veraPDF
Operations
live and demo as separate Docker Compose projects behind Caddy, encrypted backups with age

Limits

The Working Hours Act findings are not legal advice. Deliberately left out: shift planning, clocking in by GPS or terminal, automatic break deduction, dunning and a DATEV interface. Still open are answers from a tax adviser on rounding and invoice wording, and a green CI run on GitHub. So far the checks run locally.

Try it

No account needed. Choose “Start demo” on the start page, the language can be switched at the top.