V·NESTERENKO
← All work
Lead Developer · 2023–25 · 2 years

Subcontractor Time-Tracking & Accounting Platform

Client confidential – details anonymized.

A staffing provider was tracking subcontractor hours in one place, calculating leave somewhere else, and producing client invoices and payroll from a third. The numbers disagreed often enough that people checked them by hand, which is the expensive way to find out your systems disagree.

The answer was not a new product. It was self-hosted Redmine, extended hard, with the time-tracking and ERP plugins customised until one system held hours, leave, invoicing, and payroll together. Buying and bending something proven beat building a bespoke platform for a company whose business is staffing, not software.

I worked on it for two years as the primary author and lead developer.

Where the complexity actually lives

Very little of this is technically exotic and all of it is detailed:

Billable and non-billable are not a flag. Activity types decide whether an hour reaches a client invoice, and the rules differ between the client-facing attendance report and the internal payroll one. Much of the work was making sure the same hour appeared correctly in both, or in neither.

Leave accrues from the work. Creating a worktime entry updates the leave bucket, so balances follow from recorded hours rather than being maintained alongside them and drifting.

Invoices outgrew the URL. Invoice generation was a GET request until the parameters – date ranges, user sets, project filters – exceeded what a URL will carry. It became a POST, which sounds trivial and was a genuine ceiling.

Salary data leaked to the wrong group. A condition in the payroll report was inverted, showing admin-only salary figures to anyone with report access rather than restricting them to superadmins. One-line fix, and exactly the kind of bug that argues for taking permission checks seriously in a system holding pay data.

Infrastructure

It runs on AWS ECS, and I moved the whole environment into Terraform – 41 resources across roughly 1,200 lines: VPC, subnets, routing and security groups, ECS and ECR, an application load balancer with TLS, RDS PostgreSQL, service discovery, autoscaling policies, and CloudWatch log groups with seven metric alarms on database CPU and free memory.

That work is the difference between infrastructure you can rebuild and infrastructure you hope keeps running. Before it, the environment existed as a sequence of console clicks somebody had done once.

There is also a small Node.js service alongside Redmine for the integrations that did not belong inside a Rails plugin.

Why it is on this page

Every other project here ran two to twelve months. This one ran two years, and most of that was keeping something in production correct while it stayed in daily use for billing. It is the work I would point at if you want to know what I am like to have around after launch.

Next project →