All builds Book a call
Healthcare · optometry · Wichita Family Vision · Live April 2026

Time-off and payroll for an independent eye clinic: paper slips to a system that runs itself.

Green paper slips, hand-checked calendars, payroll rebuilt from a notebook. Now one form, one engine, and a payroll report that builds itself.

60 to 100hrs/yr of admin time returned 10 to 12 paper slips a pay period at 15 to 20 minutes each, plus the manual calendar checks and payroll prep they caused.
183tests in the public repo, every one written from a bug we hit
Diagram: one form feeds an Apps Script engine that writes the attendance sheet, shared calendar, approval email, balances, and payroll report
One form, one engine, five outputs.
What was broken

Before

Time off ran on green paper slips handed to the office manager. She checked the calendar by hand, tracked balances by hand, lost slips, and could not approve from home. The bookkeeper rebuilt payroll every pay period from paper. About 60 to 100 admin hours a year, and no way to see who was off next week without asking.

What I built

The build

What changed

Before and after

Before

By hand
Filing a requestA green paper slip
ApprovingIn the office, on paper
The calendarChecked by hand
BalancesHand-tracked, drifted
Payroll prepRebuilt from paper each period

After

The system
Filing a requestA form on any phone
ApprovingOne tap in an email, from anywhere
The calendarEvents post themselves
BalancesRecomputed on every write
Payroll prepA report that builds itself
Where the numbers come from. The clinic estimated 10 to 12 slips a pay period at 15 to 20 minutes of handling each, before the calendar checks and payroll prep. 26 pay periods a year puts the range at 60 to 100 hours. Not timed after launch; the slips are simply gone.
For engineers

How it's built

Architecture
Form submit trigger into a bound Apps Script project of about 7,000 lines: validation, weekday and anniversary math, attendance-sheet writes, a calendar owned by a Workspace user the script runs as, approve and deny links served by a pinned web-app deployment, and a payroll builder on a time trigger.
Where it runs
Entirely inside the clinic's Google Workspace. No server, no SaaS bill, $0 a month.
Tests and evals
183 regression assertions in the public repo, run with Node, no dependencies. Sections cover accrual routing, weekend-aware date math, partial days, recompute-on-write balance integrity, and the migration.
Cost and latency
$0 a month to run. Built at an hourly rate for the clinic.
Guardrails
The web app URL is read from a config sheet, never from the script API, because the API returns a hidden test deployment. Every change that touches the approve button rolls the pinned deployment; skipping that once put new code on form-submit and old code on approvals. Error emails go to me, action emails go to the manager, so a test can never land in her inbox.
The tradeoff we chose
Why this way and not the other

Apps Script over a hosted app. The clinic already paid for Google Workspace, the office manager lives in Sheets, and the data never leaves their account. A hosted app would have meant a login, a bill, and a vendor for a 26-person office. The cost of that choice is a deployment model with sharp edges, which the tests and the runbook now cover.

Code
github.com/alecstephens3-stack/apps-script-pto-payroll
Anniversary-aware period routing, recompute-on-write balances, no dependencies, 183 tests.
Keep going