Skip to content

TDS (India)

Tax Deducted at Source on supplier payments — sections 194C / 194J / 194Q. Calculator, threshold logic, 26Q CSV export, and the known-outstanding UI work.


1. Sections covered

Sourced from the seed rates in supabase/migrations/20260418150000_tds_scaffolding.sql:113-134 and mirrored in src/lib/tds.ts:38-79.

Section Description Deductee types Rate Single threshold Aggregate threshold
194C Contractor / works contract individual, huf 1.0% ₹30,000 ₹1,00,000
194C Contractor / works contract company 2.0% ₹30,000 ₹1,00,000
194J Professional / technical fees any 10.0% ₹30,000 — (none)
194Q Purchase of goods (FY aggregate > ₹50L) any 0.1% ₹50,00,000

Payroll TDS is out of scope

Sections 192 / 24Q (salary TDS) belong to the payroll module. They use a slab-based calculation, not a flat rate. Do not extend src/lib/tds.ts for payroll.


2. Data model

TDSRate — rate master

Keyed by (section, deducteeType, effectiveFrom) so rates can be revised without losing history. Fields:

  • section'194C' | '194J' | '194Q' (constraint-free text).
  • deducteeType'individual' | 'huf' | 'company' | 'any'.
  • rate — percentage, e.g. 1.000.
  • thresholdSingle, thresholdAggregate.
  • effectiveFrom, effectiveTo, active.

TDSDeduction — ledger

One row per actual deduction applied. Links to the source transaction (paymentId or supplierTransactionId), the challan (Form 281), and the certificate (Form 16A).

Status: pendingdepositedcertificate_issued.

Supplier — new columns

  • tdsSection — default TDS section for this supplier (e.g. '194C').
  • deducteeType — default deductee type (default 'company').
  • panNumber — for the 26Q return.

Pre-populates the "Deduct TDS" toggle on supplier-payment forms.

Chart of accounts

  • 2401 TDS Payable — Current Liability, under Duties & Taxes.
  • FinanceConfig.tdsPayableAccountCode / tdsPayableAccountId point at it. Default is '2401'.

3. Calculator

src/lib/tds.ts. All pure functions.

resolveTdsRate(section, deducteeType, rates?)

src/lib/tds.ts:112-121. Finds the rate row. Exact deducteeType match wins; falls back to the 'any' row for that section; returns null if no row matches.

calculateTds(input)

src/lib/tds.ts:136-214. Input:

{
  section: '194C' | '194J' | '194Q',
  deducteeType: 'individual' | 'huf' | 'company' | 'any',
  amount: number,
  aggregateYearToDate?: number,
  rates?: TDSRate[], // override for testing
}

Output includes tdsAmount, rateApplied, netAmount, grossAmount, belowThreshold, rate, reason.

Threshold logic (matches the Act)

Three shapes, selected by which thresholds are non-zero:

  1. 194Q-style — only thresholdAggregate > 0. TDS applies only on the portion exceeding the FY aggregate cap (src/lib/tds.ts:154-179). The first ₹50 lakh is exempt; every rupee after is taxed at the rate.
  2. 194J-style — only thresholdSingle > 0. Exempt when this single payment is below the threshold.
  3. 194C-style — both thresholds set. Exempt only when both the single payment AND the YTD aggregate are below their thresholds. Either exceeding triggers the deduction on the full amount.

Returns belowThreshold: true, tdsAmount: 0 when exempt, with a human-readable reason for the UI to surface.

Rounding

Amounts are rounded via roundMoney(...) (paise). The calculator never emits fractional paise.


4. 26Q CSV export

generate26QCsv(deductions, fromDate?, toDate?) at src/lib/tds.ts:282-319.

Columns (14)

src/lib/tds.ts:249-264. A subset of the CBDT 26Q schema — the fields most audits actually check:

Serial No, Deductee PAN, Deductee Name, Section, Deductee Type,
Payment Date, Gross Amount, Rate (%), TDS Amount, Net Paid,
Challan No, Challan Date, Certificate No, Status

Not the full return

This is a quarterly-review spreadsheet, not the upload file. The full 26Q return is filed via the TDS utility using this same data (re-keyed into the utility).

Dates are filtered by the first 10 chars of createdAt so the comparison is timezone-agnostic.


5. Endpoints

All live in handleFinanceTds at src/lib/api.ts:24289.

Method Path Permission Purpose
GET /finance/tds/rates finance.tds.view Active rate master, sorted by section + effectiveFrom.
GET /finance/tds/deductions finance.tds.view Deduction ledger with supplier name. Filterable by section, status, fromDate, toDate.
GET /finance/tds/26q finance.tds.export Returns { csv, count, fromDate, toDate, filename }.

6. Permissions

From supabase/migrations/20260418150100_tds_permissions.sql:

Permission Effect
finance.tds.view Read rate master + deduction ledger.
finance.tds.deduct Apply TDS on a supplier payment.
finance.tds.export Export the quarterly 26Q CSV.

Role grants

Role view deduct export
CEO, GM, IT_ADMIN, ADMIN_HR
FINANCE_MANAGER, ACCOUNTANT
CASHIER
AUDITOR

7. UI — current state and follow-ups

A port-forward TDS UI toggle on the supplier-payment voucher form is the primary near-term piece of work.

Known follow-ups (explicitly marked TODO)

  • UI toggle on supplier payments — surface the "Deduct TDS" switch on the supplier-payment voucher form so cashiers with finance.tds.deduct can apply TDS while recording the payment. Pre-populate section + deducteeType + PAN from Supplier.tdsSection / deducteeType / panNumber.
  • Auto-compute aggregateYearToDate — the calculator takes a YTD argument but nothing populates it today. Needs a helper that sums TDSDeduction.grossAmount YTD per (supplierId, section) and threads it into the calculateTds call.
  • Challan PATCH endpoint — no PATCH /finance/tds/deductions/:id exists. Once a challan is deposited, operators should be able to flip status to deposited and fill challanNo / challanDate from the UI.
  • Certificate upload (Form 16A) — future work. certificateNo is already on the row but there is no file-attach flow.

8. Tests

src/lib/tds.test.ts covers:

  • Rate resolution (exact vs any fallback, no-match path).
  • 194C below / above thresholds on both sides (single, aggregate).
  • 194J (single-threshold only).
  • 194Q (aggregate-only, taxed only on excess).
  • 26Q CSV header row, ordering, date filter, CSV escaping.

Run: npx vitest run src/lib/tds.test.ts.


  • src/lib/tds.ts — calculator + 26Q generator.
  • src/lib/tds.test.ts — fixtures mirror the seed rates exactly.
  • supabase/migrations/20260418150000_tds_scaffolding.sql — tables, seeds, supplier columns, chart-of-accounts entry.
  • supabase/migrations/20260418150100_tds_permissions.sql — the three finance.tds.* permissions and their role grants.
  • src/lib/api.ts:24289handleFinanceTds (three endpoints).