A leave request can look simple until the employee's balance says 12 days available, while the approval screen allows only 8.
We encountered this class of problem while working on a contract and leave administration system. The difficult part was not building a leave-request form. It was keeping balances consistent when allocations, approvals, holidays, contract dates, and different leave types affected the same employee.
That is where Leave Management Software needs more than CRUD screens.
This article explains how we approached the problem: model leave as transactions, separate allocation from consumption, validate dates before approval, and keep the balance calculation deterministic.
The same architecture applies whether you are building custom HR software or extending an ERP-based Leave Management Software implementation.
Why leave balances become inconsistent
The first mistake is treating a leave balance as a field that can simply be incremented or decremented.
Consider this naive model:
# Naive approach: directly changing the balance makes concurrent updates difficult to reason about.
employee.leave_balance -= requested_days
It appears harmless.
But imagine two requests arriving close together:
- Employee has 5 available days.
- Request A consumes 3 days.
- Request B consumes 3 days.
- Both requests read the original balance before either update commits.
The application can approve 6 days against a 5-day balance.
A second problem appears when an approved request is later cancelled. If the cancellation logic simply adds the days back, the balance can drift after repeated edits.
A Leave Management Software system should therefore treat the balance as a derived value, not the source of truth.
Step 1: Separate allocation from leave consumption
Once the balance is treated as derived data, the first design decision becomes clearer.
An employee can receive 20 annual leave days without having taken any leave. Those 20 days represent an allocation. A five-day approved vacation represents consumption.
We can model both independently:
# Leave Management Software: keep allocations and approved consumption as separate records.
allocation_days = 20
approved_leave_days = 5
available_days = allocation_days - approved_leave_days
This structure also makes auditing easier.
Instead of asking, "Why does this employee have 15 days?", we can answer:
20 days allocated - 5 days consumed = 15 days available.
Modern ERP leave systems follow a similar conceptual separation. Odoo's current Time Off documentation, for example, distinguishes allocations from time-off requests and supports accrual-based allocations.
That distinction becomes important when an employee changes contracts or moves between departments.
Step 2: Validate the date range before calculating days
The next source of errors is deceptively simple: date calculation.
Suppose an employee requests leave from Friday through Monday.
A basic calculation may return four calendar days:
from datetime import date
# Naive calculation counts weekends even when the policy excludes them.
start = date(2026, 9, 25)
end = date(2026, 9, 28)
days = (end - start).days + 1
print(days) # 4
But the employee may actually consume only two working days.
A Leave Management Software implementation therefore needs an explicit calendar policy.
At minimum, the calculation should consider:
- Employee work schedule.
- Weekly rest days.
- Public holidays.
- Half-day or hourly leave.
- Leave-type rules.
Odoo's Time Off documentation explicitly supports leave in days, half-days, and hours, while its configuration also includes public holidays and accrual plans.
The important architectural point is that date calculation should happen before approval, not after the request has already changed the balance.
Step 3: Make approval a state transition
After calculating the requested duration, the next problem is approval.
A common implementation is:
Draft → Submitted → Approved
But the balance should not necessarily change at every state.
We used the following conceptual rule:
# Only approved Leave Management Software requests consume available leave.
if request.status == "approved":
consumed_days = calculate_leave_days(request)
else:
consumed_days = 0
This prevents pending requests from permanently reducing the employee's available balance.
It also makes cancellation easier.
Instead of reversing arbitrary mutations, the system recalculates consumption from approved records.
This matters even more when there are multiple approval levels. Odoo's current documentation supports configurations where time-off requests can require approval by a Time Off Officer, the employee's approver, or both.
The workflow should therefore distinguish between submitted, approved, and rejected records.
Step 4: Recalculate instead of patching the balance
With allocations, dates, and workflow states separated, we can make the balance calculation deterministic.
A simplified implementation looks like this:
# Deterministic Leave Management Software balance calculation avoids incremental balance drift.
def calculate_balance(allocation_days, approved_requests):
consumed = sum(
request.days
for request in approved_requests
if request.status == "approved"
)
return allocation_days - consumed
The production version would additionally filter by employee, leave type, validity period, company, and policy calendar.
The important property is that the result can be reproduced from stored records.
If an administrator changes an approved request from five days to three, the balance does not need a special "add two days back" operation.
The calculation simply produces a new result from the current records.
Step 5: Handle concurrent approvals at the database layer
The calculation is correct, but there is still a concurrency problem.
Two managers can approve requests for the same employee at nearly the same time.
Application-level checks alone are not enough because both requests can read the same balance before either transaction commits.
For a PostgreSQL-backed system, we can lock the employee's relevant balance context during the approval transaction:
-- Lock the employee row while validating and committing an approval.
SELECT id
FROM employees
WHERE id = $1
FOR UPDATE;
The application can then:
- Start a database transaction.
- Lock the employee record.
- Recalculate the current balance.
- Validate the requested leave.
- Create or update the approval record.
- Commit.
The exact locking strategy depends on the data model. A system with separate balance, allocation, and ledger tables may need a more targeted lock.
This is one reason Leave Management Software becomes a database-consistency problem rather than simply an HR UI problem.
We implemented this in a contract and leave administration system
The concurrency and policy trade-off became important in a contract-management project where leave administration was connected with employee contract information.
We initially approached the feature as a collection of forms: employees submitted leave, managers reviewed it, and the application displayed the remaining balance.
That approach made the interface easy to build, but it left policy calculations spread across multiple actions.
We changed the design so that contract information, leave allocations, requests, approvals, and calculated balances had separate responsibilities. PDF contract generation remained independent from leave calculations, while the leave workflow used employee and contract information as inputs.
The project reference was Hexa Matics, where the broader requirement included contract management and integrated leave administration.
The exact production improvement figures were not provided in the project reference, so I would not invent latency or error-rate numbers here. [VERIFY: insert the measured reduction in manual processing time or leave-balance corrections from the project before publication.]
The important implementation result was architectural: leave calculations became reproducible instead of depending on a chain of manual balance updates.
What this changes in production
That architecture gives the Leave Management Software system a useful property: every balance can be explained.
For example:
Annual allocation: 20 days
Approved leave: 6 days
Pending leave: 3 days
Available balance: 14 days
The pending request does not reduce the available balance until the configured policy says it should.
This also makes reporting easier because the application can expose the underlying records instead of storing unexplained totals.
For managers, that means a balance can be traced back to allocations and approved requests.
For developers, it means fewer special-case reversal functions.
Key takeaways
- Leave Management Software should derive balances from allocations and approved consumption rather than repeatedly mutating one balance field.
- Date calculations must account for work schedules, weekends, holidays, and partial-day policies.
- Approval states should control when leave becomes actual consumption.
- Database transactions and appropriate locking matter when multiple managers can approve requests concurrently.
- Keeping contracts, allocations, requests, approvals, and calculations separate makes debugging much easier.
FAQ: What should Leave Management Software track?
A practical Leave Management Software implementation should track leave types, employee allocations, accrual rules, requests, approval states, working calendars, public holidays, supporting documents, and historical transactions.
The exact model depends on the organization's leave policies and whether the system supports multiple companies, departments, contracts, or jurisdictions.
If you've dealt with balance drift or approval race conditions in Leave Management Software, I'd be interested to hear how you modelled the leave ledger and concurrency controls.
For implementation context, the leave-management architecture discussed here is also covered in our Leave Management Software work.
Top comments (0)