Endjin.FreeAgent.Domain.Extensions
1.0.5
dotnet add package Endjin.FreeAgent.Domain.Extensions --version 1.0.5
NuGet\Install-Package Endjin.FreeAgent.Domain.Extensions -Version 1.0.5
<PackageReference Include="Endjin.FreeAgent.Domain.Extensions" Version="1.0.5" />
<PackageVersion Include="Endjin.FreeAgent.Domain.Extensions" Version="1.0.5" />
<PackageReference Include="Endjin.FreeAgent.Domain.Extensions" />
paket add Endjin.FreeAgent.Domain.Extensions --version 1.0.5
#r "nuget: Endjin.FreeAgent.Domain.Extensions, 1.0.5"
#:package Endjin.FreeAgent.Domain.Extensions@1.0.5
#addin nuget:?package=Endjin.FreeAgent.Domain.Extensions&version=1.0.5
#tool nuget:?package=Endjin.FreeAgent.Domain.Extensions&version=1.0.5
Endjin.FreeAgent.Domain.Extensions
Extension types and helper methods for the Endjin.FreeAgent.Domain library, providing additional domain models and utilities for enhanced FreeAgent API interactions.
Features
- Extended Domain Models - Timesheet aggregations, activity summaries, and employment data
- C# 14 Extension Members - Clean, property-like extension methods for domain types
- Date/Time Extensions - Helper methods using NodaTime for dates and fiscal periods
- Currency Extensions - Decimal formatting and currency handling utilities
- Timesheet Models - Weekly, monthly, and yearly timesheet aggregations
- User Settings - Employee payroll configuration loaded from YAML
- Activity Tracking - Comprehensive timesheet activity summaries by project
Installation
Install via NuGet Package Manager:
dotnet add package Endjin.FreeAgent.Domain.Extensions
Or via Package Manager Console:
Install-Package Endjin.FreeAgent.Domain.Extensions
Domain Models
Timesheet Models
Timesheet aggregation models for various time periods:
using Endjin.FreeAgent.Domain.Extensions;
using NodaTime;
// Weekly timesheet with timeslips
WeeklyTimesheet weeklyTimesheet = new()
{
Week = new Week
{
StartOfWeek = new LocalDate(2024, 10, 14),
EndOfWeek = new LocalDate(2024, 10, 20)
},
User = user,
WeekOfYear = 42,
Timeslips = timeslips
};
// Use extension members to calculate hours
decimal hours = weeklyTimesheet.CalculateHours();
bool isValid = weeklyTimesheet.IsValid(); // Validates against 40-hour week
Console.WriteLine($"Week 42: {hours}h (Valid: {isValid})");
// Monthly timesheet
MonthlyTimesheet monthlyTimesheet = new()
{
Month = new Month
{
StartOfMonth = new LocalDate(2024, 10, 1),
EndOfMonth = new LocalDate(2024, 10, 31)
},
User = user,
Timeslips = timeslips
};
// Monthly timesheets have a TargetHours() method
decimal monthlyHours = monthlyTimesheet.CalculateHours();
decimal target = monthlyTimesheet.TargetHours();
bool monthValid = monthlyTimesheet.IsValid();
Console.WriteLine($"October: {monthlyHours}h / {target}h (Valid: {monthValid})");
// Yearly timesheet aggregation
YearlyTimesheet yearlyTimesheet = new()
{
Year = 2024,
User = user,
UserSettings = userSettings,
WeeklyTimesheets = weeklyTimesheets
};
// Yearly timesheets have additional aggregation methods
decimal totalHours = yearlyTimesheet.CalculateTotalHours();
decimal weeklyAverage = yearlyTimesheet.CalculateWeeklyAverage();
decimal missingHours = yearlyTimesheet.CalculateTotalMissingHours();
Console.WriteLine($"Year 2024: {totalHours}h total, {weeklyAverage}h/week average");
Timesheet Activity Models
Track activity by project with detailed summaries:
using Endjin.FreeAgent.Domain.Extensions;
// Individual activity entry
TimesheetActivityEntry entry = new()
{
ProjectId = "123",
ProjectName = "Client Project A",
Hours = 8.5m,
Date = DateTime.Now
};
// Activity grouped by project
TimesheetActivity activity = new()
{
ProjectId = "123",
ProjectName = "Client Project A",
Entries = new List<TimesheetActivityEntry> { entry }
};
// Complete activity summary for a user
TimesheetActivitySummary summary = new()
{
UserId = "user123",
Activities = new List<TimesheetActivity> { activity },
TotalHours = 40.0m,
ProjectCount = 5
};
Console.WriteLine($"User {summary.UserId}: {summary.TotalHours}h across {summary.ProjectCount} projects");
User Settings (Employment Data)
User settings represent employee payroll configuration:
using Endjin.FreeAgent.Domain.Extensions;
// UserSettings stores employment and payroll data
UserSettings settings = new()
{
Id = "usr_12345",
FirstName = "Emma",
LastName = "Thompson",
Salutation = "Dr",
MiddleInitials = "J",
Address = "123 Main Street, Cambridge",
PayRef = "EMP001",
IsDirector = true,
StartDate = new DateTime(2020, 1, 15),
EndDate = null, // null means currently employed
Roles = new List<EmploymentRole>
{
new()
{
Title = "Software Engineer",
Cost = 65000,
StartDate = new DateTime(2020, 1, 15),
EndDate = new DateTime(2022, 6, 30)
},
new()
{
Title = "Senior Engineer",
Cost = 85000,
StartDate = new DateTime(2022, 7, 1),
EndDate = null // Current role
}
}
};
// Use extension members for convenient properties
string fullName = settings.FullName; // Extension member property
bool isActive = settings.IsActive; // Extension member property
Console.WriteLine($"{fullName} - Active: {isActive}");
// Find current role
EmploymentRole? currentRole = settings.Roles
.OrderBy(r => r.StartDate)
.LastOrDefault(r => r.EndDate == null);
Console.WriteLine($"Current Role: {currentRole?.Title} - {currentRole?.Cost:C}");
User Summary Models
// Basic user summary
UserSummary summary = new()
{
Id = "usr_123",
FirstName = "John",
LastName = "Smith",
Email = "john.smith@example.com"
};
// FullName is a computed property
string fullName = summary.FullName; // "John Smith"
// User activity summary with counts
UserActivitySummary activitySummary = new()
{
UserCount = 10,
Users = userList
};
Project Models
// Basic project summary
ProjectSummary projectSummary = new()
{
Id = "proj_123",
Name = "Website Development",
ContactName = "Acme Corp",
ContactId = "contact_456"
};
// Detailed project entry with time tracking
ProjectDetail detail = new()
{
Customer = "Acme Corp",
Project = "Website Development",
User = "John Smith",
Date = DateTimeOffset.Now,
Year = 2024,
WeekNumber = 42,
Effort = 8.5m,
DayRate = 800.0m,
HourlyRate = 100.0m,
Level = "Senior",
Cost = 850.0m,
Comment = "Frontend development work",
BillingPeriod = "2024-10",
Week = new Week
{
StartOfWeek = new LocalDate(2024, 10, 14),
EndOfWeek = new LocalDate(2024, 10, 20)
}
};
// Project collection summary
ProjectDetails projectDetails = new()
{
Project = project,
Timeslips = timeslips,
TotalHours = 120.5m,
TotalCost = 12050.0m
};
// Projects summary with counts
ProjectsSummary projectsSummary = new()
{
ProjectCount = 25,
Projects = projectList
};
Extension Members (C# 14)
This library uses C# 14 extension members for clean, intuitive APIs:
Timesheet Extensions
using Endjin.FreeAgent.Domain.Extensions;
WeeklyTimesheet weekly = /* ... */;
// Extension member methods
decimal hours = weekly.CalculateHours();
bool isValid = weekly.IsValid(); // Validates against WorkConstants.HoursPerWeek (40)
MonthlyTimesheet monthly = /* ... */;
// MonthlyTimesheet has additional extension methods
decimal monthlyHours = monthly.CalculateHours();
decimal targetHours = monthly.TargetHours(); // Calculates based on working days in month
bool monthlyValid = monthly.IsValid();
YearlyTimesheet yearly = /* ... */;
// YearlyTimesheet has aggregation methods
decimal totalHours = yearly.CalculateTotalHours();
decimal weeklyAverage = yearly.CalculateWeeklyAverage();
decimal missingHours = yearly.CalculateTotalMissingHours();
User Extensions
using Endjin.FreeAgent.Domain.Extensions;
UserSettings user = /* ... */;
// Extension member properties
string fullName = user.FullName; // "{FirstName} {LastName}"
bool isActive = user.IsActive; // true if EndDate is null
Console.WriteLine($"{fullName} - Active: {isActive}");
JournalSet Extensions
using Endjin.FreeAgent.Domain.Extensions;
using Endjin.FreeAgent.Client;
JournalSet journalSet = /* ... */;
// Calculate balance
decimal balance = journalSet.Balance();
Extension Methods
DateTime Extensions
Helper methods for working with dates and fiscal periods using NodaTime:
using Endjin.FreeAgent.Domain.Extensions;
using NodaTime;
// Get week number
int weekNumber = DateTime.Now.WeekNumber();
// Get last week number of the year
int lastWeek = DateTime.Now.LastWeekNumber();
// Get first day of month
DateTime firstDay = DateTime.Now.FirstDayOfMonth();
// Get last day of month
DateTime lastDay = DateTime.Now.LastDayOfMonth();
// Get Week object for a specific week
Week week = DateTimeExtensions.GetWeek(2024, 42);
Console.WriteLine($"Week starts: {week.StartOfWeek}");
Console.WriteLine($"Week ends: {week.EndOfWeek}");
// Get Month object for a specific month
Month month = DateTimeExtensions.GetMonth(2024, 10);
Console.WriteLine($"Month starts: {month.StartOfMonth}");
Console.WriteLine($"Month ends: {month.EndOfMonth}");
// Extract DateTime from month-year string
DateTime? date = DateTimeExtensions.ExtractDateTimeFromMonthYearString("2024-10");
// Calculate working hours between dates
LocalDate startDate = new LocalDate(2024, 10, 1);
LocalDate endDate = new LocalDate(2024, 10, 31);
decimal workingHours = DateTimeExtensions.CalculateWorkingHours(startDate, endDate);
Console.WriteLine($"Working hours in October: {workingHours}");
Currency Extensions
Decimal formatting and currency utilities:
using Endjin.FreeAgent.Domain.Extensions;
// Format as currency (default UK format)
decimal amount = 1234.56m;
string formatted = amount.ToCurrencyString(); // "£1,234.56"
// Specify culture
string usFormatted = amount.ToCurrencyString("en-US"); // "$1,234.56"
// Round to currency precision (defined in WorkConstants.CurrencyDecimalPlaces)
decimal rounded = amount.Round(); // 1234.56
// Calculate cost from timeslip
Timeslip slip = new()
{
Hours = 8.5m,
/* ... */
};
decimal cost = slip.ToCost();
UserSettingsRepository
YAML-based repository for loading employee payroll configuration:
using Endjin.FreeAgent.Domain.Extensions;
using Spectre.IO;
// Initialize file system abstraction
FileSystem fileSystem = new();
// Initialize repository with file system and YAML file
UserSettingsRepository repository = new(fileSystem, fileSystem.GetFile("Artefacts/example-employee-payroll.yml"));
// Get all user settings asynchronously
List<UserSettings> allUsers = await repository.GetAllUserSettingsAsync();
// Get only active users (EndDate is null)
List<UserSettings> activeUsers = await repository.GetActiveUserSettingsAsync();
// Clear the cache to force reload
repository.ClearCache();
// Work with user settings
foreach (UserSettings user in activeUsers)
{
string fullName = user.FullName;
bool isActive = user.IsActive;
Console.WriteLine($"{fullName} - Active: {isActive}");
// Find current role
EmploymentRole? currentRole = user.Roles
.OrderBy(r => r.StartDate)
.LastOrDefault(r => r.EndDate == null);
if (currentRole != null)
{
Console.WriteLine($" {currentRole.Title}: {currentRole.Cost:C}");
}
}
YAML File Format
The repository loads data from a YAML file:
# Artefacts/example-employee-payroll.yml
- id: "usr_12345"
firstName: "Emma"
lastName: "Thompson"
salutation: "Dr"
middleInitials: "J"
address: "123 Main Street, Cambridge"
payRef: "EMP001"
isDirector: true
startDate: 2020-01-15
roles:
- title: "Software Engineer"
cost: 65000
startDate: 2020-01-15
endDate: 2022-06-30
- title: "Senior Engineer"
cost: 85000
startDate: 2022-07-01
Time Period Models
Models representing time periods using NodaTime:
using NodaTime;
// Week model with NodaTime LocalDate
Week week = new()
{
StartOfWeek = new LocalDate(2024, 10, 14),
EndOfWeek = new LocalDate(2024, 10, 20)
};
// Month model with NodaTime LocalDate
Month month = new()
{
StartOfMonth = new LocalDate(2024, 10, 1),
EndOfMonth = new LocalDate(2024, 10, 31)
};
// Timesheet is a container for yearly timesheets
Timesheet timesheet = new()
{
YearlyTimesheets = new List<YearlyTimesheet>
{
new() { Year = 2024, /* ... */ }
}
};
Work Constants
Standard constants for time and currency calculations:
using Endjin.FreeAgent.Domain.Extensions;
// Hours per day constant
decimal hoursPerDay = WorkConstants.HoursPerDay; // 8.0
// Hours per week constant
decimal hoursPerWeek = WorkConstants.HoursPerWeek; // 40.0
// Currency decimal places
int currencyPlaces = WorkConstants.CurrencyDecimalPlaces; // 2
Employment Roles
Domain model for employment roles with compensation history:
EmploymentRole role = new()
{
Title = "Senior Developer",
Cost = 85000, // Annual cost to company
StartDate = new DateTime(2022, 7, 1),
EndDate = null // null means current role
};
// Check if role is current
bool isCurrent = role.EndDate == null;
Complete Example
Here's a complete example showing how to work with timesheets and user settings:
using Endjin.FreeAgent.Domain.Extensions;
using NodaTime;
using Spectre.IO;
// Initialize file system abstraction
var fileSystem = new FileSystem();
// Load user settings from YAML
UserSettingsRepository repository = new(fileSystem, fileSystem.GetFile("Artefacts/employee-payroll.yml"));
List<UserSettings> activeUsers = await repository.GetActiveUserSettingsAsync();
// Create a weekly timesheet
WeeklyTimesheet weekly = new()
{
Week = DateTimeExtensions.GetWeek(2024, 42),
User = user,
WeekOfYear = 42,
Timeslips = timeslips
};
// Use extension members to analyze
decimal hours = weekly.CalculateHours();
bool isValid = weekly.IsValid();
Console.WriteLine($"Week 42: {hours}h (Valid: {isValid})");
// Find user settings for the timesheet user
UserSettings? userSettings = activeUsers.FirstOrDefault(u => u.Id == weekly.User?.Id);
if (userSettings != null)
{
// Use extension member properties
string fullName = userSettings.FullName;
bool isActive = userSettings.IsActive;
Console.WriteLine($"User: {fullName} (Active: {isActive})");
// Get current role
EmploymentRole? currentRole = userSettings.Roles
.OrderBy(r => r.StartDate)
.LastOrDefault(r => r.EndDate == null);
if (currentRole != null)
{
Console.WriteLine($"Role: {currentRole.Title} - {currentRole.Cost:C}");
}
}
// Create monthly aggregation
MonthlyTimesheet monthly = new()
{
Month = DateTimeExtensions.GetMonth(2024, 10),
User = user,
Timeslips = timeslips
};
decimal monthlyHours = monthly.CalculateHours();
decimal targetHours = monthly.TargetHours();
Console.WriteLine($"October: {monthlyHours}h / {targetHours}h");
Requirements
- .NET 10.0 or later
- Endjin.FreeAgent.Domain
- NodaTime (for LocalDate types)
- YamlDotNet (for UserSettingsRepository)
License
This project is licensed under the Apache License 2.0 - see the LICENSE file for details.
Support
For support, please contact Endjin Limited or raise an issue on our GitHub repository.
Copyright (c) Endjin Limited. All rights reserved.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Endjin.FreeAgent.Domain (>= 1.0.6)
- NodaTime (>= 3.2.2)
- Spectre.IO (>= 0.21.0)
- YamlDotNet (>= 16.3.0)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Endjin.FreeAgent.Domain.Extensions:
| Package | Downloads |
|---|---|
|
Endjin.FreeAgent.Client.Extensions
Extension methods and additional functionality for the Endjin.FreeAgent.Client library, providing timesheets management, project filtering, user settings, and activity summaries. |
GitHub repositories
This package is not used by any popular GitHub repositories.