Why I Replaced Excel with a Web App for Payroll

For years, I used an Excel spreadsheet to handle Cyprus payroll calculations. Tax brackets, social insurance caps, GHS contributions, deductions—it all lived in a single workbook with formulas chained across dozens of cells.

It worked fine until Cyprus introduced a major tax reform effective January 2026.

The breaking point

New tax brackets, new deduction rules, updated caps. The spreadsheet needed a full overhaul—and I realized I’d be spending hours tracing formulas, hoping I didn’t break something downstream.

I’d been wanting to experiment with building something end-to-end with AI assistance, and this felt like the right problem for it. Well-defined domain, clear rules, verifiable output. So instead of fixing the spreadsheet, I rewrote the whole thing in code—with Claude as my pair programmer.

Why a web app?

I didn’t need a server. I didn’t need a database. I needed:

  • Correct math—tax calculations that I could test and verify
  • Transparency—readable logic instead of nested Excel formulas
  • Shareability—something anyone could open in a browser

So I built Cyprus Payroll 2026 with Vue 3, TypeScript in strict mode, and Vitest for testing—mostly through conversations with AI, feeding it the official tax reform documentation and iterating on the output. The entire tax engine is a set of pure functions—no side effects, no hidden state.

export const TAX_BRACKETS = [
  { from: 0, to: 22000, rate: 0 },
  { from: 22001, to: 32000, rate: 0.20 },
  { from: 32001, to: 42000, rate: 0.25 },
  { from: 42001, to: 72000, rate: 0.30 },
  { from: 72001, to: Infinity, rate: 0.35 },
] as const

Each bracket, cap, and rate is a named constant. Each calculation step is a function with explicit inputs and outputs. No magic cells referencing other magic cells.

What makes financial code different

Building a payroll calculator taught me a few things about financial software:

  • Every edge case matters. What happens at the exact boundary of a tax bracket? When the social insurance cap kicks in mid-month? When someone has 4 children but earns above the deduction income limit? In Excel, these edge cases hide in formulas. In code, you write a test for each one.

  • Pure functions are a must. The tax calculator takes an input object and returns a result. No global state, no mutations. Same salary and deductions always give the same answer. I have 100% test coverage on the tax engine.

  • The 20% insurance cap rule is tricky. Cyprus limits total insurance deductions (health, life, provident fund) to 20% of chargeable income. In the spreadsheet, this was one formula that nobody questioned. In code, I had to think carefully about the order of operations—calculate chargeable income first, then cap the deductions, then apply the cap to taxable income.

The stack

Nothing fancy:

  • Vue 3 with Composition API for the UI
  • TypeScript in strict mode—no implicit any, every function return typed
  • Pinia for state management
  • Vitest for testing (90%+ coverage overall, 100% for tax logic)
  • Tailwind CSS for styling
  • Vite for builds

The entire app is static. No backend. It runs entirely in the browser, which means no payroll data ever leaves your machine.

Building with AI

This was also an experiment in how far you can take AI-assisted development on a real problem. I gave Claude the full Cyprus tax reform documentation and let it generate the initial tax engine, constants, and test suite. Then I reviewed everything, tested against known payroll outputs, and iterated.

What worked well: AI is great at translating structured rules (tax brackets, caps, deduction formulas) into code. The documentation was unambiguous, and the output was verifiable—I could compare every result against the Excel spreadsheet.

What still needed me: edge cases at bracket boundaries, understanding the intent behind the 20% insurance cap rule, and catching subtle ordering issues in the calculation pipeline. AI got me 80% of the way fast, but the last 20% required actually understanding the domain.

Was it worth it?

The spreadsheet took a full day to update for the 2026 reform. The web app took about the same time—but now I have tests that prove every calculation is correct, constants I can update in one place, and a tool I can share with anyone who needs it.

It was also a good proof-of-concept for AI-assisted development. Not “AI wrote my app”—more like “AI handled the mechanical translation while I focused on correctness.” That’s a workflow I’ll keep using.

If you’re maintaining a complex spreadsheet that keeps growing, it might be worth asking whether it’s crossed the line from “quick calculation” to “business logic that deserves real code.” For me, that line was a tax reform and a formula I couldn’t trace anymore.