// Payslip: one employee's pay for one month. Earnings are prorated for // loss-of-pay days; provident fund, Employees' State Insurance and // Maharashtra professional tax are worked out from them by the rules // below; income tax (TDS) comes from accounts. Then net pay in figures and // words, and what the company contributes on top. // // The employee and every figure below are examples: replace each one. // Salary figures come from the payroll register; TDS from the tax worked // out on the employee's declaration. Never estimate either. // // Statutory assumptions, for the company's CA to confirm before the first // payslip goes out (the figures are the kit's, not verified law): // - Provident fund: 12% of basic from each side, on basic up to the // ₹15,000 wage ceiling when pf-ceiling is true. Of the company's 12%, // 8.33% of wages up to ₹15,000 goes to the Employees' Pension Scheme // (EPS) and the rest to the provident fund (EPF). // - Employees' State Insurance: covers monthly wages of ₹21,000 or less; // 0.75% from the employee and 3.25% from the company, each rounded up // to the rupee. Coverage holds for the whole contribution period // (April-September, October-March) once it starts. // - ESI and professional tax are worked out on the month's wages: the // prorated salary and `additions`, but not `reimbursements`. Whether a // particular one-off payment (a bonus, arrears) counts as wages is for // the CA to say. // - Maharashtra professional tax on the month's wages. Women: nil up to // ₹25,000, then ₹200. Men: nil up to ₹7,500, ₹175 up to ₹10,000, then // ₹200. The ₹200 slab is ₹300 in February (₹2,500 a year). For anyone // working in another state, give that state's figure. // - Loss of pay is prorated over the calendar days in the month. #import "../rivant.typ": * // ---------------------------------------------------------------- data #let data = ( // Reference: the next number in the payslips register (none = blank). seq: 84, // Date of issue. date: "2026-09-30", // Any date in the month paid for. month: "2026-09-01", // The day the salary reached the employee's account. paid-on: "2026-09-30", // Pay is personal: "Private and confidential", marked on every page. confidential: true, watermark: "confidential", employee: ( name: "Ananya Iyer", id: "RM-0014", designation: "Senior Visual Designer", department: "Design", joined: "2025-04-01", location: "Navi Mumbai", // Identity and account numbers come from HR records only (none = blank). pan: none, uan: none, // The last four digits of the salary account, e.g. "XXXX 1234". account: none, ), // Maharashtra's professional tax has separate slabs for women and men: // "women" or "men". pt-slabs: "women", // Days of loss of pay (unpaid absence) in the month. lop-days: 0, // The monthly salary, before proration, in rupees. salary: ( basic: 34000, hra: 13600, special: 20400, // Any other monthly allowance, as ("Label", amount) pairs. other: (), ), // One-off earnings this month, not prorated, counted as wages for ESI // and professional tax: ("Performance bonus", 10000), ("Arrears", 2500). additions: (), // Expenses paid back with the salary, not prorated and not wages: // ("Reimbursement: travel", 1800). reimbursements: (), // Income tax deducted this month (TDS), from accounts, on the regime and // declarations the employee chose: an input, never worked out here // (none = blank). 0 in this example. tds: 0, // Other amounts recovered: ("Salary advance", 5000). recoveries: (), // Provident fund: 12% of basic from each side. true applies it to basic // up to the ₹15,000 wage ceiling; false to the full basic. pf-ceiling: true, // Whether the employee is in the Employees' Pension Scheme. Members who // joined on or after 1 September 2014 earning over ₹15,000 are not; then // the company's whole 12% goes to their provident fund. Assumption to // confirm with the CA for each employee. eps: true, // Employees' State Insurance. auto applies it when the monthly gross // salary is ₹21,000 or less; true or false to decide it, e.g. to keep an // employee covered to the end of a contribution period. esi: auto, // Maharashtra professional tax for the month. auto works it out from the // slabs; give a figure to override. professional-tax: auto, // Leave left at the end of the month, in days: () to leave it out. leave: (("Earned leave", 11), ("Casual leave", 5)), ) // -------------------------------------------------------------- layout #let k = document-kinds.at("payslip") #let month = to-date(data.month) #let first = datetime(year: month.year(), month: month.month(), day: 1) #let next = if month.month() == 12 { datetime(year: month.year() + 1, month: 1, day: 1) } else { datetime(year: month.year(), month: month.month() + 1, day: 1) } #let last = next - duration(days: 1) #let days = int((next - first).days()) #let paid-days = days - data.lop-days #let sum(pairs) = if pairs.all(p => p.at(1) != none) { pairs.map(p => p.at(1)).sum(default: 0) } else { none } // A monthly figure for the days paid, to the rupee. #let earned(x) = if x == none { none } else { int(calc.round(x * paid-days / days)) } #let s = data.salary #let structure = (("Basic salary", s.basic), ("House rent allowance", s.hra), ("Special allowance", s.special)) + s.other #let full-gross = sum(structure) // Wages: what ESI and professional tax are worked out on. #let wage-lines = structure.map(((l, a)) => (l, earned(a))) + data.additions #let wages = sum(wage-lines) #let earnings = wage-lines + data.reimbursements #let basic = earned(s.basic) // Provident fund: 12% of basic each side, rounded to the rupee. Of the // company's 12%, 8.33% of wages up to ₹15,000 goes to the pension scheme // (EPS), the rest to the provident fund (EPF). #let pf-wages = if basic == none { none } else if data.pf-ceiling { calc.min(basic, 15000) } else { basic } #let pf = if pf-wages == none { none } else { int(calc.round(pf-wages * 12 / 100)) } #let eps = if pf-wages == none { none } else if data.eps { int(calc.round(calc.min(pf-wages, 15000) * 8.33 / 100)) } else { 0 } #let pf-label = if pf-wages != none and basic != none and pf-wages < basic { "Provident fund (12% of " + inr(pf-wages, decimals: 0) + ")" } else { "Provident fund (12% of basic)" } // Employees' State Insurance: 0.75% from the employee and 3.25% from the // company, each rounded up to the rupee. Coverage follows the monthly // salary before loss of pay; contributions, the wages actually paid. #let esi = if data.esi == auto { full-gross != none and full-gross <= 21000 } else { data.esi } #let esi-employee = if esi and wages != none { int(calc.ceil(wages * 0.75 / 100)) } else { none } #let esi-company = if esi and wages != none { int(calc.ceil(wages * 3.25 / 100)) } else { none } // Maharashtra professional tax on the month's wages (see the slabs at the // top of this file). #let pt = if data.professional-tax != auto { data.professional-tax } else if wages == none { none } else { let top = if month.month() == 2 { 300 } else { 200 } if data.pt-slabs == "women" { if wages <= 25000 { 0 } else { top } } else if wages <= 7500 { 0 } else if wages <= 10000 { 175 } else { top } } #let deductions = ( (pf-label, pf), ..if esi { (("Employees’ State Insurance (0.75%)", esi-employee),) } else { () }, ("Professional tax", pt), ("Income tax (TDS)", data.tds), ) + data.recoveries #let gross = sum(earnings) #let total-deductions = sum(deductions) #let net = if gross == none or total-deductions == none { none } else { gross - total-deductions } #show: rivant-document.with( kind: k.name, title: k.name, title-light: "for " + month-year(month), reference: ref-no(k.code, data.seq, date: data.date), date: data.date, meta: (("Pay period", str(first.day()) + "–" + short-date(last)), ("Paid on", long-date(data.paid-on))), letterhead: k.letterhead, confidential: data.confidential, watermark: data.watermark, ) #let e = data.employee #meta-grid( ("Employee", e.name), ("Employee ID", e.id), ("Designation", e.designation), ("Department", e.department), ("Date of joining", short-date(e.joined)), ("PAN", e.pan), ("UAN", e.uan), ("Salary account", e.account), ("Days in month", str(days)), ("Days paid", str(paid-days)), ("Loss-of-pay days", str(data.lop-days)), ("Work location", e.location), columns: 4, ) #pay-table(earnings: earnings, deductions: deductions, net: false) #lead-figure( "Net pay", net, note: [Paid by bank transfer on #long-date(data.paid-on).#if data.lop-days > 0 [ Earnings are for #paid-days of #days days.]], ) #let m(x) = if x == none { blank("Amount", width: 16mm) } else { inr(x, symbol: false) } #let company-lines = ( ("Provident fund (EPF)", if pf == none or eps == none { none } else { pf - eps }), ..if data.eps { (("Pension scheme (EPS, 8.33%)", eps),) } else { () }, ..if esi { (("Employees’ State Insurance (3.25%)", esi-company),) } else { () }, ) // The company's contributions beside the leave balance. #grid( columns: (1fr, 1fr), column-gutter: 12mm, data-table( columns: (1fr, 25mm), header: ("Company contributions", "Amount (₹)"), align: (left, right), rows: company-lines.map(((l, a)) => (l, m(a))), footer: (text(weight: 600)[Total], text(weight: 600, m(sum(company-lines)))), ), if data.leave.len() > 0 { data-table( columns: (1fr, 25mm), header: ("Leave left at month end", "Days"), align: (left, right), rows: data.leave.map(((l, n)) => (l, str(n))), ) }, ) #notes(title: none)[ This is a computer-generated payslip and needs no signature. Company contributions are paid on top of your pay, as part of your CTC. Provident fund is 12% of basic#if data.pf-ceiling [, on basic up to ₹15,000 a month;] else [;] both shares are paid to the EPFO under your UAN, and part of the company's share goes to the Employees' Pension Scheme. For questions about your pay, write to #company.legal-email quoting the reference above. ]