| 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)
|
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:
=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(
Non-working days (Sun/PH/Sat Off) have no validation since they use formula =E{row}F{row}="", E{row}="", F{row}<=MAX(0,E{row}-{expectedHours}-1) ) 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 |
=OR(C{row}="",
Protection: Double-layer text blocking via NOT(ISTEXT()) + ISNUMBER()
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}) ) ) |
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 |
=OR(D{row}="",
Protection: Double-layer text blocking via NOT(ISTEXT()) + ISNUMBER()
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}) ) ) |
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: 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, centeredCell 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 formatBorder: 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 |
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:
|
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):
Cell locked: Formula-only, user cannot edit Display: WrapText disabled for clean single-line display |
Column: I Warnings: 4 types Cell: Locked (formula) WrapText: โ No |
||||||||||||||||||||||||||||||||||||||||||||||||||
| ๐ SHEET PROTECTION RULES | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Protection | Locked Elements | All sheets |
Default: All cells locked Protected columns:
|
Password: None Mode: Contents protected |
||||||||||||||||||||||||||||||||||||||||||||||||||
| Protection | Unlocked (Editable) Cells | Data rows |
Editable range: Row 15 to (14 + daysInMonth) Columns unlocked:
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:
|
Format: โ
Allowed Edit: โ Restricted |
||||||||||||||||||||||||||||||||||||||||||||||||||
| ๐ TIMESHEET LAYOUT & FORMATTING | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Layout | Sheet Row Structure | Generated Sheet |
Fixed header rows (from template):
|
Data start: Row 15 Header rows: 1โ14 Max data rows: 31 |
||||||||||||||||||||||||||||||||||||||||||||||||||
| Layout | Column Schema & Widths | Columns AโI |
|
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 |
|
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
Border: Top + Bottom + Right border on entire block A8:H11Row 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) 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 : ______________________________"
Both lines are bold, left-aligned. These rows are not explicitly locked/unlocked (default to protected sheet state).
A{totalDay+5}: "Approved By Supervisor / Date: ___________________________" |
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:
|
Base: output/ Year: Auto-detect |
||||||||||||||||||||||||||||||||||||||||||||||||||
| File Mgmt | File Naming Convention | Output files |
ASK IT-Timesheet {EmployeeID} {Year} ({EmployeeType}).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:
Backward compatible: Blank Col I defaults to Mon โ no change for existing employees.
|
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:
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:
|
Required: Employee ID Optional: Year (if single) |
||||||||||||||||||||||||||||||||||||||||||||||||||
| Process | Employee Data Requirements | EmployeeData sheet |
Required columns:
|
Sheet: EmployeeData Header row: 1 |
||||||||||||||||||||||||||||||||||||||||||||||||||
| Process | Generation Sequence | All months |
For each employee:
|
Months: 12 Order: Jan โ Dec |
||||||||||||||||||||||||||||||||||||||||||||||||||
| Process | Config Sheet Cell Reference | Config Sheet |
All configurable generator settings are stored in the Config sheet:
|
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 employeesWarning: 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:
|
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: ALLSource: EmployeeData Col A Output: batch\ subfolder (generator) |
||||||||||||||||||||||||||||||||||||||||||||||||||
| ๐ง EMAIL DISTRIBUTION RULES | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 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 | SendDefault: Draft |
|||||||||||||||||||||||||||||||||||||||||||||||||||
| 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 |
|||||||||||||||||||||||||||||||||||||||||||||||||||
| 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 |
|||||||||||||||||||||||||||||||||||||||||||||||||||
| 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 |
|||||||||||||||||||||||||||||||||||||||||||||||||||
| 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 |
|||||||||||||||||||||||||||||||||||||||||||||||||||
| 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 |
|||||||||||||||||||||||||||||||||||||||||||||||||||
| 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 |
|||||||||||||||||||||||||||||||||||||||||||||||||||