Modified by www.SourceFiles.app
This commit is contained in:
@@ -0,0 +1,95 @@
|
|||||||
|
-- Vacation is for full calendar year students only — spec section 18, refined.
|
||||||
|
--
|
||||||
|
-- The seeded policy grants four weeks to year-round full-time students and to
|
||||||
|
-- nobody else. That is deliberate: school-year students and part-time
|
||||||
|
-- year-round students carry no vacation entitlement.
|
||||||
|
--
|
||||||
|
-- Nothing enforced it, though. resolve_vacation_policy() simply returns no row
|
||||||
|
-- for those students, recalc_vacation_balance() then writes weeks_allotted = 0,
|
||||||
|
-- and an approved request still counted against it — driving weeks_remaining
|
||||||
|
-- negative and reporting a balance the student never had. This adds the missing
|
||||||
|
-- guard at the only point that matters: the moment a request becomes approved.
|
||||||
|
--
|
||||||
|
-- The escape hatch is vacation_balances.override_weeks. An administrator who
|
||||||
|
-- genuinely wants to grant vacation to a student outside the policy sets an
|
||||||
|
-- override on that student's balance row and the approval then succeeds, which
|
||||||
|
-- keeps the exception deliberate, attributable and visible in reporting rather
|
||||||
|
-- than silent.
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
-- 1. ENTITLEMENT
|
||||||
|
-- ============================================================================
|
||||||
|
|
||||||
|
-- The weeks a student is actually entitled to for a policy year: an explicit
|
||||||
|
-- override if one has been set, otherwise the resolved policy, otherwise none.
|
||||||
|
-- This mirrors the COALESCE that recalc_vacation_balance() already applies, so
|
||||||
|
-- the guard and the balance can never disagree about what a student is owed.
|
||||||
|
CREATE OR REPLACE FUNCTION public.vacation_entitlement_weeks(_student UUID, _year INTEGER)
|
||||||
|
RETURNS NUMERIC
|
||||||
|
LANGUAGE plpgsql STABLE SECURITY DEFINER SET search_path = public AS $$
|
||||||
|
DECLARE
|
||||||
|
p public.vacation_policies%ROWTYPE;
|
||||||
|
override NUMERIC;
|
||||||
|
BEGIN
|
||||||
|
SELECT vb.override_weeks INTO override
|
||||||
|
FROM public.vacation_balances vb
|
||||||
|
WHERE vb.student_id = _student AND vb.policy_year = _year;
|
||||||
|
|
||||||
|
IF override IS NOT NULL THEN
|
||||||
|
RETURN override;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
SELECT * INTO p FROM public.resolve_vacation_policy(_student);
|
||||||
|
RETURN COALESCE(p.weeks_per_year, 0);
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
GRANT EXECUTE ON FUNCTION public.vacation_entitlement_weeks(UUID, INTEGER) TO authenticated;
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
-- 2. THE GUARD
|
||||||
|
-- ============================================================================
|
||||||
|
|
||||||
|
-- Fires only on the transition into 'approved'. Requests may still be created,
|
||||||
|
-- edited, rejected and cancelled freely for any student — it is consuming
|
||||||
|
-- entitlement that requires having some.
|
||||||
|
CREATE OR REPLACE FUNCTION public.enforce_vacation_entitlement()
|
||||||
|
RETURNS TRIGGER LANGUAGE plpgsql SECURITY DEFINER SET search_path = public AS $$
|
||||||
|
DECLARE
|
||||||
|
yr INTEGER;
|
||||||
|
weeks NUMERIC;
|
||||||
|
student TEXT;
|
||||||
|
BEGIN
|
||||||
|
IF NEW.status <> 'approved' THEN
|
||||||
|
RETURN NEW;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- Only the moment of approval is checked, so re-saving an already-approved
|
||||||
|
-- request (or a later edit to it) does not fail after the fact.
|
||||||
|
IF TG_OP = 'UPDATE' AND OLD.status = 'approved' THEN
|
||||||
|
RETURN NEW;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
yr := COALESCE(NEW.policy_year, public.vacation_year_for(NEW.student_id, NEW.start_date));
|
||||||
|
weeks := public.vacation_entitlement_weeks(NEW.student_id, yr);
|
||||||
|
|
||||||
|
IF weeks > 0 THEN
|
||||||
|
RETURN NEW;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
SELECT s.first_name || ' ' || s.last_name INTO student
|
||||||
|
FROM public.students s WHERE s.id = NEW.student_id;
|
||||||
|
|
||||||
|
RAISE EXCEPTION
|
||||||
|
'Vacation cannot be approved for % — no vacation entitlement for % (vacation applies to full calendar year, full-time students). To grant an exception, set override_weeks on that student''s % vacation balance first.',
|
||||||
|
COALESCE(student, NEW.student_id::text), yr, yr
|
||||||
|
USING ERRCODE = 'check_violation';
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
-- Named to sort after trg_vr_defaults, which populates policy_year on insert:
|
||||||
|
-- triggers sharing a timing and event fire in alphabetical order, and this one
|
||||||
|
-- reads the value that one sets.
|
||||||
|
DROP TRIGGER IF EXISTS trg_vr_entitlement ON public.vacation_requests;
|
||||||
|
CREATE TRIGGER trg_vr_entitlement BEFORE INSERT OR UPDATE ON public.vacation_requests
|
||||||
|
FOR EACH ROW EXECUTE FUNCTION public.enforce_vacation_entitlement();
|
||||||
Reference in New Issue
Block a user