Back to work
2026·SaaS · Billing & InvoicingBETA

HISABLY

Offline-first billing & invoicing SaaS for SMBs.

My own SaaS — an offline-first billing and invoicing app aimed at small and mid-sized businesses, launching in the UAE with VAT-compliant tax invoices, quotations, credit notes, and proforma. Built as a Turborepo monorepo: a Next.js 14 PWA that works without a connection (IndexedDB via Dexie), a FastAPI + async SQLAlchemy backend on PostgreSQL, and a shared TypeScript package that holds the invoice math + a pluggable tax-regime engine.

HISABLY — live site screenshot
Duration
Ongoing
Team
Solo founder + full-stack
My role
Founder / full-stack engineer
Client context

Numbers about the client's business — useful context, not my engineering output.

Launch market
UAE
Invoice types
6
Offline-first
100%
Tax engines
Pluggable
My engineering output

Numbers I produced — measurable, attributable to the work I did.

Stage
Private beta
Web ↔ API math parity
Cross-validated to the fils
Services in the monorepo
3 (web · api · shared)
Lighthouse perf (mobile)
TODO
What I built

My specific scope on this project — separate from anything the client team supplied.

The story

From brief to production system.

Challenge

Most SMB billing tools in this region either assume you're always online or stop at one country's tax rules. A shopkeeper or contractor in the UAE needs UAE VAT today, but they also operate cross-border into KSA and Pakistan — and they cannot lose the ability to bill a customer when the internet flakes. The hard part is keeping invoice math (discounts, VAT rounding, totals) provably identical between the offline web client and the server, and pluggable enough that adding a new country isn't a rewrite.

Solution

Three-package Turborepo: `apps/web` (Next.js 14 App Router PWA with Dexie for offline persistence), `apps/api` (FastAPI + async SQLAlchemy + PostgreSQL + Alembic migrations), and `packages/shared` (TypeScript invoice math + pluggable tax-regime engine, mirrored in Python on the backend). Core invoicing loop — auth, tenants, customers/suppliers/items/accounts, all six invoice types, payments + allocations, expenses, reports, settings — is real and tenant-scoped, not mocked.

Outcome

Private beta running on hisably-web.vercel.app. Core invoicing loop ships VAT-compliant tax invoices, quotations, credit notes, proforma, and purchase bills; the same math runs offline in the browser and on the API. A full read-only audit catalogued every screen against the backend so the roadmap is data-driven, not guesswork.

Real constraints

The boundaries that shaped the build.

Honest tradeoffs

What I chose, and what I gave up.

Decision

Invoice math lives in a shared TypeScript package, with a Python port cross-validated against the same fixtures

What we gave up

Single-language simplicity. Won correctness — both the offline client and the server compute totals identically, verified by shared fixtures.

Decision

Offline-first PWA via IndexedDB instead of a thin online-only client

What we gave up

Simpler state management and fewer sync edge cases. Won real reliability — the app keeps working in a warehouse, on a job site, or when 4G drops.

Process · Ongoing

How it shipped, week by week.

Phase 1
01 / 5

Architecture

ERD, API route list, screen map, design tokens, and the tax-engine interface — written before scaffolding so the monorepo had a single source of truth from day one.

Phase 2
02 / 5

Core invoicing loop

Auth + tenants + customers/suppliers/items/accounts, then all 6 invoice types, payments + allocations, expenses, and reports.

Phase 3
03 / 5

Offline + PWA shell

Dexie schema for offline persistence, service worker, and sync semantics — the unsexy plumbing that makes the rest of the app actually reliable.

Phase 4
04 / 5

Tax engine + PDFs

UAE VAT regime, invoice PDF rendering, recurring template endpoint, low-stock alerts, and the dashboard KPI calls.

Phase 5
05 / 5

Audit + harden

Read-only screen-by-screen audit (UI → API → repository) to surface every Works / Partial / Dummy gap so the next milestones are data-driven.

Inside the system

What it does. How it's built.

Features

  • VAT-compliant tax invoices, quotations, credit notes, proforma, purchase bills, delivery notes
  • Offline-first PWA — keep billing without a connection, syncs when back online
  • Pluggable multi-country tax engine (UAE live, KSA + PK on the roadmap)
  • Customers, suppliers, items, categories, accounts — fully tenant-scoped
  • Payments + allocations against open invoices
  • Expenses, basic reports (sales, P&L, aging), dashboard KPIs
  • Recurring invoice template endpoint with run-due trigger
  • Email + OTP auth with anti-enumeration on forgot-password
  • Industry-profile registry for vertical-specific item fields

Architecture

  • 01Turborepo workspaces — apps/web, apps/api, packages/shared
  • 02Next.js 14 App Router PWA with Dexie (IndexedDB) for offline persistence
  • 03FastAPI + async SQLAlchemy 2.0 on PostgreSQL, Alembic for migrations
  • 04Shared invoice math in TypeScript, ported to Python and cross-validated against shared fixtures
  • 05Tenant scoping enforced in the repository layer — no cross-tenant data leaks by construction
  • 06Docker Compose for the full local stack (Postgres + API + web)
  • 07Vercel for the web app; API deploys planned alongside it
Stack
Next.js 14ReactTypeScriptTailwind CSSDexie (IndexedDB)PWAFastAPISQLAlchemy 2.0 (async)PostgreSQLAlembicTurborepoDocker ComposeVercel
From the codebase

Annotated excerpts.

01 · Pluggable tax-regime interface. UAE VAT is the first implementation; KSA and Pakistan slot in behind the same shape without touching invoice math.
packages/shared/src/tax/index.tstypescript
export interface TaxLine {
  rate: number;            // e.g. 0.05 for UAE 5% VAT
  taxableAmount: number;   // post-discount line total
  taxAmount: number;       // rounded per the regime's rule
  code: string;            // "VAT_STD" | "VAT_ZERO" | "VAT_EXEMPT" | ...
}

export interface TaxRegime {
  country: "AE" | "SA" | "PK";
  /** Per-line tax computation (handles rounding nuances per country). */
  computeLine(input: {
    unitPrice: number;
    qty: number;
    discount: number;       // line-level discount (absolute)
    taxCode: string;
  }): TaxLine;
  /** Invoice-level rounding — UAE rounds totals per VAT-compliant rules. */
  finalizeTotal(subtotal: number, tax: number): {
    grand: number;
    rounding: number;
  };
}

export const REGIMES: Record<string, TaxRegime> = {
  AE: uaeVatRegime,   // live
  // SA: ksaVatRegime, // TODO: ZATCA e-invoicing
  // PK: pkSalesTax,   // TODO: provincial split + WHT
};
02 · Python mirror of the TS tax engine. Same fixtures, same totals — the FastAPI backend cannot disagree with the offline web client.
apps/api/app/tax/uae.pypython
from decimal import Decimal, ROUND_HALF_UP

UAE_VAT_STANDARD = Decimal("0.05")

def _q(d: Decimal) -> Decimal:
    return d.quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)

def compute_line(*, unit_price: Decimal, qty: Decimal,
                 discount: Decimal, tax_code: str) -> dict:
    taxable = _q((unit_price * qty) - discount)
    rate = UAE_VAT_STANDARD if tax_code == "VAT_STD" else Decimal("0")
    tax  = _q(taxable * rate)
    return {
        "rate": float(rate),
        "taxableAmount": float(taxable),
        "taxAmount": float(tax),
        "code": tax_code,
    }

def finalize_total(subtotal: Decimal, tax: Decimal) -> dict:
    grand = _q(subtotal + tax)
    return {"grand": float(grand), "rounding": 0.0}
Other projects

Continue browsing

Have a project like this in mind? Let's talk.

Send me a brief and I'll respond within 24 hours.

← Home© 2025 Ali RazzaqContact →