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
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Endjin.FreeAgent.Client.Extensions" Version="1.0.5" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Endjin.FreeAgent.Client.Extensions" Version="1.0.5" />
                    
Directory.Packages.props
<PackageReference Include="Endjin.FreeAgent.Client.Extensions" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Endjin.FreeAgent.Client.Extensions --version 1.0.5
                    
#r "nuget: Endjin.FreeAgent.Client.Extensions, 1.0.5"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Endjin.FreeAgent.Client.Extensions@1.0.5
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Endjin.FreeAgent.Client.Extensions&version=1.0.5
                    
Install as a Cake Addin
#tool nuget:?package=Endjin.FreeAgent.Client.Extensions&version=1.0.5
                    
Install as a Cake Tool

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 identifier
  • FirstName - Employee first name
  • LastName - Employee last name
  • Salutation - Title (Dr, Mr, Ms, etc.)
  • MiddleInitials - Middle name initials
  • Address - Employee address
  • PayRef - Payroll reference
  • IsDirector - Whether employee is a director
  • StartDate - Employment start date
  • EndDate - 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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.5 296 11/21/2025
1.0.4 219 11/16/2025
1.0.3 219 11/16/2025
1.0.2 214 11/16/2025
1.0.1 150 11/15/2025
1.0.0 270 11/13/2025
1.0.0-preview.2 136 9/23/2025