This post outlines the comprehensive design for an Employee Management System (EMS), covering software planning, database design, full-stack architectural design, and detailed module functionalities including efforts clocking and leave planning.
Part 1: Introduction & Project Planning
1.1 Project Overview
The Employee Management System (EMS) is a web-based application designed to streamline human resources operations within an organization. It provides a centralized platform for managing employee information, tracking work efforts (clock-in/out), handling leave requests, and generating various HR-related reports. The system aims to improve efficiency, accuracy, and transparency in HR processes.
1.2 Project Objectives
Centralize Employee Data: Provide a single source of truth for all employee-related information.
Automate Time Tracking: Implement a user-friendly system for employees to clock in and out, and for managers to review time logs.
Simplify Leave Management: Facilitate submission, approval, and tracking of various leave types, including balance management.
Enhance Reporting: Offer comprehensive reporting capabilities for HR, managers, and employees.
Improve Operational Efficiency: Reduce manual HR tasks and minimize errors.
Ensure Data Security: Protect sensitive employee data through robust security measures.
1.3 Scope of the System
The initial scope of the EMS includes:
Employee Management: Add, view, edit, delete employee profiles.
Department & Role Management: Define and manage organizational structure.
Time Tracking: Clock-in/out, manual entry (with approval), daily/weekly time logs.
Leave Management: Leave type configuration, request submission, approval workflow, balance tracking.
User Authentication & Authorization: Secure login, role-based access control (RBAC).
Basic Reporting: Time sheets, leave summaries, employee directories.
Admin Dashboard: Overview of key HR metrics.
1.4 User Roles and Permissions
The system will support distinct user roles with specific access privileges:
Role | Description | Key Permissions |
---|---|---|
Employee | Standard organizational member. | View own profile, Clock In/Out, View own time logs, Request leave, View own leave balance/history. |
Manager | Oversees a department or team. | All Employee permissions + View team's time logs, Approve/Reject leave requests for direct reports, View team's leave balances. |
HR Admin | Manages HR processes and employee data. | All Manager permissions + Add/Edit/Delete employee profiles, Manage departments/roles, Manage leave types, Generate comprehensive reports, Override leave. |
System Admin | Manages system configurations and users. | All HR Admin permissions + User account management, Role assignment, System settings. |
Part 2: Database Design
The database will be the backbone of the EMS, storing all critical employee, time, and leave data. A relational database (e.g., SQL Server, PostgreSQL, MySQL) is suitable for ensuring data integrity (ACID properties).
2.1 Entity-Relationship (ER) Diagram
+---------------+ +-----------------+ +--------------+
| Department | | Role | | Employee |
+---------------+ +-----------------+ +--------------+
| - DeptID (PK) <------>| - RoleID (PK) | | - EmployeeID (PK)
| - Name | | - Name | | - UserID (FK)
| - Description | | - Description | | - DeptID (FK)
+---------------+ +-----------------+ | - RoleID (FK)
| - ManagerID (FK, self-referencing)
| - FirstName
| - LastName
| - Email (Unique)
| - PhoneNumber
| - HireDate
| - DateOfBirth
| - Address
| - IsActive
| - CreatedAt
| - LastModifiedAt
+--------------+
|
| One-to-Many
|
+-----------------------+ +------------------+ +-------------------+
| TimeClockEntry | | Leave | | LeaveType |
+-----------------------+ +------------------+ +-------------------+
| - EntryID (PK) | | - LeaveID (PK) | | - LeaveTypeID (PK)
| - EmployeeID (FK) | | - EmployeeID (FK)| | - Name |
| - ClockInTime | | - LeaveTypeID (FK)| | - Description |
| - ClockOutTime | | - StartDate | | - MaxDaysPerYear |
| - DurationMinutes | | - EndDate | | - RequiresApproval|
| - Status (e.g., Approved) | | - TotalDays | +-------------------+
| - CreatedAt | | - Status |
+-----------------------+ | - RequestDate |
| - ApprovalDate |
| - ApprovedByEmpID (FK) |
| - Reason |
| - ManagerComment |
+------------------+
Note: For User Authentication, there would be a separate Users
table (often managed by ASP.NET Core Identity) which Employee.UserID
would link to. This separation allows an employee to have a system login without being directly tied to the employee profile data.
2.2 Schema Details
2.2.1 Departments
Table
Column Name | Data Type | Constraints | Description |
---|---|---|---|
|
|
| Unique identifier for department. |
|
|
| Department name. |
|
| Description of the department. |
2.2.2 Roles
Table
Column Name | Data Type | Constraints | Description |
---|---|---|---|
|
|
| Unique identifier for role. |
|
|
| Role name (e.g., "Software Engineer"). |
|
| Description of the role. |
2.2.3 Employees
Table
Column Name | Data Type | Constraints | Description |
---|---|---|---|
|
|
| Unique identifier for employee. |
|
|
| Links to the user's login account. |
|
|
| Department employee belongs to. |
|
|
| Role of the employee. |
|
|
| Self-referencing to employee's manager. |
|
|
| Employee's first name. |
|
|
| Employee's last name. |
|
|
| Employee's work email. |
|
|
| Employee's phone number. |
|
|
| Date employee was hired. |
|
|
| Employee's date of birth. |
|
|
| Employee's address. |
|
|
| Flag for active/inactive employee. |
|
|
| Record creation timestamp. |
|
|
| Last modification timestamp. |
2.2.4 LeaveTypes
Table
Column Name | Data Type | Constraints | Description |
---|---|---|---|
|
|
| Unique ID for leave type. |
|
|
| Leave type name (e.g., "Annual Leave", "Sick Leave"). |
|
| Description of leave type. | |
|
|
| Maximum days allowed per year for this type. |
|
|
| Does this leave type need manager approval? |
2.2.5 Leaves
Table
Column Name | Data Type | Constraints | Description |
---|---|---|---|
|
|
| Unique ID for leave request. |
|
|
| Employee requesting leave. |
|
|
| Type of leave requested. |
|
|
| Start date of leave. |
|
|
| End date of leave. |
|
|
| Total days requested (e.g., 0.5, 1.0). |
|
|
| Current status of the request. |
|
|
| Date leave was requested. |
|
|
| Date leave was approved/rejected. |
|
|
| Employee who approved/rejected. |
|
|
| Reason for leave request. |
|
|
| Manager's comment on approval/rejection. |
2.2.6 TimeClockEntries
Table
Column Name | Data Type | Constraints | Description |
---|---|---|---|
|
|
| Unique ID for time clock entry. |
|
|
| Employee who clocked in/out. |
|
|
| Timestamp of clock-in. |
|
|
| Timestamp of clock-out. Null if still clocked in. |
|
|
| Calculated duration of the shift in minutes. |
|
|
| Status of the time entry (e.g., for manual edits). |
|
|
| Timestamp of entry creation. |
2.3 Indexing Strategy
Foreign Keys: Automatically indexed by most RDBMS.
Frequently Queried Columns:
Employees
:(DeptID)
,(RoleID)
,(ManagerID)
,(IsActive)
,(LastName, FirstName)
for searching.Leaves
:(EmployeeID, Status, StartDate, EndDate)
,(ApprovedByEmpID)
.TimeClockEntries
:(EmployeeID, ClockInTime, ClockOutTime)
for efficient time log retrieval.
Unique Constraints: Enforce uniqueness where needed (e.g.,
Email
inEmployees
).
2.4 Relationships
(As illustrated in the ER Diagram: One-to-Many relationships between Department
and Employee
, Role
and Employee
, Employee
and Leaves
, Employee
and TimeClockEntries
, LeaveType
and Leaves
. Employee
also has a self-referencing relationship for ManagerID
.)
Part 3: Software Architecture Design
3.1 Overall Architecture Diagram: Layered with API Backend-for-Frontend (BFF)
The system will adopt a modern, layered architecture with an ASP.NET Core Web API acting as a Backend-for-Frontend (BFF) to serve a Blazor web application. This provides modularity, scalability, and enhanced security.
+--------------------+
| Blazor Web App | (Web Browser)
| (Presentation Layer)|
+--------------------+
| HTTP/HTTPS (RESTful API Calls)
v
+--------------------+
| ASP.NET Core API | (API Layer / BFF)
| (Controllers) |
+--------------------+
|
v
+---------------------+
| Business Logic Layer| (Services, Validators, Domain Logic)
| (BLL) |
+---------------------+
|
v
+---------------------+
| Data Access Layer | (Repositories, ORM - Entity Framework Core)
| (DAL) |
+---------------------+
|
v
+---------------------+
| SQL Server Database | (Persistent Storage)
+---------------------+
Presentation Layer (Blazor): Handles all UI rendering, user interactions, and data binding. Communicates with the API Layer.
API Layer (ASP.NET Core Web API): Exposes RESTful endpoints for the frontend. Handles request routing, model binding, and basic validation. Acts as a facade to the BLL.
Business Logic Layer (BLL): Encapsulates core business rules, workflows, calculations (e.g., leave day calculations), and orchestrates calls to the DAL. Contains services for employee management, time tracking, leave management, etc.
Data Access Layer (DAL): Responsible for abstracting database operations. Uses Entity Framework Core (EF Core) to interact with the SQL Server database. Provides repositories for each entity.
Domain Model Layer (Models): Simple C# POCOs (Plain Old CLR Objects) that represent the entities in the database. Shared across DAL, BLL, and often API.
3.2 Technology Stack
Backend Platform: .NET 8 (LTS)
Web Framework: ASP.NET Core Web API
ORM: Entity Framework Core (for database interaction)
Authentication/Authorization: ASP.NET Core Identity (with JWT Bearer Tokens)
Logging: Serilog (integrated with cloud logging like Application Insights/CloudWatch)
Dependency Injection: Built-in .NET Core DI
Frontend Platform: .NET 8 (LTS)
UI Framework: Blazor WebAssembly (recommended for rich client-side interactivity, offline capabilities, and reduced server load)
Charting: A Blazor-compatible charting library (e.g., Syncfusion Blazor Charts, Chart.js via JS Interop)
UI Components: MudBlazor, Blazorise, or a custom component library for consistent UI.
Database: SQL Server (Azure SQL Database or on-premise)
Development Tools: Visual Studio 2022, Visual Studio Code, SQL Server Management Studio.
Version Control: Git (e.g., Azure Repos, GitHub).
CI/CD: Azure DevOps Pipelines or GitHub Actions.
3.3 Security Considerations
Authentication: Secure user login using ASP.NET Core Identity, supporting features like password hashing, MFA (Multi-Factor Authentication). JWT tokens for stateless API authentication.
Authorization: Implement Role-Based Access Control (RBAC) at the API endpoint level (
[Authorize(Roles = "HR Admin")]
) and within Blazor components (e.g., show/hide UI elements).Data Encryption:
At Rest: Database encryption (e.g., Transparent Data Encryption in SQL Server).
In Transit: All communication over HTTPS/SSL.
Input Validation: Robust server-side and client-side validation to prevent injection attacks (SQL injection, XSS) and ensure data integrity.
Secure Secrets Management: Store sensitive connection strings, API keys, and other secrets securely using Azure Key Vault or AWS Secrets Manager. Never hardcode secrets.
Least Privilege Principle: Database users, application service accounts, and API identities should have only the minimum necessary permissions.
Auditing and Logging: Comprehensive logging of security-related events (failed logins, unauthorized access attempts, critical data modifications).
Vulnerability Scanning: Integrate security scanning tools into the CI/CD pipeline for code, containers, and infrastructure.
Part 4: Detailed Modules & Features
4.1 Employee Management
CRUD Operations: Add new employees, view profiles, edit details (personal, contact, job), deactivate/reactivate employees.
Employee Directory: Search and filter employees by department, role, name, status.
Profile Viewer: Dedicated page for each employee's detailed profile.
Manager Assignment: Assign managers to employees (linking to
ManagerID
).Role & Department Assignment: Assign employees to departments and roles.
Data Validation: Ensure all mandatory fields are filled and data formats are correct (e.g., email format).
4.2 Department & Role Management
CRUD Operations: Add, view, edit, delete departments and roles.
Hierarchical View: Potentially visualize department hierarchy.
Assignment Tracking: View which employees belong to which department/role.
4.3 Efforts Clocking (Time Tracking)
Clock In/Out: Simple, intuitive interface for employees to record start and end of their work day.
Real-time Status: Display current clock-in/out status.
Manual Entry & Adjustments (with Approval):
Employees can request manual time entries (e.g., forgot to clock in).
Managers/HR can approve/reject manual entries or adjust existing ones.
Time Logs:
Employee View: Daily/weekly view of own clock entries and total hours.
Manager View: View time logs for direct reports.
HR View: View time logs for all employees, with filtering.
Dashboard Integration: Display current clock-in status on employee dashboard.
Reports: Daily/Weekly/Monthly timesheets, overtime reports, attendance reports.
4.4 Leave Planning & Management
Leave Type Configuration: HR can configure various leave types (
LeaveTypes
table) with rules (e.g., "Annual Leave," "Sick Leave," "Bereavement Leave").Leave Request Submission:
Employees select leave type, start date, end date, and provide a reason.
System calculates
TotalDays
based on selected dates (considering weekends/holidays if configurable).
Approval Workflow:
Requests are routed to the employee's assigned
ManagerID
.Managers receive notifications for pending requests.
Managers can view request details, employee's leave balance, and approve/reject with comments.
HR Admins can view and manage all leave requests.
Leave Balance Tracking:
System automatically tracks and displays current leave balances for each employee (per leave type).
Accrual rules (if applicable) can be implemented in the BLL.
Leave history with statuses for each employee.
Calendar View: Optional calendar showing approved leaves across departments/teams.
Reports: Leave balance reports, leave history reports, upcoming leave schedules.
4.5 User Authentication & Authorization
Login/Logout: Secure login pages.
Role Management: System Admin assigns roles to users.
Password Management: Secure password reset, strong password policies.
User Provisioning: Link system users (
AspNetUsers
) toEmployees
.
4.6 Reporting & Analytics
Pre-defined Reports: Generate common HR reports (e.g., Employee Roster, Department Headcount, Leave Summaries, Timesheet Exports).
Filter & Export: Reports will include filtering options and export capabilities (CSV, PDF).
Dashboard Metrics: Key performance indicators (KPIs) like active employees, total leave days, average work hours.
4.7 Admin Dashboard
Centralized overview for HR/System Admins.
Quick stats on employees, pending leaves, clock-in status.
Links to key administration modules.
Part 5: Implementation Plan
The implementation will follow an agile approach, iteratively delivering features in sprints.
Phase 1: Foundation & Core Employee Management (Sprints 1-4)
Environment Setup:
Set up .NET 8 SDK, Visual Studio, SQL Server.
Initialize Git repository.
Set up basic CI/CD for backend API and frontend Blazor app (build, test, deploy to Dev environment).
Database & DAL Setup:
Create
Departments
andRoles
tables.Implement
AspNetUsers
(ASP.NET Core Identity).Create
Employees
table with initial FKs toDepartments
,Roles
, andAspNetUsers
.Develop EF Core
DbContext
and initial repositories (DepartmentRepository
,RoleRepository
,EmployeeRepository
).
Backend API & BLL:
Create ASP.NET Core Web API project.
Implement basic authentication (login/logout).
Develop BLL services for Employee, Department, Role management.
Expose CRUD API endpoints for these entities.
Frontend (Blazor):
Create Blazor project.
Develop login/logout UI.
Create UI for
Department
andRole
management (Admin/HR only).Develop
Employee Directory
andEmployee Profile
(view-only initially).Basic RBAC integration (show/hide admin sections).
Phase 2: Time Tracking (Sprints 5-8)
Database & DAL:
Create
TimeClockEntries
table.Extend
EmployeeRepository
or createTimeClockRepository
.
Backend API & BLL:
Develop BLL service for time tracking.
Expose API endpoints for Clock In/Out, submitting manual entries.
Implement logic for calculating
DurationMinutes
.
Frontend (Blazor):
Develop
Clock In/Out
UI for employees.Implement
My Time Logs
view for employees.Create
Team Time Logs
view for managers.Add
Timesheet Report
for HR.
Phase 3: Leave Management (Sprints 9-12)
Database & DAL:
Create
LeaveTypes
andLeaves
tables.Develop
LeaveTypeRepository
andLeaveRepository
.
Backend API & BLL:
Develop BLL service for leave management (request, approval, balance calculation).
Expose API endpoints for
LeaveTypes
CRUD.Expose API endpoints for submitting leave requests, approving/rejecting.
Frontend (Blazor):
Develop
Leave Types
configuration UI (HR/Admin).Create
Request Leave
form for employees.Implement
My Leave History/Balance
view for employees.Develop
Leave Approval Dashboard
for managers.Add
Leave Summary Reports
for HR.
Phase 4: Reporting, Admin & Security Refinement (Sprints 13-16)
Reporting Enhancements:
Develop advanced reporting features (e.g., custom filters, advanced exports).
Implement specific HR-required reports.
Admin Dashboard:
Develop the Admin Dashboard UI with key metrics.
Implement
User Management
UI (linkingAspNetUsers
toEmployees
).
Security Hardening:
Conduct internal security audits and penetration testing.
Implement secure secret management (e.g., Azure Key Vault).
Refine authorization policies.
Performance Optimization:
Optimize database queries and indexes.
Tune API and Blazor performance.
Final Testing & Documentation:
Comprehensive UAT (User Acceptance Testing).
Create user manuals and technical documentation.
Prepare for production deployment.
Part 6: Illustrative C# Code Snippets
These snippets provide a conceptual understanding of the C# implementation across layers.
6.1 Domain Models (Similar to Database Schema)
// Models/Employee.cs
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.AspNetCore.Identity; // For linking to AspNetUsers
namespace EmployeeManagement.Domain.Models
{
public class Employee
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int EmployeeID { get; set; }
[Required]
[StringLength(450)] // Matches AspNetUsers.Id length
public string UserID { get; set; } // Foreign Key to AspNetUsers.Id
[Required]
public int DeptID { get; set; } // Foreign Key
[Required]
public int RoleID { get; set; } // Foreign Key
public int? ManagerID { get; set; } // Self-referencing Foreign Key
[Required]
[StringLength(50)]
public string FirstName { get; set; }
[Required]
[StringLength(50)]
public string LastName { get; set; }
[Required]
[StringLength(255)]
public string Email { get; set; }
[StringLength(20)]
public string PhoneNumber { get; set; }
[Required]
public DateTime HireDate { get; set; }
public DateTime? DateOfBirth { get; set; }
[StringLength(500)]
public string Address { get; set; }
[Required]
public bool IsActive { get; set; } = true;
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime LastModifiedAt { get; set; } = DateTime.UtcNow;
// Navigation Properties
[ForeignKey("UserID")]
public IdentityUser User { get; set; } // Link to ASP.NET Core Identity User
[ForeignKey("DeptID")]
public Department Department { get; set; }
[ForeignKey("RoleID")]
public Role Role { get; set; }
[ForeignKey("ManagerID")]
public Employee Manager { get; set; } // Self-referencing navigation
public ICollection<Employee> DirectReports { get; set; } // For manager's direct reports
public ICollection<Leave> Leaves { get; set; }
public ICollection<TimeClockEntry> TimeClockEntries { get; set; }
}
// Other models (Department, Role, LeaveType, Leave, TimeClockEntry) similar to DB schema
}
6.2 - 6.2 Data Access Layer (DAL) Example: ApplicationDbContext
and EmployeeRepository
EMS Data Access Layer (DAL)
// Data/ApplicationDbContext.csusing EmployeeManagement.Domain.Models;using Microsoft.AspNetCore.Identity.EntityFrameworkCore;using Microsoft.EntityFrameworkCore;
namespace EmployeeManagement.Infrastructure.Data{ public class ApplicationDbContext : IdentityDbContext { public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { }
public DbSet<Employee> Employees { get; set; } public DbSet<Department> Departments { get; set; } public DbSet<Role> Roles { get; set; } public DbSet<LeaveType> LeaveTypes { get; set; } public DbSet<Leave> Leaves { get; set; } public DbSet<TimeClockEntry> TimeClockEntries { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); // Important for IdentityDbContext
// Configure Employee self-referencing relationship modelBuilder.Entity<Employee>() .HasOne(e => e.Manager) .WithMany(e => e.DirectReports) .HasForeignKey(e => e.ManagerID) .IsRequired(false); // ManagerID can be null for top-level employees
// Configure Leave status enum mapping modelBuilder.Entity<Leave>() .Property(l => l.Status) .HasConversion<string>(); // Store enum as string in DB
// Configure TimeClockEntry status enum mapping modelBuilder.Entity<TimeClockEntry>() .Property(t => t.Status) .HasConversion<string>(); // Store enum as string in DB } }}
// Data/Repositories/IEmployeeRepository.csusing EmployeeManagement.Domain.Models;using System.Collections.Generic;using System.Threading.Tasks;
namespace EmployeeManagement.Infrastructure.Repositories{ public interface IEmployeeRepository : IGenericRepository<Employee> { Task<Employee> GetEmployeeByUserIdAsync(string userId); Task<IEnumerable<Employee>> GetEmployeesByDepartmentAsync(int departmentId); Task<IEnumerable<Employee>> GetDirectReportsAsync(int managerId); // ... more specific employee queries }}
// Data/Repositories/EmployeeRepository.csusing EmployeeManagement.Domain.Models;using Microsoft.EntityFrameworkCore;using System.Collections.Generic;using System.Linq;using System.Threading.Tasks;
namespace EmployeeManagement.Infrastructure.Repositories{ public class EmployeeRepository : GenericRepository<Employee>, IEmployeeRepository { private readonly ApplicationDbContext _context;
public EmployeeRepository(ApplicationDbContext context) : base(context) { _context = context; }
public async Task<Employee> GetEmployeeByUserIdAsync(string userId) { return await _context.Employees .Include(e => e.Department) .Include(e => e.Role) .FirstOrDefaultAsync(e => e.UserID == userId); }
public async Task<IEnumerable<Employee>> GetEmployeesByDepartmentAsync(int departmentId) { return await _context.Employees .Where(e => e.DeptID == departmentId) .Include(e => e.Role) .ToListAsync(); }
public async Task<IEnumerable<Employee>> GetDirectReportsAsync(int managerId) { return await _context.Employees .Where(e => e.ManagerID == managerId) .Include(e => e.Department) .Include(e => e.Role) .ToListAsync(); } // ... implementation of other repository methods }
// Generic Repository (for common CRUD operations) public interface IGenericRepository<T> where T : class { Task<T> GetByIdAsync(int id); Task<IEnumerable<T>> GetAllAsync(); Task AddAsync(T entity); void Update(T entity); void Delete(T entity); Task SaveChangesAsync(); }
public class GenericRepository<T> : IGenericRepository<T> where T : class { protected readonly ApplicationDbContext _context;
public GenericRepository(ApplicationDbContext context) { _context = context; }
public async Task<T> GetByIdAsync(int id) { return await _context.Set<T>().FindAsync(id); }
public async Task<IEnumerable<T>> GetAllAsync() { return await _context.Set<T>().ToListAsync(); }
public async Task AddAsync(T entity) { await _context.Set<T>().AddAsync(entity); }
public void Update(T entity) { _context.Set<T>().Update(entity); }
public void Delete(T entity) { _context.Set<T>().Remove(entity); }
public async Task SaveChangesAsync() { await _context.SaveChangesAsync(); } }}
6.3 Business Logic Layer (BLL) Example:
TimeTrackingService
// Services/TimeTrackingService.csusing EmployeeManagement.Domain.Models;using EmployeeManagement.Infrastructure.Repositories;using EmployeeManagement.Application.DTOs;using System;using System.Linq;using System.Threading.Tasks;using System.Collections.Generic;
namespace EmployeeManagement.Application.Services{ public class TimeTrackingService { private readonly ITimeClockEntryRepository _timeClockEntryRepository; private readonly IEmployeeRepository _employeeRepository;
public TimeTrackingService(ITimeClockEntryRepository timeClockEntryRepository, IEmployeeRepository employeeRepository) { _timeClockEntryRepository = timeClockEntryRepository; _employeeRepository = employeeRepository; }
public async Task<bool> ClockInAsync(string userId) { var employee = await _employeeRepository.GetEmployeeByUserIdAsync(userId); if (employee == null) return false;
// Check if already clocked in var activeEntry = await _timeClockEntryRepository.GetActiveEntryByEmployeeIdAsync(employee.EmployeeID); if (activeEntry != null) { // Already clocked in. Consider throwing an exception or returning specific error. return false; }
var newEntry = new TimeClockEntry { EmployeeID = employee.EmployeeID, ClockInTime = DateTime.UtcNow, Status = TimeEntryStatus.Pending // Initially pending, can be auto-approved }; await _timeClockEntryRepository.AddAsync(newEntry); await _timeClockEntryRepository.SaveChangesAsync(); return true; }
public async Task<bool> ClockOutAsync(string userId) { var employee = await _employeeRepository.GetEmployeeByUserIdAsync(userId); if (employee == null) return false;
var activeEntry = await _timeClockEntryRepository.GetActiveEntryByEmployeeIdAsync(employee.EmployeeID); if (activeEntry == null) { // Not clocked in return false; }
activeEntry.ClockOutTime = DateTime.UtcNow; activeEntry.DurationMinutes = (int)(activeEntry.ClockOutTime.Value - activeEntry.ClockInTime).TotalMinutes; activeEntry.Status = TimeEntryStatus.Approved; // Auto-approve on clock-out for simplicity
_timeClockEntryRepository.Update(activeEntry); await _timeClockEntryRepository.SaveChangesAsync(); return true; }
public async Task<IEnumerable<TimeClockEntryDto>> GetEmployeeTimeLogsAsync(string userId, DateTime? startDate, DateTime? endDate) { var employee = await _employeeRepository.GetEmployeeByUserIdAsync(userId); if (employee == null) return Enumerable.Empty<TimeClockEntryDto>();
var entries = await _timeClockEntryRepository.GetEntriesByEmployeeIdAsync(employee.EmployeeID, startDate, endDate);
return entries.Select(e => new TimeClockEntryDto { EntryID = e.EntryID, ClockInTime = e.ClockInTime, ClockOutTime = e.ClockOutTime, DurationMinutes = e.DurationMinutes, Status = e.Status.ToString() }); }
// DTOs for clean API responses public class TimeClockEntryDto { public long EntryID { get; set; } public DateTime ClockInTime { get; set; } public DateTime? ClockOutTime { get; set; } public int? DurationMinutes { get; set; } public string Status { get; set; } } }}
6.4 API Layer Example: TimeTrackingController
(ASP.NET Core Web API)
// API/Controllers/TimeTrackingController.csusing EmployeeManagement.Application.Services;using Microsoft.AspNetCore.Authorization;using Microsoft.AspNetCore.Mvc;using System.Security.Claims;using System.Threading.Tasks;using System.Collections.Generic;
namespace EmployeeManagement.Api.Controllers{ [ApiController] [Route("api/[controller]")] [Authorize] // All actions require authentication public class TimeTrackingController : ControllerBase { private readonly TimeTrackingService _timeTrackingService;
public TimeTrackingController(TimeTrackingService timeTrackingService) { _timeTrackingService = timeTrackingService; }
/// <summary> /// Allows an employee to clock in. /// </summary> [HttpPost("clockin")] [Authorize(Roles = "Employee,Manager,HR Admin")] // Only these roles can clock in public async Task<IActionResult> ClockIn() { var userId = User.FindFirstValue(ClaimTypes.NameIdentifier); // Get current user's ID if (string.IsNullOrEmpty(userId)) return Unauthorized();
var result = await _timeTrackingService.ClockInAsync(userId); if (!result) return BadRequest("Already clocked in or unable to clock in.");
return Ok("Clocked in successfully."); }
/// <summary> /// Allows an employee to clock out. /// </summary> [HttpPost("clockout")] [Authorize(Roles = "Employee,Manager,HR Admin")] public async Task<IActionResult> ClockOut() { var userId = User.FindFirstValue(ClaimTypes.NameIdentifier); if (string.IsNullOrEmpty(userId)) return Unauthorized();
var result = await _timeTrackingService.ClockOutAsync(userId); if (!result) return BadRequest("Not currently clocked in or unable to clock out.");
return Ok("Clocked out successfully."); }
/// <summary> /// Gets time logs for the authenticated employee. /// </summary> [HttpGet("my-logs")] [Authorize(Roles = "Employee,Manager,HR Admin")] [ProducesResponseType(typeof(IEnumerable<TimeTrackingService.TimeClockEntryDto>), 200)] public async Task<ActionResult<IEnumerable<TimeTrackingService.TimeClockEntryDto>>> GetMyTimeLogs(DateTime? startDate, DateTime? endDate) { var userId = User.FindFirstValue(ClaimTypes.NameIdentifier); if (string.IsNullOrEmpty(userId)) return Unauthorized();
var logs = await _timeTrackingService.GetEmployeeTimeLogsAsync(userId, startDate, endDate); return Ok(logs); }
// Add more endpoints for managers/HR to view team/all time logs // e.g., [HttpGet("team-logs")] [Authorize(Roles = "Manager,HR Admin")] // e.g., [HttpGet("all-logs")] [Authorize(Roles = "HR Admin")] }}
6.5 Presentation Layer Example: Blazor Component (Conceptual Time Tracking)
<!-- Pages/TimeTracking.razor (Blazor Component) -->@page "/timetracking"@using EmployeeManagement.Application.Services@inject HttpClient Http@inject NavigationManager NavManager
<h3>My Time Tracking</h3>
@if (errorMessage != null){ <div class="alert alert-danger">@errorMessage</div>}
<div class="time-controls"> <button class="btn btn-primary" @onclick="ClockIn" disabled="@isClockedIn">Clock In</button> <button class="btn btn-danger" @onclick="ClockOut" disabled="@(!isClockedIn)">Clock Out</button> <p class="status-message">Current Status: <strong>@(isClockedIn ? "Clocked In" : "Clocked Out")</strong></p></div>
<h4>My Recent Time Logs</h4>@if (timeLogs == null){ <p><em>Loading time logs...</em></p>}else if (!timeLogs.Any()){ <p>No time logs found.</p>}else{ <table class="table table-striped"> <thead> <tr> <th>Clock In</th> <th>Clock Out</th> <th>Duration (Minutes)</th> <th>Status</th> </tr> </thead> <tbody> @foreach (var log in timeLogs) { <tr> <td>@log.ClockInTime.ToLocalTime().ToString("g")</td> <td>@(log.ClockOutTime?.ToLocalTime().ToString("g") ?? "N/A")</td> <td>@(log.DurationMinutes ?? 0)</td> <td>@log.Status</td> </tr> } </tbody> </table>}
@code { private bool isClockedIn = false; // This state would ideally come from API private IEnumerable<TimeTrackingService.TimeClockEntryDto> timeLogs; private string errorMessage;
protected override async Task OnInitializedAsync() { await LoadTimeLogs(); // Determine initial clock-in status (e.g., fetch last entry from API) isClockedIn = timeLogs.Any() && timeLogs.OrderByDescending(l => l.ClockInTime).FirstOrDefault()?.ClockOutTime == null; }
private async Task ClockIn() { try { var response = await Http.PostAsync("api/TimeTracking/clockin", null); response.EnsureSuccessStatusCode(); // Throws on 4xx/5xx isClockedIn = true; errorMessage = null; await LoadTimeLogs(); } catch (HttpRequestException ex) { errorMessage = $"Error clocking in: {ex.Message}"; } }
private async Task ClockOut() { try { var response = await Http.PostAsync("api/TimeTracking/clockout", null); response.EnsureSuccessStatusCode(); isClockedIn = false; errorMessage = null; await LoadTimeLogs(); } catch (HttpRequestException ex) { errorMessage = $"Error clocking out: {ex.Message}"; } }
private async Task LoadTimeLogs() { try { // Fetch logs for the past 30 days, for example var endDate = DateTime.Now; var startDate = endDate.AddDays(-30); timeLogs = await Http.GetFromJsonAsync<IEnumerable<TimeTrackingService.TimeClockEntryDto>>($"api/TimeTracking/my-logs?startDate={startDate:yyyy-MM-dd}&endDate={endDate:yyyy-MM-dd}"); errorMessage = null; } catch (HttpRequestException ex) { errorMessage = $"Error loading time logs: {ex.Message}"; } }}
Lets refine using this frame !! thanks...
No comments:
Post a Comment