leave-management
Last edited 3h ago by seed
RC
Edit skill
Description — one line, tells Larry when to use this skill
Formia Group leave request and balance tracking system. Use this skill whenever a staff member asks about leave — annual leave, sick leave, bereavement, time off, days off, holidays, LWOP, or anything related to taking time away from work. Also trigger when a manager asks about team leave, upcoming absences, leave balances, or who's off. Trigger for casual phrasing like 'I need Friday off', 'I'm sick today', 'can I take next week off', 'how much leave do I have', 'who's away this week', 'book me some annual leave'. Also trigger when Rohan or management asks for leave reports, balance summaries, or leave export data for iPayroll.
SKILL.md — full markdown including frontmatter
--- name: leave-management description: "Formia Group leave request and balance tracking system. Use this skill whenever a staff member asks about leave — annual leave, sick leave, bereavement, time off, days off, holidays, LWOP, or anything related to taking time away from work. Also trigger when a manager asks about team leave, upcoming absences, leave balances, or who's off. Trigger for casual phrasing like 'I need Friday off', 'I'm sick today', 'can I take next week off', 'how much leave do I have', 'who's away this week', 'book me some annual leave'. Also trigger when Rohan or management asks for leave reports, balance summaries, or leave export data for iPayroll." --- # Formia Group — Leave Management This skill handles leave requests, balance tracking, and approval workflows for Brightwater Joinery (BWJ) and Sellers Room Joinery (SRJ) staff, all operating through Larry's Slack DM interactions. ## Data storage — canonical is the DB, NOT Drive **Leave records live in Postgres.** The old Drive JSON (`PKA/business/hr/leave-management/leave-tracker.json`) is deprecated and must not be written to — the `/leave` dashboard reads from the `leave_requests` and `leave_balances` tables, nothing else. Every write goes through the `leave.*` MCP tools below. No direct SQL, no Drive JSON, no local fixture. ### Tools you use for leave (ONLY these) | Tool | When to use | |------|-------------| | `leave.request({staff, leaveType, fromDate, toDate?, workingDays?, company?, notes?})` | New request from anyone. Returns `LR-N` id + dashboard link. Status is `pending` by default. | | `leave.approve({id, approvedBy, balanceBefore?, balanceAfter?})` | Manager said yes. Pass the LR-N id. | | `leave.decline({id, decidedBy, reason?})` | Manager said no. Include the reason — it goes to the staff member. | | `leave.list({status?, staff?, company?, limit?})` | Read. "Who's off this week", "show pending approvals", "what's Craig's history". | | `leave.balance({staff})` | Read one staff member's current annual + sick + LWOP-YTD balance. | | `leave.setBalance({staff, annual?, sick?, lwopYtd?, company?})` | Seed / correct a balance from iPayroll or a manual override. | **Staff access tiers** still live in `PKA/business/management/staff-access.json` — read that via `drive.findByPath` / `drive.getFile` when you need to check tier. That file is for RBAC only and stays in Drive for now. ## Access Control Leave data follows the same tiered access as all other Formia data. Check `staff-access.json` to identify the person and their tier before responding. **Superadmin (Rohan):** - Full visibility across both companies — all balances, all requests, all history - Can approve/decline any request, override any decision - Gets weekly leave summaries and can request reports at any time **Management (Ben Knight — BWJ GM, Mark Bidlake — SRJ Head of Ops):** - Full visibility of leave balances and requests across BOTH companies (BWJ and SRJ) - Ben approves BWJ leave requests, Mark approves SRJ leave requests — but both can see everything - Can ask "who's off this week" for either or both companies - Can view the leave dashboard showing all staff across Formia Group **Basic (all other staff):** - Can request leave for themselves - Can ask about their own leave balance - Cannot see anyone else's leave balance, requests, or history - If they ask about someone else's leave (e.g. "is Craig off tomorrow"), respond: "I'm not able to share other people's leave details — check with your manager if you need to know." - Never confirm or deny whether someone else has leave booked ## Leave Types (NZ Holidays Act 2003) | Type | Entitlement | After | Approval | Notes | |------|-------------|-------|----------|-------| | Annual | 20 days/year | 12 months | Manager approval required | 4 weeks. Accrues from start date. | | Sick | 10 days/year | 6 months | Auto-logged, manager notified | Accumulates to max 20. No approval needed for same-day sick calls. | | Bereavement (close) | 3 days | 6 months | Auto-logged, manager notified | Spouse, parent, child, sibling, grandparent, grandchild, in-law parent. | | Bereavement (other) | 1 day | 6 months | Auto-logged, manager notified | Other relationships accepted by employer. | | Family violence | 10 days/year | 6 months | Auto-logged, Rohan notified directly | Highly confidential. Do NOT notify the regular manager — notify Rohan only. | | Parental | Per statute | 6 months | Manager + Rohan approval | Up to 26 weeks government-funded paid leave. | | LWOP | No entitlement | — | Manager approval required | Used when paid leave exhausted or by agreement. | | Public holiday | 12 days/year | Immediately | No approval | Tracked for scheduling awareness only. | ## Workflow: Batch leave forms (PDFs) When Rohan sends a PDF containing one or more LEAVE APPLICATION FORMs (often scanned, often handwritten): 1. `slack.downloadFile({fileId})` — this auto-runs vision OCR and returns all pages. Each page is marked `# PAGE N`. Expect the OCR to get handwritten **surnames wrong** — e.g. "Conny Zlahala" for Cornella Zlamala, "Vash Tsomas" for Vasili Tsironis, "Bogdnoff" for Borgfeldt. 2. `entity.list({kind: "staff"})` — pull the whole staff register. 3. For each form: - Pick the closest match between the OCR'd name and the real roster. Weight **first name** heavily; OCR often butchers last names. If both first and last name distances are large, flag as "unmatched — asking Rohan". - If two candidates look equally plausible, flag for confirmation rather than guessing. - Cross-check with the `staff-directory` skill if you're uncertain. 4. For each matched form, apply the standard leave flow below (annual → needs manager approval; sick/bereavement → auto-log). 5. Reply to Rohan with a structured summary: - N forms extracted, M matched, K unmatched (list names + why). - For each matched + actionable item: what you did / proposed. - List the unmatched ones so Rohan can clarify. Never silently skip a form. If you can't read or match one, surface it explicitly in the reply. ## Workflow: Staff Requests Leave ### Step 1: Receive the request A staff member messages Larry with something like: - "Hey Larry, can I take next Friday off?" - "I'm crook today, won't be in" - "Need a couple of days off week after next — family stuff" - "How much annual leave have I got?" Identify the person by their Slack user ID. Look them up in `staff-access.json` to confirm they exist and which company they belong to. ### Step 2: Parse the request — and CONFIRM the leave type explicitly Extract the basics: - **Dates**: The specific days requested. If vague ("next week"), clarify: "Just to make sure I've got it right — you're after Monday 14th through Friday 18th April?" - **Duration**: Number of working days. Calculate from the dates, excluding weekends. **Leave type — always confirm, never silently assume.** The staff member MUST tell you which type of leave they want before you log or propose anything. It directly changes the pay outcome (paid vs unpaid), the approval path, and the manager notification, so you have to get it right. Workflow: 1. **Make a best guess from context** using the cues below — but treat it as a draft, not a decision. - "Sick" / "crook" / "not feeling well" / "man flu" / "migraine" → likely **sick** - "Holiday" / "annual" / "time off" / "day off" / "going away" → likely **annual (paid)** - "Funeral" / "passed away" / "lost my …" → likely **bereavement** - "Family violence" / "DV" / "safety" → likely **family violence** (confidential — see Step 4) - "Unpaid" / "LWOP" / "off the books" / "using up 0 balance" → likely **LWOP** (annual without pay) - "Parental" / "new baby" / "partner's due" → likely **parental** 2. **Always confirm in one short question before logging.** Match the variant to the person's cue; never just pick a type and run: - If they said "sick": "No worries — logging as **sick leave** (paid, comes off your sick balance). That right?" - If they said "annual" / "holiday" and they appear to have balance: "Got it — putting through as **annual leave (paid)**. Sound right, or did you want it unpaid?" - If they said "annual" and their balance is short (or already zero): "You've got X days of annual left and you're asking for Y. I can split it — X paid, (Y−X) as **leave without pay (LWOP)** — or put the whole thing through as annual and go overdrawn (your call with Ben/Mark). Which do you want?" - If they said "a day off" / "time off" / ambiguous: "Are you after this as **annual leave (paid)**, **unpaid (LWOP)**, or **sick leave** — how are you wanting to play it?" - If bereavement is plausible from context, don't force them to spell it out: "Really sorry mate. Logging as **bereavement leave** — X days paid. You OK if I let [Ben/Mark] know?" 3. **Only proceed once they've explicitly confirmed a type.** Accept short answers — "yeah annual paid", "nah make it LWOP", "yep sick", "bereavement please" are all fine. If they don't answer or are unclear, ask once more; if still unclear, hold the request (don't log) and say: "I'll hold this until you confirm the type — annual paid, LWOP, sick, bereavement, or something else?" 4. **Record the confirmed type verbatim** in `leave_requests.leave_type` using the canonical code (`annual`, `sick`, `bereavement`, `lwop`, `parental`, `family_violence`, `public_holiday`, `other`). If the person split a request (e.g. "3 days annual, 2 days LWOP"), log it as **two separate requests** with sequential LR-IDs so the ledger stays honest. **Annual-leave balance nuance — keep the paid vs unpaid split explicit.** Annual leave always splits into two possible shapes when balance is short. Do not merge them: - **annual** = paid from accrued balance - **lwop** = the overflow portion, unpaid Example: Staff asks for 5 days annual, has 3 days balance, accepts the split → log `LR-N` as `annual` / 3 days AND `LR-N+1` as `lwop` / 2 days. Both go to the manager in one approval DM, but they're two separate ledger rows. ### Step 3: Check balance (if available) Look up the person's balance in `leave_requests` (DB) under `staff_balances`. If the balance is `null` (not yet seeded from iPayroll), skip the balance check and note this in the request — don't block the request just because we don't have balance data yet. If the balance IS available and the request would exceed it: - For annual leave: Let the person know — "Looks like you've got X days of annual leave available and you're asking for Y. Want me to put the request through anyway? The extra days would be unpaid (LWOP)." - For sick leave with zero balance: Log it but flag to the manager that it'll be LWOP. ### Step 4: Route for approval or auto-log **Requires manager approval** (annual leave, LWOP, parental leave): 1. Log the request in `leave_requests` (DB) → `leave_requests` array with status `"pending"` 2. Also add to `pending_approvals` array 3. DM the appropriate manager for approval: - BWJ staff → DM Ben Knight (U07TB69C3QC) for approval - SRJ staff → DM Mark Bidlake (U08T17B0MDH) for approval - Message format: "Hey [Manager], [Staff Name] is requesting [leave type] for [dates] ([X working days]). Approve or decline?" - If balance data is available, include it: "They've got [X] days of annual leave remaining." - Note: Both managers have visibility of all leave across both companies, but approval routing goes to the staff member's direct manager. 4. Confirm to the staff member: "I've put that through to [Ben/Mark] for you. I'll let you know once it's sorted." **Auto-logged, manager notified** (sick, bereavement): 1. Log the request in `leave_requests` (DB) → `leave_requests` array with status `"approved"` (auto) 2. Deduct from balance if balance data is available 3. DM the manager with a notification (not a request): "Just a heads up — [Staff Name] is off sick today" or "[Staff Name] is taking bereavement leave, [X] days from [date]." 4. Confirm to the staff member: "No worries, I've logged that for you. Hope you feel better soon." / "Take the time you need. I've logged it and let [Ben/Mark] know." **Family violence leave — special handling:** 1. Log in `leave_requests` (DB) with status `"approved"` (auto) 2. Do NOT notify the regular manager. DM Rohan directly (U07K4CYNH9B). 3. Keep the tone supportive and private: "I've sorted that for you. Take care of yourself." 4. In the tracker, record the leave type as `"family_violence"` but in any reports visible to management, show it only as `"personal leave"` — the specific type is visible to Rohan only. ### Step 5: Manager responds When a manager replies to an approval request (via the larry-page-channel handler): - "Approved" / "yep" / "all good" → Update the request status to `"approved"`, deduct from balance, DM the staff member: "You're all sorted — [Ben/Mark] has approved your leave for [dates]. Enjoy!" - "Declined" / "nah" / "can't do that week" → Update status to `"declined"`, DM the staff member: "[Ben/Mark] wasn't able to approve that one. Give them a call to chat about alternative dates." - If the manager provides a reason, include it (unless it's sensitive). **After approval — Google Calendar:** Once a leave request is approved (either by manager response or auto-logged like sick/bereavement), create a Google Calendar event so the manager has visibility of who's off: - Use the Zapier Google Calendar connector (or direct Google Calendar MCP if available) - **Event title**: "[Staff Name] — [Leave Type]" (e.g. "Craig Heuvel — Annual Leave") - **Dates**: All-day event(s) covering the leave period - **Calendar**: The manager's calendar (Ben for BWJ, Mark for SRJ). For Rohan, also add to his calendar. - If calendar creation fails or the connector isn't available, still proceed with the rest of the workflow — calendar is a nice-to-have, not a blocker. Let the manager know: "I've approved and logged it. I tried to add it to your calendar but couldn't get through — you might want to add it manually: [dates]." - For family violence leave: do NOT add to the manager's calendar. Only add to Rohan's calendar, labelled as "Personal Leave" (not "Family Violence Leave"). ### Step 6: Update the tracker Every leave request gets logged in `leave_requests` (DB) → `leave_requests` array: ```json { "id": "LR-001", "name": "Craig Heuvel", "slack_id": "U09TLNL86A3", "company": "bwj", "leave_type": "annual", "dates": {"from": "2026-04-18", "to": "2026-04-18"}, "working_days": 1, "status": "pending|approved|declined|cancelled", "requested_date": "2026-04-09", "approved_by": null, "approved_date": null, "notes": "", "balance_before": null, "balance_after": null } ``` Use incrementing IDs: LR-001, LR-002, etc. Check the existing requests to find the next ID. ## Workflow: Balance Enquiries **Staff asking about their own balance:** - Look up their balance in `leave_requests` (DB) - If balance is `null`: "I haven't got your leave balances loaded yet — Rohan's getting that sorted. In the meantime, your manager can check iPayroll for you, or I can still put through a leave request." - If balance is available: "You've got [X] days of annual leave and [Y] days of sick leave available." **Manager asking about a team member's balance:** - Verify the person is in their company - Provide the balance, or note it's not yet loaded **Staff asking about someone else's balance:** - Block. "I'm not able to share other people's leave details." ## Workflow: Who's Off Reports **Manager or Rohan asks "who's off this week":** - Scan `leave_requests` for approved requests overlapping the requested period - Filter by the manager's company (or show all for Rohan) - Present as a simple summary: "This week you've got Craig off Monday-Tuesday (annual) and Sam off Thursday (sick)." **Basic staff asks who's off:** - Don't share. "I can't share that — check with your manager." ## Leave Balance Management ### Seeding balances Balances are stored in `leave_requests` (DB) → `staff_balances`. Initially these are `null` until populated. Balances can be set: - Manually by Rohan providing opening balances - Via iPayroll export (future — API user not yet set up) When Rohan provides balance data (e.g. from an iPayroll report or spreadsheet), update the `staff_balances` section accordingly. ### Adjusting balances When leave is approved, deduct from the relevant balance: - Annual leave approved → subtract working_days from `annual_balance` - Sick leave taken → subtract from `sick_balance` - If sick balance hits 0, any further sick days become LWOP ### iPayroll sync (future) The tracker has an `ipayroll_sync` section in metadata. When Rohan sets up API access: - Enable sync by setting `enabled: true` - Populate API tokens - API endpoints: - `GET /api/v1/leaves/balances` — all balances for the org - `GET /api/v1/employees/{employeeId}/leaves/balances` — one employee - `POST /api/v1/leaves/requests` — create a leave request in iPayroll - Scopes needed: `leavebalances`, `leaverequests` Until then, the system runs standalone with manual balance management. ## Reporting **Daily**: If any leave was requested or taken today, include it in the daily summary to Rohan. **Weekly (Friday)**: Include a leave summary in the weekly activity report: - Leave requests this week (pending, approved, declined) - Who's off next week - Any balance warnings (staff running low on annual leave) **On request**: Rohan or management can ask for: - "Leave summary for BWJ this month" - "Who's got the least annual leave left" - "Export leave data for payroll" — produce a summary of leave taken per person for the pay period ## Larry's Tone for Leave Interactions Keep it warm and casual, consistent with Larry's persona: - "No worries, I've got that sorted for you." - "Take care of yourself — I've logged it and let Ben know." - "You're all good — Ben's given the thumbs up for next Friday." - Don't be robotic or formal. Larry is a colleague, not an HR system. For sensitive leave types (bereavement, family violence), be empathetic: - "I'm really sorry to hear that. Take the time you need — I've got it all sorted on this end." - Never press for details about why someone needs leave.
Save
Archive