Timesheet Validator - Validation Rules

Version 4.0.0 | Last Updated: March, 2026 | ASK IT Limited
Total Rules
19
Completed
17
Pending
2
Completion
89%
Priority Rule Name Scope Condition (Logic) Output / Remark Pattern Status
Group A No Work Times Recorded — C / D empty A1→A2→A2.5: If/ElseIf chain (mutually exclusive); A2.1 = implicit exemption inside A2. Remarks scans Col G + Col H together.
GRP A1 Leave without reason (all empty) Weekday Weekday AND
• Start Time (C) = Empty
• End Time (D) = Empty
• Remarks (H) = Empty
Business Logic:
On weekdays, employees are expected to work. If no time is entered and no remarks explain the absence, this indicates unapproved leave.

Example:
Date: Jan 15 (Mon) | C/D/H: All empty → Error
dispDate & " Leave without reason (weekday, no time in C/D, no remarks in H)" Done
GRP A2 Leave without reason (missing time, no AL/SL/PH) Weekday Weekday AND
• (C = Empty OR D = Empty)
• Remarks (H) NOT contain:
  AL/SL/CL/ML/PL/NPL/NPSL/Public Holiday
Business Logic:
Partial attendance must be explained with a valid leave type or have both start/end times filled.

Example:
Date: Jan 20 (Mon) | C: Empty, D: 18:00, Remarks: "Doctor appointment" → Error
dispDate & " Leave without reason (weekday, no time in C/D, no AL/SL/Public Holiday in remarks)" Done
GRP A2.1 Special paid leave (CL/ML/PL) Weekday Saturday PH (Weekday OR Sat OR PH) AND
• C = Empty AND D = Empty
• actualHours = 0
• Remarks contain: CL or ML or PL
Business Logic:
CL, ML, and PL are special paid leave types that don't require work hours. These are exempt from "leave without reason" rules.

Example:
Date: Jan 25 (Mon) | C/D: Empty, Remarks: "CL - Family emergency" → OK ✅
(No remark - valid paid leave CL/ML/PL)
Why no remark?
This rule documents a silent pass. CL, ML, and PL keywords are listed in Rule 2's exemption conditions, so Rule 2 never fires for these entries — the system quietly accepts them with no remark generated.

In plain terms: "This employee took CL/ML/PL. No time needed, no error raised."
Done
GRP A2.5 Half-day leave without work hours Weekday Weekday AND
• Remarks contain "(AM)" or "(PM)"
• C = Empty AND D = Empty
Business Logic:
If marking half-day leave (AM or PM), the employee should still work the other half and enter those hours in columns C/D.

Example:
Date: Feb 5 (Wed) | C/D: Empty, Remarks: "AL (AM)" → Error
dispDate & " - Half-day leave but no work hours (expected work time in C/D for other half)" Done
Group B Work Times Present — C & D both filled Each rule is an independent parallel check — not an If/ElseIf chain.
GRP B2.6 Full-day NPL/NPSL with work hours present Weekday Saturday (5.5-day) (Weekday OR Saturday 5.5-day) AND
• C filled AND D filled
• Remarks contain "NPL" or "NPSL"
• No "(AM)" or "(PM)" marker in remarks
Business Logic:
Full-day NPL or NPSL means no work should be recorded. If C/D are filled alongside a full-day NPL/NPSL remark (without an AM/PM marker), the entry is contradictory.

Half-day NPL(AM) or NPL(PM) is valid — the employee works the other half and enters times in C/D.

Example (error):
Date: Mar 4 (Tue) | C: 09:00, D: 18:00, Remarks: "NPL" → Full-day NPL but 9.0h recorded → Error

Example (valid):
Date: Mar 4 (Tue) | C: 13:00, D: 18:00, Remarks: "NPL (AM)" → Half-day NPL, afternoon worked → OK
dispDate & " - Full-day NPL/NPSL but work hours present (C-D=" & Format(roundedActual,"0.0") & "h)" Done
GRP B3 Working hours not numeric (E is N/A) Weekday Weekday AND
• C filled AND D filled
• actualHours > 0 (from C-D)
• E NOT numeric (N/A, text, error)
Business Logic:
If time is entered (C & D), the working hours formula (E) should calculate a numeric value. "N/A" or errors indicate formula/data issues.

Example:
Date: Jan 12 (Mon) | C: 09:00, D: 18:00, E: "N/A", actual: 9.0h → Error
dispDate & " Working hours missing C-D=" & Format(actualHours,"0.0") & "h E is not numeric (N/A)" Done
GRP B4 C/D vs E mismatch Weekday Weekday AND
• C filled AND D filled
• E is numeric
• E ≠ (D-C) × 24
Business Logic:
The working hours in column E must match the calculated difference between end time and start time [(D-C) × 24].

Example:
Date: Jan 18 (Mon) | C: 09:00, D: 18:00, E: 8.5h, actual: 9.0h → Error
dispDate & " Working hours mismatch C-D=" & Format(actualHours,"0.0") & "h E=" & Format(workHours,"0.0") & "h" Done
GRP B5 Actual < expected hours Weekday Weekday AND
• actualHours + tolerance(0.5) < expectedHours
Business Logic:
Employees should work their expected hours unless on approved leave. Small discrepancies within tolerance (0.5h) are allowed for rounding.

Example:
Date: Feb 8 (Mon) | actual: 7.5h, expected: 9.0h → 7.5+0.5=8.0<9.0 → Error
dispDate & " Hours mismatch actual " & Format(actualHours,"0.0") & "h expected " & Format(expectedHours,"0.0") & "h" Done
GRP B6 OT not allowed (no OT eligibility) Weekday Weekday AND
• actualHours ≤ expectedHours + tolerance
• OT (G) > 0
Business Logic:
Overtime is only permitted when actual hours exceed expected hours + tolerance. Working exactly expected hours = no OT allowed.

Example:
Date: Jan 22 (Mon) | actual: 9.0h, expected: 9.0h, OT: 1.0h → Error
dispDate & " OT not allowed actual " & Format(actualHours,"0.0") & "h expected " & Format(expectedHours,"0.0") & "h OT(col F)=" & Format(otFromCol,"0.0") & "h" Done
GRP B7 OT miscalculation (1-hour buffer) Weekday Weekday AND
• actualHours > expectedHours
• calcOT = MAX(0, actual - expected - 1)
• calcOT ≠ OT in col F
Business Logic - 1-hour buffer rule:
Employees must work >1 hour beyond expected hours before OT is counted.
• actual - expected ≤ 1h → No OT allowed
• actual - expected > 1h → OT = excess hours - 1

Example:
Date: Feb 3 (Wed) | actual: 11.0h, expected: 9.0h, OT(F): 2.0h, calcOT: 11-9-1=1.0h → Error
dispDate & " OT miscalculation actual " & Format(actualHours,"0.0") & "h expected " & Format(expectedHours,"0.0") & "h OT(col F)=" & Format(otFromCol,"0.0") & "h OT(calc)=" & Format(calcOT,"0.0") & "h (OT = hours - 1)"
What does "(OT = hours - 1)" mean?
The message reminds the reviewer of the 1-hour buffer formula. OT only starts counting after the employee has worked more than 1 hour beyond their expected hours.

Example breakdown:
actual=11h, expected=9h → excess=2h → OT allowed = 2−1 = 1.0h
But col F shows 2.0h → mismatch → Error

OT(col F) = what the employee declared in the timesheet
OT(calc) = what the system calculated using the formula
Done
GRP B8 Late coming (time-based) Weekday Weekday AND
• C filled AND D filled
• startTime > scheduledStartTime + 15min
Business Logic:
Employees arriving later than scheduled start time + 15min tolerance are marked as late. Requires employee schedule data.

Example:
Date: Jan 10 (Mon) | Scheduled: 09:00, Actual: 09:25 → 09:25>09:15 → Error

⚠️ Status: Not started - requires employee schedule data
dispDate & " Late coming start " & Format(startTime,"hh:nn") Pending
GRP B9 Early leave (time-based) Weekday Weekday AND
• C filled AND D filled
• endTime < scheduledEndTime - 15min
Business Logic:
Employees leaving earlier than scheduled end time - 15min tolerance are marked as early departure. Requires employee schedule data.

Example:
Date: Jan 15 (Mon) | Scheduled: 18:00, Actual: 17:30 → 17:30<17:45 → Error

⚠️ Status: Not started - requires employee schedule data
dispDate & " Early leave end " & Format(endTime,"hh:nn") Pending
GRP B10 Short hours (possible early leave/late coming) Weekday Weekday AND
• C filled AND D filled
• actualHours + tolerance < expectedHours
Business Logic:
When hours worked are significantly less than expected (beyond 0.5h tolerance), it suggests late arrival or early departure. This flags for manager review.

Example:
Date: Feb 12 (Fri) | actual: 7.0h, expected: 9.0h → Error (warning)
dispDate & " Working hours less than standard (possible early leave/late coming) actual " & Format(actualHours,"0.0") & "h expected " & Format(expectedHours,"0.0") & "h" Done
Group C Saturday — 5.5-day Staff Only Applies only when HR flag "5.5 days (Y/N)" = Y. 5-day staff Saturday falls into Group D instead.
GRP C11 Saturday leave without reason (5.5-day staff) Saturday Saturday AND 5.5-day employee AND
• 0 < actualHours ≤ 4
• actualHours < expectedHours
• Remarks NOT contain leave types
Business Logic:
5.5-day employees are expected to work Saturdays (typically 4 hours). Partial hours without valid leave explanation require justification.

Example:
Date: Jan 11 (Sat) | Type: 5.5-day, expected: 4.0h, actual: 2.5h, Remarks: "Personal matters" → Error
dispDate & " - Sat leave without reason (5.5-day staff) actual " & Format(actualHours,"0.0") & "h expected " & Format(expectedHours,"0.0") & "h" Done
GRP C12 Saturday OT after 5.5 days (4h+ only) Saturday Part A: Sat AND 5.5-day AND
• C/D filled, weeklyDays>5.5, hrs≥4

Part B: Sat AND 5.5-day AND
• actualHours > 4
• calcOT = actualHours - 4
• calcOT ≠ OT in col F
Business Logic - Saturday OT Rule:
• First 4 hours = regular working hours
• Hours beyond 4 = overtime
• OT only counted if worked >5.5 days that week
• Formula: Saturday OT = actualHours - 4

Example:
Date: Jan 18 (Sat) | Type: 5.5-day, actual: 6.0h, OT(F): 2.5h, calcOT: 6.0-4.0=2.0h → Error
Part A:
dispDate & " Saturday OT hours=" & Format(actualHours,"0.0") & "h (after 5.5 days rule)"

Part B:
dispDate & " - Sat OT miscalculation (5.5-day staff) actual " & Format(actualHours,"0.0") & "h OT(col F)=" & Format(otFromCol,"0.0") & "h OT(calc)=" & Format(satOT,"0.0") & "h (OT = hours - 4)"
Part A vs Part B — what's the difference?

Part A — OT was recorded but the employee didn't even qualify for Saturday OT (didn't work >5.5 days that week). The message just states the hours; no formula comparison needed.

Part B — Employee did qualify for OT, but the amount in col F is wrong. Saturday OT formula is actual − 4 (first 4h = regular, beyond 4h = OT).

Example (Part B):
actual=6.0h → OT should be 6−4=2.0h
But col F shows 2.5h → mismatch → Error
Done
GRP C11.5 Saturday leave without AM/PM marker (5.5-day staff) Saturday Saturday AND 5.5-day employee AND
• Remarks contain any leave type:
  AL/SL/CL/ML/PL/NPL/NPSL/COMPASSION
• No "(AM)" or "(PM)" marker in remarks
Business Logic:
5.5-day staff Saturdays are working half-days. Any leave taken on a Saturday should specify AM or PM to clarify which half was taken. A warning is generated when a leave type is present but no half-day marker is found.

Example (warning):
Date: Jan 11 (Sat) | 5.5-day, Remarks: "SL" → Warning: "Jan 11 - Saturday SL should specify (AM) or (PM)"

Example (valid):
Date: Jan 11 (Sat) | 5.5-day, Remarks: "SL (AM)" → OK ✅

Leave types checked: AL, SL, CL, ML, PL, NPL, NPSL, COMPASSION
dispDate & " - Saturday AL should specify (AM) or (PM)"
dispDate & " - Saturday SL should specify (AM) or (PM)"
dispDate & " - Saturday CL should specify (AM) or (PM)"
dispDate & " - Saturday ML should specify (AM) or (PM)"
dispDate & " - Saturday PL should specify (AM) or (PM)"
dispDate & " - Saturday NPL should specify (AM) or (PM)"
dispDate & " - Saturday NPSL should specify (AM) or (PM)"
dispDate & " - Saturday COMPASSION should specify (AM) or (PM)"
(8 warning variants, one per leave type)
Done
Group D Rest Day — Sunday / Public Holiday / Saturday (5-day staff) 5-day staff Saturday treated as rest day (same as Sun/PH). 5.5-day staff Saturday handled by Group C.
GRP D13 Rest day hours mismatch (Sun/PH/Sat-5day) Sunday/PH Saturday (5-day) (Sunday OR Public Holiday OR
Saturday with 5-day staff) AND
• At least one of C-D / E / F filled
• NOT (C-D = E = F)
Business Logic - Rest day work rule:
• All hours worked on a rest day = OT (no "regular hours")
• C-D, E, and F must all match
• Applies to: Sunday, Public Holiday, AND Saturday for 5-day staff
• 5-day staff Saturday is treated as a rest day (same as Sun/PH)
• 5.5-day staff Saturday is handled separately by Rules 11 & 12

Example (Sun/PH):
Date: Jan 1 (Sun-PH) | C: 10:00, D: 15:00, E: 5.0h, F: 4.5h → Error

Example (5-day Saturday):
Date: Jan 11 (Sat) | 5-day staff | C: 10:00, D: 14:00, E: 4.0h, F: 3.5h → Error
Version 1:
dispDate & " Public Holiday hours mismatch C-D=" & Format(actualHours,"0.0") & "h E=" & Format(vE,"0.0") & "h F=" & Format(vF,"0.0") & "h"

Version 2:
dispDate & " - Public Holiday hours mismatch C-D=" & Format(actualHours,"0.0") & "h E=" & Format(CDbl(colE),"0.0") & "h"
Why two versions?
Two slightly different message formats exist in the code due to different execution paths within the same rule.

Version 1 — Fired when all three of C−D, E, and F are available. Includes the F column value in the message for full visibility.

Version 2 — Fired when F is missing or zero. Only compares C−D vs E.

Both indicate the same underlying issue: the hours across C−D, E, and F don't all match. A future cleanup could unify these into one message format.
Done
GRP D14 Rest day no work, no issue (Sun/PH/Sat-5day) Sunday/PH Saturday (5-day) (Sunday OR Public Holiday OR
Saturday with 5-day staff) AND
• C = Empty
• D = Empty
Business Logic:
Sundays, public holidays, and Saturdays for 5-day staff are all rest days. No work = no error.
5-day staff Saturday is a rest day (same treatment as Sun/PH).

Example (Sun/PH):
Date: Jan 1 (Sun-New Year) | C/D: Empty → OK ✅ (no error)

Example (5-day Saturday):
Date: Jan 11 (Sat) | 5-day staff | C/D: Empty → OK ✅ (no error)
(No remark) Done
GRP D15 Leave marked on rest day (warning) Sunday/PH Saturday (5-day) (Sunday OR Public Holiday OR
Saturday with 5-day staff) AND
• Remarks contain any leave type:
  AL/SL/CL/ML/PL/NPL/NPSL/
  COMPASSION
Business Logic:
Leave types shouldn't be marked on rest days (Sunday, Public Holiday, or Saturday for 5-day staff) since these are already non-working days. This is a WARNING to prevent accidental leave deduction.
Leave is NOT counted but a warning remark is generated.

Example (Sun/PH):
Date: Jan 1 (Sun-New Year) | Remarks: "AL" → Warning: "Jan 1 - AL marked on rest day (Sun/PH)"

Example (5-day Saturday):
Date: Jan 11 (Sat) | 5-day staff | Remarks: "SL" → Warning: "Jan 11 - SL marked on rest day (Sun/PH)"

7 different messages: One for each leave type (AL, SL, CL, ML, PL, NPL, COMPASSION)
dispDate & " - AL marked on rest day (Sun/PH)"
dispDate & " - SL marked on rest day (Sun/PH)"
dispDate & " - CL marked on rest day (Sun/PH)"
dispDate & " - ML marked on rest day (Sun/PH)"
dispDate & " - PL marked on rest day (Sun/PH)"
dispDate & " - NPL marked on rest day (Sun/PH)"
dispDate & " - COMPASSION marked on rest day (Sun/PH)"
(7 different messages based on leave type)
Why does 5-day Saturday say "(Sun/PH)" in the message?
The message template was written when this rule only covered Sunday and Public Holidays. 5-day staff Saturday was added later as a third rest-day type, but the label in the remark string was not updated.

Impact: The warning is functionally correct — it still alerts HR that leave was marked on a rest day. Only the label is slightly misleading for Saturday cases.

Future: Could be improved to say "(rest day)" or detect the day type and output "(Sat-5day)" vs "(Sun/PH)" separately.
Done

📋 Intermediate Sheet — intermediate - after scanning

One row is written per employee per run. Columns A–N are always written; special annotations (red highlights) are applied afterwards.

Column Schema (A–N)

Col Header Description
Aemployee IDEmployee code (e.g. A0012). Row is matched by this key on subsequent runs — updated in-place if already exists.
Btotal working hours (PT)PT employees only — sum of all C–D hours for the month. Written as "N/A" for FT employees.
Ctotal working days (PT)PT employees only — count of days with any recorded hours. Written as "N/A" for FT employees.
D468Reserved column (468-hour statutory cap tracking). Currently written as "N/A".
EOT - weekdays/sat (hours)Total OT hours accumulated on weekdays and 5.5-day Saturdays for the month.
FOT - sun/PH (hours)Total OT hours accumulated on Sundays, Public Holidays, and 5-day-staff Saturdays.
Gno. of AL taken (days)Count of AL day-equivalents taken (full-day = 1, half-day = 0.5). Validated against HR quota; overtaking AL highlighted red in remarks.
Hno. of SL taken (days)Count of SL day-equivalents taken (full-day = 1, half-day = 0.5). Validated against HR quota; overtaking SL highlighted red in remarks.
I4 consecutive SL (Y/N)"Y" if 4 or more consecutive SL days detected, "N" otherwise. "Y" displayed in red font (RGB 255,0,0); "N" resets to automatic colour.
Jno. of NPL/NPSL taken (days)Count of NPL/NPSL day-equivalents taken (full-day = 1, half-day = 0.5).
KCL hours used (compensation leave)Total CL hours deducted. Uses actual C–D hours when available; falls back to HR paid-hours (Sat default: 4h). Validated against hrCLCurrent balance; overtaking CL hours highlighted red in remarks.
Lstatus"completed" — no issues found. "needs review" — one or more remarks generated.
MremarksAll validation messages for the employee, newline-separated. "No issue" when clean. Certain keywords are highlighted red inline: SL > 3 days, overtaking AL, overtaking SL, overtaking CL hours.
Nissues"Y" if any remarks exist, blank otherwise. Used by downstream processing to filter employees needing attention.

Remarks Column (M) — Inline Red Highlights

After all remarks are written, specific keywords are coloured red within the cell text (character-level formatting, not cell background):

Keyword Trigger condition
SL > 3 daysMonthly SL count exceeds 3 days
overtaking ALAL taken exceeds HR quota (no. of AL column in input (HR))
overtaking SLSL taken exceeds HR quota (no. of SL column in input (HR))
overtaking CL hoursCL hours used exceeds hrCLCurrent balance from input (HR)

📄 Standalone Validation Log File

Each validation run appends one row per issue to timesheet - validation log.xlsx in the same source folder. The file and "issue log" sheet are auto-created if missing. SharePoint URL paths are supported. The log is append-only — it preserves full run history and never overwrites previous results.

Column Description
A — Run TimestampDate/time the validation ran (format: yyyy-mm-dd hh:mm:ss)
B — Employee IDEmployee code parsed from filename (e.g. A0012)
C — Employee NameFull name read from timesheet cell C6
D — YearTimesheet year
E — Month3-letter month code (e.g. JAN)
F — DateDate extracted from the remark line (text before first " - "); blank for "No issue" rows
G — MessageRemark text after the date prefix; "No issue" when no errors detected
H — Severity "Warning" — message contains "rest day" or "should specify"
"Error" — all other issues
Blank — "No issue" rows