How the four core pillars of HRSanad interlock — and why a change in one always ripples through the others.
Four pillars, one data flow
Think of HRSanad like a school system. Every child (employee) is enrolled in a class (shift), attendance is taken every day (attendance log), and at the end of the month the school issues a report card with grades (payslip). If a child changes class, misses lessons, or joins mid-term — all of it changes what the final report card shows.
From employee creation to payslip generation
The dashed arrows show data dependencies: when payroll engine runs, it reaches back across all three pillars — Employee (for salary rates), Shift (for expected hours), and Attendance (for actuals) — to compute the final number.
Every calculation starts here
The Employee record is the anchor. It does not just store a name — it stores the financial blueprint for every other pillar.
full_time vs part_time) affects eligible shift patterns.tna.shift_rosters, tna.attendance_logs, and finance.payroll_employee_details
carries a employee_id foreign key pointing to personnel.employees.id.
Deleting or separating an employee does not delete those records — they are retained for 7 years (UAE PDPL retention rule).
What the system thinks should happen
A Shift is a template that says: "This role works from 09:00 to 18:00, Sunday to Thursday, with a 1-hour lunch break, totalling 8 hours per day."
| Field | Example | Why it matters |
|---|---|---|
shift_name | Morning Shift | Display label |
start_time | 09:00 | Defines "on time" threshold |
end_time | 18:00 | Defines "early-out" threshold |
break_minutes | 60 | Subtracted from total hours worked |
grace_minutes | 15 | Late arrival allowed without deduction |
ot_start_after | 30 min | OT counted only after this buffer beyond end_time |
weekend_days | [5, 6] = Fri, Sat | Weekend days → no attendance expected |
work_hours_per_day | 8 | Used to calculate daily rate for LOP deduction |
Think of it like this:
The shift's end_time and ot_start_after buffer define when overtime begins:
ot_start_after = 30 min → OT counted from 18:30 onwards.Raw clock events → processed daily summaries → monthly aggregates
Attendance has two layers: raw events (clock in/out timestamps) and processed records (the interpretation of those events against the expected shift).
tna.attendance_logs)| Column | What it stores |
|---|---|
employee_id | Links to personnel.employees |
clock_in_time | Exact timestamp when employee clocked in |
clock_out_time | Exact timestamp when employee clocked out (null until they do) |
clock_in_lat / lng | GPS coordinates at clock-in |
clock_out_lat / lng | GPS coordinates at clock-out |
clock_in_photo_url | Face photo taken at clock-in (uploaded to Azure Blob) |
source | android / web / biometric / import |
tna.processed_attendance)The attendance processor runs daily (or can be triggered manually) and compares each raw log against the shift roster to produce:
| Output field | How it's calculated |
|---|---|
status | present / absent / late / half_day / holiday / weekend |
late_minutes | clock_in_time minus shift start_time (minus grace minutes) |
early_out_minutes | shift end_time minus clock_out_time |
ot_minutes | clock_out_time minus (shift end_time + ot_start_after buffer) |
total_hours | (clock_out − clock_in − break_minutes) / 60 |
lop_days | 0 or 1 depending on status and policy |
At the end of each month (before payroll runs), the system totals the daily records into a monthly summary per employee:
| Aggregate | Used by Payroll for… |
|---|---|
| Total days present | Confirming full-month entitlement |
| Total LOP (Loss of Pay) days | Salary deduction: basic ÷ 30 × LOP days |
| Total late minutes | Late deduction if policy enabled: basic ÷ (30 × 8 × 60) × late_mins |
| Total OT hours | OT earnings: OT hours × hourly rate × 1.5 or 2 |
Where all four pillars converge into a single number
Payroll is the consumer of the other three pillars. When the payroll engine runs for a month, it reads from all of them simultaneously.
Gross Earnings = Basic + All earning components (HRA, Transport, OT pay…)
Deductions = LOP deduction + Late deduction + Loan instalment + Employee statutory deductions
Net Pay = Gross Earnings − Deductions
| Attendance fact | Payroll impact | Formula |
|---|---|---|
| 0 LOP days | Full month salary paid | — |
| 3 LOP days | Deduction added as line item | basic ÷ 30 × 3 |
| 2 hours OT | OT earnings added | (basic ÷ 30 ÷ 8) × 2 × 1.5 |
| 45 late minutes (deduction policy ON) | Minute-level deduction | (basic ÷ 30 ÷ 8 ÷ 60) × 45 |
| 5 approved paid leave days | No LOP; leave days treated as present | Attendance overridden by leave record |
| Joined on 15th of month | Half-month pro-ration | each component × (days remaining ÷ 30) |
Approved leave overrides absence so salary is protected
Leave is not stored inside the attendance table. It lives in its own module (leave.leave_requests). But when payroll runs, it joins against approved leaves so that a day marked "absent" in attendance becomes "paid leave" in payroll — and no LOP deduction is applied.
Conversely, if an employee takes unpaid leave (or unpaid absence), the attendance record stays as ABSENT and payroll applies the LOP deduction normally.
Every change has a downstream consequence
| You change… | Immediate effect | Downstream effect |
|---|---|---|
| Employee Basic salary | New salary stored in employee record | All formula-based components (HRA = 25% basic) recalculate at next payroll run. Current month unaffected if already calculated. |
| Employee Grade | Salary revision workflow triggered | New grade's default pay components inherited. Applies from next payroll run after approval. |
| Employee Status → Separated | Separation date recorded | Attendance logging stops. Final payroll run generated with pro-rated last month. Gratuity calculation triggered. |
| Shift End time moved from 18:00 to 17:00 | Shift master updated | All future attendance processed with new end time. OT now accumulates 1 hour earlier. Historical records unaffected. |
| Shift Weekend changed (Fri+Sat → Sat+Sun) | Shift master updated | Friday attendance now expected. Employees working previously will show as present; those who don't will show absent (LOP) from the roster change date onward. |
| Attendance Clock-in corrected (HR edit) | Raw log updated; processed record recalculated | If payroll already calculated for this month, payroll must be recalculated to pick up the correction. You cannot edit attendance after payroll is Finalized/Locked. |
| Attendance Manual absence → approved leave entered retroactively | Leave record created with approved status | Payroll recalculation reverses the LOP deduction and replaces it with paid leave credit. |
| Payroll Run finalized | Payroll locked for the month | Attendance records for that month become read-only. Any corrections require HR admin unlock + new payroll revision run. |
How the tables connect
The ordered sequence at the end of each payroll month
HR or TNA manager reviews the month's processed attendance. Corrects any missed clock-outs, manual entries for field staff, and retroactive leave adjustments. Signs off to freeze attendance.
Any pending leave requests covering the month should be approved or rejected before payroll runs. Approved leaves protect the employee from LOP deductions on those days.
HR creates the payroll run (month + company). System pulls employee pay components, attendance summary (LOP, OT, late minutes), and calculates gross, deductions, net for every active employee.
Payroll admin reviews the calculated run — checks outliers (unusually high OT, large LOP deductions). Can recalculate after corrections.
Workflow approval (if configured). Once approved → Finalized status. Attendance for this period locks. Payslips generated as PDFs. WPS/SIF file (UAE) or JV export (India/US) generated.
Employees can log into the ESS portal to view and download their payslip. Push notification sent via Android app. Email notification sent if SMTP configured.
Tracing one employee through all four pillars
| Date | Event | Attendance record | Pay impact |
|---|---|---|---|
| 1–9 May | Normal work | Present, on time | — |
| 11 May | Arrived at 09:52 (37 min late, grace=15, net 22 min) | Late: 22 min | Minute deduction if policy on |
| 12–13 May | Annual leave approved | Present (paid leave override) | No LOP ✓ |
| 14 May | No clock-in, no leave | Absent (LOP) | LOP: AED 5000 ÷ 30 = AED 167 |
| 20 May | Stayed until 20:15 (2h 45m OT after 30min buffer) | OT: 135 min = 2.25h | OT: (5000÷30÷8) × 2.25 × 1.5 = AED 70 |
| Rest of May | Normal work | Present, on time | — |
Earnings
| Basic Salary | AED 5,000.00 |
| Housing Allowance (25%) | AED 1,250.00 |
| Transport Allowance | AED 500.00 |
| Overtime (2.25 hrs) | AED 70.31 |
| Gross Earnings | AED 6,820.31 |
Deductions
| LOP (1 day) | AED 166.67 |
| Late deduction (22 min) | AED 3.83 |
| Total Deductions | AED 170.50 |
Where to look in the database for each concept
| Concept | Schema.Table | Key columns |
|---|---|---|
| Employee master | personnel.employees | id, grade_id, basic_salary, joining_date, status |
| Pay components per employee | personnel.employee_pay_components | employee_id, component_id, amount, formula |
| Shift definition | tna.shifts | start_time, end_time, grace_minutes, ot_start_after, weekend_days |
| Shift assignment | tna.shift_rosters | employee_id, shift_id, effective_from, effective_to |
| Raw attendance | tna.attendance_logs | employee_id, clock_in_time, clock_out_time, source, GPS columns, photo URLs |
| Processed attendance | tna.processed_attendance | employee_id, date, status, late_min, ot_min, lop_days |
| Payroll run header | finance.payroll_runs | period_year, period_month, status, run_type |
| Payroll per employee | finance.payroll_employee_details | payroll_run_id, employee_id, gross, deductions, net, lop_days, ot_hours |
| Leave requests | leave.leave_requests | employee_id, leave_type_id, from_date, to_date, status |
| Gratuity slabs | finance.gratuity_slabs | country_code, years_from, years_to, days_per_year, salary_divisor |