[ Case study ]

[ 10 ]

← All workFull-stack reference build, solo

OpsPortal.

B2B operations platform for service businesses with recurring work — contracts, scheduling, performance tracking, and document control in one auditable system.

Year
2025
Duration
3 months
Stack
Laravel 11 · Filament 3 · PostgreSQL · Redis
OpsPortal hero image
Brief

Why services businesses outgrow spreadsheets

There's a moment most service-business operators recognise. You start with three contracts and a Google Sheet. By the second year you have thirty contracts, four people, recurring inspections every month, photo evidence in WhatsApp threads, signatures in PDF email attachments, and an Excel file someone updates "when they remember." Then a client asks for an audit trail of last year's deliveries and you realise you don't actually have one.

OpsPortal is a reference build for that wall. Not a SaaS I'm selling — a demonstration of the surface area an operations platform needs to cover when the spreadsheet finally breaks. Contract lifecycle, recurring schedules, photo-documented performances, document control, role-based access, audit log. The unsexy infrastructure that quietly runs services businesses once they cross a certain size.

Approach

Five modules that earn their place

ops-portal-production.up.railway.app/

OpsPortal public landing — hero, feature cards, Sign In and Create Account

Public landing for the portal — three-feature card row (Contract Management, Calendar & Scheduling, Performance Tracking), Sign In and Create Account paths. Registration is admin-approved by default; invite-based registrations are auto-approved.

ops-portal-production.up.railway.app/admin

OpsPortal admin dashboard — KPI cards, upcoming schedule, active contracts

Operator landing after login. Four KPI cards top — active contracts (with total portfolio value), expiring soon, events this week, pending review. Upcoming schedule on the left with type and priority chips. Active contracts on the right with elapsed-progress bars and end dates. The dashboard tells the operator what to look at next, not what happened last quarter.

Contract management. Seven statuses (draft, pending approval, active, paused, completed, cancelled, expired). Auto-numbered (OPS-YYYY-NNN). Multi-party contracts — customer, supplier, subcontractor — each with its own role and contact person. Status changes are written to a dedicated audit table, so "who approved this contract on which date" has a real answer.

ops-portal-production.up.railway.app/admin/contracts

OpsPortal contracts table — filters, status and priority badges, search

Contracts table with tabs for All / Customers / Suppliers / Active / Expiring Soon. Per-row status badge, priority badge (Critical, High, Normal), responsible person, contract type (1-year through 5-year). Search and filter on top, bulk actions per row. Real B2B users want this view, not a Kanban board.

Document control with versioning. Documents attach to contracts. Categories include contracts, revisions (electrical, gas/pressure, lifting equipment, fire safety), certificates, other. Version history per file. Per-user and per-organisation permissions — view, download, edit, delete. Every document access is logged with IP address. Digital signature capture in-browser via SignaturePad.

Calendar & scheduling. FullCalendar with RRULE recurrence — yearly, monthly, weekly inspection patterns. Six event types: audit, monthly fulfillment, delivery, milestone, maintenance, revision. Urgency colours based on days until execution. Per-event organisation, equipment, operating location fields. 24-hour email reminders before scheduled events.

Performance tracking. Plan vs actual delivery records linked to events. Five-step workflow: planned, in progress, completed, approved, rejected. Photo evidence upload, in-browser signature capture for approval. Out-of-scope work flagged with reason. Cost variance computed automatically.

Reports. Seven configurable reports with date-range filtering and Excel export — contracts overview, expiring contracts, performance summary, financial overview, events calendar, documents by category, organisation activity.

Boring stack, fast to ship

Backend       Laravel 11 · PHP 8.3 · FrankenPHP runtime
Admin UI      Filament 3 · Livewire 3 · Alpine.js
Frontend      Tailwind CSS · FullCalendar · SignaturePad
Database      PostgreSQL 15 · 39 tables · soft deletes everywhere critical
Cache/Queue   Redis 7
Auth          Spatie Permission (4 roles · 45 permissions)
Audit         Spatie Activity Log (every model change · who · when · before/after)
File handling Spatie Media Library
Email         Resend API
Search        OCR text extraction via Tesseract for documents
Build         Composer · npm · Vite
Deploy        Railway · Docker · auto-migrate on startup

Three decisions worth calling out:

1. Laravel + Filament instead of Next.js. A counterpart project in this portfolio (OmniShop) is built on Next.js. So why Laravel here? Because the job is different. OmniShop is a customer-facing storefront with SEO weight and a separate admin. OpsPortal is 90% admin — twenty-two domain models, forty-five permissions, role-based access, audit trail, file uploads, signatures, calendar, recurring events, Excel export. Filament gives all of that as primitives. Building it from scratch in Next.js would have meant six months of infrastructure before any business logic ships. With Filament, three months of work goes into the domain — contract workflow, performance approval, document permissions — and the framework handles the rest.

The right architectural decision is the one that lets you ship the domain faster, not the one that scores best in a Twitter thread.

2. PostgreSQL with full-text search, not Elasticsearch. The system has full-text search across contracts and documents. PostgreSQL handles this natively up to surprisingly large datasets. Adding Elasticsearch would have meant another service to run, another index to keep in sync, and another failure mode to monitor — for a feature that PostgreSQL FTS handles in single-digit milliseconds at the volumes services businesses actually have. If a deployment grows past that, swapping in is straightforward. Premature optimisation is rarely premature; usually it's just optimisation in the wrong direction.

3. Audit trail as a first-class concern, not an afterthought. Spatie Activity Log records every model change (who, when, old value vs new value) for the lifetime of the record. Document access logs include IP address. Contract status changes have a dedicated table. The audit trail is read-only in the admin (/admin/audit-log) and exists because services businesses get audited — by clients, by certifying bodies, by tax authorities. "I think we did that, let me check Slack" is not an answer. The system has to remember.

OpsPortal admin dashboard on an iMac in a bright workspace — desk mockup
Operator view on a real desk — same KPI and schedule patterns as in the cropped screens above, readable under daylight.

Four roles, scoped to what they actually do

RoleWhat they seeWhat they can do
AdministratorEverythingFull CRUD on every module
CustomerTheir organisation's contracts, documents, performancesCreate, edit, approve, run reports
SupplierContracts they're a party to, their own performancesView contracts, submit performances, comment
AuditorRead-only across the systemView only · time-restricted (8:00–16:00 access)

Forty-five granular permissions powered by Spatie Permission. Organisation-level data isolation: a customer sees only their contracts, not the rest of the portfolio. The auditor role exists because real B2B engagements include third-party verification, and "give the auditor a temporary admin account and hope" is how breaches happen.

Outcome
22
Domain models
45
Permissions
4
Roles
7
Contract statuses

What this demonstrates

For prospects evaluating B2B operations work, this build covers:

Domain modeling discipline. Twenty-two models with proper relationships, soft deletes on critical tables, polymorphic comments across contracts/documents/performances, full audit trail.

Access control depth. Four roles, forty-five permissions, organisation-level data isolation, time-restricted access for auditors. Not "we have an admin and a regular user."

Lifecycle management. Seven contract statuses with auditable transitions, five-step performance workflow with approval, document versioning with per-user permissions.

Operational realities. RRULE-based recurring events, 24-hour reminders, OCR document search, Excel export with branded styling, in-browser signatures, photo evidence capture.

Stack judgment. Laravel + Filament chosen for admin-heavy work where it earns three months. PostgreSQL FTS instead of Elasticsearch. Spatie packages instead of custom RBAC.

The codebase is seed-data only. If you brief me with a real services-business problem, I'd start from a Discovery Sprint to understand the domain — contract types you write, the regulatory regime you operate in, the integrations that matter (accounting, CRM, field tools) — not by lifting this scaffold.

1/ 3slots open · Q2 2026
Booking open nowlive times on /book
Solo operator · Custom work

Brief me on something similar

B2B operations platforms, contract lifecycle systems, recurring-service scheduling, role-based dashboards for services businesses — this is core territory. Send a brief or book a 30-min call. Discovery Sprint first, fixed-price build after.

Send a brief
mail@nkovalcin.comReply within 1 working day