“Can your system enforce a month-end deadline, handle late submissions fairly, and still produce a clean payroll summary?”
That one question changed how I approached what I initially thought was “just Excel processing”.
I was building Timesheetflow—a small system to automate monthly employee timesheets that are still submitted as Excel files (because, realistically, many teams still live in spreadsheets).
At first, I designed it like a typical file-processing app:
- read Excel files
- validate rows
- merge results
- export a summary
It worked.
But it didn’t explain the business. And it didn’t protect the business rules that actually matter at month-end.
The Real Workflow (What the Business Actually Does)
Timesheetflow’s workflow is simple—and very real:
- Admin sets a processing deadline (usually month-end)
- Employees upload their own monthly timesheet to a Google Drive folder before the deadline
- submissions are for the current month only
- At the deadline, the system processes timesheets one by one
- Approvers review and approve one, many, or all timesheets
- late submissions are allowed but clearly marked Late
- Admin downloads an Excel salary summary for that month
When I wrote it down like this, I realized something:
This isn’t an “Excel automation” problem.
This is a monthly payroll close problem.
What I Thought the Problem Was
My first approach was very implementation-driven:
- “There’s a Drive folder”
- “There are Excel files”
- “I need to parse and export”
So my architecture naturally became a pipeline:
- Drive client downloads files
- a parser extracts rows
- a validator checks columns
- an exporter builds a consolidated report
It looked clean… but the code kept reading like:
“update tables, move files, generate output”
Instead of:
“close the month, enforce the deadline, approve submissions, finalize payroll”
That difference sounds subtle, but it changes everything—especially when requirements evolve.
What I Realized Afterward
Good architecture isn’t just about layers and patterns.
It’s about meaning.
The moment I treated the month-end workflow as a _domain _(instead of a batch job), the design got clearer:
A deadline isn’t a timestamp field—it’s a rule that changes system behavior.
“Late” isn’t a warning label—it affects approval visibility and auditability.
“Latest submission wins” isn’t a query trick—it’s a policy the business depends on.
The Ubiquitous Language I Started Using
Once I stopped thinking in “files” and started thinking in “business”, the vocabulary became obvious:
- Monthly Run / Payroll Period (the month being closed)
- Deadline (the cutoff time)
- Timesheet Submission (one employee’s monthly entry)
- Late Submission (allowed, but flagged)
- Latest Submission Wins (policy when employees upload multiple versions)
- Approval (approve/reject, bulk approve valid)
- Salary Summary Export (the admin deliverable)
- When your code uses this language, you don’t need long comments to explain what it does.
Before vs After (Pipeline Thinking vs Domain Thinking)
Before: “Process files”
// Pipeline mindset: process whatever is in the folder
var files = drive.ListXlsxFiles(folderId);
foreach (var file in files)
{
var rows = ParseExcel(file);
Validate(rows);
Save(rows);
}
ExportSalarySummary();
It works—but it hides the real rules:
- Which month is this?
- Is the deadline enforced?
- Which file wins if an employee uploads twice?
- What happens after approval?
After: “Close a monthly run”
// Domain mindset: close a payroll period with explicit rules
run.LockAtDeadline(now);
run.RefreshSubmissionsFromDrive(); // latest submission wins
run.FlagLateSubmissions(); // uploaded after deadline
foreach (var submission in run.ActiveSubmissions())
{
submission.ValidateCurrentMonthOnly();
}
approvals.ApproveAllValid(run); // late is a badge, invalid blocks approval
var report = payroll.ExportSalarySummary(run); // Excel for admin
Now the code reads like the business process:
- lock the period
- evaluate submissions under known rules
- approve what’s valid
- export the payroll summary
That shift made everything easier to maintain and explain.
Key Domain Rules (That Matter More Than Excel)
Here are the policies I implemented in Timesheetflow’s MVP:
1) Current month only
Employees can submit timesheets for the active month only.
2) Deadline locks the run
At the deadline, the run transitions into a “locked” phase for processing.
3) Late submissions are allowed, but flagged
After the deadline, submissions are marked Late and stay visible in approval/export.
4) Latest submission wins
If an employee uploads multiple timesheets for the same month:
- the system uses the most recent file (by Drive timestamps)
- older ones are marked superseded
5) Invalid beats late
If a timesheet is invalid, it’s invalid—whether it’s late or not.
Late is a badge. Invalid is a blocker.
6) Salary export is the admin deliverable
The end result is not “processed rows”—it’s a monthly Excel summary that payroll/admin can use.
Implementation Notes (Tech Stack)
Timesheetflow is built with:
- ASP.NET Core (.NET 8) for Web + API
- Hangfire for deadline scheduling and background processing
- ClosedXML for Excel parsing/export
- Google Drive as the submission channel (Workspace folder + service account access)
Even with a simple stack, modeling the domain clearly made the system feel “product-like” instead of “script-like”.
Key Takeaways
- This wasn’t really an Excel problem—it was a month-end payroll close problem.
- Deadlines, late flags, and “latest submission wins” are domain rules, not utility logic.
- When code mirrors the business language, it becomes easier to read, test, and extend.
- The best architecture doesn’t just run—it communicates intent.
Closing Thoughts
I started Timesheetflow thinking I was building an Excel automation tool.
But the real value came when I stopped designing around files and started designing around the workflow the business lives by every month.
If you’ve ever had a moment where a “simple automation” turned out to be a domain problem in disguise, I’d love to hear it.
Connect with Me
GitHub: https://github.com/timleunghk
LinkedIn: https://www.linkedin.com/in/timothy-leung-48261b8b
If you’d like, I can also share:
a minimal domain model (MonthlyRun, TimesheetSubmission, Approval) for .NET 8
a Hangfire job layout for the deadline + processing pipeline
an example Salary_Summary.xlsx structure (ByEmployee / Details / Exceptions)
Just tell me whether you want the article to include code that compiles (full sample classes), or keep it at conceptual snippets like this post.
Top comments (0)