@zenuinebit/expense-claims
v0.1.0
Published
Employee expense claims: policy limits, receipts, duplicates, approval flow, mileage and advance settlement.
Readme
@zenuinebit/expense-claims
Employee expense claims checked against a policy you define: limits per expense, day and claim, receipt and description rules, mileage by distance, duplicate warnings, an approval flow, advance settlement and claimable GST. Immutable: every function returns a new claim. Amounts are integers in minor units (see @zenuinebit/money); dates are "YYYY-MM-DD".
import { createClaim, submit, approve, markPaid, settlement, evaluateClaim, totalsByCategory, taxSummary, type Policy } from "@zenuinebit/expense-claims";
// Your policy (example numbers only)
const policy: Policy = {
maxDaysOld: 60,
categories: {
meals: { limitPerExpense: 80_000, limitPerDay: 150_000, receiptRequiredAbove: 20_000 },
hotel: { limitPerExpense: 600_000, receiptRequiredAbove: 0, descriptionRequired: true, itcEligible: true },
mileage: { mileage: { ratePerKm: 800 } }, // 8.00 per km
fines: { blocked: true },
},
};
let claim = createClaim({
id: "C-1", employee: "asha", advance: 100_000,
expenses: [
{ id: "e1", date: "2025-04-10", category: "meals", amount: 50_000, receipt: "IMG_1" },
{ id: "e2", date: "2025-04-11", category: "mileage", kilometres: 120 },
{ id: "e3", date: "2025-04-11", category: "hotel", amount: 400_000, taxAmount: 48_000, description: "2 nights", receipt: "INV-77" },
],
});
evaluateClaim(claim, policy, { asOf: "2025-04-15" }); // per expense: claimed, allowed, violations; totals; hasErrors
claim = submit(claim, "2025-04-15", policy); // refused while any expense has an error
claim = approve(claim, { by: "boss", date: "2025-04-16", policy }); // or overrides: { e3: 300_000 }
settlement(claim); // { approved, advance, payable, recoverable }
claim = markPaid(claim, { date: "2025-04-20", ref: "NEFT-1" });
totalsByCategory(evaluateClaim(claim, policy)); // claimed and allowed per category
taxSummary(claim, evaluateClaim(claim, policy), policy); // { taxPaid, itcClaimable } for categories marked itcEligibleHow the checks work
- Errors stop submission and allow nothing for that expense: missing receipt (above your threshold), missing description, blocked or unknown category, mileage without kilometres, a future date, or a date older than
maxDaysOld. - Warnings go to the approver: an expense over a limit is paid up to the limit and flagged
capped; a likely duplicate (same date, category, amount and merchant, in this claim or inprior); a typed amount that differs from the distance rate. - Limits apply in order: per expense, per day, per claim, in date order then entry order. Pass approved expenses from earlier claims as
priorso daily limits and duplicate checks see them. - Mileage is paid as kilometres x rate, whatever amount was typed.
- Approval overrides can lower an expense (down to zero) but never raise it above what was claimed.
- Claimable GST is the expense's tax scaled to the allowed part, for categories you mark
itcEligible. Whether credit is really available is for your accountant to decide.
Not included: your policy (nothing is built in), foreign currency, multi-level approvals, storage, and per-diem rules (model a per-diem as a category with a daily limit).
