> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ontime.hosai.app/llms.txt
> Use this file to discover all available pages before exploring further.

# The attendance day lifecycle

> How raw punches become a single reconciled day record, when that record is final, and what can still change it.

A **punch** is a raw event: someone clocked in or out, at a time, from a source, maybe with a location and a photo. A **day record** is the reconciled answer for one employee on one date: what status the day has, how many hours were worked, how late they were, how much overtime accrued.

Punches are facts. The day record is a *derivation* over those facts and the shift policy that applies. Understanding that split explains most of what follows.

## Where punches come from

| Source                   | How it arrives                                                                                      | Carries                                        |
| ------------------------ | --------------------------------------------------------------------------------------------------- | ---------------------------------------------- |
| **Biometric terminal**   | The terminal sends it over the local network to the OnTime connector, which forwards it over HTTPS. | Terminal user ID, timestamp                    |
| **Mobile — with selfie** | The employee app captures a photo and the device's coordinates.                                     | Photo reference, GPS position, geofence result |
| **Mobile — GPS only**    | The employee app, when a selfie is not required.                                                    | GPS position, geofence result                  |
| **Web**                  | Recorded in the admin console on someone's behalf.                                                  | Who recorded it (audited)                      |

Every punch carries a **direction** (in or out) and a **status**:

* **Approved** — counted. The ordinary case.
* **Pending** — recorded but waiting on a decision. This is what an out-of-geofence punch becomes.
* **Rejected** — decided against; not counted.

A mobile punch also carries a **geofence result**: `Inside`, `Outside`, or `Pending` when the device could not establish a position. The server does this check, not the phone.

## Becoming a day record

```mermaid theme={null}
stateDiagram-v2
  [*] --> InProgress: first punch of the day
  InProgress --> InProgress: further punches
  InProgress --> Closed: nightly close (the following day)
  Closed --> Closed: approval rewrites the day
  note right of InProgress
    Status is provisional.
    Absent is never written
    while the day is live.
  end note
  note right of Closed
    Re-derived, not asserted —
    running the close twice
    produces the same row.
  end note
```

Each punch immediately updates the day record for its date. Through the working day that record is **in progress**: hours accumulate, the status reflects what has happened so far.

A day record ends up with exactly one status:

| Status      | Meaning                                                                         |
| ----------- | ------------------------------------------------------------------------------- |
| `Present`   | Worked.                                                                         |
| `Absent`    | Expected at work and no attendance.                                             |
| `Half`      | Half a day — either half-day leave, or hours below the shift's half-day cutoff. |
| `Leave`     | Covered by an approved leave request.                                           |
| `WeeklyOff` | A rest day per the shift template.                                              |
| `Holiday`   | A date on the organization's holiday calendar.                                  |

The shift template supplies the judgement: what time work should have started, how much grace before a punch counts as late, when the unpaid break is deducted, what the half-day cutoff is, and when hours start counting as overtime.

## The nightly close

Once a day is over, OnTime **closes** it — a sweep that finalizes every employee's record for that date.

Two properties matter:

<CardGroup cols={2}>
  <Card title="It closes yesterday, never today" icon="calendar">
    A day still in progress has a live clock. Someone who has not punched *yet* is not a no-show, so `Absent` is never written to a day that is still running.
  </Card>

  <Card title="It re-derives rather than asserts" icon="refresh-cw">
    The close runs the same engine every other writer uses. Running it twice, or after a punch has already been reconciled, produces the same row it produced the first time.
  </Card>
</CardGroup>

<Note>
  **This is why a missing absence appears the next morning.** If someone did not turn up today, today's record will not say `Absent` until the close runs. That is a day's delay, not a bug — and it is the reason the live board and the closed day report can differ.
</Note>

## What changes a closed day

A closed day is not frozen. Three approvals rewrite it:

```mermaid theme={null}
flowchart LR
  L[Leave approved] --> D[Day record]
  R[Regularization approved] --> D
  O[Overtime approved] --> D
  D --> P[Payroll reads it]
```

* **Approved leave** sets the covered dates to `Leave` (or `Half` for a half-day).
* **An approved regularization** applies the correction that was requested — a missing punch filled in, an off-zone punch accepted, a whole day manually entered.
* **Approved overtime** writes the approved minutes onto the day.

Each of these happens inside the same transaction as the approval itself. There is no window where a request is approved but the day has not caught up.

<Warning>
  **Payroll freezes attendance.** Once a payroll cycle has frozen a month, a regularization that would rewrite a day in that month is refused rather than silently ignored. Clear the queue before you freeze.
</Warning>

## Approvals are inputs, not a parallel ledger

There is one day record per employee per date, and it is the only place the answer lives. Approvals feed into it; payroll, reports, exports and the API all read out of it. Nothing keeps a second, competing version of the truth.

That is why an approved leave request shows up on the calendar, in the month report, in the payroll cycle's paid-days count, and in the statutory register — without any of those being told about it separately.
