Build When I Work

KINDAreplaces $2.5/mosaves $30/yrback to the verdict

0%0 of 20 items done

Saved on this device only. Tick prerequisites first, then work the phases in order · do not start one until the checks above it pass.

A schedule for one team: shifts on a rota, availability submitted by staff, swap and cover requests that require a named approval, a change history nobody can quietly edit, and an export that a payroll system or an accountant can consume without this application interpreting it.

estimated effort multi-daythe files for this build are in the project pack

RuntimeNode 22 with PostgreSQLDeploymentSelf-hosted and private, not publicly indexedTimeA library with a full time zone database, storing instants plus the local zoneAuditAppend-only change records, never updated or deletedExportCSV of hours worked, with no monetary calculation anywhere

Before step 1

Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.

  1. installfree

    Why Runs the application.

    Get it nodejs.org, or nvm install 22. open ↗

    Verify node --version prints v22 or higher

  2. installfree

    Why Holds staff records and an append-only change log, both of which want a real database.

    Get it Install locally or run it in Docker. open ↗

    Verify psql connects and reports 16 or higher

  3. decidefree

    Why Scheduling software sits next to employment law, and the boundary between recording a decision and making one is the whole design. Crossing it changes your responsibility.

    Get it Write down, before building, that this application will not calculate pay, will not assert compliance and will not decide anything. If your situation needs those, buy a product whose vendor carries that responsibility.

    Verify The boundary statement is written and appears in the README

  4. decidefree

    Why This holds personal data about identifiable people, including absence patterns. How long you keep it is a decision with legal weight in many places.

    Get it Decide the retention period for each kind of record and write the reason. Employment records often have a minimum retention that differs from the maximum you would otherwise want.

    Verify The policy is written with a reason per record kind

  5. have readyfree

    Why Overnight shifts, split shifts and someone who only works Tuesdays are all present in real rotas and absent from invented ones, and each breaks a naive model.

    Get it Use an actual week from the team this is for, including its awkward shifts.

    Verify A real week is entered including at least one overnight or split shift

Data model

Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.

- employees(id, name, email, role, employment_kind, active_from, active_to) · employment_kind is recorded, never interpreted
- positions(id, name, colour), locations(id, name, timezone)
- shifts(id, location_id, position_id, employee_id, starts_at_utc, ends_at_utc, local_date, state, published_at)
- availability(id, employee_id, weekday, start_local, end_local, kind, effective_from) · kind is available, preferred or unavailable
- time_off(id, employee_id, starts_on, ends_on, kind, state, decided_by, decided_at, note)
- swaps(id, shift_id, from_employee_id, to_employee_id, state, requested_at, decided_by, decided_at)
- changes(id, entity, entity_id, at, actor_id, before_json, after_json) · append only, no update or delete path
- Every state change on a shift, swap or absence records who decided it. There is no automatic approval anywhere.

Environment variables

These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.

VariableNeededExampleWhere the value comes from
DATABASE_URLsecretrequiredpostgres://user:pass@localhost:5432/rotaYour PostgreSQL connection string. This database holds personal data.
DEFAULT_LOCATION_TZrequiredEurope/LondonTime zone for a location. Shifts are local wall clock times in it.
OVERNIGHT_BELONGS_TOrequiredstartWhether a shift crossing midnight counts on its start or end date. State it, because payroll will ask.
RETENTION_MONTHS_SCHEDULErequired24How long schedule records are kept, per your written policy.
RETENTION_MONTHS_AUDITrequired72How long the change log is kept. Usually longer, because it is the record of decisions.
PUBLIC_URLrequiredhttps://rota.internal.example.comWhere the application is reachable. Private and not publicly indexed.

The build, in order

  1. Shifts on a rota, with time handled properly

    A published week shows the right shifts at the right local times.

    1. Instant plus the local date. The zone belongs to the location, not the server.

      Files src/shifts.js

    2. A shift crossing midnight belongs to the day it started, which is what staff and payroll both expect. Decide and document it.

      Files src/shifts.js

    3. A draft rota is not a commitment. Publishing is an action with an actor and a timestamp.

      Files src/publish.js

    done when · tick each as it passes
    watch out
    • A shift is a local wall clock commitment: an eight in the morning shift stays at eight after a clock change. Store the instant computed from the local time and the location's zone, and recompute rather than shifting instants when a schedule is copied to another week that crosses a transition.
  2. Availability, submitted by the people it describes

    Staff say when they can work, and the schedule warns when it contradicts them.

    1. It describes them, so they enter it. A manager editing it silently is the pattern to avoid.

      Files src/availability.js

    2. A change agreed in March applies from March, not to January's rota.

      Files src/availability.js

    3. Never block. Record who acknowledged, because the record is the point.

      Files src/warnings.js

    done when · tick each as it passes
    watch out
    • Blocking an assignment outside availability sounds protective and is wrong, because there are legitimate reasons to ask and the manager needs to be able to. Warn, require acknowledgement, and record it, so the conversation happens and the record shows it did.
  3. Swaps and cover, with a named approver

    Staff can arrange cover between themselves, and a manager approves it explicitly.

    1. Request, accept, approve. Each with an actor and a timestamp.

      Files src/swaps.js

    2. A pending swap is not a schedule change. Verify the published rota is unaffected.

      Files src/swaps.js

    3. A refused swap is as important a record as an approved one.

      Files src/swaps.js

    done when · tick each as it passes
    watch out
    • Auto-approving a swap when both employees agree removes the manager from a decision that affects coverage and cost, and it is exactly the record that matters when something goes wrong. Keep the approval step, and make the approver a named person rather than a role.
  4. Absence, recorded rather than adjudicated

    Time off is requested, decided by a person, and the schedule reflects it.

    1. Separate rows. A decision references a request rather than mutating it.

      Files src/timeoff.js

    2. Approved absence is a strong warning, still not a block, and still acknowledged.

      Files src/warnings.js

    3. A number a person entered from the contract. No accrual arithmetic anywhere.

      Files src/entitlement.js

    done when · tick each as it passes
    watch out
    • Calculating leave entitlement means encoding employment terms and statutory rules that vary by jurisdiction, contract and length of service, and getting it wrong affects someone's actual leave. Record balances as figures a person entered from the contract, and never derive them.
  5. Export, where the money is not calculated

    A payroll period exports as hours that somebody else turns into pay.

    1. No rates, no money, no classification. Grep the exporter for currency handling and find none.

      Files src/export.js

    2. One employee, one period, counted by hand. Arithmetic that nobody checked is arithmetic that is wrong.

    3. So a payroll query about a changed shift can be answered without opening the application.

      Files src/export.js

    done when · tick each as it passes
    watch out
    • Classifying hours as overtime is a legal determination that depends on jurisdiction, contract and averaging periods, and an incorrect classification underpays someone. Export raw hours with their dates and let the payroll system, which is built and maintained for that, do the classification.
what this build does not replace
after v1, if you want it

Need the files? The project pack on the verdict page hands your agent the whole brief · more hr.