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.

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.

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.

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.
- Switch roles without a password: employee, team lead, HR, administration, freelancer
- Mails do not go out, they land in an outbox inside the demo
- Invoices carry a watermark and a MUSTER- number, depending on the tenant kind, not on a switch
- The sample company is deleted after 24 hours, together with everything entered
- A limited number of demos per connection and in total
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.
- Passwords with argon2id and a pepper
- Passkeys, TOTP and recovery codes
- Fresh confirmation before issuing or cancelling an invoice
- Session cookie with the __Host- prefix and a CSRF token
- Content security policy without unsafe-inline
- Threat model for the invoicing part, every finding backed by a 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
- Demo zeit-demo.dmn-software.com
- Live zeit.dmn-software.com, by invitation only
No account needed. Choose “Start demo” on the start page, the language can be switched at the top.