Project Init
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
using ColaFlow.Shared.Kernel.Common;
|
||||
using ColaFlow.Modules.ProjectManagement.Domain.Exceptions;
|
||||
using ColaFlow.Modules.ProjectManagement.Domain.ValueObjects;
|
||||
|
||||
using ColaFlow.Modules.ProjectManagement.Domain.Events;
|
||||
namespace ColaFlow.Modules.ProjectManagement.Domain.Aggregates.ProjectAggregate;
|
||||
|
||||
/// <summary>
|
||||
/// Epic Entity (part of Project aggregate)
|
||||
/// </summary>
|
||||
public class Epic : Entity
|
||||
{
|
||||
public new EpicId Id { get; private set; }
|
||||
public string Name { get; private set; }
|
||||
public string Description { get; private set; }
|
||||
public ProjectId ProjectId { get; private set; }
|
||||
public WorkItemStatus Status { get; private set; }
|
||||
public TaskPriority Priority { get; private set; }
|
||||
|
||||
private readonly List<Story> _stories = new();
|
||||
public IReadOnlyCollection<Story> Stories => _stories.AsReadOnly();
|
||||
|
||||
public DateTime CreatedAt { get; private set; }
|
||||
public UserId CreatedBy { get; private set; }
|
||||
public DateTime? UpdatedAt { get; private set; }
|
||||
|
||||
// EF Core constructor
|
||||
private Epic()
|
||||
{
|
||||
Id = null!;
|
||||
Name = null!;
|
||||
Description = null!;
|
||||
ProjectId = null!;
|
||||
Status = null!;
|
||||
Priority = null!;
|
||||
CreatedBy = null!;
|
||||
}
|
||||
|
||||
public static Epic Create(string name, string description, ProjectId projectId, UserId createdBy)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
throw new DomainException("Epic name cannot be empty");
|
||||
|
||||
if (name.Length > 200)
|
||||
throw new DomainException("Epic name cannot exceed 200 characters");
|
||||
|
||||
return new Epic
|
||||
{
|
||||
Id = EpicId.Create(),
|
||||
Name = name,
|
||||
Description = description ?? string.Empty,
|
||||
ProjectId = projectId,
|
||||
Status = WorkItemStatus.ToDo,
|
||||
Priority = TaskPriority.Medium,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
CreatedBy = createdBy
|
||||
};
|
||||
}
|
||||
|
||||
public Story CreateStory(string title, string description, TaskPriority priority, UserId createdBy)
|
||||
{
|
||||
var story = Story.Create(title, description, this.Id, priority, createdBy);
|
||||
_stories.Add(story);
|
||||
return story;
|
||||
}
|
||||
|
||||
public void UpdateDetails(string name, string description)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
throw new DomainException("Epic name cannot be empty");
|
||||
|
||||
if (name.Length > 200)
|
||||
throw new DomainException("Epic name cannot exceed 200 characters");
|
||||
|
||||
Name = name;
|
||||
Description = description ?? string.Empty;
|
||||
UpdatedAt = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
public void UpdateStatus(WorkItemStatus newStatus)
|
||||
{
|
||||
Status = newStatus;
|
||||
UpdatedAt = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
public void UpdatePriority(TaskPriority newPriority)
|
||||
{
|
||||
Priority = newPriority;
|
||||
UpdatedAt = DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
using ColaFlow.Shared.Kernel.Common;
|
||||
using ColaFlow.Shared.Kernel.Events;
|
||||
using ColaFlow.Modules.ProjectManagement.Domain.Exceptions;
|
||||
using ColaFlow.Modules.ProjectManagement.Domain.ValueObjects;
|
||||
using ColaFlow.Modules.ProjectManagement.Domain.Events;
|
||||
|
||||
namespace ColaFlow.Modules.ProjectManagement.Domain.Aggregates.ProjectAggregate;
|
||||
|
||||
/// <summary>
|
||||
/// Project Aggregate Root
|
||||
/// Enforces consistency boundary for Project -> Epic -> Story -> Task hierarchy
|
||||
/// </summary>
|
||||
public class Project : AggregateRoot
|
||||
{
|
||||
public new ProjectId Id { get; private set; }
|
||||
public string Name { get; private set; }
|
||||
public string Description { get; private set; }
|
||||
public ProjectKey Key { get; private set; }
|
||||
public ProjectStatus Status { get; private set; }
|
||||
public UserId OwnerId { get; private set; }
|
||||
|
||||
private readonly List<Epic> _epics = new();
|
||||
public IReadOnlyCollection<Epic> Epics => _epics.AsReadOnly();
|
||||
|
||||
public DateTime CreatedAt { get; private set; }
|
||||
public DateTime? UpdatedAt { get; private set; }
|
||||
|
||||
// EF Core constructor
|
||||
private Project()
|
||||
{
|
||||
Id = null!;
|
||||
Name = null!;
|
||||
Description = null!;
|
||||
Key = null!;
|
||||
Status = null!;
|
||||
OwnerId = null!;
|
||||
}
|
||||
|
||||
// Factory method
|
||||
public static Project Create(string name, string description, string key, UserId ownerId)
|
||||
{
|
||||
// Validation
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
throw new DomainException("Project name cannot be empty");
|
||||
|
||||
if (name.Length > 200)
|
||||
throw new DomainException("Project name cannot exceed 200 characters");
|
||||
|
||||
var project = new Project
|
||||
{
|
||||
Id = ProjectId.Create(),
|
||||
Name = name,
|
||||
Description = description ?? string.Empty,
|
||||
Key = ProjectKey.Create(key),
|
||||
Status = ProjectStatus.Active,
|
||||
OwnerId = ownerId,
|
||||
CreatedAt = DateTime.UtcNow
|
||||
};
|
||||
|
||||
// Raise domain event
|
||||
project.AddDomainEvent(new ProjectCreatedEvent(project.Id, project.Name, ownerId));
|
||||
|
||||
return project;
|
||||
}
|
||||
|
||||
// Business methods
|
||||
public void UpdateDetails(string name, string description)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
throw new DomainException("Project name cannot be empty");
|
||||
|
||||
if (name.Length > 200)
|
||||
throw new DomainException("Project name cannot exceed 200 characters");
|
||||
|
||||
Name = name;
|
||||
Description = description ?? string.Empty;
|
||||
UpdatedAt = DateTime.UtcNow;
|
||||
|
||||
AddDomainEvent(new ProjectUpdatedEvent(Id, Name, Description));
|
||||
}
|
||||
|
||||
public Epic CreateEpic(string name, string description, UserId createdBy)
|
||||
{
|
||||
if (Status == ProjectStatus.Archived)
|
||||
throw new DomainException("Cannot create epic in an archived project");
|
||||
|
||||
var epic = Epic.Create(name, description, this.Id, createdBy);
|
||||
_epics.Add(epic);
|
||||
|
||||
AddDomainEvent(new EpicCreatedEvent(epic.Id, epic.Name, this.Id));
|
||||
|
||||
return epic;
|
||||
}
|
||||
|
||||
public void Archive()
|
||||
{
|
||||
if (Status == ProjectStatus.Archived)
|
||||
throw new DomainException("Project is already archived");
|
||||
|
||||
Status = ProjectStatus.Archived;
|
||||
UpdatedAt = DateTime.UtcNow;
|
||||
|
||||
AddDomainEvent(new ProjectArchivedEvent(Id));
|
||||
}
|
||||
|
||||
public void Activate()
|
||||
{
|
||||
if (Status == ProjectStatus.Active)
|
||||
throw new DomainException("Project is already active");
|
||||
|
||||
Status = ProjectStatus.Active;
|
||||
UpdatedAt = DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
using ColaFlow.Shared.Kernel.Common;
|
||||
using ColaFlow.Modules.ProjectManagement.Domain.Exceptions;
|
||||
using ColaFlow.Modules.ProjectManagement.Domain.ValueObjects;
|
||||
|
||||
using ColaFlow.Modules.ProjectManagement.Domain.Events;
|
||||
namespace ColaFlow.Modules.ProjectManagement.Domain.Aggregates.ProjectAggregate;
|
||||
|
||||
/// <summary>
|
||||
/// Story Entity (part of Project aggregate)
|
||||
/// </summary>
|
||||
public class Story : Entity
|
||||
{
|
||||
public new StoryId Id { get; private set; }
|
||||
public string Title { get; private set; }
|
||||
public string Description { get; private set; }
|
||||
public EpicId EpicId { get; private set; }
|
||||
public WorkItemStatus Status { get; private set; }
|
||||
public TaskPriority Priority { get; private set; }
|
||||
public decimal? EstimatedHours { get; private set; }
|
||||
public decimal? ActualHours { get; private set; }
|
||||
public UserId? AssigneeId { get; private set; }
|
||||
|
||||
private readonly List<WorkTask> _tasks = new();
|
||||
public IReadOnlyCollection<WorkTask> Tasks => _tasks.AsReadOnly();
|
||||
|
||||
public DateTime CreatedAt { get; private set; }
|
||||
public UserId CreatedBy { get; private set; }
|
||||
public DateTime? UpdatedAt { get; private set; }
|
||||
|
||||
// EF Core constructor
|
||||
private Story()
|
||||
{
|
||||
Id = null!;
|
||||
Title = null!;
|
||||
Description = null!;
|
||||
EpicId = null!;
|
||||
Status = null!;
|
||||
Priority = null!;
|
||||
CreatedBy = null!;
|
||||
}
|
||||
|
||||
public static Story Create(string title, string description, EpicId epicId, TaskPriority priority, UserId createdBy)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(title))
|
||||
throw new DomainException("Story title cannot be empty");
|
||||
|
||||
if (title.Length > 200)
|
||||
throw new DomainException("Story title cannot exceed 200 characters");
|
||||
|
||||
return new Story
|
||||
{
|
||||
Id = StoryId.Create(),
|
||||
Title = title,
|
||||
Description = description ?? string.Empty,
|
||||
EpicId = epicId,
|
||||
Status = WorkItemStatus.ToDo,
|
||||
Priority = priority,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
CreatedBy = createdBy
|
||||
};
|
||||
}
|
||||
|
||||
public WorkTask CreateTask(string title, string description, TaskPriority priority, UserId createdBy)
|
||||
{
|
||||
var task = WorkTask.Create(title, description, this.Id, priority, createdBy);
|
||||
_tasks.Add(task);
|
||||
return task;
|
||||
}
|
||||
|
||||
public void UpdateDetails(string title, string description)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(title))
|
||||
throw new DomainException("Story title cannot be empty");
|
||||
|
||||
if (title.Length > 200)
|
||||
throw new DomainException("Story title cannot exceed 200 characters");
|
||||
|
||||
Title = title;
|
||||
Description = description ?? string.Empty;
|
||||
UpdatedAt = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
public void UpdateStatus(WorkItemStatus newStatus)
|
||||
{
|
||||
Status = newStatus;
|
||||
UpdatedAt = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
public void AssignTo(UserId assigneeId)
|
||||
{
|
||||
AssigneeId = assigneeId;
|
||||
UpdatedAt = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
public void UpdateEstimate(decimal hours)
|
||||
{
|
||||
if (hours < 0)
|
||||
throw new DomainException("Estimated hours cannot be negative");
|
||||
|
||||
EstimatedHours = hours;
|
||||
UpdatedAt = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
public void LogActualHours(decimal hours)
|
||||
{
|
||||
if (hours < 0)
|
||||
throw new DomainException("Actual hours cannot be negative");
|
||||
|
||||
ActualHours = hours;
|
||||
UpdatedAt = DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
using ColaFlow.Shared.Kernel.Common;
|
||||
using ColaFlow.Modules.ProjectManagement.Domain.Exceptions;
|
||||
using ColaFlow.Modules.ProjectManagement.Domain.ValueObjects;
|
||||
|
||||
using ColaFlow.Modules.ProjectManagement.Domain.Events;
|
||||
namespace ColaFlow.Modules.ProjectManagement.Domain.Aggregates.ProjectAggregate;
|
||||
|
||||
/// <summary>
|
||||
/// Task Entity (part of Project aggregate)
|
||||
/// Named "WorkTask" to avoid conflict with System.Threading.Tasks.Task
|
||||
/// </summary>
|
||||
public class WorkTask : Entity
|
||||
{
|
||||
public new TaskId Id { get; private set; }
|
||||
public string Title { get; private set; }
|
||||
public string Description { get; private set; }
|
||||
public StoryId StoryId { get; private set; }
|
||||
public WorkItemStatus Status { get; private set; }
|
||||
public TaskPriority Priority { get; private set; }
|
||||
public decimal? EstimatedHours { get; private set; }
|
||||
public decimal? ActualHours { get; private set; }
|
||||
public UserId? AssigneeId { get; private set; }
|
||||
|
||||
public DateTime CreatedAt { get; private set; }
|
||||
public UserId CreatedBy { get; private set; }
|
||||
public DateTime? UpdatedAt { get; private set; }
|
||||
|
||||
// EF Core constructor
|
||||
private WorkTask()
|
||||
{
|
||||
Id = null!;
|
||||
Title = null!;
|
||||
Description = null!;
|
||||
StoryId = null!;
|
||||
Status = null!;
|
||||
Priority = null!;
|
||||
CreatedBy = null!;
|
||||
}
|
||||
|
||||
public static WorkTask Create(string title, string description, StoryId storyId, TaskPriority priority, UserId createdBy)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(title))
|
||||
throw new DomainException("Task title cannot be empty");
|
||||
|
||||
if (title.Length > 200)
|
||||
throw new DomainException("Task title cannot exceed 200 characters");
|
||||
|
||||
return new WorkTask
|
||||
{
|
||||
Id = TaskId.Create(),
|
||||
Title = title,
|
||||
Description = description ?? string.Empty,
|
||||
StoryId = storyId,
|
||||
Status = WorkItemStatus.ToDo,
|
||||
Priority = priority,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
CreatedBy = createdBy
|
||||
};
|
||||
}
|
||||
|
||||
public void UpdateDetails(string title, string description)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(title))
|
||||
throw new DomainException("Task title cannot be empty");
|
||||
|
||||
if (title.Length > 200)
|
||||
throw new DomainException("Task title cannot exceed 200 characters");
|
||||
|
||||
Title = title;
|
||||
Description = description ?? string.Empty;
|
||||
UpdatedAt = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
public void UpdateStatus(WorkItemStatus newStatus)
|
||||
{
|
||||
Status = newStatus;
|
||||
UpdatedAt = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
public void AssignTo(UserId assigneeId)
|
||||
{
|
||||
AssigneeId = assigneeId;
|
||||
UpdatedAt = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
public void UpdatePriority(TaskPriority newPriority)
|
||||
{
|
||||
Priority = newPriority;
|
||||
UpdatedAt = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
public void UpdateEstimate(decimal hours)
|
||||
{
|
||||
if (hours < 0)
|
||||
throw new DomainException("Estimated hours cannot be negative");
|
||||
|
||||
EstimatedHours = hours;
|
||||
UpdatedAt = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
public void LogActualHours(decimal hours)
|
||||
{
|
||||
if (hours < 0)
|
||||
throw new DomainException("Actual hours cannot be negative");
|
||||
|
||||
ActualHours = hours;
|
||||
UpdatedAt = DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\Shared\ColaFlow.Shared.Kernel\ColaFlow.Shared.Kernel.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<AssemblyName>ColaFlow.Modules.ProjectManagement.Domain</AssemblyName>
|
||||
<RootNamespace>ColaFlow.Modules.ProjectManagement.Domain</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,13 @@
|
||||
using ColaFlow.Shared.Kernel.Events;
|
||||
using ColaFlow.Modules.ProjectManagement.Domain.ValueObjects;
|
||||
|
||||
namespace ColaFlow.Modules.ProjectManagement.Domain.Events;
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when an epic is created
|
||||
/// </summary>
|
||||
public sealed record EpicCreatedEvent(
|
||||
EpicId EpicId,
|
||||
string EpicName,
|
||||
ProjectId ProjectId
|
||||
) : DomainEvent;
|
||||
@@ -0,0 +1,11 @@
|
||||
using ColaFlow.Shared.Kernel.Events;
|
||||
using ColaFlow.Modules.ProjectManagement.Domain.ValueObjects;
|
||||
|
||||
namespace ColaFlow.Modules.ProjectManagement.Domain.Events;
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when a project is archived
|
||||
/// </summary>
|
||||
public sealed record ProjectArchivedEvent(
|
||||
ProjectId ProjectId
|
||||
) : DomainEvent;
|
||||
@@ -0,0 +1,13 @@
|
||||
using ColaFlow.Shared.Kernel.Events;
|
||||
using ColaFlow.Modules.ProjectManagement.Domain.ValueObjects;
|
||||
|
||||
namespace ColaFlow.Modules.ProjectManagement.Domain.Events;
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when a project is created
|
||||
/// </summary>
|
||||
public sealed record ProjectCreatedEvent(
|
||||
ProjectId ProjectId,
|
||||
string ProjectName,
|
||||
UserId CreatedBy
|
||||
) : DomainEvent;
|
||||
@@ -0,0 +1,13 @@
|
||||
using ColaFlow.Shared.Kernel.Events;
|
||||
using ColaFlow.Modules.ProjectManagement.Domain.ValueObjects;
|
||||
|
||||
namespace ColaFlow.Modules.ProjectManagement.Domain.Events;
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when a project is updated
|
||||
/// </summary>
|
||||
public sealed record ProjectUpdatedEvent(
|
||||
ProjectId ProjectId,
|
||||
string Name,
|
||||
string Description
|
||||
) : DomainEvent;
|
||||
@@ -0,0 +1,18 @@
|
||||
namespace ColaFlow.Modules.ProjectManagement.Domain.Exceptions;
|
||||
|
||||
/// <summary>
|
||||
/// Exception type for domain layer
|
||||
/// </summary>
|
||||
public class DomainException : Exception
|
||||
{
|
||||
public DomainException()
|
||||
{ }
|
||||
|
||||
public DomainException(string message)
|
||||
: base(message)
|
||||
{ }
|
||||
|
||||
public DomainException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{ }
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
namespace ColaFlow.Modules.ProjectManagement.Domain.Exceptions;
|
||||
|
||||
/// <summary>
|
||||
/// Exception type for not found resources
|
||||
/// </summary>
|
||||
public class NotFoundException : Exception
|
||||
{
|
||||
public NotFoundException()
|
||||
{ }
|
||||
|
||||
public NotFoundException(string message)
|
||||
: base(message)
|
||||
{ }
|
||||
|
||||
public NotFoundException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{ }
|
||||
|
||||
public NotFoundException(string entityName, object key)
|
||||
: base($"Entity '{entityName}' with key '{key}' was not found.")
|
||||
{ }
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
using ColaFlow.Modules.ProjectManagement.Domain.Aggregates.ProjectAggregate;
|
||||
using ColaFlow.Modules.ProjectManagement.Domain.ValueObjects;
|
||||
|
||||
namespace ColaFlow.Modules.ProjectManagement.Domain.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// Repository interface for Project aggregate
|
||||
/// </summary>
|
||||
public interface IProjectRepository
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a project by its ID
|
||||
/// </summary>
|
||||
Task<Project?> GetByIdAsync(ProjectId id, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a project by its unique key
|
||||
/// </summary>
|
||||
Task<Project?> GetByKeyAsync(string key, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets all projects with pagination
|
||||
/// </summary>
|
||||
Task<List<Project>> GetAllAsync(CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets project containing specific epic
|
||||
/// </summary>
|
||||
Task<Project?> GetProjectWithEpicAsync(EpicId epicId, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets project containing specific story
|
||||
/// </summary>
|
||||
Task<Project?> GetProjectWithStoryAsync(StoryId storyId, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets project containing specific task
|
||||
/// </summary>
|
||||
Task<Project?> GetProjectWithTaskAsync(TaskId taskId, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new project
|
||||
/// </summary>
|
||||
Task AddAsync(Project project, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Updates an existing project
|
||||
/// </summary>
|
||||
void Update(Project project);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a project
|
||||
/// </summary>
|
||||
void Delete(Project project);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace ColaFlow.Modules.ProjectManagement.Domain.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// Unit of Work pattern interface
|
||||
/// Coordinates the work of multiple repositories and ensures transactional consistency
|
||||
/// </summary>
|
||||
public interface IUnitOfWork
|
||||
{
|
||||
/// <summary>
|
||||
/// Saves all changes made in this unit of work to the database
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">Cancellation token</param>
|
||||
/// <returns>The number of entities written to the database</returns>
|
||||
Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using ColaFlow.Shared.Kernel.Common;
|
||||
|
||||
namespace ColaFlow.Modules.ProjectManagement.Domain.ValueObjects;
|
||||
|
||||
/// <summary>
|
||||
/// EpicId Value Object (strongly-typed ID)
|
||||
/// </summary>
|
||||
public sealed class EpicId : ValueObject
|
||||
{
|
||||
public Guid Value { get; private set; }
|
||||
|
||||
private EpicId(Guid value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public static EpicId Create() => new EpicId(Guid.NewGuid());
|
||||
public static EpicId Create(Guid value) => new EpicId(value);
|
||||
public static EpicId From(Guid value) => new EpicId(value);
|
||||
|
||||
protected override IEnumerable<object> GetAtomicValues()
|
||||
{
|
||||
yield return Value;
|
||||
}
|
||||
|
||||
public override string ToString() => Value.ToString();
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using ColaFlow.Shared.Kernel.Common;
|
||||
|
||||
namespace ColaFlow.Modules.ProjectManagement.Domain.ValueObjects;
|
||||
|
||||
/// <summary>
|
||||
/// ProjectId Value Object (strongly-typed ID)
|
||||
/// </summary>
|
||||
public sealed class ProjectId : ValueObject
|
||||
{
|
||||
public Guid Value { get; private set; }
|
||||
|
||||
private ProjectId(Guid value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public static ProjectId Create() => new ProjectId(Guid.NewGuid());
|
||||
public static ProjectId Create(Guid value) => new ProjectId(value);
|
||||
public static ProjectId From(Guid value) => new ProjectId(value);
|
||||
|
||||
protected override IEnumerable<object> GetAtomicValues()
|
||||
{
|
||||
yield return Value;
|
||||
}
|
||||
|
||||
public override string ToString() => Value.ToString();
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using ColaFlow.Shared.Kernel.Common;
|
||||
using ColaFlow.Modules.ProjectManagement.Domain.Exceptions;
|
||||
|
||||
namespace ColaFlow.Modules.ProjectManagement.Domain.ValueObjects;
|
||||
|
||||
/// <summary>
|
||||
/// ProjectKey Value Object (e.g., "COLA", "FLOW")
|
||||
/// </summary>
|
||||
public sealed class ProjectKey : ValueObject
|
||||
{
|
||||
public string Value { get; private set; }
|
||||
|
||||
private ProjectKey(string value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public static ProjectKey Create(string value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
throw new DomainException("Project key cannot be empty");
|
||||
|
||||
if (value.Length > 10)
|
||||
throw new DomainException("Project key cannot exceed 10 characters");
|
||||
|
||||
if (!System.Text.RegularExpressions.Regex.IsMatch(value, "^[A-Z0-9]+$"))
|
||||
throw new DomainException("Project key must contain only uppercase letters and numbers");
|
||||
|
||||
return new ProjectKey(value);
|
||||
}
|
||||
|
||||
protected override IEnumerable<object> GetAtomicValues()
|
||||
{
|
||||
yield return Value;
|
||||
}
|
||||
|
||||
public override string ToString() => Value;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using ColaFlow.Shared.Kernel.Common;
|
||||
|
||||
namespace ColaFlow.Modules.ProjectManagement.Domain.ValueObjects;
|
||||
|
||||
/// <summary>
|
||||
/// ProjectStatus Enumeration
|
||||
/// </summary>
|
||||
public sealed class ProjectStatus : Enumeration
|
||||
{
|
||||
public static readonly ProjectStatus Active = new(1, "Active");
|
||||
public static readonly ProjectStatus Archived = new(2, "Archived");
|
||||
public static readonly ProjectStatus OnHold = new(3, "On Hold");
|
||||
|
||||
private ProjectStatus(int id, string name) : base(id, name)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using ColaFlow.Shared.Kernel.Common;
|
||||
|
||||
namespace ColaFlow.Modules.ProjectManagement.Domain.ValueObjects;
|
||||
|
||||
/// <summary>
|
||||
/// StoryId Value Object (strongly-typed ID)
|
||||
/// </summary>
|
||||
public sealed class StoryId : ValueObject
|
||||
{
|
||||
public Guid Value { get; private set; }
|
||||
|
||||
private StoryId(Guid value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public static StoryId Create() => new StoryId(Guid.NewGuid());
|
||||
public static StoryId Create(Guid value) => new StoryId(value);
|
||||
public static StoryId From(Guid value) => new StoryId(value);
|
||||
|
||||
protected override IEnumerable<object> GetAtomicValues()
|
||||
{
|
||||
yield return Value;
|
||||
}
|
||||
|
||||
public override string ToString() => Value.ToString();
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using ColaFlow.Shared.Kernel.Common;
|
||||
|
||||
namespace ColaFlow.Modules.ProjectManagement.Domain.ValueObjects;
|
||||
|
||||
/// <summary>
|
||||
/// TaskId Value Object (strongly-typed ID)
|
||||
/// </summary>
|
||||
public sealed class TaskId : ValueObject
|
||||
{
|
||||
public Guid Value { get; private set; }
|
||||
|
||||
private TaskId(Guid value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public static TaskId Create() => new TaskId(Guid.NewGuid());
|
||||
public static TaskId Create(Guid value) => new TaskId(value);
|
||||
public static TaskId From(Guid value) => new TaskId(value);
|
||||
|
||||
protected override IEnumerable<object> GetAtomicValues()
|
||||
{
|
||||
yield return Value;
|
||||
}
|
||||
|
||||
public override string ToString() => Value.ToString();
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using ColaFlow.Shared.Kernel.Common;
|
||||
|
||||
namespace ColaFlow.Modules.ProjectManagement.Domain.ValueObjects;
|
||||
|
||||
/// <summary>
|
||||
/// TaskPriority Enumeration
|
||||
/// </summary>
|
||||
public sealed class TaskPriority : Enumeration
|
||||
{
|
||||
public static readonly TaskPriority Low = new(1, "Low");
|
||||
public static readonly TaskPriority Medium = new(2, "Medium");
|
||||
public static readonly TaskPriority High = new(3, "High");
|
||||
public static readonly TaskPriority Urgent = new(4, "Urgent");
|
||||
|
||||
private TaskPriority(int id, string name) : base(id, name)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using ColaFlow.Shared.Kernel.Common;
|
||||
|
||||
namespace ColaFlow.Modules.ProjectManagement.Domain.ValueObjects;
|
||||
|
||||
/// <summary>
|
||||
/// UserId Value Object (strongly-typed ID)
|
||||
/// </summary>
|
||||
public sealed class UserId : ValueObject
|
||||
{
|
||||
public Guid Value { get; private set; }
|
||||
|
||||
private UserId(Guid value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public static UserId Create() => new UserId(Guid.NewGuid());
|
||||
public static UserId Create(Guid value) => new UserId(value);
|
||||
public static UserId From(Guid value) => new UserId(value);
|
||||
|
||||
protected override IEnumerable<object> GetAtomicValues()
|
||||
{
|
||||
yield return Value;
|
||||
}
|
||||
|
||||
public override string ToString() => Value.ToString();
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using ColaFlow.Shared.Kernel.Common;
|
||||
|
||||
namespace ColaFlow.Modules.ProjectManagement.Domain.ValueObjects;
|
||||
|
||||
/// <summary>
|
||||
/// WorkItemStatus Enumeration (renamed from TaskStatus to avoid conflict with System.Threading.Tasks.TaskStatus)
|
||||
/// </summary>
|
||||
public sealed class WorkItemStatus : Enumeration
|
||||
{
|
||||
public static readonly WorkItemStatus ToDo = new(1, "To Do");
|
||||
public static readonly WorkItemStatus InProgress = new(2, "In Progress");
|
||||
public static readonly WorkItemStatus InReview = new(3, "In Review");
|
||||
public static readonly WorkItemStatus Done = new(4, "Done");
|
||||
public static readonly WorkItemStatus Blocked = new(5, "Blocked");
|
||||
|
||||
private WorkItemStatus(int id, string name) : base(id, name)
|
||||
{
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user