Update OKF docs bundle, README, and CLAUDE.md for today's features
Documents the shift planning and closure/payroll modules (new tables, routes, stores), the dark redesign, and the docker-compose-broken-host workaround. Adds CLAUDE.md pointing agents at docs/. Translates README to English and keeps host-specific infrastructure details out of the repo.
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
---
|
||||
type: API Endpoint
|
||||
title: Admin routes
|
||||
description: Employee CRUD (add/soft-delete), org-wide and per-employee stats, and CSV export — all requireAdmin.
|
||||
description: Employee CRUD + pay rates, org-wide and per-employee stats, shift planning admin, payroll, and CSV export — all requireAdmin.
|
||||
resource: backend/src/routes/admin.ts
|
||||
tags: [api, admin]
|
||||
timestamp: 2026-08-12T00:00:00Z
|
||||
timestamp: 2026-08-16T00:00:00Z
|
||||
---
|
||||
|
||||
# Admin routes
|
||||
@@ -16,8 +16,19 @@ Mounted at `/api/admin`. Every route requires `requireAuth` + `requireAdmin`
|
||||
|
||||
```
|
||||
GET /api/admin/employees
|
||||
200 -> { "employees": Employee[] } // includes inactive (soft-deleted) rows
|
||||
200 -> { "employees": (Employee & { hourly_rate: number })[] } // includes inactive (soft-deleted) rows
|
||||
```
|
||||
`hourly_rate` is the employee's *current* rate (`getCurrentRate`, see
|
||||
[pay_rates](/docs/data-model/pay-rates.md)) — joined in at read time, not a
|
||||
column on `employees` itself.
|
||||
|
||||
```
|
||||
POST /api/admin/employees/:id/rate
|
||||
Body: { "hourly_rate": number, "valid_from"?: "YYYY-MM-DD" }
|
||||
201 -> { "hourly_rate": number }
|
||||
```
|
||||
Appends a new [pay_rates](/docs/data-model/pay-rates.md) row rather than
|
||||
overwriting — see that doc for the retroactive-first-rate behavior.
|
||||
|
||||
```
|
||||
POST /api/admin/employees
|
||||
@@ -61,11 +72,103 @@ a UTF-8 BOM so Excel renders the Czech diacritics correctly. Downloaded from
|
||||
the frontend as a plain `<a href=... download>` — the session cookie rides
|
||||
along automatically since it's a same-origin GET.
|
||||
|
||||
## Shift planning admin
|
||||
|
||||
See [shift_slots](/docs/data-model/shift-slots.md) /
|
||||
[shift_signups](/docs/data-model/shift-signups.md) for the data model.
|
||||
|
||||
```
|
||||
GET /api/admin/shifts?period=YYYY-MM
|
||||
200 -> { "slots": AdminSlot[] } // AdminSlot includes occupied employees per slot
|
||||
```
|
||||
Same lazy-generation trigger as the employee-side
|
||||
[`GET /api/shifts/available`](./shifts-routes.md).
|
||||
|
||||
```
|
||||
POST /api/admin/shifts
|
||||
Body: { "date", "start_time", "end_time", "capacity", "note"? }
|
||||
201 -> { "id": number }
|
||||
400 -> "Stejná směna už existuje." (unique date+start+end already taken)
|
||||
```
|
||||
|
||||
```
|
||||
PATCH /api/admin/shifts/:slotId
|
||||
Body: { "date", "start_time", "end_time", "capacity", "note"? }
|
||||
200 -> { "ok": true }
|
||||
400 -> capacity dropped below the number of already-approved signups
|
||||
```
|
||||
|
||||
```
|
||||
POST /api/admin/shifts/:slotId/assign
|
||||
Body: { "employeeId": number }
|
||||
201 -> { "ok": true }
|
||||
```
|
||||
Goes through the exact same collision/capacity checks as a self-signup —
|
||||
see [shift_signups](/docs/data-model/shift-signups.md).
|
||||
|
||||
```
|
||||
DELETE /api/admin/shifts/:slotId/assign/:employeeId
|
||||
204 on success
|
||||
```
|
||||
|
||||
## Payroll admin
|
||||
|
||||
See [month_closures](/docs/data-model/month-closures.md) and
|
||||
[payroll](/docs/data-model/payroll.md) for the state machines these drive.
|
||||
|
||||
```
|
||||
GET /api/admin/payroll?period=YYYY-MM
|
||||
200 -> { "rows": PayrollOverviewRow[] }
|
||||
```
|
||||
One row per **active** employee, whether or not they've touched their
|
||||
closure for that period yet (`closure_status` defaults to
|
||||
`"waiting_employee"` when no row exists).
|
||||
|
||||
```
|
||||
POST /api/admin/payroll/:employeeId/adjustments
|
||||
Body: { "period", "tips_amount", "bonus_amount", "other_amount" }
|
||||
200 -> { "payroll": Payroll }
|
||||
400 -> not confirmed yet, or already locked
|
||||
```
|
||||
|
||||
```
|
||||
POST /api/admin/payroll/:employeeId/lock
|
||||
Body: { "period": "YYYY-MM" }
|
||||
200 -> { "payroll": Payroll }
|
||||
400 -> employee hasn't confirmed, or still has an open shift
|
||||
```
|
||||
|
||||
```
|
||||
POST /api/admin/payroll/:employeeId/reopen
|
||||
Body: { "period": "YYYY-MM" }
|
||||
200 -> { "payroll": Payroll }
|
||||
400 -> not currently locked+ready (e.g. already paid)
|
||||
```
|
||||
|
||||
```
|
||||
POST /api/admin/payroll/:employeeId/paid
|
||||
Body: { "period": "YYYY-MM" }
|
||||
200 -> { "payroll": Payroll }
|
||||
400 -> not yet locked (status isn't "ready")
|
||||
```
|
||||
|
||||
```
|
||||
GET /api/admin/payroll/export?period=YYYY-MM
|
||||
200, Content-Type: text/csv; charset=utf-8
|
||||
Content-Disposition: attachment; filename="mzdy_<YYYY-MM>.csv"
|
||||
```
|
||||
Built by `payrollToCsv` in `backend/src/util/csv.ts`, same BOM-prefixed
|
||||
pattern as the attendance CSV export below.
|
||||
|
||||
# Related
|
||||
|
||||
- [employees](/docs/data-model/employees.md)
|
||||
- [attendance_events](/docs/data-model/attendance-events.md)
|
||||
- [pay_rates](/docs/data-model/pay-rates.md), [month_closures](/docs/data-model/month-closures.md), [payroll](/docs/data-model/payroll.md)
|
||||
- [shift_slots](/docs/data-model/shift-slots.md), [shift_signups](/docs/data-model/shift-signups.md)
|
||||
- [Attendance routes](./attendance-routes.md)
|
||||
- [Shift planning routes (employee)](./shifts-routes.md), [Closure routes (employee)](./closure-routes.md)
|
||||
- [Frontend stores](/docs/frontend/stores.md) — `adminStore` owns the
|
||||
selected month and drives both the summary table and the per-employee
|
||||
chart off it
|
||||
chart off it; `adminShiftStore` and `adminPayrollStore` own the other
|
||||
two admin tabs
|
||||
|
||||
39
docs/api/closure-routes.md
Normal file
39
docs/api/closure-routes.md
Normal file
@@ -0,0 +1,39 @@
|
||||
---
|
||||
type: API Endpoint
|
||||
title: Closure routes (employee)
|
||||
description: GET /api/closure, POST /api/closure/confirm — the employee's own month-end confirmation.
|
||||
resource: backend/src/routes/closure.ts
|
||||
tags: [api, payroll]
|
||||
timestamp: 2026-08-16T00:00:00Z
|
||||
---
|
||||
|
||||
# Closure routes (employee)
|
||||
|
||||
Mounted at `/api/closure`. Every route requires `requireAuth` +
|
||||
`requireEmployee`.
|
||||
|
||||
# Examples
|
||||
|
||||
```
|
||||
GET /api/closure?period=YYYY-MM
|
||||
200 -> { "closure": MonthClosure, "summary": ClosureSummary }
|
||||
```
|
||||
Lazily creates the closure row (`waiting_employee`) the first time this
|
||||
period is opened. `summary` (`worked_minutes`, `shift_count`,
|
||||
`earned_estimate`) is always computed live from
|
||||
[attendance_events](/docs/data-model/attendance-events.md) — never stored
|
||||
on the closure row itself.
|
||||
|
||||
```
|
||||
POST /api/closure/confirm
|
||||
Body: { "period": "YYYY-MM" }
|
||||
200 -> { "ok": true }
|
||||
400 -> "V tomto měsíci máš stále otevřenou směnu." (open shift blocks it)
|
||||
| "Docházka je už uzamčena." (already locked)
|
||||
```
|
||||
|
||||
# Related
|
||||
|
||||
- [month_closures](/docs/data-model/month-closures.md)
|
||||
- [Admin routes](./admin-routes.md) — lock/reopen, tips/bonus adjustments, mark paid
|
||||
- [Frontend stores](/docs/frontend/stores.md) — `closureStore`
|
||||
@@ -1,8 +1,10 @@
|
||||
# API
|
||||
|
||||
Express routers under `/api/*`, mounted in `backend/src/index.ts`. All
|
||||
responses are JSON except the CSV export. Errors follow `{ "error": string }`.
|
||||
responses are JSON except the CSV exports. Errors follow `{ "error": string }`.
|
||||
|
||||
- [Auth routes](./auth-routes.md) - `/api/auth/*` - Google sign-in, logout, current session
|
||||
- [Attendance routes](./attendance-routes.md) - `/api/attendance/*` - the employee's own clock in/out and stats
|
||||
- [Admin routes](./admin-routes.md) - `/api/admin/*` - employee management, org/individual stats, CSV export
|
||||
- [Shift planning routes (employee)](./shifts-routes.md) - `/api/shifts/*` - browse open slots, sign up, cancel
|
||||
- [Closure routes (employee)](./closure-routes.md) - `/api/closure/*` - confirm the month's docházka
|
||||
- [Admin routes](./admin-routes.md) - `/api/admin/*` - employee management + pay rates, org/individual stats, shift planning admin, payroll, CSV exports
|
||||
|
||||
48
docs/api/shifts-routes.md
Normal file
48
docs/api/shifts-routes.md
Normal file
@@ -0,0 +1,48 @@
|
||||
---
|
||||
type: API Endpoint
|
||||
title: Shift planning routes (employee)
|
||||
description: GET /api/shifts/available, GET /api/shifts/mine, POST/DELETE /api/shifts/:slotId/signup.
|
||||
resource: backend/src/routes/shifts.ts
|
||||
tags: [api, shift-planning]
|
||||
timestamp: 2026-08-16T00:00:00Z
|
||||
---
|
||||
|
||||
# Shift planning routes (employee)
|
||||
|
||||
Mounted at `/api/shifts`. Every route requires `requireAuth` +
|
||||
`requireEmployee`.
|
||||
|
||||
# Examples
|
||||
|
||||
```
|
||||
GET /api/shifts/available?period=YYYY-MM
|
||||
200 -> { "slots": AvailableSlot[] }
|
||||
```
|
||||
Open slots the employee isn't already signed up for. `period` defaults to
|
||||
the current month if omitted/invalid. Also the trigger point for lazily
|
||||
generating that month's template — see
|
||||
[shift_slots](/docs/data-model/shift-slots.md).
|
||||
|
||||
```
|
||||
GET /api/shifts/mine?period=YYYY-MM
|
||||
200 -> { "slots": MySlot[] }
|
||||
```
|
||||
|
||||
```
|
||||
POST /api/shifts/:slotId/signup
|
||||
201 -> { "ok": true }
|
||||
400 -> collision with another approved slot, already full, already signed
|
||||
up, or the slot is closed — see [shift_signups](/docs/data-model/shift-signups.md)
|
||||
```
|
||||
|
||||
```
|
||||
DELETE /api/shifts/:slotId/signup
|
||||
204 on success
|
||||
400 -> not signed up for that slot
|
||||
```
|
||||
|
||||
# Related
|
||||
|
||||
- [shift_slots](/docs/data-model/shift-slots.md), [shift_signups](/docs/data-model/shift-signups.md)
|
||||
- [Admin routes](./admin-routes.md) — the admin-side calendar, manual assignment, and month generation
|
||||
- [Frontend stores](/docs/frontend/stores.md) — `shiftPlanningStore`
|
||||
Reference in New Issue
Block a user