| Priority | Rule Name | Scope | Condition (Logic) | Output / Remark Pattern | Status |
|---|---|---|---|---|---|
| ā¼ Group A No Work Times Recorded ā C / D empty A1āA2āA2.5(FT)āA2.7(FT)āA2.7(PT)āA2.5(PT): If/ElseIf chain (mutually exclusive). PT employees are exempt from Rules 1, 2, and 2.5 (FT); they fall into 2.7(PT) (incomplete time ā one of C/D filled) or 2.5(PT) (half-day marker, both C/D empty) where applicable. | |||||
| GRP A1 | Leave without reason (all empty)PT: ā Skipped | Weekday |
Weekday AND FT ⢠Start Time (C) = Empty ⢠End Time (D) = Empty ⢠Col G = Empty AND Col H = Empty |
DD-MMM & " - Leave without reason (weekday, no time in C/D, no leave type in G/H)" | Done |
| GRP A2 | Leave without reason (has text in G/H, no recognised leave code)PT: ā Skipped | Weekday |
Weekday AND FT ⢠C = Empty AND D = Empty ⢠Col G or Col H has text (distinguishes from Rule 1) ⢠upCombined (Col G + Col H) contains none of:AL, SL, NPL ā Col G dropdown NPSL ā Col H only (Others path) PUBLIC HOLIDAY ā substring match |
DD-MMM & " - Leave without reason (weekday, no time in C/D, no leave code in G/H)" | Done |
| GRP A2.5 (FT) | Half-day leave without work hours (FT)PT: ā Skipped (see 2.5 (PT)) | Weekday |
Weekday AND FT ⢠Remarks contain "(AM)" or "(PM)" ⢠C = Empty AND D = Empty (both) |
DD-MMM & " - Half-day leave but no work hours (expected work time in C/D for other half)" | Done |
| GRP A2.5 (PT) | Half-day leave without work hours (PT)PT: ā Exclusive | Weekday |
isPartTime = True AND isWeekday AND ⢠hasRemark = True ⢠C = Empty AND D = Empty (both) ⢠Remarks contain "(AM)" or "(PM)" |
DD-MMM & " - Half-day leave but no work hours (expected work time in C/D for other half)" | Done |
| GRP A2.7 (FT) | Incomplete time entry ā one of start/end missing (FT)PT: ā Skipped (see 2.7 (PT)) | Weekday |
Weekday AND FT ⢠Exactly one of C or D is filled |
DD-MMM & " - Incomplete time entry (has start time in C but missing end time in D)" or DD-MMM & " - Incomplete time entry (has end time in D but missing start time in C)" |
Done |
| GRP A2.7 (PT) | Incomplete time entry ā one of start/end missing (PT)PT: ā Exclusive | All Days |
isPartTime = True AND ⢠Exactly one of C or D is filled |
DD-MMM & " - Incomplete time entry (has start time in C but missing end time in D)" or DD-MMM & " - Incomplete time entry (has end time in D but missing start time in C)" |
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 presentPT: ā Applied | Weekday |
Weekday ⢠C filled AND D filled ⢠Col G starts with "NPL" or "NPSL" (non-Others path) OR Col G = "OTHERS" AND Col H contains "NPSL" (exact case) ⢠No "(AM)" or "(PM)" marker |
DD-MMM & " - 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)PT: ā Applied | Weekday |
Weekday ⢠C filled AND D filled ⢠actualHours > 0 (from C-D) ⢠E NOT numeric (N/A, text, error) |
DD-MMM & " - Working hours missing C-D=" & Format(actualHours,"0.0") & "h E is not numeric (N/A)" | Done |
| GRP B4 | C/D vs E mismatchPT: ā Applied | All Days |
⢠C filled AND D filled ⢠E is numeric ⢠E ā (D-C) Ć 24 |
DD-MMM & " - Working hours mismatch C-D=" & Format(actualHours,"0.0") & "h E=" & Format(workHours,"0.0") & "h" | Done |
| GRP B5 | Actual < expected hoursPT: ā Skipped | Weekday |
Weekday ⢠actualHours + tolerance(0.5) < expectedHours |
DD-MMM & " - Hours mismatch actual " & Format(actualHours,"0.0") & "h expected " & Format(expectedHours,"0.0") & "h" | Done |
| GRP B6 | OT not allowed (no OT eligibility)PT: ā Skipped | Weekday |
Weekday ⢠actualHours ⤠expectedHours + tolerance ⢠OT (F) > 0 |
DD-MMM & " - 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)PT: ā Skipped | Weekday |
Weekday ⢠actualHours > expectedHours ⢠If hrOTFreeHr = Y: calcOT = MAX(0, actual - expected - 1) If hrOTFreeHr = N: calcOT = actual - expected ⢠calcOT ā OT in col F |
DD-MMM & " - 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" | 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)PT: ā Skipped | Saturday |
Saturday AND 5.5-day employee AND ⢠0 < actualHours < 4 ⢠actualHours < expectedHours ⢠No recognised leave code in Col G or Col H (AL/SL/NPL from Col G; NPSL from Col H; PH/Public Holiday) |
DD-MMM & " - Sat leave without reason (5.5-day staff) actual " & Format(actualHours,"0.0") & "h expected " & Format(expectedHours,"0.0") & "h" | Done |
| GRP C11.5 | Saturday leave without AM/PM marker (5.5-day staff)PT: ā Skipped | Saturday |
Saturday AND 5.5-day employee AND ⢠Active leave type detected (effHasAL / effHasSL / effHasNPL) ⢠No "(AM)" or "(PM)" marker in remarks |
DD-MMM & " - Saturday AL should specify (AM) or (PM)" DD-MMM & " - Saturday SL should specify (AM) or (PM)" DD-MMM & " - Saturday NPL should specify (AM) or (PM)" (3 warning variants ā AL, SL, NPL only) |
Done |
| GRP C12 | Saturday OT after 5.5 days (4h+ only)PT: ā Skipped | Saturday |
Sat AND 5.5-day AND ⢠actualHours > 4 ⢠calcOT = actualHours - 4 ⢠calcOT ā OT in col F |
DD-MMM & " - 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)" | 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)PT: ā Skipped | 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) |
Weekend (Sun/Sat-5day): DD-MMM & " - Weekend hours mismatch C-D=" & Format(actualHours,"0.0") & "h E=" & Format(vE,"0.0") & "h F=" & Format(vF,"0.0") & "h" Public Holiday: DD-MMM & " - Public Holiday hours mismatch C-D=" & Format(actualHours,"0.0") & "h E=" & Format(vE,"0.0") & "h F=" & Format(vF,"0.0") & "h" |
Done |
| GRP D14 | Rest day no work, no issue (Sun/PH/Sat-5day)PT: ā Applied | Sunday/PH Saturday (5-day) |
(Sunday OR Public Holiday OR Saturday with 5-day staff) AND ⢠C = Empty ⢠D = Empty |
(No remark) | Done |
| GRP D15 | Leave marked on rest day (warning)PT: ā Applied | Sunday/PH Saturday (5-day) |
(Sunday OR Public Holiday OR Saturday with 5-day staff) AND ⢠Active leave type detected (effHasAL / effHasSL / effHasNPL) |
DD-MMM & " - AL marked on rest day (Sun/PH)" DD-MMM & " - SL on rest day (included in consecutive SL streak)" DD-MMM & " - NPL marked on rest day (Sun/PH)" (3 active leave types only ā AL warning, SL informational, NPL warning) |
Done |
| GRP D16 | Others (Col G) informational remarkPT: ā Applied | All Days | Col G = "Others" (any employee, any day type) | DD-MMM & " - Others (Col G): '" & Col H value & "' - please review" | Done |
| ā¼ Group E Part-Time (PT) Employees ā Specific Outputs These outputs apply exclusively to employees with work schedule "PT" in the HR sheet. FT employees receive "N/A" for PT-specific columns. | |||||
| GRP EPT-1 | PT working hours & daysPT: ā Exclusive | All Days |
isPartTime = True After day loop ends: ⢠Read footer "TOTAL WORKING HOURS" col E ā totalPTHours ⢠Read footer "TOTAL WORKING DAYS" col E ā totalPTDays |
Intermediate col B: totalPTHours Intermediate col C: totalPTDays (FT employees: both written as "N/A") |
Done |
| GRP EPT-2 | 468-hour statutory cap statusPT: ā Exclusive | All Days |
isPartTime = True AND HR column "468" = "Y" |
Intermediate col P (pt_468_status): "ā entitled" (Only written if HR 468 = Y AND isPartTime) |
Done |
intermediate - after scanningOne row is written per employee per run. Col P (pt_468_status) is written only for PT employees where HR 468 = Y. Col Q (total allowance) is the parsed numeric total from HR "other allowance" column. All other columns always written.
Column Schema (AāQ)
| Col | Header | Description |
|---|---|---|
| A | employee ID | Employee code (e.g. A0012). Row is matched by this key on subsequent runs ā updated in-place if already exists. |
| B | total working hours (PT) | PT employees only ā read from timesheet footer row "TOTAL WORKING HOURS" col E (pre-calculated by Generator). Written as "N/A" for FT employees. |
| C | total working days (PT) | PT employees only ā read from timesheet footer row "TOTAL WORKING DAYS" col E (pre-calculated by Generator). Written as "N/A" for FT employees. |
| D | 468 | Reserved column (468-hour statutory cap tracking). Currently written as "N/A". |
| E | OT - weekdays/sat (hours) | Total OT hours accumulated on weekdays and scheduled Saturdays for the month. |
| F | OT - sun/PH (hours) | Total OT hours accumulated on Sundays, Public Holidays, and 5-day-staff Saturdays. |
| G | no. 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. |
| H | no. 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. |
| I | 4 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. |
| J | no. of NPL/NPSL taken (days) | Count of NPL/NPSL day-equivalents taken (full-day = 1, half-day = 0.5). |
| L | status | "completed" ā no issues found. "needs review" ā one or more remarks generated. |
| M | remarks | All validation messages for the employee, newline-separated. "No issue" when clean. Certain keywords are colour-highlighted inline (see table below). |
| N | issues | "Y" if any remarks exist, blank otherwise. Used by downstream processing to filter employees needing attention. |
| O | scan timestamp | Date/time of the most recent validation run for this employee (format: yyyy-mm-dd hh:mm). Always written; overwrites previous value. |
| P | pt_468_status | Written as "ā entitled" only when isPartTime = True AND HR column "468" = "Y". Blank for all FT employees and PT employees without 468 flag. |
| Q | total allowance | Parsed numeric total from HR "other allowance" column (e.g. "Housing 500; Standby 200" ā 700). Always written; overwrites previous value. |
Remarks Column (M) ā Inline Colour Highlights
After all remarks are written, specific keywords are coloured within the cell text (character-level formatting, not cell background):
| Keyword | Colour | Trigger condition |
|---|---|---|
| SL > 3 days | Red | Monthly SL count exceeds 3 days |
| overtaking AL | Red | AL taken exceeds HR quota (no. of AL column in input (HR)) |
| overtaking SL | Red | SL taken exceeds HR quota (no. of SL column in input (HR)) |
| overtaking CL hours | Red | CL hours taken exceeds HR quota |
| Blank date/day | Red | Inserted row with blank date/day detected ā entire remark line coloured red |
| Others (Col G): | Orange | Col G = "Others" ā informational, HR to review Col H content (RGB 255,140,0) |
| Statutory Holiday: | Blue | 468 PT employee SL on statutory holiday ā HR to action full-pay (RGB 0,112,192) |
| SL gap across rest days | Black | SL streak spans across rest days ā informational only, not an error (vbBlack) |
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 Timestamp | Date/time the validation ran (format: yyyy-mm-dd hh:mm:ss) |
| B ā Employee ID | Employee code parsed from filename (e.g. A0012) |
| C ā Employee Name | Full name read from timesheet cell C6 |
| D ā Year | Timesheet year |
| E ā Month | 3-letter month code (e.g. JAN) |
| F ā Date | Date extracted from the remark line (text before first " - "); blank for "No issue" rows |
| G ā Message | Remark text after the date prefix; "No issue" when no errors detected |
| H ā Severity |
"Warning" ā message contains "rest day" or "should specify""Error" ā all other issuesBlank ā "No issue" rows |