ttushar.

Full-stack developer · 2026

CICO PMS

A property management system for hotels and RV parks: reservations, rate plans, housekeeping, payments across three gateways, and two-way inventory sync with the OTA channel manager. I joined an existing codebase and spent four months on the reservation and integration core.

PythonFlaskVue 3PostgreSQLCelery
Role
Full-stack developer
Engagement
Since Mar 2026, joined an existing team
Scope
Reservations, channel sync & payments core
Contribution
~1,050 commits across backend and frontend

The problem

What was broken

A property management system is the software a hotel or campground actually runs on. Someone books a room, the front desk checks them in, housekeeping gets told to clean it, a card gets charged, and at the end of the night an audit rolls the books forward. If any of that breaks, a real person is standing at a real desk with a real guest waiting — and the hardest failures are the ones where two systems quietly disagree.

Overselling through the channels

Properties sell through the big travel sites via a channel manager. Report four rooms free on a night when you have one, and a booking site cheerfully sells three rooms that don't exist — a failure that only shows up under load, when it hurts most.

Two systems, no shared transaction

A reservation write hits the local database, then calls the channel manager over HTTP. When the second step fails, local truth and channel truth disagree with nothing recording that they disagree.

Pricing that is never just a number

A nightly rate is a rate plan plus restrictions, occupancy, taxes, fees and discounts, resolved per stay on specific dates — and booking channels want a separate variant per occupancy level, all of them correct.

Payments at a physical front desk

Card terminals are devices in a building, not redirects. The guest taps, the network drops, and you cannot tell whether the card was charged — idempotency stops being a nicety.

The product

What I built

CICO PMS runs hotels and RV parks end to end: reservations, rooms and rate plans, folios, housekeeping, night audit, guest messaging, a public booking page, and a website builder for property owners. Three processes share one codebase — a Flask REST API, a Socket.IO server for live calendar updates, and a Celery worker for email, nightly jobs and channel pushes — against PostgreSQL and RabbitMQ, with a Vue 3 frontend of about 760 components.

I joined about a year into the project, in March 2026, and landed roughly 550 of the backend's 1,850 commits and 500 of the frontend's 2,100 over four months. My work clustered where correctness meets the outside world: keeping availability true across the channel manager, making writes that touch external systems survive failure, and payments. That included fixing a sync defect that reported a room type's full configured count as available for 365 days regardless of bookings — a bug that only ever fails as an oversell — across all three code paths that shared it.

Capabilities

Key features

Reservations & availability

The full booking core — reservations, rooms and room types, folios, and a resource-timeline availability calendar the front desk plans against.

Rate plans & occupancy pricing

Rate plans with restrictions, taxes, fees and discount codes, priced per occupancy level, with a quick-quote wizard that agrees with the backend to the cent.

Two-way channel manager sync

Rates and availability flow out to Channex and every connected travel site; bookings, modifications and cancellations flow back in over a webhook and reconcile against local rooms and rate plans.

Housekeeping & night audit

Check-ins hand off to housekeeping, and the nightly audit rolls the books forward — the operational loop a property runs every single day.

Payments across three gateways

Stripe, Authorize.Net and CardConnect, selectable per account, plus physical CardConnect terminals for front-desk swipes. Card data never touches the servers — hosted tokenization only.

Guest messaging & booking site

Direct guest messaging, a public booking page, and a website builder so property owners can run their own storefront.

Under the hood

How it's engineered

The transactional outbox

Side effects to external systems are written as outbox rows inside the same database transaction as the business write, then drained by a worker with FOR UPDATE SKIP LOCKED and per-event backoff. The write and the intent to sync are the same commit, so they can no longer disagree.

A migration you can't cheat

A CI script fails the build if a file commits the session directly unless it's on an allowlist that started at 102 files — and entries can only be deleted, never added. That turned a refactor nobody would finish into a burn-down list with a deadline.

Per-date availability, audited

Availability pushed to channels is computed per date from actual overlapping reservations — not a static room count — and I wrote an audit tracing all eleven code paths that mutate availability to the sync each one calls. Coverage is known, not hoped.

A boring convention that scales

120 SQLAlchemy models across 76 feature modules, every module the same four files, 255 migrations behind it. The convention is dull, and it's the reason I could work in unfamiliar corners without reading the whole system first.

Where it landed

Outcomes

The class of ticket that shaped my first months — local data and channel data quietly disagreeing — stopped arriving once the outbox pattern landed, because the write and the sync intent became atomic. The availability audit answered in an afternoon the questions I had been guessing at for weeks, and the CI allowlist keeps the commit-discipline migration shrinking instead of stalling.

The honest ledger: payments are deliberately the one area still outside the outbox, because a retried availability push is harmless and a retried charge is not — the idempotency work lands first. And if I did it again, the outbox would come before the availability fix, not after: I spent real time investigating individual inconsistencies that were all symptoms of the same dual-write problem.