---
task_id: sprint_2_story_3_task_1
story: sprint_2_story_3
status: not_started
estimated_hours: 5
created_date: 2025-11-05
assignee: Backend Team
---

# Task 1: Create Sprint Aggregate Root and Domain Events

**Story**: Story 3 - Sprint Management Module
**Estimated**: 5 hours

## Description

Design and implement the Sprint aggregate root with proper domain logic, business rules validation, and domain events for sprint lifecycle management.

## Acceptance Criteria

- [ ] Sprint entity created with all required properties
- [ ] Domain events defined (SprintCreated, SprintUpdated, SprintStarted, SprintCompleted, SprintDeleted)
- [ ] Business logic for status transitions implemented
- [ ] Validation rules enforced (dates, status)
- [ ] Unit tests for domain logic

## Implementation Details

**Files to Create**:

1. **Sprint Entity**: `colaflow-api/src/ColaFlow.Domain/Entities/Sprint.cs`
```csharp
public class Sprint
{
    public Guid Id { get; private set; }
    public Guid TenantId { get; private set; }
    public Guid ProjectId { get; private set; }
    public string Name { get; private set; } = string.Empty;
    public string? Goal { get; private set; }
    public DateTime StartDate { get; private set; }
    public DateTime EndDate { get; private set; }
    public SprintStatus Status { get; private set; }
    public DateTime CreatedAt { get; private set; }
    public DateTime? UpdatedAt { get; private set; }

    // Navigation properties
    public Project Project { get; private set; } = null!;
    public ICollection<WorkTask> Tasks { get; private set; } = new List<WorkTask>();

    // Domain events
    private readonly List<DomainEvent> _domainEvents = new();
    public IReadOnlyList<DomainEvent> DomainEvents => _domainEvents.AsReadOnly();

    // Factory method
    public static Sprint Create(Guid tenantId, Guid projectId, string name, string? goal,
        DateTime startDate, DateTime endDate)
    {
        ValidateDates(startDate, endDate);

        var sprint = new Sprint
        {
            Id = Guid.NewGuid(),
            TenantId = tenantId,
            ProjectId = projectId,
            Name = name,
            Goal = goal,
            StartDate = startDate,
            EndDate = endDate,
            Status = SprintStatus.Planned,
            CreatedAt = DateTime.UtcNow
        };

        sprint.AddDomainEvent(new SprintCreatedEvent(sprint.Id, sprint.Name, sprint.ProjectId));
        return sprint;
    }

    // Business logic methods
    public void Update(string name, string? goal, DateTime startDate, DateTime endDate)
    {
        ValidateDates(startDate, endDate);

        if (Status == SprintStatus.Completed)
            throw new InvalidOperationException("Cannot update a completed sprint");

        Name = name;
        Goal = goal;
        StartDate = startDate;
        EndDate = endDate;
        UpdatedAt = DateTime.UtcNow;

        AddDomainEvent(new SprintUpdatedEvent(Id, Name));
    }

    public void Start()
    {
        if (Status != SprintStatus.Planned)
            throw new InvalidOperationException($"Cannot start sprint in {Status} status");

        if (DateTime.UtcNow < StartDate)
            throw new InvalidOperationException("Cannot start sprint before start date");

        Status = SprintStatus.Active;
        UpdatedAt = DateTime.UtcNow;

        AddDomainEvent(new SprintStartedEvent(Id, Name));
    }

    public void Complete()
    {
        if (Status != SprintStatus.Active)
            throw new InvalidOperationException($"Cannot complete sprint in {Status} status");

        Status = SprintStatus.Completed;
        UpdatedAt = DateTime.UtcNow;

        AddDomainEvent(new SprintCompletedEvent(Id, Name, Tasks.Count));
    }

    public void AddTask(WorkTask task)
    {
        if (Status == SprintStatus.Completed)
            throw new InvalidOperationException("Cannot add tasks to a completed sprint");

        if (!Tasks.Contains(task))
        {
            Tasks.Add(task);
            UpdatedAt = DateTime.UtcNow;
        }
    }

    public void RemoveTask(WorkTask task)
    {
        if (Status == SprintStatus.Completed)
            throw new InvalidOperationException("Cannot remove tasks from a completed sprint");

        Tasks.Remove(task);
        UpdatedAt = DateTime.UtcNow;
    }

    private static void ValidateDates(DateTime startDate, DateTime endDate)
    {
        if (endDate <= startDate)
            throw new ArgumentException("End date must be after start date");

        if ((endDate - startDate).TotalDays > 30)
            throw new ArgumentException("Sprint duration cannot exceed 30 days");
    }

    private void AddDomainEvent(DomainEvent @event)
    {
        _domainEvents.Add(@event);
    }

    public void ClearDomainEvents()
    {
        _domainEvents.Clear();
    }
}

public enum SprintStatus
{
    Planned = 0,
    Active = 1,
    Completed = 2
}
```

2. **Domain Events**: `colaflow-api/src/ColaFlow.Domain/Events/Sprint/`
```csharp
// SprintCreatedEvent.cs
public record SprintCreatedEvent(Guid SprintId, string SprintName, Guid ProjectId) : DomainEvent;

// SprintUpdatedEvent.cs
public record SprintUpdatedEvent(Guid SprintId, string SprintName) : DomainEvent;

// SprintStartedEvent.cs
public record SprintStartedEvent(Guid SprintId, string SprintName) : DomainEvent;

// SprintCompletedEvent.cs
public record SprintCompletedEvent(Guid SprintId, string SprintName, int TaskCount) : DomainEvent;

// SprintDeletedEvent.cs
public record SprintDeletedEvent(Guid SprintId, string SprintName) : DomainEvent;
```

## Technical Notes

**Business Rules**:
- Sprint duration: 1-30 days (typical Scrum 2-4 weeks)
- Status transitions: Planned → Active → Completed (one-way)
- Cannot update/delete completed sprints
- Cannot add/remove tasks from completed sprints
- End date must be after start date

**Validation**:
- Name: Required, max 100 characters
- Goal: Optional, max 500 characters
- Dates: StartDate < EndDate, duration <= 30 days

## Testing

**Unit Tests**: `colaflow-api/tests/ColaFlow.Domain.Tests/Entities/SprintTests.cs`
```csharp
public class SprintTests
{
    [Fact]
    public void Create_ShouldCreateValidSprint()
    {
        // Arrange
        var tenantId = Guid.NewGuid();
        var projectId = Guid.NewGuid();
        var startDate = DateTime.UtcNow;
        var endDate = startDate.AddDays(14);

        // Act
        var sprint = Sprint.Create(tenantId, projectId, "Sprint 1", "Complete Feature X", startDate, endDate);

        // Assert
        Assert.NotEqual(Guid.Empty, sprint.Id);
        Assert.Equal("Sprint 1", sprint.Name);
        Assert.Equal(SprintStatus.Planned, sprint.Status);
        Assert.Single(sprint.DomainEvents); // SprintCreatedEvent
    }

    [Fact]
    public void Create_ShouldThrowException_WhenEndDateBeforeStartDate()
    {
        // Arrange
        var startDate = DateTime.UtcNow;
        var endDate = startDate.AddDays(-1);

        // Act & Assert
        Assert.Throws<ArgumentException>(() =>
            Sprint.Create(Guid.NewGuid(), Guid.NewGuid(), "Sprint 1", null, startDate, endDate));
    }

    [Fact]
    public void Start_ShouldChangeStatusToActive()
    {
        // Arrange
        var sprint = CreateTestSprint();

        // Act
        sprint.Start();

        // Assert
        Assert.Equal(SprintStatus.Active, sprint.Status);
        Assert.Contains(sprint.DomainEvents, e => e is SprintStartedEvent);
    }

    [Fact]
    public void Complete_ShouldThrowException_WhenNotActive()
    {
        // Arrange
        var sprint = CreateTestSprint(); // Status = Planned

        // Act & Assert
        Assert.Throws<InvalidOperationException>(() => sprint.Complete());
    }

    [Fact]
    public void AddTask_ShouldThrowException_WhenSprintCompleted()
    {
        // Arrange
        var sprint = CreateTestSprint();
        sprint.Start();
        sprint.Complete();

        // Act & Assert
        Assert.Throws<InvalidOperationException>(() => sprint.AddTask(new WorkTask()));
    }

    private Sprint CreateTestSprint()
    {
        return Sprint.Create(
            Guid.NewGuid(),
            Guid.NewGuid(),
            "Test Sprint",
            "Test Goal",
            DateTime.UtcNow,
            DateTime.UtcNow.AddDays(14)
        );
    }
}
```

---

**Created**: 2025-11-05 by Backend Agent
