@zenuinebit/attendance-leave
v0.1.0
Published
Holiday calendars, working days, leave balances and rules, year-end rollover, and monthly attendance that feeds payroll.
Readme
@zenuinebit/attendance-leave
Holiday calendars and working days, leave balances and rules, year-end rollover, and monthly attendance that gives payroll its working days and days paid. No dependencies. Dates are "YYYY-MM-DD"; leave days may be fractional (0.5 is a half day).
No leave policy or holiday list is built in. Leave types, quotas, carry-forward and holidays differ by company, state and law, so you define them. The names and numbers in the example are made up.
import {
workingDaysInMonth, checkLeave, leaveEntry, accrual, annualGrant, rollover, balance, summarizeMonth, marksFromLeave,
} from "@zenuinebit/attendance-leave";
import { computePayslip } from "@zenuinebit/payroll-basics";
const calendar = { weeklyOff: [0], holidays: [{ date: "2025-04-14", name: "Holiday" }] }; // 0 = Sunday
const CL = { code: "CL", annualQuota: 12, carryForwardMax: 5, minNoticeDays: 2 };
let ledger = [annualGrant(CL, "2025-01-01")!]; // keep your own list of entries
// A request: how many days, paid or unpaid?
const req = { leaveType: "CL", from: "2025-04-15", to: "2025-04-17", appliedOn: "2025-04-10" };
const check = checkLeave(req, { type: CL, calendar, entries: ledger, existing: [] });
check; // { ok, days, paidDays, lossOfPayDays, balance, problems: [] }
if (check.ok) ledger.push(leaveEntry(req, check)!); // record it once approved
balance(ledger, "CL", "2025-12-31");
ledger.push(...rollover(ledger, [CL], { closingDate: "2025-12-31", openingDate: "2026-01-01" })); // lapse the excess, grant the new year
// A month of attendance for payroll
const marks = [...marksFromLeave(req, check, calendar, CL), { date: "2025-04-22", status: "absent" as const }];
const month = summarizeMonth({ year: 2025, month: 4, calendar, marks, unmarked: "present" });
month.lossOfPayDays;
month.payroll; // { workingDays, paidDays } -> straight into computePayslip({ ..., ...month.payroll })How it works
- A working day is neither a weekly off nor a holiday.
workingDaysInMonthandworkingDaysBetweencount them. - A leave request uses its working days (plus weekly offs and holidays inside it if the type has
countNonWorkingDays), less half days.checkLeavereports problems (too little notice, too long, overlap, no working days, too little balance) and splits the days into paid and loss-of-pay. If the balance is short the request is refused, or withonInsufficient: "lop"the shortfall becomes loss of pay. Unpaid types are always loss of pay. - The ledger is a list of entries (
grant,accrual,leave,lapse,adjustment) with signed days; the balance is derived from it on any date.accrualhandles monthly earning (with pro-rata for a mid-month joiner) andmaxBalance. rolloverlapses what exceedscarryForwardMax(default none) and grants the next year's quota, reduced to fitmaxBalance.summarizeMonth: weekly offs and holidays are paid and never cause loss of pay. A working day with no mark counts as absent unless you sayunmarked: "present".basis: "calendar-days"gives payroll the days in the month instead of working days, for pay rules that work that way.
Not included: shift rosters, clock-in and clock-out hours, late-mark rules, leave encashment, optional or restricted holidays, and approval workflows (see @zenuinebit/expense-claims for a pattern).
