All files / lib/partners tds.ts

100% Statements 6/6
100% Branches 4/4
100% Functions 1/1
100% Lines 4/4

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61                                                                                                43x   26x 26x   7x              
/**
 * The TDS gate (FR-M-7).
 *
 * Above a threshold of payments in one Indian financial year we cannot pay a
 * partner without their PAN, because past that point the payment is one we have
 * to deduct tax at source on and report against a PAN. The threshold is
 * ₹15,000 by default and lives in `program_config` so it can follow the law
 * without a deploy.
 *
 * This is arithmetic only — no clock of its own, no database. `fyStart` in
 * `lib/rrm/time.ts` decides which payments count; this decides what to do about
 * the total. Keeping them apart is what makes the 1-April boundary testable
 * without a ledger fixture.
 *
 * PAISE, integers. Same rule as the rest of the money path.
 */
 
export type TdsGate =
	| { blocked: false }
	| {
			blocked: true;
			/** Already paid this FY, excluding the request being assessed. */
			fyPaidPaise: number;
			/** What this payment would take the FY total to. */
			wouldTotalPaise: number;
			thresholdPaise: number;
	  };
 
/**
 * May this payment be made?
 *
 * Assessed on the total INCLUDING the payment in question, not on what has
 * already been paid. The obligation attaches to the payment that crosses the
 * threshold, so checking afterwards means the one payment we most needed a PAN
 * for is the one that went out without it.
 *
 * A partner with a PAN on file is never blocked — recording the deduction is
 * then an accounting step, not a reason to withhold someone's money.
 */
export function tdsGate(input: {
	/** Sum of payments already made to this partner this financial year. */
	fyPaidPaise: number;
	/** The payment being assessed. */
	requestPaise: number;
	thresholdPaise: number;
	/** Whether `partners.pan_last4` is populated. Never the value itself. */
	hasPan: boolean;
}): TdsGate {
	if (input.hasPan) return { blocked: false };
 
	const wouldTotalPaise = input.fyPaidPaise + input.requestPaise;
	if (wouldTotalPaise < input.thresholdPaise) return { blocked: false };
 
	return {
		blocked: true,
		fyPaidPaise: input.fyPaidPaise,
		wouldTotalPaise,
		thresholdPaise: input.thresholdPaise,
	};
}