---
title: Marking attendance
order: 3
updated: 2026-07-12
---

# Marking attendance

Attendance lives inside the *Attendance* screen (open *Human Resources → Attendance*). This page covers everything to do with recording who showed up: your own calendar and self check-in on *My Attendance*, the bulk *Mark* screen for supervisors, and the per-person *Team* tools for viewing and correcting one employee's record. Leave, approvals, and salary each have their own page — see [Attendance](/docs/human-resources/attendance) for the full tab map.

Two things decide what you can do here, and the backend re-checks both on every request:

- **Your permissions** — each tab and action is gated (details in each section below).
- **The firm's attendance mode** — set on the *General* tab of [HR settings](/docs/human-resources/hr-settings). In *automatic* mode the system marks every past working day *present* for you; in *manual* mode nothing is recorded until someone marks it. This single switch changes whether the *Mark Me Present* button even appears.

## Automatic vs manual mode (read this first)

The firm runs in one of two modes, and it governs the whole page:

- **Automatic attendance** (default) — when anyone opens a calendar, the system quietly back-fills a *present* record for every past **working day** in view that has no record yet, skipping weekends, holidays, days before the employee joined, and days after their termination date. These filled-in days carry a faint *Auto-marked* dot and a *"Auto-marked by the system"* source note in the day detail. Self check-in is switched **off** in this mode, because a forged self-mark would only overwrite the audit trail without changing the effective state.
- **Manual attendance** — nothing is auto-generated. Every day starts blank until a person with the right permission marks it. The *Mark Me Present* button appears (for those who hold the permission), and the *Auto-marked* legend entry is hidden.

The back-fill only ever touches **past** days — today and future days are never auto-marked, so "today" always reflects a real decision.

## My Attendance

The *My Attendance* tab is your own monthly calendar. It needs the *View Own Attendance* (`viewOwnAttendance`) permission (firm administrators always have it). If you lack it, the tab does not appear.

### Summary tiles

Six tiles sit above the calendar and recompute for the month you are viewing:

- *Days in Month* — the calendar length (`28`–`31`).
- *Working Days* — days that match the firm's working-day pattern **minus** any holiday that falls on one. Weekends and holidays are excluded.
- *Present* — count of days recorded `present` this month.
- *Absent* — count of days recorded `absent`.
- *On Leave* — count of days recorded `on_leave` (these come from approved leave, not from this screen — see [Applying for leave](/docs/human-resources/applying-leave)).
- *Half Day* — count of days recorded `half_day`.

### The calendar grid

Each cell shows the day number, colour-coded: green *present*, red *absent* / *holiday* / *week-off*, blue *on leave*, amber *half day*. A half day shows a small `½`. Auto-generated days show a faint dot in the top-right corner. Future days are dimmed. Hovering a cell shows the holiday reason or your note as a tooltip. A legend runs along the bottom.

Use the chevron buttons beside the month name to move between months. Click (or press Enter/Space on) any cell to open its [day detail](#day-detail). The grid itself is read-only — you cannot change a status by clicking a cell.

### Mark Me Present (self check-in)

A *Mark Me Present* button appears next to the month header, but only when **all** of these are true:

- the firm is in **manual** attendance mode;
- you hold *Self Check-In* (`markOwnPresent`) — or you are an administrator;
- you are viewing the **current** month;
- **today** is a working day; and
- today is **not** a holiday.

Click it to record yourself `present` for today. The button is disabled the moment a record exists for today, and its label reflects the current state — *Marked Present*, *Half Day Recorded*, *On Leave Today*, or *Marked Absent* — so you can tell at a glance whether you still need to check in.

The server enforces the real rules regardless of what the button does:

- Self check-in is **rejected in automatic mode** (`403`) — the auto-generator already handles you.
- A self-mark can only set status `present`. You cannot flip yourself to absent, half-day, or on-leave.
- If you are already `on_leave` today, self check-in is refused (`409`) so it can't silently cancel your approved leave.
- Marking yourself for any date **before today** requires the *Mark Attendance in Past Days* (`markAttendanceInPastDays`) permission. Since the button only ever targets today, this mainly matters if a request is forged.

### View Analytics

The *View Analytics* button opens the [attendance analytics](#attendance-analytics) modal for your own record over a date range you choose.

## Mark (bulk)

The *Mark* tab is the fast, many-people-at-once screen for HR and supervisors. It needs *Bulk Mark Attendance* (`bulkMarkAttendance`) — or administrator. Without it the tab is hidden, and the bulk endpoint rejects the request server-side.

**This tab marks today only.** There is no date field on it — every submission is dated to the current day. To record or correct attendance for a **past** date, or to add arrival/departure times, use the per-person *Mark* action on the [Team](#team) tab instead.

### Who appears in the list

The roster is **your team**: everyone below you in the reporting hierarchy, plus yourself (you are injected at the top so you can bulk-mark your own day). This scope is enforced on the server — even an administrator or a *View All Attendance* holder can only bulk-mark people inside their own downline; anyone outside it is rejected (`403`). A search box filters the visible rows by name or username.

### Setting each person's status

Every row has a set of status chips:

- *Present* — records `present`.
- *Absent* — records `absent`.
- *Half (AM)* — half day, present in the morning (`morning_present`).
- *Half (PM)* — half day, present in the evening (`evening_present`).
- *Skip* — leave the row exactly as it is. This is the default for every unmarked row.

Rows already recorded as *On Leave* or *Holiday* are **locked** — they show *"Locked (on leave / holiday)"* and cannot be overwritten from this tab, so a bulk sweep can never wipe out an approved leave.

The *Set all visible* row applies *Present*, *Absent*, or *Clear* (back to *Skip*) to every row currently matching the search filter in one click.

### Submitting

Only rows whose chip **differs** from what is already recorded count as changes — the footer shows the running count (e.g. *"3 changes for 12/07/26"*), and the *Submit* button stays disabled until there is at least one. Rows that already match their recorded state are silently dropped from the payload, so re-submitting never rewrites the *"marked by / marked at"* audit trail on an unchanged day. After a successful submit the list refreshes and the just-marked rows flip to their new *Already marked* badge.

## Team

The *Team* tab lists the people who report to you (it appears only when you have subordinates and hold *View Team Attendance* or *View All Attendance*). A month selector across the top covers the **last 12 months**. Each row shows the person, a Present/Absent/Leave snapshot for the selected month, and today's status pill. Three actions per row:

- *View* — opens that person's monthly [calendar](#viewing-someones-calendar) in a modal (same grid as your own).
- *Analytics* — opens the [attendance analytics](#attendance-analytics) modal for that person.
- *Mark* — opens the single-person mark modal. This button appears only if you hold *Mark Others Present* (`markOthersPresent`) or are an administrator.

### Marking one person (with times and back-dating)

The per-person *Mark* modal is richer than the bulk tab:

- *Date* — a real date picker. You can pick today or, with the *Mark Attendance in Past Days* permission, any earlier date. Without that permission a past date is blocked with a *"Past-date marking is locked"* notice and the *Mark Attendance* button is disabled.
- *Status* — *Present*, *Absent*, *Half (AM)*, or *Half (PM)*.
- *Arrival Time* / *Departure Time* — optional, shown only for *Present* and half-day statuses, entered as a 24-hour `HH:mm` time. They are dropped for *Absent* (times make no sense there). The server validates the format and silently discards times on non-attending statuses.
- *Note (optional)* — free text, e.g. *"Late arrival, WFH"*.

The server checks that you hold *Mark Others Present* for anyone but yourself, that the target user exists, and that a past date carries the past-days permission — the same rules the UI hints at.

### Viewing someone's calendar

The *View* action opens the same month grid as *My Attendance*, read-only, with the person's name at the top. Their calendar obeys the same auto-generation and holiday logic. You can only open calendars for people inside your downline — the view endpoint refuses anyone outside your hierarchy (`403`), even for administrators, so there is no firm-wide attendance browser here.

## Day detail

Clicking any day cell — on your own calendar or a team member's — opens a read-only day-detail panel. It adapts to what happened that day:

- **Future day** — *"This day hasn't happened yet."*
- **Holiday** — the holiday reason, from [HR settings](/docs/human-resources/hr-settings).
- **On leave** — the leave type (name, code, and an *Unpaid* tag when applicable), the leave period, the reason, and whether it was a half-day morning/evening session. See [Applying for leave](/docs/human-resources/applying-leave).
- **Half day** — which half was worked (*"Present in the morning, off in the evening"*, or the reverse) plus any note.
- **Present / Absent** — any note, and a *"Auto-marked by the system"* source line for days the auto-generator filled in.
- **Weekly off** — for a non-working weekday.

For a manually entered *present*, *absent*, or *half-day* record it also shows a *Marked by* card: who entered it and when, plus the *Arrival* and *Departure* times if they were recorded. Auto-generated days show no marker (there was no human).

## Attendance analytics

The analytics modal (from *View Analytics* on your calendar, or *Analytics* on a team row) summarises a **date range** you pick — it defaults to the current calendar month. It shows:

- *Attendance Rate* — the headline percentage, computed as `(present + half_day × 0.5) ÷ working days`, rounded to one decimal. Working days here exclude weekends and holidays in the range.
- *Total Days* — every calendar day in the range.
- *Working Days* — days matching the firm's working-day pattern, minus holidays.
- *Paid Holidays* — total days minus working days (weekends and holidays).
- *Present*, *Half Day* — direct counts.
- *Absent + Leave* — a combined tile; hover it for the *Absent* vs *On Leave* split.

## Comp-off from working a holiday

When someone is marked *present* or *half-day* on a day that is a holiday for them — whether through the bulk *Mark* tab or the per-person modal — the system automatically credits one comp-off entry to that user (once per day). If the firm has set a comp-off expiry, the credit is stamped with an expiry date. This runs on both the single-mark and bulk-mark paths.

## Common questions

### Why is there no date field on the *Mark* tab?

The bulk *Mark* tab always marks **today** — that is by design for the daily roster sweep. To mark or fix a past date, or to add arrival/departure times, open the *Team* tab, find the person, and use their *Mark* action, which has a full date picker.

### Why is the *Mark* tab not visible to me?

You do not hold *Bulk Mark Attendance* (`bulkMarkAttendance`). Ask an administrator. The bulk endpoint also rejects the request server-side, so the tab isn't just cosmetically hidden.

### Why can't I see the *Mark Me Present* button?

It only shows in **manual** attendance mode, for the current month, on a working day that isn't a holiday, when you hold *Self Check-In* (`markOwnPresent`). If your firm uses **automatic** attendance, the button never appears — the system records your present days for you.

### Can I mark a day in a previous month?

Only with the *Mark Attendance in Past Days* (`markAttendanceInPastDays`) permission, and only through the *Team* tab's per-person *Mark* modal. Without the permission the modal blocks past dates with a locked notice. This guard exists specifically so employees or a secretary can't back-fill a missed day as *present*.

### I can bulk-mark, so why can't I mark a particular employee?

The roster — on both the bulk tab and the team views — is limited to **your own downline plus yourself**. Even administrators can only mark people who report up to them. If someone is missing, check the reporting hierarchy under Settings → Users.

### What if I forget to check in on a manual-mode day?

Ask your supervisor or HR to record it from the *Team* tab. If it is a past date they will need the *Mark Attendance in Past Days* permission.

### Why is a day showing *present* that I never marked?

Your firm is in automatic mode. Past working days without a record are filled in as *present* the moment a calendar is opened. Such days carry a faint *Auto-marked* dot and say *"Auto-marked by the system"* in their day detail. Switch to manual mode under [HR settings](/docs/human-resources/hr-settings) if you want every day marked by hand.

### Where do weekends and holidays come from?

The working-day pattern and the holiday list are firm-wide settings an administrator manages under [HR settings](/docs/human-resources/hr-settings). Changing the working days changes the *Working Days* count and which cells render as a week-off on every calendar.
