POS Feature Plan · pos-v1.4

Thailand VAT Implementation

How the Setpoint POS will handle Thai VAT — calculating 7% on every sale, printing legally compliant tax invoices, and generating sequential invoice numbers per order.

DraftRequested by Nestor2026-03-21

Overview

What we're building and why

🧾
Every receipt becomes a legal tax document

In Thailand, a receipt is a government-recognised 'Tax Invoice'. It must include the club's registered name, government business number, a unique sequential receipt number, and the VAT amount shown separately. We'll update every receipt the POS prints to include all of this.

🔢
7% VAT on every sale — automatic

Thailand's current VAT rate is 7%. It applies to court time, food and drinks, rackets, and memberships. The POS will automatically add 7% to every order at checkout. Staff don't need to think about it.

➕
VAT also applies to the service charge

Thai law requires VAT on the combined total: items + service charge. So ฿100 food + ฿10 service = ฿110 base, then 7% VAT = ฿7.70. The POS will handle this automatically using the existing is_taxable flag.

⚙️
One-time setup by the manager

A manager enters the club's registered business name, address, and government business number once in a new settings screen. All receipts are filled in correctly from that point on. Cashiers don't need to do anything differently.

🔑
Auto-numbered receipts

Thai law requires every tax invoice to have a unique sequential number (INV-2026-000001, INV-2026-000002, etc.). Generated automatically for every order. Counter resets each new year. No two receipts ever share a number.

🏢
Corporate receipts — rare, optional

Occasionally a company booking a court needs a full corporate receipt for their accounting team. The cashier can enter the company's details before completing the sale. This is off by default — regular customers never see this prompt.

Thai VAT Rules

What applies at a sports club POS

7%
Standard Rate

In force until 30 Sep 2026

0%
Zero-rated

Exports only — not applicable locally

Exempt
VAT Exempt

Raw unprocessed agriculture, healthcare — not sports clubs or cafés

1.8M THB
Annual Threshold

Above this: mandatory registration + monthly filing

Sale TypeRateReason
Court rental (padel, tennis, pickleball)7%Commercial venue — amateur sports exemption does not apply to businesses charging fees
Membership fees7%Taxable commercial service
Prepared food & beverages (café)7%Restaurant/café service is taxable — the food exemption only covers raw unprocessed agriculture
Sports equipment & accessories7%Standard taxable goods
Service charge (10%)Included in VAT baseThai law: VAT applies to the combined subtotal + service charge
Tips / gratuityNot applicableVoluntary — not subject to VAT

Key rule — VAT on service charge

Items subtotal฿740.00
+ Service Charge (10%)฿74.00
VAT base (items + service charge)฿814.00
+ VAT 7%฿56.98
Total charged to customer฿870.98

Existing Infrastructure

What already exists — we build on top of it

The POS already has tax tables, order tax breakdowns, and a tax CRUD UI. We are not starting from zero — we activate and extend what's there.
ComponentHow we use it
pos_taxes tableAlready has name, rate, is_inclusive, apply_to_all, is_active. Will be used as-is — just create a 'VAT 7%' entry.
pos_order_taxes tableAlready designed to record per-order tax breakdowns. Will be populated at checkout.
Tax CRUD UI (/items/taxes)Already built. Staff can create, edit, and toggle taxes. No changes needed.
Service charge is_taxable flagAlready exists on pos_order_service_charges. Will use this to include taxable service charges in the VAT base.
tax_total_cents on ordersColumn already exists on pos_orders. Will be populated correctly at checkout.

New Components

What we're adding

calculate-taxes.ts utility

Tax engine: computes VAT (inclusive or exclusive), returns per-tax line results for recording.

pos_facility_tax_profile table

Stores club's 13-digit government business number, registered name, address, and branch — all required on Thai tax invoices.

pos_invoice_sequences table + DB function

Generates unique sequential invoice numbers (e.g. INV-2026-000042). Atomic — prevents duplicates.

Invoice fields on pos_orders

Adds tax_invoice_number, tax_invoice_type, and buyer detail columns.

Tax Profile settings page (/settings/tax-profile)

UI for the operator to enter their registration details once. All receipts use it automatically.

Updated receipt layout

'Tax Invoice' header, seller business number, invoice number, VAT line — all legally required on Thai receipts.

Build Phases

6 phases · 3 new migrations

1
Tax Calculation Engine

Wire the existing tax configuration into the checkout so VAT is computed and recorded on every order. The infrastructure is already in place — this phase activates it.

  • Create calculate-taxes.ts — handles inclusive and exclusive VAT
  • Update use-order-mutations.ts — fetch active taxes, run calculation, populate tax_total_cents, insert pos_order_taxes rows
  • Include taxable service charges in the VAT base (using existing is_taxable flag)
  • Update total_cents to include exclusive VAT amounts
  • Show VAT preview in terminal checkout before payment is confirmed
2
Tax Settings Screenmigration: pos_facility_tax_profile

A settings page where the manager enters the club's government-registered details once. These appear on every receipt automatically.

  • Migration: create pos_facility_tax_profile table
  • Create useFacilityTaxProfile() query hook
  • Create useUpsertFacilityTaxProfile() mutation hook
  • New settings page at /settings/tax-profile
  • Fields: VAT registered toggle, 13-digit business number with validation, registered name, address, branch number/name, invoice format preferences
3
Auto-Numbered Invoicesmigration: pos_invoice_sequences + ALTER pos_orders

Every order gets a unique sequential invoice number automatically at checkout. Resets each year. No duplicates.

  • Migration: create pos_invoice_sequences table + next_invoice_number() PostgreSQL function
  • Migration: alter pos_orders — add tax_invoice_number, tax_invoice_type, and buyer columns
  • Create format-invoice-number.ts — formats per facility settings (e.g. INV-2026-000042)
  • Call next_invoice_number RPC after order insert, write invoice number back to order
4
Receipt / Tax Invoice Layout

Update receipts to display all legally required Thai tax invoice fields.

  • Tax Invoice / ใบกำกับภาษี header
  • Seller's registered name, government business number, and branch
  • Unique sequential invoice number and date in DD/MM/YYYY
  • VAT as a separate line item clearly labelled
  • Print-friendly layout for thermal receipt printers
5
Corporate Receipt Option

For the rare case where a company (not a regular customer) needs a full corporate invoice for their accounts department. Optional, off by default — regular customers never see this.

  • 'This is a corporate booking' toggle in checkout — hidden by default
  • When toggled: prompts for company name, address, and government business number
  • Validates business number format (13 digits) before allowing checkout
  • Receipt includes company details for their accounting records
6
Test Data & Automated Tests

Load realistic test data and run automated tests to confirm everything works correctly before going live.

  • Seed: VAT 7% tax with apply_to_all = true + inclusive variant for testing
  • Seed: demo facility tax profile with sample business number and registered details
  • E2E: order total includes 7% VAT; service charge included in VAT base; invoice number generated; pos_order_taxes row created; corporate receipt flow

Open Questions

Needs answers from Nestor before build starts

All questions must be resolved upfront. Defaults are listed — those will be used if not answered.
#QuestionDefault
Q1Prices before or after VAT? Are menu prices shown before VAT (e.g. ฿100 + 7% = ฿107), or does the listed price already include VAT (฿107 shown, ฿7 extracted)?Before VAT — 7% added at checkout
Q2Is the club VAT registered? Has it exceeded the 1.8M THB threshold and registered with the Revenue Department?Assume yes — build for registered
Q3Service charge in VAT base? Thai law requires VAT on (items + service charge) combined — confirm the club follows this.Yes — Thai law requires it
Q4Corporate receipts in v1? Should Phase 5 (company details at terminal) be included in the first release, or deferred?Defer to v2
Q5Invoice number format? Preferred format — e.g. INV-2026-000001 or something else?INV-{YYYY}-{000000}
Q6Any VAT-free items? Are there items that should not have VAT, or is 7% universal across everything?Universal 7% on all items
Q7Show VAT before payment? Should the cashier screen show the VAT breakdown before the customer pays?Yes — show in checkout summary
Q8Historical orders? Past orders were recorded without VAT. Leave as-is or add a marker indicating they predate tax?Leave as-is — no changes to history