DEV Community

Cover image for The Deadline That Made Me Rethink My Architecture: Building Timesheetflow with Domain Thinking
Timothy Leung
Timothy Leung

Posted on

The Deadline That Made Me Rethink My Architecture: Building Timesheetflow with Domain Thinking

“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:

  1. Admin sets a processing deadline (usually month-end)
  2. Employees upload their own monthly timesheet to a Google Drive folder before the deadline
  3. submissions are for the current month only
  4. At the deadline, the system processes timesheets one by one
  5. Approvers review and approve one, many, or all timesheets
  6. late submissions are allowed but clearly marked Late
  7. 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();

Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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)