An internal ERP for running a team
An IT company's internal product
Close the month
was 18–37 hours
1–2 hours
Two to four full working days a month come back to HR and finance — for work nobody else will do for them
Request a vacation
was chat threads and reminders
30 seconds
The employee answers a Telegram bot, the manager taps one button. No login, no training — for any of the thirty
Enter the data
was four places to type it in
Once
From there the request reaches the team calendar, the vacation balance and the payroll sheet on its own. Discrepancies have nowhere to come from
Find a disputed line
was after the payout
Before the payout
Disputed amounts show up in the draft, and any of them unfolds down to the formula with the numbers filled in — while the money is still in place
A note on the data
The system is internal and access to its production data is closed: real data is not disclosed. The screens and examples in this case study use a demonstration dataset for a company of the same size: 30+ employees, 6 teams. Amounts, names, team composition and vacation balances on the screens are illustrative, and none of that changes the mechanics: the demonstration data goes through the same calculation rules as production data. The dataset is deterministic — loading it again produces the same numbers. The interface on the screens is in English.
Context
The task
What was needed was a system where a request is entered once — and the calendar, the vacation balance and the amount on the payroll sheet recalculate themselves and stay in the history. A company of 30+ people outgrew chat threads and spreadsheets: the processes existed but were written down nowhere, the rules lived in the HR manager’s head, the data lay in five unconnected places.
- Vacation and time-off requests arrived as private messages. A week later neither the manager nor the employee could reconstruct what had been agreed.
- The vacation balance was counted by hand. On termination that regularly turned into a dispute.
- Hours from the trackers were copied into a spreadsheet — one or two days of work a month, plus transfer errors.
- The payroll sheet was assembled in a spreadsheet: no change history, with formulas that broke whenever someone was hired or left mid-month.
- Public holidays, sick leave and overtime were tracked from memory. Access to payout data rested on trust.
A setup like that works until the first argument about severance pay, until a payout error spotted a quarter later, and until the HR manager goes on vacation and it turns out there is nobody left to close the month.
Approach
The solution
Not a single day of training was needed. The employee lives in a messenger that is already open: a request takes half a minute, no login and no manual required. The manager approves with one button straight from the notification. Only HR and administrators have to learn the interface — the people who work in the panel every day anyway.
The heart of the architecture is no double entry. A single approval updates the team calendar, the payroll sheet and the vacation balance by itself, and the employee gets the answer in the same chat where they asked.
Inside the system: employees and team structure, absences, vacation day balance, time tracking and payroll calculation. Each of these areas gets its own section below.
- The employee sends a request to the Telegram bot
- The request appears in the employer's panel at once
- The employer approves with one button
- The employee gets the answer in the same chat
- Calendar, payroll sheet and balance updated themselves
This will use 3 days, leaving 13. 13:02
The event has been added to the team calendar. 13:04
The event has appeared in the shared calendar and will disappear if the request is cancelled.
Days are drawn from the oldest balance first, so that none of them expire.
Between the request and a closed payroll sheet there is one tap. HR does not copy the vacation into the calendar or into the sheet: it happens on its own. The history keeps who approved it and when.
People
Employees and team structure
The IT department is split into teams: Leadership & PMO, Platform, Web, Mobile, QA and DevOps. Each has its own lead — the approval route for requests and the addressing of notifications are built from that.
Five contract types and four pay models work in the system at the same time.
Teams and leads
Contract types
Pay terms
Hiring and termination
The employee list with filters by status and team.
Absences
Vacation, sick leave, time off
Three kinds of absence with different rules — and that matters, because merging them into a single "request" loses the meaning of each one.
All three are visible in the employee card together with the day balance and the history.
Vacation
Sick leave
Time off
Balance, accruals and usage for the year, broken down by year with expiry dates.
Balance
Vacation day accounting: accrual and write-off
The vacation balance is kept as a ledger of movements, the way accounting does it. Every change to the balance is a separate entry with a date, a source and an author. Previously the balance was a single field in the employee card and was not calculated anywhere. Keeping the balance as a single number is a classic architectural trap of B2B systems: a dispute on termination needs the history, not a number. The base norm is 28 calendar days.
- Accrual — one twelfth of the annual quota for each month that has passed; the cron job runs on the first day. Pro-rating by days applies only to the year of hire; absences do not reduce the accrual. Naive rounding would give 27.96 instead of 28 over a year, so each month accrues the difference between accumulated rounded values: the yearly total matches the quota exactly.
- Write-off is FIFO. The rule was chosen in the employee's favour: the oldest days are spent first, so a carried-over balance is used before a fresh accrual and less of it expires.
- Carry-over and expiry — separate ledger entries: an unused balance is carried over for a year and then expires, once a year on schedule.
- Adjustment — an entry with a mandatory comment and author, available under a separate permission. The balance never goes negative: the check sits both in the form and in the service.
- Vacation beyond the balance does not block the request: the days are split into a paid and an unpaid part — the paid ones are written off from the ledger, the unpaid ones reduce the base without compensation.
Integrations
Time tracking
The system does not make the team start tracking again in a new interface — it takes the data from where people already work and brings it to a single shape.
The source is assigned to each employee individually.
Hubstaff — time and activity
Worksection — projects and tasks
Google Calendar — the team calendar
Telegram and REST API
Scheduled synchronisation
Hours linked to projects with an activity percentage; the time of the last synchronisation is visible.
Money
Payroll calculation
A payroll sheet is created in one action. The system builds the roster from all active employees, pulls hours from the trackers, approved absences, public holidays and recurring additions — and calculates every line.
The sheet goes through a cycle: roster build → draft → approved → paid. After approval the amounts are frozen: later changes in hours and absences no longer rewrite a closed month. This is the property such systems are built for — history cannot be rewritten after the fact.
Trust
Transparency: any amount can be taken apart
The main question asked of any payroll system is "where did this number come from". Here the system answers it itself: every line unfolds down to the formula with the numbers filled in — the base for days worked, holiday compensation, additions, the productivity coefficient, the reduction factor, the gross total and the amount to pay.
The full calculation can be opened up to the employee. The same calculation is exported to PDF for the finance team.
Checks
Data quality control
The system flags the lines that must not be closed without a second look: zero hours for an hourly employee, unapproved time off inside the period, a missing exchange rate, a fixed payment when there are absences. Each flag explains what exactly is wrong and how it affected the amount.
Errors are visible in the draft, before the period is closed. That is an order of magnitude cheaper.
Payouts
Bonuses, payouts and compensations
Everything that affects the final amount lives inside the system and enters the calculation automatically — no separate bonus spreadsheets for HR to reconcile by hand afterwards.
It is set once in the employee card and applies to every following period.
Recurring additions
One-off bonuses and compensations
Deductions
Absence compensation
Overtime and currency
Additions with validity periods and an active toggle.
Analytics
Reports on an employee
A manager needs a picture of one particular person. A single page collects average hours per day, total time worked, activity percentage, days of vacation, sick leave and time off, the productivity index, active additions and their effect on the payroll fund, the dynamics of hours and the payout history.
Separately — a counter of days until the contract ends: there is still time to find a replacement.
Timeline
Timeline and development rhythm
The actual development time is about 80 person-hours. That is roughly two working weeks of one developer: it covers people and absences, time tracking, payroll calculation, integrations and the vacation balance together.
The active phase was April and May: about 60 hours, three quarters of the whole time. Everything that counts money fits into it: the payroll calculation core, the pay rules, the second source of hours and the external integrations. Before that — roughly 20 hours on the skeleton, people, permissions, the API and time tracking: on 25 April 2026 live accounts appeared in the panel and the company stopped keeping its people records in spreadsheets. After that — targeted work on a product already in use.
The figures are taken from the repository history: commits are grouped into working sessions by the intervals between them, so the hours are a lower bound, not a time tracker export.
Modules arrived as they became ready, not as one release at the end. Merged pull requests fell in April, May and July, and the deployment on the main branch was started manually every time. You can work this way when a rollback to the previous version costs one command and every branch runs a style → analysis → tests pipeline, three steps in parallel.
At launch the team got not a stub but a working loop — people, absences and time tracking, — and used it while the pay rules, the integrations and the vacation balance were still being written. The usual order for us: first what can be used tomorrow, then what counts money. Not a single day was spent waiting for “everything to be ready”.
Effect
Commercial effect: hours and money
The system pays for itself in hours that are no longer spent and in errors that no longer reach the payout. The hours are simple to count: below is the work that used to be done by hand every month, and what is left of it. Errors are harder to count, but a single underpayment surfacing a quarter later costs more than a whole month of saved time.
| What used to be done by hand every month | Before | Now |
|---|---|---|
| Assembling the payroll sheet in a spreadsheetRoster, hours, absences, additions, reconciling the totals | 8–16 h | 1–2 h |
| Transferring hours from the trackersExport, copy, link to projects | 4–8 h | 0 |
| Counting and reconciling vacation balancesAccruals, carry-overs, expiry | 2–4 h | 0 |
| Keeping requests in chats, a spreadsheet and a calendarThe same vacation in four places | 3–6 h | 0 |
| Settling "how many days do I have" disputesDig up the chat, recount, explain | 1–3 h | 0 |
| Manual work per month | 18–37 h | 1–2 h |
An estimate for a company of 30 people — put in your own volumes and the rates of your HR manager and finance team to get the figure in money.
What the system brings over one year and over two. Every month it gives HR and finance back two to four full working days. Over a year that adds up to 24–52 working days, over two years — between 48 and 105. The whole development, including the integrations, the vacation ledger and notification routing, took about 80 person-hours — some ten working days — so the invested time comes back within the first three to five months of use, and after that it is pure gain.
The twenty hours that carried the system to launch paid for themselves in the very first month. From there the effect only accumulates: the system keeps returning the same two to four days every month for as long as the company uses it. The count is in time, which is why the figures transfer to any company: put in your own rates and you get the sum.
One and a half to four working weeks a year that HR and finance take back from routine. This is the dullest and the most predictable part of the payback.
An overpayment or an underpayment, a disputed severance calculation, a forgotten addition. One error like that costs more than a month of saved hours — and surfaces at the least convenient moment.
One working loop for every process. The stack is open source: no licences, hosting in your own infrastructure, further development in-house without vendor lock-in.
Effect
Practical effect: less to keep in your head
The most expensive load is the one that stays in people's heads. Remembering the day balance, remembering who to write to, remembering what has to be checked before the payout — all of it takes attention even while a person is busy with something else. The system takes it over.
- Employee does not keep the day balance in mind, does not look for who to write to, does not fill in a form. One channel — the one that is already open. There is nothing to master: an interface that has to be learned simply does not exist for them.
- The manager does not have to find out who else from the team is away on those dates — it is in the notification itself. The decision is made with one button, without switching to another system and without coming back to the task later.
- HR does not remember the rules of accrual, carry-over and expiry — the system remembers them. And does not check by hand that nothing was forgotten: suspicious lines flag themselves.
- Finance does not have to work out where an amount came from: the calculation unfolds down to the formula with the numbers filled in.
- The company stops depending on one person. The rules live in the system. The HR manager can go on vacation, fall ill or leave — the procedure stays in place.
- Nobody reconciles the calendar, the spreadsheet and the payroll sheet against each other by hand — a discrepancy between them has nowhere to come from.
Three techniques that make it work:
- The system decides for people everything it can decide itself. Working days, public holidays for the employee's country and the currency rate are filled in automatically. The only questions asked are the ones that genuinely need a human decision.
- The hint arrives at the moment of the decision. The day balance and overlaps with colleagues' vacations are shown right in the request, where the decision is made. There is no need to look them up in a separate report.
- The system catches the errors. Zero hours for an hourly employee, unapproved time off, a missing exchange rate — all of it is flagged in the draft. There is no checklist to remember before closing the month.
Foundation
The technical foundation: why this is dependable
An internal system lives for years and outlives several generations of the team. So dependability here is measured by one thing: can a calculation rule be changed safely two years from now without breaking closed periods. Below are six pillars, each of them verifiable in the code.
21 models and 42 classes in the service layer: the business logic is moved out of controllers and the panel into services. Payroll calculation is a separate core made of the calculator, the roster builder, average earnings and working days. The sources of hours sit behind a common interface: the resolver appeared for the second tracker and cost the calculator 7 insertions and 8 deletions, and a third one is added without touching it. Modules are connected through events and queues.
The code follows PSR-12 and a single formatter, with PHPStan level five static analysis and an extension for Laravel on top — types and model calls are checked before anything runs. The “style → analysis → tests” pipeline runs on every branch, three steps in parallel.
64 test classes, 497 test methods, 11,656 lines of tests. Under test: the pay models, accrual, carry-over and expiry of vacation days, the API and tracker synchronisation. The tests run on an in-memory database — no separate database has to be spun up for a run.
The schema is described by 55 migrations across 1,844 lines, unique indexes appear in 21 migration files. The vacation balance is stored as a ledger of movements, and an approved period is immutable. Background jobs and scheduled runs are idempotent: a missed run is picked up by the next one, with no duplicates.
Deployment goes into two slots: the new version is built in the free one, switching is a symlink change, a rollback to the previous version is one command. A database backup is taken before migration, and after switching a health check runs with an automatic rollback. The local environment comes up with one command in containers.
17 architecture decisions are written down together with the reason and the rejected alternatives — every one of them has an alternatives section. Next to them are 9 changelog entries and 18 specs: 13 active and 5 archived. The system can be handed to another team without reconstructing the logic from the code.
Drag the image to explore it
We will build the same system for your business
Absences and approvals, time tracking, payroll, bonuses and compensations, integrations with your own services — the loop is configured to your rules. We work out on your numbers how many hours a month it takes off your HR managers and finance team, and show on demonstration data how the system keeps those records.
The existing core can be taken as a starting point: the calculation core, the vacation ledger, permissions and integrations are already written, covered with tests and documented. None of that has to be written again — and you get code that any team can carry forward.
It suits companies of 20–300 people: product studios and outsourcing teams, distributed teams across several countries, agencies and manufacturing.