๐Ÿ“‹ Timesheet Generator - Business Rules & Validation

Version 2.4.5 | Last Updated: March, 2026 | ASK IT Limited
Rule Categories
10
OT Buffer
1h
Time Rounding
0.5h
Holiday Refresh
180d
Category โ‡… Rule Name โ‡… Applies To Description / Formula / Rule Key Values
๐Ÿ”ถ OVERTIME (OT) RULES
OT Rules 1-Hour Buffer Rule WeekdayWorking Sat Employees must work >1 hour beyond standard hours before OT is allowed.
Applies to: Weekdays (Mon-Fri) and Saturdays with timeslot (expectedSatHours > 0)
Does NOT apply to: Sun / PH / Saturday Off (see Non-working Day Full OT Rule)
OT โ‰ค MAX(0, ActualHours - ExpectedHours - 1)
Examples:
โ€ข Standard: 9h, Actual: 9h โ†’ Max OT: 0h โŒ (no OT allowed)
โ€ข Standard: 9h, Actual: 10h โ†’ Max OT: 0h โŒ (only 1h over, buffer not met)
โ€ข Standard: 9h, Actual: 10.5h โ†’ Max OT: 0.5h โœ… (1.5h over)
โ€ข Standard: 9h, Actual: 11h โ†’ Max OT: 1h โœ… (2h over)
โ€ข Standard: 9h, Actual: 12h โ†’ Max OT: 2h โœ… (3h over)
Buffer: 1 hour
Formula: MAX(0, actual-expected-1)
OT Rules Non-working Day Full OT Rule Sun/PH/Sat Off If there is work on a non-working day, all working hours are treated as OT:
  • Sundays (Expected = 0)
  • Public Holidays (Expected = 0)
  • Saturday Off (Expected = 0, 5-day workweek)
Behavior: Column F uses formula =E{row} (auto-equals working hours)
No 1-hour buffer rule applies on these days
Cell locked: User cannot edit OT (formula-only)
Expected Hours: 0
OT Formula: =E{row}
Editable: โŒ No
OT Rules OT Validation Formula Column F (Working Days) Excel data validation formula applied to OT column only for working days (weekdays + working Saturdays):
=OR(
  F{row}="",
  E{row}="",
  F{row}<=MAX(0,E{row}-{expectedHours}-1)
)
Non-working days (Sun/PH/Sat Off) have no validation since they use formula =E{row}
Prevents invalid OT entries at source
Column: F
Type: Custom validation
Scope: Working days only
๐Ÿ•’ TIME ENTRY VALIDATION RULES
Validation Start Time Rules Column C
  • Range: 00:00 to 23:59
  • Blank: Allowed
  • Relationship: Start โ‰ค End (when both present)
  • Type Check: Must be numeric time value (blocks text input)
=OR(C{row}="",
  AND(
    NOT(ISTEXT(C{row})),
    ISNUMBER(C{row}),
    C{row}>=TIME(0,0,0),
    C{row}<=TIME(23,59,0),
    OR(D{row}="", C{row}<=D{row})
  )
)
Protection: Double-layer text blocking via NOT(ISTEXT()) + ISNUMBER()
Error: "Start Time must be a valid time between 00:00 and 24:00, and cannot be later than End Time."
Validation End Time Rules Column D
  • Range: 00:00 to 23:59
  • Blank: Allowed
  • Relationship: End โ‰ฅ Start (when both present)
  • Type Check: Must be numeric time value (blocks text input)
=OR(D{row}="",
  AND(
    NOT(ISTEXT(D{row})),
    ISNUMBER(D{row}),
    D{row}>=TIME(0,0,0),
    D{row}<=TIME(23,59,0),
    OR(C{row}="", D{row}>=C{row})
  )
)
Protection: Double-layer text blocking via NOT(ISTEXT()) + ISNUMBER()
Error: "End Time must be a valid time between 00:00 and 24:00, and cannot be earlier than Start Time."
๐Ÿงฎ WORKING HOURS CALCULATION RULES
Calculation Working Hours Formula Column E Automatic calculation with half-hour rounding:
=IFERROR(FLOOR.MATH((D{row}-C{row})*24, 0.5), "")
Rounding Examples:
โ€ข 4.25h โ†’ 4.0h
โ€ข 4.51h โ†’ 4.5h
โ€ข 8.99h โ†’ 8.5h
โ€ข 9.01h โ†’ 9.0h
Rounding: 0.5h (down)
Error fallback: Empty string
Calculation Number Format Column E Format: General
Clean display without unnecessary decimals
โ€ข 9 (not 9.0)
โ€ข 9.5 (not 9.50)
Display: Automatic
Type: Number
Calculation Total Working Hours Summary Row Row: Immediately after last day row
Label: "TOTAL WORKING HOURS" (merged A:D, grey background)
Formula (Column E):
=SUM(E15:E{lastDay})
Format: Sky blue background, bold, centered
Cell locked: Yes (formula-only)
Column: E
Function: SUM
Editable: โŒ No
Calculation Total OT Hours Summary Row Placed in Column F of the same TOTAL WORKING HOURS row as the working hours sum:
=SUM(F15:F{lastDay})
Format: Sky blue background, bold, centered, General number format
Border: Double bottom border on F (matches Col E)
Cell locked: Yes (formula-only)
Column: F
Function: SUM
Row: TOTAL WORKING HOURS row
Editable: โŒ No
Calculation Total Working Days Counter Summary Row Row: Directly below "TOTAL WORKING HOURS"
Label: "TOTAL WORKING DAYS" (merged A:D, grey background)
Formula (Column E):
=COUNT(E15:E{lastDay})
Purpose: Counts the number of working days (non-empty working hours cells)
Format: Sky blue background, bold, centered, General number format
Cell locked: Yes (formula-only)
Note: COUNT function ignores blank cells, only counts days with working hours
Example:
โ€ข January: 31 days total
โ€ข Sundays: 4 days (no working hours) โ†’ Not counted
โ€ข Public Holidays: 1 day (no working hours) โ†’ Not counted
โ€ข Full Day Leave: 2 days (blank E) โ†’ Not counted
โ€ข Working days: 24 days (with E values) โ†’ COUNT = 24
Column: E
Function: COUNT
Position: Below TOTAL WORKING HOURS
Editable: โŒ No
๐ŸŽ‰ PUBLIC HOLIDAY RULES
Holiday Holiday Data Source API API: data.gov.hk JSON endpoint
https://www.1823.gov.hk/common/ical/gc/data.json
Storage: Holidays sheet (Col A: Date, Col B: Name)
Years: Current year + Next year
Refresh: Manual/Reminder
Last Refresh: Config!A2
Holiday Holiday Refresh Policy 6 months Interval: Every 180 days
Reminder: Automatic prompt when generating timesheets
Tracking: Last refresh date in Config!A2
Trigger: CheckHolidayRefreshReminder() on generation
Days: 180
Prompt: Yes/No dialog
Holiday Holiday Marking Timesheet Label: "PUB" in Day Name column (B)
Text Color: Red
Background: Light Gray (RGB: 217, 217, 217)
Expected Hours: 0 (auto-set)
OT Allowed: โŒ No
Column B: "PUB"
Expected: 0h
๐Ÿ–๏ธ LEAVE MANAGEMENT RULES
Leave Leave Dropdown Validation Column G Source: Template_Clean!G15 validation
Type: Data validation list
Behavior:
  • In-cell dropdown: โœ… Enabled
  • Allow blank: โœ… Yes
  • Show error: โœ… Yes (blocks invalid entries)
Types:
AL (Full Day)
AL (AM) / AL (PM)
SL (Full Day)
SL (AM) / SL (PM)
NPL (Full Day)
NPL (AM) / NPL (PM)
Others
Alert: Stop
Leave Leave Helper Column
(Combined Warnings)
Column I Type: Soft warning (non-blocking)
Column I displays four types of warnings (in priority order):
  • AM Leave Warning: If Leave (Column G) contains "AM" AND Start time (Column C) is before 12:00:
    "Please check: AM Leave start time should be 12:00 or later."
  • PM Leave Warning: If Leave (Column G) contains "PM" AND End time (Column D) is after 15:00:
    "Please check: PM Leave end time should be 15:00 or earlier."
  • Full Day Leave Warning: If Leave (Column G) contains "Full Day" AND any working time field (C/D/E/F) is non-empty:
    "Please leave working time blank for full-day leave."
  • Others Remarks Required: If Leave (Column G) = "Others" AND Remarks (Column H) is blank:
    "Please specify leave type in Remarks."
Formatting: Red text (via Conditional Formatting)
Cell locked: Formula-only, user cannot edit
Display: WrapText disabled for clean single-line display
=IF(AND(ISNUMBER(SEARCH("AM",G{row})),C{row}<TIME(12,0,0)),"Please check: AM Leave start time should be 12:00 or later.",
  IF(AND(ISNUMBER(SEARCH("PM",G{row})),D{row}>TIME(15,0,0)),"Please check: PM Leave end time should be 15:00 or earlier.",
  IF(AND(ISNUMBER(SEARCH("Full Day",G{row})),OR(C{row}<>"",D{row}<>"",E{row}<>"",F{row}<>"")),"Please leave working time blank for full-day leave.",
  IF(AND(G{row}="Others",H{row}=""),"Please specify leave type in Remarks.",""))))
Column: I
Warnings: 4 types
Cell: Locked (formula)
WrapText: โŒ No
๐Ÿ”’ SHEET PROTECTION RULES
Protection Locked Elements All sheets Default: All cells locked
Protected columns:
  • Column A: Day Number (1, 2, 3...)
  • Column B: Day Name (Mon, Tue, PUB...)
  • Column E: Working Hours (formula)
  • TOTAL row: E & F (totals)
Password: None
Mode: Contents protected
Protection Unlocked (Editable) Cells Data rows Editable range: Row 15 to (14 + daysInMonth)
Columns unlocked:
  • Column C: Start Time
  • Column D: End Time
  • Column F: Daily OT (working days only - weekdays + working Saturdays)
  • Column G: Leave
  • Column H: Remarks
Locked (Formula-only):
  • Column E: Working Hours (formula)
  • Column F: Non-working days (Sun/PH/Sat Off) - uses formula =E{row}
  • Column I: Leave Helper (combined Full Day + Others warnings)
Note: Column F selective locking implemented via two-phase protection:
1. Row loop sets initial lock state based on day type
2. Re-unlock loop after global .Cells.Locked = True restores working day unlock
Editable: C/D/F*/G/H
Locked: E, F* (non-working), I
Rows: 15 to 14+days
F* Phases: 2 (initial + re-unlock)
Protection Formatting Permissions All sheets Users can format even when protected:
  • AllowFormattingCells: โœ… True
  • AllowFormattingColumns: โœ… True
  • AllowFormattingRows: โœ… True
Format: โœ… Allowed
Edit: โŒ Restricted
๐Ÿ“ TIMESHEET LAYOUT & FORMATTING
Layout Sheet Row Structure Generated Sheet Fixed header rows (from template):
  • Rows 1โ€“5: Company logo, timesheet title, inquiry email
  • Row 6: Employee Name label (A6) + name value (C6) + Month/Year (H6)
  • Row 7: Blank spacing
  • Rows 8โ€“11: Notice section (4 rows, border block A8:H11)
  • Row 12: Blank spacing before column headers
  • Rows 13โ€“14: Two-row merged column headers (sky blue)
Dynamic data rows:
  • Row 15: Day 1 of the month
  • Row 15+Nโˆ’1: Day N (N = 1 to 28/29/30/31)
  • TOTAL WORKING HOURS row: Immediately after last day row
  • TOTAL WORKING DAYS row: One row below TOTAL WORKING HOURS
  • Signature rows: +3 and +5 rows after TOTAL WORKING DAYS
Data start: Row 15
Header rows: 1โ€“14
Max data rows: 31
Layout Column Schema & Widths Columns Aโ€“I
ColHeaderTypeWidthEditable
ADay NumberNumber format "00"6โŒ Locked
BDay Name / PUBText10โŒ Locked
CStart Timehh:mm (input)10โœ… Unlocked
DEnd Timehh:mm (input)10โœ… Unlocked
EWorking HoursFormula FLOOR.MATH16โŒ Locked
FOvertime Hours (OT)Input (working days) / =E (non-working)14โœ…/โŒ Mixed
GLeaveDropdown (input)18โœ… Unlocked
HRemarksFree text (input)28โœ… Unlocked
I(no header)Formula helper (no border, no bg)32โŒ Locked
Input cols: C, D, F* (working days), G, H
Formula cols: E, F* (non-working), I
Locked: A, B, E, F* (non-working), I
Layout Row Color Coding All data rows
Day TypeColumns coloredBackgroundText highlight
Weekday (Monโ€“Fri)A & B onlySky Blue RGB(218,238,243)Default (black)
SaturdayFull row A:HGray RGB(220,220,220)Default (black)
SundayFull row A:HGray RGB(220,220,220)Col B text: Red
Public HolidayFull row A:HGray RGB(220,220,220)Col B "PUB": Red, Bold
Note: Column I always has no background color regardless of day type
Weekday bg: RGB(218,238,243)
Non-working bg: RGB(220,220,220)
Red text: Sun / PUB
Col I: No background
Layout Notice Section (Rows 8โ€“11) Rows 8โ€“11 Fixed notice block generated above the data header rows:

Row 8: "Notice" โ€” Bold + Underline
Row 9 (A:H merged): "1. Working Time format must be 00:00 to 24:00 (Must input ":" in between HH and MM)"
Row 10 (A:H merged): "2. Each unit is calculated by 30 minutes, the total working hours will be rounded down if less than half hour."
Row 11 (A:H merged): (blank)
Border: Top + Bottom + Right border on entire block A8:H11
WrapText: Disabled on rows 9โ€“11 (single-line display)
Rows: 8โ€“11
Merge: A:H (rows 9โ€“11)
WrapText: โŒ No
Layout Signature Rows After totals Two signature lines appended after the TOTAL WORKING DAYS row:
A{totalDay+3}: "Employee Signature / Date : ______________________________"
A{totalDay+5}: "Approved By Supervisor / Date: ___________________________"
Both lines are bold, left-aligned. These rows are not explicitly locked/unlocked (default to protected sheet state).
Position: +3 & +5 rows after TOTAL WORKING DAYS
Format: Bold, left-aligned
๐Ÿ“ FILE MANAGEMENT RULES
File Mgmt Output Folder Structure Directory Pattern: output/{Year}-Generated
Examples:
  • output/2025-Generated/
  • output/2026-Generated/
Auto-created: If folder doesn't exist
Base: output/
Year: Auto-detect
File Mgmt File Naming Convention Output files
ASK IT-Timesheet {EmployeeID} {Year} ({EmployeeType}).xlsx
Examples:
โ€ข ASK IT-Timesheet A0012 2026 (Full-time).xlsx
โ€ข ASK IT-Timesheet B0045 2026 (Part-time).xlsx
Format: .xlsx
Macros: โŒ Not included
File Mgmt Config Auto-Update Config!B2 Function: UpdateConfigOutputFolder_WithYear()
Automatically updates output folder path to match current generation year
Before: output/2025-Generated
After: output/2026-Generated (when generating 2026)
Cell: Config!B2
Label: Config!B1
โš™๏ธ GENERATION PROCESS RULES
Process WorkWeekStart
(Tueโ€“Fri / Wedโ€“Fri Support)
EmployeeData Col I Employees with a non-Monday start have their off-days (e.g. Monday for a Tueโ€“Fri worker) automatically treated as non-working days.

Supported values: Mon / Tue / Wed / Thu / Fri  (blank = Mon)
Logic: Any weekday with VBA Weekday number less than WorkWeekStart is treated as non-working:
  • Grey background (same as Saturday/Sunday)
  • Start/End times left blank
  • OT column uses formula =E{row} (locked)
  • Cell locked โ€” user cannot edit
Scope: Consecutive trailing days only (e.g. Mon off โ†’ Tueโ€“Fri working). Non-consecutive patterns not supported.
Backward compatible: Blank Col I defaults to Mon โ€” no change for existing employees.
Examples:
โ€ข Col I blank or Mon โ†’ normal Monโ€“Fri (default)
โ€ข Col I = Tue โ†’ Monday treated as non-working day
โ€ข Col I = Wed โ†’ Monday & Tuesday treated as non-working days
Column: EmployeeData!I
Default: Mon (blank)
Off-day style: Grey, locked
OT formula: =E{row}
Editable: โŒ No
Process Part-time Employee
(PT Timesheet Format)
EmployeeData Col G Employees with Col G = PT (or any value containing “Part”) receive a distinct timesheet layout:

Col F (Overtime) — ALL rows: blank "", locked, entire column hidden.
Col F is hidden but still read by Validator; any formula value (even on Sun/PH rows) would trigger false Rule 6/7 OT errors. All rows suppressed to blank to prevent this.

Working day rows:
  • White background (instead of sky blue A:B)
  • Start/End times (C/D) blank — employee fills manually
  • No 1-hour OT buffer rule applied
Col F display: entire Overtime column hidden in generated sheet.
Unchanged: Sunday/PH rows (grey, locked); Leave (G) and Remarks (H) unlocked.
Backward compatible: Blank Col G or FT — no change.
Column: EmployeeData!G
Trigger: “PT” or contains “Part”
Weekday bg: White
Col F (all rows): blank "", hidden
OT buffer: ❌ None
Process Pre-Generation Checks Validation Sequence:
  1. Holiday refresh check (โ‰ฅ180 days prompt)
  2. Employee ID validation (must exist in EmployeeData)
  3. Year selection (auto-detect or prompt)
  4. Time slot selection (UserForm if multiple)
Required: Employee ID
Optional: Year (if single)
Process Employee Data Requirements EmployeeData sheet Required columns:
  • A: Employee ID (e.g., A0012)
  • B: Employee Name
  • C: Weekday Start Time
  • D: Weekday End Time
  • E: Saturday Start Time (blank if 5-day)
  • F: Saturday End Time (blank if 5-day)
  • G: Employee Type (Full-time/Part-time)
  • H: Employee Email(s) โ€” semicolon-separated; first = To, rest = CC
  • I: WorkWeekStart (optional) โ€” first working day: Mon / Tue / Wed / Thu / Fri; blank = Mon (default)
  • J: OTBuffer (optional) โ€” per-employee OT buffer hours (numeric; blank = 1h default)
Sheet: EmployeeData
Header row: 1
Process Generation Sequence All months For each employee:
  1. Load employee data & time slots
  2. Calculate expected hours (weekday/Saturday)
  3. Create new workbook
  4. Generate all 12 months (Jan-Dec)
  5. For each month:
    • Copy template header
    • Populate calendar grid
    • Mark weekends & holidays
    • Apply formulas & validation
    • Protect sheet
  6. Save as .xlsx in output/{Year}-Generated/
Months: 12
Order: Jan โ†’ Dec
Process Config Sheet Cell Reference Config Sheet All configurable generator settings are stored in the Config sheet:
CellLabelValue / Purpose
A1LastRefreshDateLabel header
A2โ€”Date of last holiday refresh; used for 180-day reminder check
B1OutputFolderLabel header
B2โ€”Output folder path; auto-updated by UpdateConfigOutputFolder_WithYear() on each generation run
D1EmailModeLabel header
D2โ€”"Draft" (default) or "Send" โ€” controls whether SendTimesheetEmails() creates Outlook drafts or sends immediately
E1SenderEmailLabel header
E2โ€”Sender Outlook account address (e.g. radmin@askit.com.hk); must be added in Outlook File > Add Account
A2: Last holiday refresh
B2: Output folder
D2: EmailMode (Draft / Send)
E2: SenderEmail
Process LeaveTypes Sheet (Hidden) Output workbook A hidden sheet named LeaveTypes is copied from the generator workbook into every output timesheet.
Purpose: Provides the data source list for Leave dropdown validation in Column G
Visibility: xlSheetHidden โ€” hidden from employees
Warning: If LeaveTypes is missing from the generator workbook, the Column G dropdown will not function in any generated file
Sheet: LeaveTypes (hidden)
Role: Col G dropdown source
Required: โœ… Yes
Process Template Source Template sheets Primary: Template_Clean sheet
Fallback: December sheet (if Template_Clean not found)
Copied elements:
  • Logo & company header (rows 1-4)
  • Inquiry email
  • Leave dropdown validation (Column G)
Source: Template_Clean
Rows: 1-14 (headers)
Process ALL Keyword (Batch All Employees) GenerateTimesheetSendTimesheetEmails Both GenerateTimesheet and SendTimesheetEmails accept ALL (case-insensitive) at the Employee ID prompt.
Expands to the full list of active EmpIDs from EmployeeData Col A (rows 2 onwards, skips blanks).
GenerateTimesheet: saves to batch\ subfolder when more than 1 employee is processed.
SendTimesheetEmails: scans output folder for each employee's .xlsx and emails in sequence.
Input: ALL
Source: EmployeeData Col A
Output: batch\ subfolder (generator)
๐Ÿ“ง EMAIL DISTRIBUTION RULES
Email Email Mode (Config D2) Config Sheet Controls whether SendTimesheetEmails() creates Outlook Drafts or sends immediately.
Draft mode (default): Creates a saved draft in Outlook โ€” safe to review before sending.
Send mode: Immediately delivers email to recipients.
Cell: Config!D2
Values: Draft | Send
Default: Draft
Email Sender Account (Config E2) Config Sheet Specifies the Outlook sender account email (e.g. radmin@askit.com.hk).
Uses triple-property matching: SmtpAddress โ†’ DisplayName โ†’ UserName to handle shared/delegated mailboxes.
Requires both .SendUsingAccount and .SentOnBehalfOfName for Exchange/M365 environments.
Cell: Config!E2
Format: Full email address
Exchange fix: Both properties required
Email Employee Email
(EmployeeData Col H)
EmployeeData Sheet Semicolon-separated email list per employee in Column H.
First address = To (primary recipient).
Additional addresses = CC (e.g. manager or secondary account).
Example: andy.chan@askit.com.hk;andychan25.askit@gmail.com
Column: EmployeeData!H
Separator: ;
First: To | Rest: CC
Email File Matching by EmpID Output Folder Scans the output folder (batch or year folder) for .xlsx files containing the EmpID.
Uses space-padded matching: searches for " EmpID " to prevent partial ID hits (e.g., A001 matching A0012).
Prefers batch/ subfolder; falls back to YYYY-Generated/ year folder.
Pattern: " EmpID " (space-padded)
Extension: .xlsx
Folder priority: batch โ†’ year
Email Cancel-Safe Year Dialog Email Flow If the user presses Cancel or X on the year-selection InputBox, the entire email send flow is aborted silently.
GetYearFromHolidays() returns 0 on cancel โ†’ ResolveOutputPath() exits immediately โ†’ main sub exits.
No drafts or sends are created when the user abandons the dialog.
Cancel signal: yr = 0
Abort point: ResolveOutputPath()
Behavior: Silent abort
Email FT / PT Template Auto-Detection Email FlowEmployeeData Sheet Per-employee template selection inside the send loop โ€” a single Send Emails run can dispatch mixed FT and PT emails in one pass.
Reads EmployeeData Col G via GetEmployeeType(empID). If the value equals "PT" (case-insensitive, trimmed), the PT template is used; otherwise defaults to FT.
FT subject: "Your Support Needed for New Attendance Process โ€“ Mandatory Testing Starting March"
PT subject: "Important: Your Support Needed for New Attendance Process"
Both templates use Aptos font, 11pt. PT body is a simplified bullet-point format with a bold underlined Submission Deadline (2nd of the Month).
Source column: EmployeeData!G
PT trigger: value = PT (case-insensitive)
FT fallback: any other value
Font: Aptos 11pt
Email Outlook Connection Prerequisite Email Flow Outlook must already be open before running Send Emails. The macro uses GetObject(, "Outlook.Application") to attach to a running instance โ€” it does not launch Outlook automatically.
If Outlook is not running, a dialog prompts: "Outlook is not open. Please start Outlook first, then re-run this macro." and the macro exits cleanly without sending or creating drafts.
Connection method: GetObject only (no CreateObject)
Prerequisite: Outlook must be open and signed in
Error behaviour: MsgBox + Exit Sub