Endjin.FreeAgent.Client.Extensions
1.0.5
dotnet add package Endjin.FreeAgent.Client.Extensions --version 1.0.5
NuGet\Install-Package Endjin.FreeAgent.Client.Extensions -Version 1.0.5
<PackageReference Include="Endjin.FreeAgent.Client.Extensions" Version="1.0.5" />
<PackageVersion Include="Endjin.FreeAgent.Client.Extensions" Version="1.0.5" />
<PackageReference Include="Endjin.FreeAgent.Client.Extensions" />
paket add Endjin.FreeAgent.Client.Extensions --version 1.0.5
#r "nuget: Endjin.FreeAgent.Client.Extensions, 1.0.5"
#:package Endjin.FreeAgent.Client.Extensions@1.0.5
#addin nuget:?package=Endjin.FreeAgent.Client.Extensions&version=1.0.5
#tool nuget:?package=Endjin.FreeAgent.Client.Extensions&version=1.0.5
Endjin.FreeAgent.Client.Extensions
Extension methods and additional functionality for the Endjin.FreeAgent.Client library, providing enhanced features for working with the FreeAgent API.
Features
- Extension Members - .NET 10 extension member support for FreeAgent client
- Timesheets Module - Comprehensive timesheet management and aggregation
- Project Extensions - Advanced project filtering with flexible rule-based system
- User Settings - Employee payroll configuration and management
- Activity Summaries - Time tracking and activity reporting
- Flexible Architecture - Extensible registry pattern for adding custom modules
Installation
Install via NuGet Package Manager:
dotnet add package Endjin.FreeAgent.Client.Extensions
Or via Package Manager Console:
Install-Package Endjin.FreeAgent.Client.Extensions
Quick Start
Timesheets Extension
The Timesheets extension provides comprehensive time tracking functionality:
using Endjin.FreeAgent.Client;
using Endjin.FreeAgent.Client.Extensions.Extensions;
using Endjin.FreeAgent.Domain.Extensions;
using Microsoft.Extensions.Caching.Memory;
using Spectre.IO;
// Create FilePath for employee profile
FilePath employeeProfilePath = new("Artefacts/example-employee-payroll.yml");
// Initialize client with Timesheets extension
FreeAgentClient client = FreeAgentClient.CreateWithTimesheets(
options,
cache,
httpClientFactory,
loggerFactory,
employeeProfilePath);
// Or add to existing client
client.WithTimesheets(cache, employeeProfilePath);
// Get timesheets for current week
List<WeeklyTimesheet> weeklyTimesheets = await client.Timesheets
.GetTimesheetsForCurrentWeekAsync();
// Get timesheets for specific week
List<WeeklyTimesheet> specificWeek = await client.Timesheets
.GetTimeslipsForYearAndWeekNumberAsync(2024, 42);
// Get monthly timesheets
List<MonthlyTimesheet> monthlyTimesheets = await client.Timesheets
.GetTimesheetsForMonthAsync(DateTime.Now.Year, DateTime.Now.Month);
Project Extensions
Advanced project filtering with flexible rule-based system:
using Endjin.FreeAgent.Client.Extensions;
using Endjin.FreeAgent.Client.Extensions.Extensions;
// Register projects extension
client.WithProjectsExtended(cache);
// Configure project filtering with rules
var filterOptions = new ProjectFilterOptions
{
Rules = new[]
{
// Exclude projects with specific name prefixes
new FilterRule(FilterField.ProjectName, FilterRuleType.Prefix, "test", FilterAction.Exclude),
new FilterRule(FilterField.ProjectName, FilterRuleType.Prefix, "demo", FilterAction.Exclude),
// Exclude projects containing certain keywords
new FilterRule(FilterField.ProjectName, FilterRuleType.Contains, "internal", FilterAction.Exclude),
// Use regex for complex patterns
new FilterRule(FilterField.ProjectName, FilterRuleType.Regex, @"^(DEV|TEST)-\d{3}-",
FilterAction.Exclude, caseSensitive: true)
},
ExcludedProjectIds = new[] { "999", "888" },
RequireContactEntry = true,
RequireProjectName = true,
RequireOrganizationName = false
};
// Get filtered active projects
IEnumerable<Project> filteredProjects = await client.Projects.GetIncludedActiveAsync(filterOptions);
// Get detailed project information using NodaTime DateInterval
using NodaTime;
LocalDate start = LocalDate.FromDateTime(DateTime.Now.AddMonths(-1));
LocalDate end = LocalDate.FromDateTime(DateTime.Now);
DateInterval range = new DateInterval(start, end);
List<ProjectDetails> projectDetails = await client.ProjectsExtended
.GetAllProjectDetailsAsync(range);
foreach (ProjectDetails details in projectDetails)
{
Console.WriteLine($"Project: {details.Project.Name}");
Console.WriteLine($" Total Hours: {details.TotalHours}");
Console.WriteLine($" Total Cost: {details.TotalCost:C}");
}
User Settings
Manage employee payroll configuration from YAML:
using Endjin.FreeAgent.Client.Extensions.Extensions;
using Endjin.FreeAgent.Domain.Extensions;
using Spectre.IO;
// Create FilePath for employee profile
FilePath employeeProfilePath = new("Artefacts/example-employee-payroll.yml");
// Initialize user settings service
client.WithUserSettings(cache, employeeProfilePath);
// Access via extension property
UserSettingsService settingsService = client.UserSettings;
// Get all user settings (async methods)
List<UserSettings> allUsers = await settingsService.GetAllUserSettingsAsync();
// Get only directors
List<UserSettings> directors = await settingsService.GetAllDirectorsUserSettingsAsync();
// Get active employees (excluding directors)
List<UserSettings> employees = await settingsService.GetAllActiveEmployeeUserSettingsAsync();
// Work with user settings
foreach (UserSettings user in allUsers)
{
// Use extension members
string fullName = user.FullName; // Extension member property
bool isActive = user.IsActive; // Extension member property
Console.WriteLine($"{fullName} - Active: {isActive}");
// Access roles
EmploymentRole? currentRole = user.Roles
.OrderBy(r => r.StartDate)
.LastOrDefault(r => r.EndDate == null);
if (currentRole != null)
{
Console.WriteLine($" Role: {currentRole.Title}");
Console.WriteLine($" Cost: {currentRole.Cost:C}");
}
}
Extension Registry
The extension registry pattern allows for modular functionality:
// Check if extensions are available
if (client.HasTimesheets)
{
Timesheets timesheets = client.Timesheets;
// Use timesheet functionality
}
// Check extension count
int count = client.ExtensionCount;
Console.WriteLine($"Loaded {count} extensions");
// Get all extension types
IReadOnlyCollection<Type> extensions = client.Extensions;
// Register all extensions at once
FilePath employeeProfilePath = new("Artefacts/example-employee-payroll.yml");
client.WithAllExtensions(cache, employeeProfilePath);
Advanced Features
Activity Summaries
Aggregate time tracking data:
// Get timesheet activity for a user
TimesheetActivitySummary summary = await client.Timesheets.GetTimesheetActivityAsync(
userId: "123",
fromDate: "2024-01-01",
toDate: "2024-01-31");
Console.WriteLine($"Total Hours: {summary.TotalHours}");
Console.WriteLine($"Total Projects: {summary.ProjectCount}");
// Get weekly timesheet activity
TimesheetActivitySummary weeklySummary = await client.Timesheets
.GetWeeklyTimesheetActivityAsync("123", 2024, 42);
Weekly and Monthly Aggregations
// Get last week's timesheets
List<WeeklyTimesheet> lastWeek = await client.Timesheets.GetTimesheetsForLastWeekAsync();
foreach (WeeklyTimesheet timesheet in lastWeek)
{
// Use extension members for calculations
decimal hours = timesheet.CalculateHours();
bool isValid = timesheet.IsValid(); // Validates against 40-hour week
string userName = $"{timesheet.User?.FirstName} {timesheet.User?.LastName}";
Console.WriteLine($"{userName}: {hours}h (Valid: {isValid})");
// Access individual timeslips
if (timesheet.Timeslips != null)
{
foreach (Timeslip slip in timesheet.Timeslips)
{
Console.WriteLine($" {slip.Project}: {slip.Hours}h");
}
}
}
// Get current month's timesheets
List<MonthlyTimesheet> currentMonth = await client.Timesheets
.GetTimesheetsForCurrentMonthAsync();
foreach (MonthlyTimesheet monthly in currentMonth)
{
decimal hours = monthly.CalculateHours();
decimal target = monthly.TargetHours(); // MonthlyTimesheet has TargetHours() method
bool isValid = monthly.IsValid();
Console.WriteLine($"Month {monthly.MonthOfYear}: {hours}h / {target}h (Valid: {isValid})");
}
// Get full year timesheet
Timesheet yearlyTimesheet = await client.Timesheets.GetTimeslipsForYearAsync(2024);
Project Filtering Rules
The filtering system supports multiple rule types and actions:
// Available FilterRuleTypes:
// - Prefix: Matches if field starts with pattern
// - Suffix: Matches if field ends with pattern
// - Contains: Matches if field contains pattern
// - Exact: Matches if field exactly equals pattern
// - Regex: Matches using regular expression
// Available FilterActions:
// - Exclude: Exclude matching projects
// - Include: Include matching projects (stops processing further rules)
// - RequireExplicitInclusion: Only include if project ID is in explicit inclusion list
var advancedFilters = new ProjectFilterOptions
{
Rules = new[]
{
// Exclude test projects
new FilterRule(FilterField.ProjectName, FilterRuleType.Prefix, "TEST-", FilterAction.Exclude),
// Require explicit inclusion for partner projects
new FilterRule(FilterField.OrganizationName, FilterRuleType.Contains, "Partner",
FilterAction.RequireExplicitInclusion)
{
ExplicitInclusions = new[] { "proj-123", "proj-456" }
},
// Include specific projects by exact name
new FilterRule(FilterField.ProjectName, FilterRuleType.Exact, "Important Project",
FilterAction.Include)
},
ExcludedProjectIds = new[] { "excluded-1", "excluded-2" },
RequireContactEntry = true,
RequireProjectName = true
};
IEnumerable<Project> projects = await client.Projects.GetIncludedActiveAsync(advancedFilters);
Loading Filter Configuration from JSON
You can also load filter rules from a JSON configuration file:
// Load from JSON configuration
string json = await File.ReadAllTextAsync("project-filters.json");
ProjectFilterConfiguration config = ProjectFilterConfiguration.LoadFromJson(json);
ProjectFilterOptions filterOptions = config.ToFilterOptions();
// Apply configured filters
IEnumerable<Project> projects = await client.Projects.GetIncludedActiveAsync(filterOptions);
Example project-filters.json:
{
"rules": [
{
"field": "projectName",
"type": "prefix",
"pattern": "test",
"action": "exclude",
"isCaseSensitive": false
},
{
"field": "organizationName",
"type": "regex",
"pattern": "^(Partner|Vendor)-",
"action": "requireExplicitInclusion",
"isCaseSensitive": true,
"explicitInclusions": ["123", "456"]
}
],
"excludedProjectIds": ["999"],
"requireContactEntry": true,
"requireProjectName": true,
"requireOrganizationName": false
}
Extension Members (C# 14)
This library leverages .NET 10's extension members feature for cleaner API design:
// Extension members provide direct access to extended functionality
client.Timesheets // Access Timesheets extension
client.ProjectsExtended // Access ProjectsExtended extension
client.UserSettings // Access UserSettingsService extension
// Check if extensions are loaded
bool hasTimesheets = client.HasTimesheets;
bool hasProjects = client.HasProjectsExtended;
bool hasUserSettings = client.HasUserSettings;
// Extension member properties on domain objects
WeeklyTimesheet weekly = /* ... */;
decimal hours = weekly.CalculateHours(); // Extension member method
bool valid = weekly.IsValid(); // Extension member method
UserSettings user = /* ... */;
string fullName = user.FullName; // Extension member property
bool active = user.IsActive; // Extension member property
Factory Methods
Multiple ways to create clients with extensions:
using Spectre.IO;
// Create FilePath for employee profile
FilePath employeeProfilePath = new("Artefacts/employee-payroll.yml");
// Create with Timesheets only
FreeAgentClient client1 = FreeAgentClient.CreateWithTimesheets(
options, cache, httpClientFactory, loggerFactory,
employeeProfilePath);
// Create with Projects only
FreeAgentClient client2 = FreeAgentClient.CreateWithProjectsExtended(
options, cache, httpClientFactory, loggerFactory);
// Create with UserSettings only
FreeAgentClient client3 = FreeAgentClient.CreateWithUserSettings(
options, cache, httpClientFactory, loggerFactory,
employeeProfilePath);
// Create with selective extensions
FreeAgentClient client4 = FreeAgentClient.CreateWithExtensions(
options, cache, httpClientFactory, loggerFactory,
employeeProfilePath,
includeTimesheets: true,
includeProjectsExtended: true,
includeUserSettings: false);
// Create with all extensions
FreeAgentClient client5 = FreeAgentClient.CreateFullyExtended(
options, cache, httpClientFactory, loggerFactory,
employeeProfilePath);
UserSettings YAML Configuration
The UserSettings service loads employee configuration from YAML:
# Artefacts/example-employee-payroll.yml
- id: "usr_12345"
firstName: "Emma"
lastName: "Thompson"
salutation: "Dr"
middleInitials: "J"
address: "123 Main Street"
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
UserSettings properties:
Id- User identifierFirstName- Employee first nameLastName- Employee last nameSalutation- Title (Dr, Mr, Ms, etc.)MiddleInitials- Middle name initialsAddress- Employee addressPayRef- Payroll referenceIsDirector- Whether employee is a directorStartDate- Employment start dateEndDate- Employment end date (if applicable)Roles- List of employment roles with title, cost, start/end dates
Requirements
- .NET 10.0 or later
- Endjin.FreeAgent.Client
- Endjin.FreeAgent.Domain
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.Client (>= 1.0.6)
- Endjin.FreeAgent.Domain.Extensions (>= 1.0.5)
- Spectre.IO (>= 0.21.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.