Created detailed implementation plans for Sprint 2 backend work: Story 1: Audit Log Foundation (Phase 1) - Task 1: Design AuditLog database schema and create migration - Task 2: Create AuditLog entity and Repository - Task 3: Implement EF Core SaveChangesInterceptor - Task 4: Write unit tests for audit logging - Task 5: Integrate with ProjectManagement Module Story 2: Audit Log Core Features (Phase 2) - Task 1: Implement Changed Fields Detection (JSON Diff) - Task 2: Integrate User Context Tracking - Task 3: Add Multi-Tenant Isolation - Task 4: Implement Audit Query API - Task 5: Write Integration Tests Story 3: Sprint Management Module - Task 1: Create Sprint Aggregate Root and Domain Events - Task 2: Implement Sprint Repository and EF Core Configuration - Task 3: Create CQRS Commands and Queries - Task 4: Implement Burndown Chart Calculation - Task 5: Add SignalR Real-Time Notifications - Task 6: Write Integration Tests Total: 3 Stories, 16 Tasks, 24 Story Points (8+8+8) Estimated Duration: 10-12 days All tasks include: - Detailed technical implementation guidance - Code examples and file paths - Testing requirements (>= 90% coverage) - Performance benchmarks (< 5ms audit overhead) - Multi-tenant security validation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
8.2 KiB
8.2 KiB
task_id, story, status, estimated_hours, created_date, assignee
| task_id | story | status | estimated_hours | created_date | assignee |
|---|---|---|---|---|---|
| sprint_2_story_3_task_1 | sprint_2_story_3 | not_started | 5 | 2025-11-05 | 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:
- Sprint Entity:
colaflow-api/src/ColaFlow.Domain/Entities/Sprint.cs
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
}
- Domain Events:
colaflow-api/src/ColaFlow.Domain/Events/Sprint/
// 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
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