diff --git a/ProgramManager/src/Application/GozareshgirProgramManager.Application/GozareshgirProgramManager.Application.csproj b/ProgramManager/src/Application/GozareshgirProgramManager.Application/GozareshgirProgramManager.Application.csproj
index d28d7b48..cc16cc3a 100644
--- a/ProgramManager/src/Application/GozareshgirProgramManager.Application/GozareshgirProgramManager.Application.csproj
+++ b/ProgramManager/src/Application/GozareshgirProgramManager.Application/GozareshgirProgramManager.Application.csproj
@@ -25,4 +25,8 @@
+
+
+
+
diff --git a/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Commands/CreateTaskSectionRevision/CreateTaskSectionRevisionCommand.cs b/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Commands/CreateTaskSectionRevision/CreateTaskSectionRevisionCommand.cs
new file mode 100644
index 00000000..d524694e
--- /dev/null
+++ b/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Commands/CreateTaskSectionRevision/CreateTaskSectionRevisionCommand.cs
@@ -0,0 +1,134 @@
+using GozareshgirProgramManager.Application._Common.Interfaces;
+using GozareshgirProgramManager.Application._Common.Models;
+using GozareshgirProgramManager.Application.Modules.TaskChat.DTOs;
+using GozareshgirProgramManager.Application.Services.FileManagement;
+using GozareshgirProgramManager.Domain._Common;
+using GozareshgirProgramManager.Domain.FileManagementAgg.Entities;
+using GozareshgirProgramManager.Domain.FileManagementAgg.Enums;
+using GozareshgirProgramManager.Domain.FileManagementAgg.Repositories;
+using GozareshgirProgramManager.Domain.ProjectAgg.Entities.Task.TaskSection;
+using GozareshgirProgramManager.Domain.ProjectAgg.Repositories;
+using Microsoft.AspNetCore.Http;
+
+namespace GozareshgirProgramManager.Application.Modules.Projects.Commands.CreateTaskSectionRevision;
+
+public record CreateTaskSectionRevisionCommand(string Message, List Files, Guid SectionId) : IBaseCommand;
+
+public class CreateTaskSectionRevisionCommandHandler : IBaseCommandHandler
+{
+ private readonly ITaskSectionRevisionRepository _revisionRepository;
+ private readonly IFileStorageService _fileStorageService;
+ private readonly IAuthHelper _authHelper;
+ private readonly IUnitOfWork _unitOfWork;
+ private readonly IUploadedFileRepository _fileRepository;
+ private readonly IThumbnailGeneratorService _thumbnailService;
+
+ public CreateTaskSectionRevisionCommandHandler(ITaskSectionRevisionRepository revisionRepository,
+ IFileStorageService fileStorageService, IAuthHelper authHelper, IUnitOfWork unitOfWork, IUploadedFileRepository fileRepository, IThumbnailGeneratorService thumbnailService)
+ {
+ _revisionRepository = revisionRepository;
+ _fileStorageService = fileStorageService;
+ _authHelper = authHelper;
+ _unitOfWork = unitOfWork;
+ _fileRepository = fileRepository;
+ _thumbnailService = thumbnailService;
+ }
+
+ public async Task Handle(CreateTaskSectionRevisionCommand request,
+ CancellationToken cancellationToken)
+ {
+ var currentId = _authHelper.GetCurrentUserId();
+
+ var entity = new TaskSectionRevision(request.SectionId, request.Message, currentId!.Value);
+
+ foreach (var file in request.Files)
+ {
+ if (file.Length == 0)
+ {
+ return OperationResult.ValidationError("فایل خالی است");
+ }
+
+ const long maxFileSize = 100 * 1024 * 1024;
+ if (file.Length > maxFileSize)
+ {
+ return OperationResult.ValidationError("حجم فایل بیش از حد مجاز است (حداکثر 100MB)");
+ }
+
+ var fileType = DetectFileType(file.ContentType, Path.GetExtension(file.FileName));
+
+ var uploadedFile = new UploadedFile(
+ originalFileName: file.FileName,
+ fileSizeBytes: file.Length,
+ mimeType: file.ContentType,
+ fileType: fileType,
+ category: FileCategory.TaskSectionRevision,
+ uploadedByUserId: currentId!.Value,
+ storageProvider: StorageProvider.LocalFileSystem
+ );
+
+ await _fileRepository.AddAsync(uploadedFile);
+ await _fileRepository.SaveChangesAsync();
+
+ try
+ {
+ await using var stream = file.OpenReadStream();
+ var uploadResult = await _fileStorageService.UploadAsync(
+ stream,
+ uploadedFile.UniqueFileName,
+ "TaskSectionRevision"
+ );
+
+ uploadedFile.CompleteUpload(uploadResult.StoragePath, uploadResult.StorageUrl);
+
+ if (fileType == FileType.Image)
+ {
+ var dimensions = await _thumbnailService.GetImageDimensionsAsync(uploadResult.StoragePath);
+ if (dimensions.HasValue)
+ {
+ uploadedFile.SetImageDimensions(dimensions.Value.Width, dimensions.Value.Height);
+ }
+
+ var thumbnail = await _thumbnailService
+ .GenerateImageThumbnailAsync(uploadResult.StoragePath, category: "TaskSectionRevision");
+ if (thumbnail.HasValue)
+ {
+ uploadedFile.SetThumbnail(thumbnail.Value.ThumbnailUrl);
+ }
+ }
+
+ await _fileRepository.UpdateAsync(uploadedFile);
+ await _fileRepository.SaveChangesAsync();
+
+ var taskRevisionFile = new TaskRevisionFile(uploadedFile.Id);
+ entity.AddFile(taskRevisionFile);
+ }
+ catch (Exception ex)
+ {
+ await _fileRepository.DeleteAsync(uploadedFile);
+ await _fileRepository.SaveChangesAsync();
+
+ return OperationResult.ValidationError($"خطا در آپلود فایل: {ex.Message}");
+ }
+ }
+
+ await _revisionRepository.CreateAsync(entity);
+ await _unitOfWork.SaveChangesAsync(cancellationToken);
+ return OperationResult.Success();
+ }
+ private FileType DetectFileType(string mimeType, string extension)
+ {
+ if (mimeType.StartsWith("image/", StringComparison.OrdinalIgnoreCase))
+ return FileType.Image;
+
+ if (mimeType.StartsWith("video/", StringComparison.OrdinalIgnoreCase))
+ return FileType.Video;
+
+ if (mimeType.StartsWith("audio/", StringComparison.OrdinalIgnoreCase))
+ return FileType.Audio;
+
+ if (new[] { ".zip", ".rar", ".7z", ".tar", ".gz" }.Contains(extension.ToLower()))
+ return FileType.Archive;
+
+ return FileType.Document;
+ }
+}
\ No newline at end of file
diff --git a/ProgramManager/src/Domain/GozareshgirProgramManager.Domain/FileManagementAgg/Enums/FileCategory.cs b/ProgramManager/src/Domain/GozareshgirProgramManager.Domain/FileManagementAgg/Enums/FileCategory.cs
index 25e7c3f6..e24d441b 100644
--- a/ProgramManager/src/Domain/GozareshgirProgramManager.Domain/FileManagementAgg/Enums/FileCategory.cs
+++ b/ProgramManager/src/Domain/GozareshgirProgramManager.Domain/FileManagementAgg/Enums/FileCategory.cs
@@ -10,6 +10,7 @@ public enum FileCategory
ProjectDocument = 3, // مستندات پروژه
UserProfilePhoto = 4, // عکس پروفایل کاربر
Report = 5, // گزارش
- Other = 6 // سایر
+ Other = 6, // سایر
+ TaskSectionRevision
}
diff --git a/ProgramManager/src/Domain/GozareshgirProgramManager.Domain/ProjectAgg/Entities/Task/TaskSection/TaskSectionRevision.cs b/ProgramManager/src/Domain/GozareshgirProgramManager.Domain/ProjectAgg/Entities/Task/TaskSection/TaskSectionRevision.cs
index 6dfa7c12..07407e12 100644
--- a/ProgramManager/src/Domain/GozareshgirProgramManager.Domain/ProjectAgg/Entities/Task/TaskSection/TaskSectionRevision.cs
+++ b/ProgramManager/src/Domain/GozareshgirProgramManager.Domain/ProjectAgg/Entities/Task/TaskSection/TaskSectionRevision.cs
@@ -1,14 +1,49 @@
using GozareshgirProgramManager.Domain._Common;
+using GozareshgirProgramManager.Domain.FileManagementAgg.Entities;
namespace GozareshgirProgramManager.Domain.ProjectAgg.Entities.Task.TaskSection;
-public class TaskSectionRevision:EntityBase
+public class TaskSectionRevision : EntityBase
{
+ public TaskSectionRevision(Guid taskSectionId,
+ string message, long createdByUserId)
+ {
+ TaskSectionId = taskSectionId;
+ Status = RevisionReviewStatus.Pending;
+ Message = message;
+ CreatedByUserId = createdByUserId;
+ }
+
public Guid TaskSectionId { get; private set; }
- public TaskSectionRevisionStatus Status { get; private set; }
+ public RevisionReviewStatus Status { get; private set; }
+
+
public string Message { get; private set; }
- public Guid CreatedByUserId { get; private set; }
- public DateTime CreatedAt { get; private set; }
+ public long CreatedByUserId { get; private set; }
+
+ public IReadOnlyCollection Files => _files;
+ private readonly List _files = new();
+
+ public void AddFile(TaskRevisionFile file)
+ {
+ _files.Add(file);
+ }
+
+}
+public class TaskRevisionFile: EntityBase
+{
+ public TaskRevisionFile(Guid fileId)
+ {
+ FileId = fileId;
+ }
+
+ public Guid FileId { get; private set; }
+}
+
+public enum RevisionReviewStatus : short
+{
+ Pending = 1,
+ Reviewed = 2
}
\ No newline at end of file
diff --git a/ProgramManager/src/Domain/GozareshgirProgramManager.Domain/ProjectAgg/Repositories/ITaskSectionRevisionRepository.cs b/ProgramManager/src/Domain/GozareshgirProgramManager.Domain/ProjectAgg/Repositories/ITaskSectionRevisionRepository.cs
new file mode 100644
index 00000000..34397d31
--- /dev/null
+++ b/ProgramManager/src/Domain/GozareshgirProgramManager.Domain/ProjectAgg/Repositories/ITaskSectionRevisionRepository.cs
@@ -0,0 +1,9 @@
+using GozareshgirProgramManager.Domain._Common;
+using GozareshgirProgramManager.Domain.ProjectAgg.Entities.Task.TaskSection;
+
+namespace GozareshgirProgramManager.Domain.ProjectAgg.Repositories;
+
+public interface ITaskSectionRevisionRepository:IRepository
+{
+
+}
\ No newline at end of file
diff --git a/ProgramManager/src/Infrastructure/GozareshgirProgramManager.Infrastructure/DependencyInjection.cs b/ProgramManager/src/Infrastructure/GozareshgirProgramManager.Infrastructure/DependencyInjection.cs
index 20618330..7679b07b 100644
--- a/ProgramManager/src/Infrastructure/GozareshgirProgramManager.Infrastructure/DependencyInjection.cs
+++ b/ProgramManager/src/Infrastructure/GozareshgirProgramManager.Infrastructure/DependencyInjection.cs
@@ -100,7 +100,12 @@ public static class DependencyInjection
services.AddScoped();
services.AddScoped();
+
+ //TaskSection Time Request
+ services.AddScoped();
+ //TaskSection Revision
+ services.AddScoped();
#region ServicesInjection
diff --git a/ProgramManager/src/Infrastructure/GozareshgirProgramManager.Infrastructure/Persistence/Repositories/TaskChat/TaskChatMessageRepository.cs b/ProgramManager/src/Infrastructure/GozareshgirProgramManager.Infrastructure/Persistence/Repositories/TaskChatMessageRepository.cs
similarity index 100%
rename from ProgramManager/src/Infrastructure/GozareshgirProgramManager.Infrastructure/Persistence/Repositories/TaskChat/TaskChatMessageRepository.cs
rename to ProgramManager/src/Infrastructure/GozareshgirProgramManager.Infrastructure/Persistence/Repositories/TaskChatMessageRepository.cs
diff --git a/ProgramManager/src/Infrastructure/GozareshgirProgramManager.Infrastructure/Persistence/Repositories/TaskSectionRevisionRepository.cs b/ProgramManager/src/Infrastructure/GozareshgirProgramManager.Infrastructure/Persistence/Repositories/TaskSectionRevisionRepository.cs
new file mode 100644
index 00000000..53290925
--- /dev/null
+++ b/ProgramManager/src/Infrastructure/GozareshgirProgramManager.Infrastructure/Persistence/Repositories/TaskSectionRevisionRepository.cs
@@ -0,0 +1,16 @@
+using GozareshgirProgramManager.Domain.ProjectAgg.Entities.Task.TaskSection;
+using GozareshgirProgramManager.Domain.ProjectAgg.Repositories;
+using GozareshgirProgramManager.Infrastructure.Persistence._Common;
+using GozareshgirProgramManager.Infrastructure.Persistence.Context;
+using Microsoft.EntityFrameworkCore;
+
+namespace GozareshgirProgramManager.Infrastructure.Persistence.Repositories;
+
+public class TaskSectionRevisionRepository:RepositoryBase,ITaskSectionRevisionRepository
+{
+ private readonly ProgramManagerDbContext _programManagerDbContext;
+ public TaskSectionRevisionRepository(ProgramManagerDbContext programManagerDbContext) : base(programManagerDbContext)
+ {
+ _programManagerDbContext = programManagerDbContext;
+ }
+}
\ No newline at end of file
diff --git a/ProgramManager/src/Infrastructure/GozareshgirProgramManager.Infrastructure/Persistence/Repositories/TaskSectionTimeRequestRepository.cs b/ProgramManager/src/Infrastructure/GozareshgirProgramManager.Infrastructure/Persistence/Repositories/TaskSectionTimeRequestRepository.cs
new file mode 100644
index 00000000..48d6b181
--- /dev/null
+++ b/ProgramManager/src/Infrastructure/GozareshgirProgramManager.Infrastructure/Persistence/Repositories/TaskSectionTimeRequestRepository.cs
@@ -0,0 +1,16 @@
+using GozareshgirProgramManager.Domain.ProjectAgg.Entities.Task.TaskSection;
+using GozareshgirProgramManager.Domain.ProjectAgg.Repositories;
+using GozareshgirProgramManager.Infrastructure.Persistence._Common;
+using GozareshgirProgramManager.Infrastructure.Persistence.Context;
+using Microsoft.EntityFrameworkCore;
+
+namespace GozareshgirProgramManager.Infrastructure.Persistence.Repositories;
+
+public class TaskSectionTimeRequestRepository:RepositoryBase,ITaskSectionTimeRequestRepository
+{
+ private readonly ProgramManagerDbContext _context;
+ public TaskSectionTimeRequestRepository(ProgramManagerDbContext context) : base(context)
+ {
+ _context = context;
+ }
+}
\ No newline at end of file
diff --git a/ProgramManager/src/Infrastructure/GozareshgirProgramManager.Infrastructure/Persistence/Repositories/FileManagement/UploadedFileRepository.cs b/ProgramManager/src/Infrastructure/GozareshgirProgramManager.Infrastructure/Persistence/Repositories/UploadedFileRepository.cs
similarity index 100%
rename from ProgramManager/src/Infrastructure/GozareshgirProgramManager.Infrastructure/Persistence/Repositories/FileManagement/UploadedFileRepository.cs
rename to ProgramManager/src/Infrastructure/GozareshgirProgramManager.Infrastructure/Persistence/Repositories/UploadedFileRepository.cs
diff --git a/ServiceHost/Areas/Admin/Controllers/ProgramManager/TaskSectionRevisionController.cs b/ServiceHost/Areas/Admin/Controllers/ProgramManager/TaskSectionRevisionController.cs
new file mode 100644
index 00000000..04648882
--- /dev/null
+++ b/ServiceHost/Areas/Admin/Controllers/ProgramManager/TaskSectionRevisionController.cs
@@ -0,0 +1,23 @@
+using GozareshgirProgramManager.Application._Common.Models;
+using GozareshgirProgramManager.Application.Modules.Projects.Commands.CreateTaskSectionRevision;
+using MediatR;
+using Microsoft.AspNetCore.Mvc;
+using ServiceHost.BaseControllers;
+
+namespace ServiceHost.Areas.Admin.Controllers.ProgramManager;
+
+public class TaskSectionRevisionController:ProgramManagerBaseController
+{
+ private readonly IMediator _mediator;
+
+ public TaskSectionRevisionController(IMediator mediator)
+ {
+ _mediator = mediator;
+ }
+
+ public async Task> CreateTaskRevision([FromForm]CreateTaskSectionRevisionCommand command)
+ {
+ var res =await _mediator.Send(command);
+ return Ok(res);
+ }
+}
\ No newline at end of file