Oracle HCM Cloud Fast Formula: Regular and OT Bucket Allocation in a TCR — The Day-Type Branch, the l_total Threshold Cascade, and the l_ot_counter Tracking Pattern
Oracle HCM Cloud Fast Formula: Regular and OT Bucket Allocation in a TCR — The Day-Type Branch, the l_total Threshold Cascade, and the l_ot_counter...
Oracle HCM Cloud Fast Formula: Regular and OT Bucket Allocation in a TCR — The Day-Type Branch, the l_total Threshold Cascade, and the l_ot_counter Tracking Pattern
Out_Measure_RegHours, Out_Measure_OT_150_Hours, or Out_Measure_OT_200_Hours — driven by the day type (PH / SAT / SUN / Weekday), the daily threshold compare against l_total, and the spillover logic that splits a single entry across multiple buckets when it straddles a threshold.Part 4 covered the absence side of the DETAIL fork — when AbsenceType.exists(nidx) returns true. This post covers the other side: when it returns false and the entry represents worked time. That's where the bulk of the TCR's allocation logic lives.
The worked-time branch has one job — decide which output bucket every hour belongs in. The decision rests on two inputs: the day type (public holiday, Saturday, Sunday, or weekday) determined via Part 2's day-type branching logic, and the running daily total held in l_total. Day type controls which thresholds apply; l_total controls whether the current entry has already crossed them.
A worked example threads the whole post: a single Wednesday on which a worker logs two DETAIL entries — a 4-hour morning shift (09:00–13:00) and an 8-hour afternoon shift (14:00–22:00), for a 12-hour day. The daily thresholds are 8 hours for Regular and 10 hours for OT 150%, above which everything is OT 200%. The formula's job is to split those 12 hours across three buckets correctly: 8 Regular, 2 OT 150%, 2 OT 200% — even though no single entry is split that way in isolation.
The Worked-Time Branch — Where the DETAIL Decision Tree Leads
Inside every DETAIL iteration, the formula first checks AbsenceType.exists(nidx). If true, the absence branch (Part 4) fires. If false, control flows here — into a sequence of four steps that map an entry's hours onto the right output buckets:
The Four-Way Day-Type Branch — One Rule Set Per Day Category
Day type changes everything about allocation. The same worked hour on a Wednesday is Regular pay; on a Sunday it's OT at 200%; on a public holiday it's a different output bucket entirely. Part 2 covered the day-type detection logic (GET_DATE_DAY_OF_WEEK, the FRI-anchored weekly compare, and the holiday calendar lookup). Part 5 uses the result:
The l_total Threshold Cascade — Where Each Hour Lands
The Weekday branch is the only one with a true threshold cascade, so it's the one worth visualizing. l_total holds the running daily total of worked hours (introduced in Part 3, reset at every END_DAY phase marker). The cascade asks one question for every incoming hour: where is l_total sitting right now relative to the two thresholds?
The Spillover Logic — One Entry Crossing Multiple Thresholds
The interesting case is when a single DETAIL entry's measure straddles a threshold. Walk through the worked example. The second entry is 8 hours long, but l_total already sits at 4 from the first entry. There are 4 hours of Regular capacity left, then 2 hours of OT 150 capacity, then everything above goes to OT 200. The 8-hour measure has to split three ways:
l_reg_remaining = GREATEST(0, l_daily_reg_cap - l_total)
l_ot150_remaining = GREATEST(0, l_daily_ot_cap - GREATEST(l_total, l_daily_reg_cap))
l_remaining = l_measure
/* Regular portion */
l_reg_alloc = LEAST(l_remaining, l_reg_remaining)
Out_Measure_RegHours = Out_Measure_RegHours + l_reg_alloc
l_total = l_total + l_reg_alloc
l_remaining = l_remaining - l_reg_alloc
/* OT 150 portion */
l_ot150_alloc = LEAST(l_remaining, l_ot150_remaining)
Out_Measure_OT_150_Hours = Out_Measure_OT_150_Hours + l_ot150_alloc
l_total = l_total + l_ot150_alloc
l_ot_counter = l_ot_counter + l_ot150_alloc
l_remaining = l_remaining - l_ot150_alloc
/* OT 200 portion — whatever is left */
IF (l_remaining > 0) THEN
(
Out_Measure_OT_200_Hours = Out_Measure_OT_200_Hours + l_remaining
l_total = l_total + l_remaining
l_ot_counter = l_ot_counter + l_remaining
)
LEAST and GREATEST are delivered Fast Formula functions — they're the idiomatic way to clamp values without nested IF blocks. The pattern reads as: "give the current zone as much as it can take, then move on." Figure 04 traces every step:
The l_ot_counter — A Running Tally for the Downstream Cascade
Alongside l_total, the formula maintains a second running counter: l_ot_counter. It increments only when OT hours (150 or 200) are allocated. l_total tracks how much was worked; l_ot_counter tracks how much of that was OT.
Why a separate counter? Because Part 9 introduces a twelve-tier OT_200_counter cascade that fires once for every cumulative OT hour accumulated across the period — and that cascade needs an exact running count, not a derived value. Maintaining the counter inline during allocation is cheaper than recomputing it later from the output buckets.
Unlike l_total, l_ot_counter is not reset at END_DAY. It accumulates across the entire period because the downstream cascade is period-level, not daily.
The Output Buckets — Where Worked Hours Land
Three output arrays capture the worked-time classification for the period. Together with the Out_Abs_* buckets from Part 4, they form the complete picture of compensable time the formula returns to payroll:
Part 6 — Night Surcharge Detection with the Night-Time Code Match and the Monthly Night Accumulator
A second classification layered on top of the day-type allocation — how the formula identifies night-time hours by matching PayrollTimeType against a configured night-time code, why l_daily_night_total resets on END_DAY while l_period_night_total doesn't, and how night-surcharge hours stack alongside the Reg/OT-150/OT-200 buckets without disturbing them.