---
type: SQLite Table
title: shift_slots
description: Bookable shift slots (a date + time range + headcount capacity), generated from a weekly template or created ad hoc by the admin.
resource: backend/src/db/index.ts
tags: [data-model, sqlite, shift-planning]
timestamp: 2026-08-16T00:00:00Z
---

# shift_slots

# Schema

| Column | Type | Description |
|---|---|---|
| `id` | INTEGER PK | autoincrement |
| `date` | TEXT | `YYYY-MM-DD` |
| `start_time` / `end_time` | TEXT | `HH:MM`; `end_time <= start_time` means the slot rolls past midnight |
| `capacity` | INTEGER | headcount |
| `status` | TEXT | `open` \| `full` \| `closed` — recomputed from capacity vs. approved signups on every change |
| `note` | TEXT | free text |
| `generated` | INTEGER | 1 if created by the weekly template, 0 if manually added by the admin |
| `created_at` / `updated_at` | TEXT | ISO 8601 UTC |

`UNIQUE (date, start_time, end_time)` — this is what makes template
generation idempotent (`INSERT OR IGNORE`).

# Weekly template

`ensureSlotsForPeriod` (`backend/src/services/shiftPlanning.ts`) generates a
whole calendar month lazily, the first time anyone (employee or admin)
requests that period — not on a cron schedule. There's no scheduler in this
app, and lazy + idempotent is simpler and just as reliable as the source
system's Apps Script monthly trigger
(`createShiftPlanningTrigger_` in `gscript/ShiftPlanningService.js`, which
could only fire in a coarse hourly window).

| Day | Slots |
|---|---|
| Sun–Thu | 18:00–23:00, capacity 1 |
| Fri, Sat | 18:00–02:00, capacity 1 **and** 20:00–00:00, capacity 1 |

The two Friday/Saturday slots deliberately overlap in time — a real
scheduling choice from the source bistro (an early shift and a late shift),
not a bug. It's also exactly what the collision test in
`backend/src/services/shiftPlanning.test.ts` exercises.

# Related

- [shift_signups](./shift-signups.md)
- [Shift planning API](/docs/api/shifts-routes.md)
