Nilado
Back to case studies

Enterprise / Public Sector

DMS-IS — Contract & Procurement Management

One system for contracts, framework agreements, suppliers, requisitions and reporting — built multi-tenant from the first migration, with data isolation enforced by the database itself rather than by developer discipline.

0

report areas, every one exportable to Excel

0%

tenant isolation enforced in the database

0h

worst-case data loss, with restore-tested backups

Analytics — contracts overview
The analytics overview: every number is computed from live contract and delivery records, not entered by hand.
01

The problem

Procurement teams were running on spreadsheets, shared folders and paper trails. A contract lived in one file, its amendments in another, a supplier complaint in an email thread — and nobody could say, without an afternoon of digging, which contracts were about to expire or how much of a contract's value had actually been delivered.

Three things had to change: one source of truth for the whole contract lifecycle; delivery numbers tied to real invoices instead of estimates; and a hard guarantee that one organization's data can never surface in another's, because the same platform would serve several organizations.

02

What we built

Six workspaces that share one data model, so a supplier, a contract and a complaint are never copies of each other — they're linked records.

Contracts

Full lifecycle with line items, delivery tracking, amendments, complaints and documents on one record. Status — active, expiring soon, expired, fulfilled — is derived, never typed in.

Framework agreements

Umbrella agreements with a maximum value and duration. Contracts are raised from them and inherit the supplier.

Suppliers

A directory with tax-ID validation, a reputation flag, and a profile that rolls up every contract with that supplier.

Complaints & court disputes

Quality and quantity complaints with contractual sanctions, and disputes tracked by court and status — each linked back to its contract.

Requisitions

A role-based ordering workflow against successive-delivery contracts, from draft to recorded delivery.

Analytics

Charts, KPI cards and the table behind every chart, with one-click Excel export.

Contracts
The contract register: server-side status filters, search, and delivery progress for every contract.
03

Contracts, tracked to the last invoice

A contract isn't a PDF — it's a promise being delivered against. Every contract carries its line items: what was ordered, in which unit, how much has shipped. Every delivery is logged as an activity with quantity, value, invoice number, invoice date and payment date, and the invoice itself attached.

Completion is calculated from those records on the server, per row. The progress bar in the register and the 100% on a contract page are the same number — and because nobody types it in, it can't drift from reality.

Contract — overview
One record, six tabs: overview, line items, deliveries, amendments, complaints and documents.
Contract — deliveries
Each delivery carries its invoice number, invoice date and payment date, with the document attached.
04

Framework agreements and the suppliers behind them

Framework agreements sit above contracts. Raise a contract from an agreement and the supplier is pre-filled and locked — the form says so — so a call-off can't quietly drift to a different vendor.

Supplier KPIs — contracts concluded, contracts fulfilled, value of fulfilled work — come from a dedicated aggregate query, not from the page of rows on screen. A paginated list would otherwise understate them, and the whole point of the card is to be right.

Framework agreement
Every contract concluded under an agreement, with its status and delivery progress.
Supplier profile
A supplier profile: lifetime KPIs on top, every contract beneath.
05

A paper trail for when things go wrong

Quality and quantity complaints and court disputes used to be the least visible part of a contract's life. Here they're first-class records, each linked to the contract it concerns.

Complaints get a “contractual sanctions” view that swaps the status column for the consequence — and the consequence field itself only appears while a complaint is unresolved. Court disputes are tracked by competent court and status: in progress, or finally resolved.

Complaints
Complaints raised during delivery, linked to their contract.
Court disputes
Court disputes by status, with the contract one click away.
06

Requisitions: an approval workflow with rules

Requisitions are the most intricate module: organizational units order against successive-delivery contracts, a verifier approves, and headquarters records the delivery. Each step is gated by role, and each rule is enforced twice — the interface hides what you can't do, and the API refuses it independently.

  • Each requisition snapshots the submitter's organizational unit at creation, so reorganizing the org chart never rewrites history.
  • Only successive-delivery contracts can be ordered against. The form filters them out for convenience; the API rejects the rest on its own.
  • An evidence view rolls requisitions up by organizational unit and unit of measure — kilograms and pieces are never added together.
  1. 01

    Draft

    Editable by its author

  2. 02

    Submitted

    Waiting for a verifier

  3. 03

    Accepted

    Approved, optional comment

  4. 04

    Delivered

    Headquarters records the delivery

A verifier can also reject (final) or return for correction (back to editable and resubmittable). Both require a comment.
07

Analytics that answer real questions

Six report areas — contracts, framework agreements, requisitions, complaints, court disputes and suppliers — each with KPI cards, charts and the table behind them. Contracts alone have eight views: overview, by year, by type, by value, by goods category, by place of delivery, quality and quantity, and by execution method.

Every report exports to Excel from exactly the data on screen, formatted in local number conventions. “Realized” means delivered in full; the quality and quantity view deliberately looks at all contracts, and ranks suppliers by how many quality complaints they've drawn.

Analytics — by type
Goods, services and works: count, share and value.
Analytics — quantity
Fully, partially and not-yet-delivered contracts at a glance.
Analytics — suppliers
Supplier analytics: registered, active and new suppliers, and the top ten by contract value.
08

Architecture

A Spring Boot 4 API on Java 21 behind a Next.js 16 / React 19 frontend. Keycloak handles sign-in and carries the tenant in the user's token; the API validates every token by introspection. PostgreSQL holds the data under 11 Flyway migrations, MinIO holds the documents, and Caddy terminates TLS in front.

  1. 01

    Next.js frontend

    React 19 and TanStack Query, with a server-rendered first paint

  2. 02

    Spring Boot API

    Java 21, role-checked endpoints, token introspection

  3. 03

    PostgreSQL

    Row-Level Security on every tenant table

Alongside

  • Keycloak

    Single sign-on, roles and the tenant claim

  • MinIO

    Contract, supplier and dispute documents

  • Caddy

    Automatic TLS in front of the app and login

Only the frontend and the login service are reachable from outside; the API, database and storage live on the internal Docker network.
09

Isolation that fails loudly, not silently

In a multi-tenant system, one customer's data must never leak into another's. Instead of trusting every query to remember its tenant filter, isolation lives in the database: each time the API borrows a connection, the tenant from the user's token is stamped onto it, and PostgreSQL's Row-Level Security policies then filter every query on that connection — and fill in the tenant on every insert. Application code never carries the tenant at all.

The API connects as a restricted role that cannot bypass those policies; schema changes run under a separate, privileged role. And on startup the app scans the schema and refuses to boot if any tenant-scoped table is missing its policy — so a forgotten safeguard becomes a failed deploy, not a data leak discovered months later.

10

Running fully offline

One requirement broke the cloud playbook: the client needed to carry the whole system on a USB stick and run it on a single Windows machine with no internet connection, for a formal registration process.

Rather than fork the architecture, we packaged it. A script exports the five container images, a full copy of the repository and the configuration into one folder; a launcher script then brings up the identical stack on localhost. The only prerequisite is Docker Desktop, installed once.

Building it surfaced two real bugs that we fixed in the main codebase: Docker Compose derived image names from the folder name, so a copied package tried to rebuild from source — which needs the internet — and the demo seed data had quietly fallen behind recent schema migrations, so it failed on any fresh database.

11

Operations that don't depend on heroics

  • Push to main deploys. GitHub Actions connects over SSH with a pinned host key — and fails the deploy if the server's identity ever changes — rebuilds only what changed, then checks the live site and the login service from outside.
  • Encrypted, deduplicated backups every night — databases, identity store and documents — to off-site storage, with seven daily, four weekly and six monthly restore points. A full test-restore into throwaway containers matched production before we called it done.
  • Tuned for a small server: capped JVM heaps, a smaller connection pool, request throttling and swap, all verified under load with container stats.
  • A cloud firewall, a host firewall and fail2ban sit in front of an automatic-TLS reverse proxy. Development-only ports don't exist in production, because the production deploy excludes the override file that opens them.
12

Results

Live on its own production infrastructure since July 2026, with automated deploys, nightly restore-tested backups, and a portable offline edition running the identical application.

For the people using it, the answer to “which contracts expire soon, and how much has actually been delivered?” went from an afternoon of spreadsheets to a filter and a progress bar.

Product tour

More from the product

A few more screens from the interface. Click any image to enlarge it.

Sign-in on Keycloak

Single sign-on with a custom-themed login. The tenant travels in the user's token from this point on.

All data shown is seeded demo data.