Merge branch 'master' of https://pm.gozareshgir.ir/gozareshgir/OriginalGozareshgir into Feature/program-manager/BugSection
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -368,3 +368,6 @@ MigrationBackup/
|
||||
# Storage folder - ignore all uploaded files, thumbnails, and temporary files
|
||||
ServiceHost/Storage
|
||||
|
||||
.env
|
||||
.env.*
|
||||
|
||||
|
||||
@@ -31,8 +31,9 @@ public static class StaticWorkshopAccounts
|
||||
/// 381 - مهدی قربانی
|
||||
/// 392 - عمار حسن دوست
|
||||
/// 20 - سمیرا الهی نیا
|
||||
/// 322 - ماهان چمنی
|
||||
/// </summary>
|
||||
public static List<long> StaticAccountIds = [2, 3, 380, 381, 392, 20, 476];
|
||||
public static List<long> StaticAccountIds = [2, 3, 380, 381, 392, 20, 476,322];
|
||||
|
||||
/// <summary>
|
||||
/// این تاریخ در جدول اکانت لفت ورک به این معنیست
|
||||
|
||||
@@ -447,8 +447,7 @@ public class RollCallApplication : IRollCallApplication
|
||||
return operation.Failed("کارمند در بازه انتخاب شده مرخصی ساعتی دارد");
|
||||
}
|
||||
|
||||
if (newRollCallDates == null || !newRollCallDates.All(x => employeeStatuses.Any(y => x.StartDate.Value.Date >= y.StartDateGr.Date && x.EndDate.Value.Date <= y.EndDateGr.Date)))
|
||||
return operation.Failed("کارمند در بازه وارد شده غیر فعال است");
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -458,7 +457,10 @@ public class RollCallApplication : IRollCallApplication
|
||||
_rollCallDomainService.GetEmployeeShiftDateByRollCallStartDate(command.WorkshopId, command.EmployeeId,
|
||||
x.StartDate!.Value,x.EndDate.Value);
|
||||
});
|
||||
|
||||
if (newRollCallDates == null || !newRollCallDates.All(x => employeeStatuses.Any(y => x.ShiftDate.Date >= y.StartDateGr.Date && x.ShiftDate.Date <= y.EndDateGr.Date)))
|
||||
return operation.Failed("کارمند در بازه وارد شده غیر فعال است");
|
||||
|
||||
|
||||
if (newRollCallDates.Any(x => x.ShiftDate.Date != date.Date))
|
||||
{
|
||||
return operation.Failed("حضور غیاب در حال ویرایش را نمیتوانید از تاریخ شیفت عقب تر یا جلو تر ببرید");
|
||||
@@ -487,8 +489,8 @@ public class RollCallApplication : IRollCallApplication
|
||||
|
||||
|
||||
|
||||
if (newRollCallDates == null || !newRollCallDates.All(x => employeeStatuses.Any(y => x.StartDate.Value.Date >= y.StartDateGr.Date
|
||||
&& x.EndDate.Value.Date <= y.EndDateGr.Date)))
|
||||
if (newRollCallDates == null || !newRollCallDates.All(x => employeeStatuses.Any(y => x.ShiftDate.Date >= y.StartDateGr.Date
|
||||
&& x.ShiftDate.Date <= y.EndDateGr.Date)))
|
||||
return operation.Failed("کارمند در بازه وارد شده غیر فعال است");
|
||||
|
||||
|
||||
@@ -632,9 +634,6 @@ public class RollCallApplication : IRollCallApplication
|
||||
return operation.Failed("کارمند در بازه انتخاب شده مرخصی ساعتی دارد");
|
||||
}
|
||||
|
||||
if (newRollCallDates == null || !newRollCallDates.All(x => employeeStatuses.Any(y => x.StartDate.Value.Date >= y.StartDateGr.Date && x.EndDate.Value.Date <= y.EndDateGr.Date)))
|
||||
return operation.Failed("کارمند در بازه وارد شده غیر فعال است");
|
||||
|
||||
|
||||
newRollCallDates.ForEach(x =>
|
||||
{
|
||||
@@ -642,6 +641,11 @@ public class RollCallApplication : IRollCallApplication
|
||||
_rollCallDomainService.GetEmployeeShiftDateByRollCallStartDate(command.WorkshopId, command.EmployeeId,
|
||||
x.StartDate!.Value,x.EndDate.Value);
|
||||
});
|
||||
|
||||
if (newRollCallDates == null || !newRollCallDates.All(x => employeeStatuses.Any(y => x.ShiftDate.Date >= y.StartDateGr.Date && x.ShiftDate.Date <= y.EndDateGr.Date)))
|
||||
return operation.Failed("کارمند در بازه وارد شده غیر فعال است");
|
||||
|
||||
|
||||
if (newRollCallDates.Any(x => x.ShiftDate.Date != date.Date))
|
||||
{
|
||||
return operation.Failed("حضور غیاب در حال ویرایش را نمیتوانید از تاریخ شیفت عقب تر یا جلو تر ببرید");
|
||||
@@ -664,7 +668,7 @@ public class RollCallApplication : IRollCallApplication
|
||||
&& (y.StartDate.Value.Date <= x.ContractEndGr.Date))))
|
||||
return operation.Failed("برای بازه های وارد شده فیش حقوقی ثبت شده است");
|
||||
|
||||
if (newRollCallDates == null || !newRollCallDates.All(x => employeeStatuses.Any(y => x.StartDate.Value.Date >= y.StartDateGr.Date && x.EndDate.Value.Date <= y.EndDateGr.Date)))
|
||||
if (newRollCallDates == null || !newRollCallDates.All(x => employeeStatuses.Any(y => x.ShiftDate.Date >= y.StartDateGr.Date && x.ShiftDate.Date <= y.EndDateGr.Date)))
|
||||
return operation.Failed("کارمند در بازه وارد شده غیر فعال است");
|
||||
|
||||
var currentDayRollCall = employeeRollCalls.FirstOrDefault(x => x.EndDate == null);
|
||||
|
||||
@@ -28,26 +28,25 @@ public class SendMessageCommandHandler : IBaseCommandHandler<SendMessageCommand,
|
||||
private readonly ITaskChatMessageRepository _messageRepository;
|
||||
private readonly IUploadedFileRepository _fileRepository;
|
||||
private readonly IProjectTaskRepository _taskRepository;
|
||||
private readonly IFileStorageService _fileStorageService;
|
||||
private readonly IThumbnailGeneratorService _thumbnailService;
|
||||
private readonly IFileUploadService _fileUploadService;
|
||||
private readonly IAuthHelper _authHelper;
|
||||
|
||||
public SendMessageCommandHandler(
|
||||
ITaskChatMessageRepository messageRepository,
|
||||
IUploadedFileRepository fileRepository,
|
||||
IProjectTaskRepository taskRepository,
|
||||
IFileStorageService fileStorageService,
|
||||
IThumbnailGeneratorService thumbnailService, IAuthHelper authHelper)
|
||||
IProjectTaskRepository taskRepository,
|
||||
IAuthHelper authHelper,
|
||||
IFileUploadService fileUploadService)
|
||||
{
|
||||
_messageRepository = messageRepository;
|
||||
_fileRepository = fileRepository;
|
||||
_taskRepository = taskRepository;
|
||||
_fileStorageService = fileStorageService;
|
||||
_thumbnailService = thumbnailService;
|
||||
_authHelper = authHelper;
|
||||
_fileUploadService = fileUploadService;
|
||||
}
|
||||
|
||||
public async Task<OperationResult<MessageDto>> Handle(SendMessageCommand request, CancellationToken cancellationToken)
|
||||
public async Task<OperationResult<MessageDto>> Handle(SendMessageCommand request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var currentUserId = _authHelper.GetCurrentUserId()
|
||||
?? throw new UnAuthorizedException("کاربر احراز هویت نشده است");
|
||||
@@ -57,75 +56,21 @@ public class SendMessageCommandHandler : IBaseCommandHandler<SendMessageCommand,
|
||||
{
|
||||
return OperationResult<MessageDto>.NotFound("تسک یافت نشد");
|
||||
}
|
||||
|
||||
|
||||
Guid? uploadedFileId = null;
|
||||
if (request.File != null)
|
||||
{
|
||||
if (request.File.Length == 0)
|
||||
{
|
||||
return OperationResult<MessageDto>.ValidationError("فایل خالی است");
|
||||
}
|
||||
|
||||
const long maxFileSize = 100 * 1024 * 1024;
|
||||
if (request.File.Length > maxFileSize)
|
||||
{
|
||||
return OperationResult<MessageDto>.ValidationError("حجم فایل بیش از حد مجاز است (حداکثر 100MB)");
|
||||
}
|
||||
|
||||
var fileType = DetectFileType(request.File.ContentType, Path.GetExtension(request.File.FileName));
|
||||
|
||||
var uploadedFile = new UploadedFile(
|
||||
originalFileName: request.File.FileName,
|
||||
fileSizeBytes: request.File.Length,
|
||||
mimeType: request.File.ContentType,
|
||||
fileType: fileType,
|
||||
category: FileCategory.TaskChatMessage,
|
||||
uploadedByUserId: currentUserId,
|
||||
storageProvider: StorageProvider.LocalFileSystem
|
||||
var uploadedFile = await _fileUploadService.UploadFileAsync
|
||||
(
|
||||
request.File,
|
||||
FileCategory.TaskChatMessage,
|
||||
currentUserId
|
||||
);
|
||||
|
||||
await _fileRepository.AddAsync(uploadedFile);
|
||||
await _fileRepository.SaveChangesAsync();
|
||||
|
||||
try
|
||||
if (!uploadedFile.IsSuccess)
|
||||
{
|
||||
using var stream = request.File.OpenReadStream();
|
||||
var uploadResult = await _fileStorageService.UploadAsync(
|
||||
stream,
|
||||
uploadedFile.UniqueFileName,
|
||||
"TaskChatMessage"
|
||||
);
|
||||
|
||||
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: "TaskChatMessage");
|
||||
if (thumbnail.HasValue)
|
||||
{
|
||||
uploadedFile.SetThumbnail(thumbnail.Value.ThumbnailUrl);
|
||||
}
|
||||
}
|
||||
|
||||
await _fileRepository.UpdateAsync(uploadedFile);
|
||||
await _fileRepository.SaveChangesAsync();
|
||||
|
||||
uploadedFileId = uploadedFile.Id;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await _fileRepository.DeleteAsync(uploadedFile);
|
||||
await _fileRepository.SaveChangesAsync();
|
||||
|
||||
return OperationResult<MessageDto>.ValidationError($"خطا در آپلود فایل: {ex.Message}");
|
||||
return OperationResult<MessageDto>.Failure(uploadedFile.ErrorMessage ?? "خطا در آپلود فایل");
|
||||
}
|
||||
uploadedFileId = uploadedFile.FileId!.Value;
|
||||
}
|
||||
|
||||
var message = new TaskChatMessage(
|
||||
@@ -209,4 +154,4 @@ public class SendMessageCommandHandler : IBaseCommandHandler<SendMessageCommand,
|
||||
|
||||
return FileType.Document;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
using GozareshgirProgramManager.Domain.FileManagementAgg.Enums;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace GozareshgirProgramManager.Application.Services.FileManagement;
|
||||
|
||||
/// <summary>
|
||||
/// سرویس آپلود و مدیریت کامل فایل
|
||||
/// این سرویس تمام مراحل آپلود، ذخیره، تولید thumbnail و... را انجام میدهد
|
||||
/// </summary>
|
||||
public interface IFileUploadService
|
||||
{
|
||||
/// <summary>
|
||||
/// آپلود فایل با تمام مراحل پردازش
|
||||
/// </summary>
|
||||
/// <param name="file">فایل برای آپلود</param>
|
||||
/// <param name="category">دستهبندی فایل</param>
|
||||
/// <param name="uploadedByUserId">شناسه کاربر آپلودکننده</param>
|
||||
/// <param name="maxFileSizeBytes">حداکثر حجم مجاز فایل (پیشفرض: 100MB)</param>
|
||||
/// <returns>شناسه فایل آپلود شده یا null در صورت خطا</returns>
|
||||
Task<FileUploadResult> UploadFileAsync(
|
||||
IFormFile file,
|
||||
FileCategory category,
|
||||
long uploadedByUserId,
|
||||
long maxFileSizeBytes = 100 * 1024 * 1024);
|
||||
|
||||
/// <summary>
|
||||
/// آپلود فایل با Stream
|
||||
/// </summary>
|
||||
Task<FileUploadResult> UploadFileFromStreamAsync(
|
||||
Stream fileStream,
|
||||
string fileName,
|
||||
string contentType,
|
||||
FileCategory category,
|
||||
long uploadedByUserId,
|
||||
long maxFileSizeBytes = 100 * 1024 * 1024);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// نتیجه عملیات آپلود فایل
|
||||
/// </summary>
|
||||
public class FileUploadResult
|
||||
{
|
||||
public bool IsSuccess { get; set; }
|
||||
public Guid? FileId { get; set; }
|
||||
public string? ErrorMessage { get; set; }
|
||||
public string? StorageUrl { get; set; }
|
||||
public string? ThumbnailUrl { get; set; }
|
||||
|
||||
public static FileUploadResult Success(Guid fileId, string storageUrl, string? thumbnailUrl = null)
|
||||
{
|
||||
return new FileUploadResult
|
||||
{
|
||||
IsSuccess = true,
|
||||
FileId = fileId,
|
||||
StorageUrl = storageUrl,
|
||||
ThumbnailUrl = thumbnailUrl
|
||||
};
|
||||
}
|
||||
|
||||
public static FileUploadResult Failed(string errorMessage)
|
||||
{
|
||||
return new FileUploadResult
|
||||
{
|
||||
IsSuccess = false,
|
||||
ErrorMessage = errorMessage
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,6 +149,22 @@ public static class ProgramManagerPermissionCode
|
||||
{
|
||||
public const int Code = 990111;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// اولویت بندی
|
||||
/// </summary>
|
||||
public static class Priority
|
||||
{
|
||||
public const int Code = 990112;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ایجاد تسک باگ
|
||||
/// </summary>
|
||||
public static class CreateBug
|
||||
{
|
||||
public const int Code = 990113;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -226,11 +242,26 @@ public static class ProgramManagerPermissionCode
|
||||
{
|
||||
public const int Code = 990208;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// رد با تایید اتمام اجرا
|
||||
/// </summary>
|
||||
public static class RejectOrApproveTaskComplete
|
||||
{
|
||||
public const int Code = 990209;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Workflow[تب کارپوشه]
|
||||
public static class Workflow
|
||||
{
|
||||
public const int Code = 9903;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
public static Dictionary<string, object> GetAllCodes()
|
||||
{
|
||||
var result = new Dictionary<string, object>();
|
||||
|
||||
@@ -93,6 +93,7 @@ public static class DependencyInjection
|
||||
// File Storage Services
|
||||
services.AddScoped<IFileStorageService, Services.FileManagement.LocalFileStorageService>();
|
||||
services.AddScoped<IThumbnailGeneratorService, Services.FileManagement.ThumbnailGeneratorService>();
|
||||
services.AddScoped<IFileUploadService, Services.FileManagement.FileUploadService>();
|
||||
|
||||
// JWT Settings
|
||||
services.Configure<JwtSettings>(configuration.GetSection("JwtSettings"));
|
||||
|
||||
@@ -0,0 +1,247 @@
|
||||
using GozareshgirProgramManager.Application.Services.FileManagement;
|
||||
using GozareshgirProgramManager.Domain.FileManagementAgg.Entities;
|
||||
using GozareshgirProgramManager.Domain.FileManagementAgg.Enums;
|
||||
using GozareshgirProgramManager.Domain.FileManagementAgg.Repositories;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace GozareshgirProgramManager.Infrastructure.Services.FileManagement;
|
||||
|
||||
/// <summary>
|
||||
/// پیادهسازی سرویس آپلود کامل فایل
|
||||
/// </summary>
|
||||
public class FileUploadService : IFileUploadService
|
||||
{
|
||||
private readonly IUploadedFileRepository _fileRepository;
|
||||
private readonly IFileStorageService _fileStorageService;
|
||||
private readonly IThumbnailGeneratorService _thumbnailService;
|
||||
private readonly ILogger<FileUploadService> _logger;
|
||||
|
||||
public FileUploadService(
|
||||
IUploadedFileRepository fileRepository,
|
||||
IFileStorageService fileStorageService,
|
||||
IThumbnailGeneratorService thumbnailService,
|
||||
ILogger<FileUploadService> logger)
|
||||
{
|
||||
_fileRepository = fileRepository;
|
||||
_fileStorageService = fileStorageService;
|
||||
_thumbnailService = thumbnailService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<FileUploadResult> UploadFileAsync(
|
||||
IFormFile file,
|
||||
FileCategory category,
|
||||
long uploadedByUserId,
|
||||
long maxFileSizeBytes = 100 * 1024 * 1024)
|
||||
{
|
||||
try
|
||||
{
|
||||
// اعتبارسنجی ورودی
|
||||
if (file.Length == 0)
|
||||
{
|
||||
return FileUploadResult.Failed("فایل خالی است");
|
||||
}
|
||||
|
||||
if (file.Length > maxFileSizeBytes)
|
||||
{
|
||||
var maxSizeMb = maxFileSizeBytes / (1024 * 1024);
|
||||
return FileUploadResult.Failed($"حجم فایل بیش از حد مجاز است (حداکثر {maxSizeMb}MB)");
|
||||
}
|
||||
|
||||
using var stream = file.OpenReadStream();
|
||||
return await UploadFileFromStreamAsync(
|
||||
stream,
|
||||
file.FileName,
|
||||
file.ContentType,
|
||||
category,
|
||||
uploadedByUserId,
|
||||
maxFileSizeBytes);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "خطا در آپلود فایل {FileName}", file.FileName);
|
||||
return FileUploadResult.Failed($"خطا در آپلود فایل: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<FileUploadResult> UploadFileFromStreamAsync(
|
||||
Stream fileStream,
|
||||
string fileName,
|
||||
string contentType,
|
||||
FileCategory category,
|
||||
long uploadedByUserId,
|
||||
long maxFileSizeBytes = 100 * 1024 * 1024)
|
||||
{
|
||||
UploadedFile? uploadedFile = null;
|
||||
|
||||
try
|
||||
{
|
||||
// تشخیص نوع فایل
|
||||
var fileType = DetectFileType(contentType, Path.GetExtension(fileName));
|
||||
|
||||
// ایجاد رکورد فایل در دیتابیس
|
||||
uploadedFile = new UploadedFile(
|
||||
originalFileName: fileName,
|
||||
fileSizeBytes: fileStream.Length,
|
||||
mimeType: contentType,
|
||||
fileType: fileType,
|
||||
category: category,
|
||||
uploadedByUserId: uploadedByUserId,
|
||||
storageProvider: StorageProvider.LocalFileSystem
|
||||
);
|
||||
|
||||
await _fileRepository.AddAsync(uploadedFile);
|
||||
await _fileRepository.SaveChangesAsync();
|
||||
|
||||
// آپلود فایل به استوریج
|
||||
var categoryFolder = GetCategoryFolderName(category);
|
||||
var uploadResult = await _fileStorageService.UploadAsync(
|
||||
fileStream,
|
||||
uploadedFile.UniqueFileName,
|
||||
categoryFolder
|
||||
);
|
||||
|
||||
// بهروزرسانی اطلاعات آپلود
|
||||
uploadedFile.CompleteUpload(uploadResult.StoragePath, uploadResult.StorageUrl);
|
||||
|
||||
// پردازشهای خاص بر اساس نوع فایل
|
||||
string? thumbnailUrl = null;
|
||||
if (fileType == FileType.Image)
|
||||
{
|
||||
thumbnailUrl = await ProcessImageAsync(uploadedFile, uploadResult.StoragePath, categoryFolder);
|
||||
}
|
||||
else if (fileType == FileType.Video)
|
||||
{
|
||||
thumbnailUrl = await ProcessVideoAsync(uploadedFile, uploadResult.StoragePath, categoryFolder);
|
||||
}
|
||||
|
||||
// ذخیره تغییرات نهایی
|
||||
await _fileRepository.UpdateAsync(uploadedFile);
|
||||
await _fileRepository.SaveChangesAsync();
|
||||
|
||||
_logger.LogInformation(
|
||||
"فایل {FileName} با شناسه {FileId} با موفقیت آپلود شد",
|
||||
fileName,
|
||||
uploadedFile.Id);
|
||||
|
||||
return FileUploadResult.Success(uploadedFile.Id, uploadResult.StorageUrl, thumbnailUrl);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "خطا در آپلود فایل {FileName}", fileName);
|
||||
|
||||
// در صورت خطا، فایل آپلود شده را حذف کنیم
|
||||
if (uploadedFile != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _fileRepository.DeleteAsync(uploadedFile);
|
||||
await _fileRepository.SaveChangesAsync();
|
||||
}
|
||||
catch (Exception deleteEx)
|
||||
{
|
||||
_logger.LogError(deleteEx, "خطا در حذف فایل ناموفق {FileId}", uploadedFile.Id);
|
||||
}
|
||||
}
|
||||
|
||||
return FileUploadResult.Failed($"خطا در آپلود فایل: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// پردازش تصویر (ابعاد و thumbnail)
|
||||
/// </summary>
|
||||
private async Task<string?> ProcessImageAsync(UploadedFile file, string storagePath, string categoryFolder)
|
||||
{
|
||||
try
|
||||
{
|
||||
// دریافت ابعاد تصویر
|
||||
var dimensions = await _thumbnailService.GetImageDimensionsAsync(storagePath);
|
||||
if (dimensions.HasValue)
|
||||
{
|
||||
file.SetImageDimensions(dimensions.Value.Width, dimensions.Value.Height);
|
||||
}
|
||||
|
||||
// تولید thumbnail
|
||||
var thumbnail = await _thumbnailService.GenerateImageThumbnailAsync(
|
||||
storagePath,
|
||||
category: categoryFolder);
|
||||
|
||||
if (thumbnail.HasValue)
|
||||
{
|
||||
file.SetThumbnail(thumbnail.Value.ThumbnailUrl);
|
||||
return thumbnail.Value.ThumbnailUrl;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "خطا در پردازش تصویر {FileId}", file.Id);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// پردازش ویدیو (thumbnail)
|
||||
/// </summary>
|
||||
private async Task<string?> ProcessVideoAsync(UploadedFile file, string storagePath, string categoryFolder)
|
||||
{
|
||||
try
|
||||
{
|
||||
// تولید thumbnail از ویدیو
|
||||
var thumbnail = await _thumbnailService.GenerateVideoThumbnailAsync(
|
||||
storagePath,
|
||||
category: categoryFolder);
|
||||
|
||||
if (thumbnail.HasValue)
|
||||
{
|
||||
file.SetThumbnail(thumbnail.Value.ThumbnailUrl);
|
||||
return thumbnail.Value.ThumbnailUrl;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "خطا در پردازش ویدیو {FileId}", file.Id);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// تشخیص نوع فایل از روی MIME type و extension
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// دریافت نام پوشه بر اساس دستهبندی
|
||||
/// </summary>
|
||||
private string GetCategoryFolderName(FileCategory category)
|
||||
{
|
||||
return category switch
|
||||
{
|
||||
FileCategory.TaskChatMessage => "TaskChatMessage",
|
||||
FileCategory.TaskAttachment => "TaskAttachment",
|
||||
FileCategory.ProjectDocument => "ProjectDocument",
|
||||
FileCategory.UserProfilePhoto => "UserProfilePhoto",
|
||||
FileCategory.Report => "Report",
|
||||
_ => "Other"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1216,10 +1216,31 @@
|
||||
<label class="btn btn-icon waves-effect btn-default m-b-5 open-close">
|
||||
<i class="ion-plus"></i> <i class="ion-minus" style="display: none;"></i><input type="checkbox" style="display: none" class="open-btn" />
|
||||
</label>
|
||||
<label class="btn btn-inverse waves-effect waves-light m-b-5 parentLevel2"> <input type="checkbox" disabled="disabled" value="990111" class="check-btn" data-pm=""> <span style="bottom: 2px;position: relative"> ایجاد بخش فرعی </span> </label>
|
||||
<label class="btn btn-inverse waves-effect waves-light m-b-5 parentLevel2"> <input type="checkbox" disabled="disabled" value="990111" class="check-btn" data-pm=""> <span style="bottom: 2px;position: relative"> ایجاد بخش فرعی </span> </label>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<!-- اولویت بندی-->
|
||||
<div class="child-check level3">
|
||||
<label class="btn btn-icon waves-effect btn-default m-b-5 open-close">
|
||||
<i class="ion-plus"></i> <i class="ion-minus" style="display: none;"></i><input type="checkbox" style="display: none" class="open-btn" />
|
||||
</label>
|
||||
<label class="btn btn-inverse waves-effect waves-light m-b-5 parentLevel2"> <input type="checkbox" disabled="disabled" value="990112" class="check-btn" data-pm=""> <span style="bottom: 2px;position: relative"> اولویت بندی </span> </label>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<!-- ایجاد تسک باگ-->
|
||||
<div class="child-check level3">
|
||||
<label class="btn btn-icon waves-effect btn-default m-b-5 open-close">
|
||||
<i class="ion-plus"></i> <i class="ion-minus" style="display: none;"></i><input type="checkbox" style="display: none" class="open-btn" />
|
||||
</label>
|
||||
<label class="btn btn-inverse waves-effect waves-light m-b-5 parentLevel2"> <input type="checkbox" disabled="disabled" value="990113" class="check-btn" data-pm=""> <span style="bottom: 2px;position: relative"> ایجاد تسک باگ </span> </label>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!--=================================================-->
|
||||
@@ -1310,8 +1331,28 @@
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- تایید یا رد اتمام اجرا -->
|
||||
<div class="child-check level3">
|
||||
<label class="btn btn-icon waves-effect btn-default m-b-5 open-close">
|
||||
<i class="ion-plus"></i> <i class="ion-minus" style="display: none;"></i><input type="checkbox" style="display: none" class="open-btn" />
|
||||
</label>
|
||||
<label class="btn btn-inverse waves-effect waves-light m-b-5 parentLevel2"> <input type="checkbox" disabled="disabled" value="990209" class="check-btn" data-pm=""> <span style="bottom: 2px;position: relative"> تایید یا رد اتمام اجرا </span> </label>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<!--=================================================-->
|
||||
<!--#### کارپوشه ####-->
|
||||
<div class="child-check level2">
|
||||
<label class="btn btn-icon waves-effect btn-default m-b-5 open-close">
|
||||
<i class="ion-plus"></i> <i class="ion-minus" style="display: none;"></i><input type="checkbox" style="display: none" class="open-btn" />
|
||||
</label>
|
||||
<label class="btn btn-inverse waves-effect waves-light m-b-5 parentLevel2"> <input type="checkbox" disabled="disabled" value="9903" class="check-btn" data-pm=""> <span style="bottom: 2px;position: relative"> کارپوشه </span> </label>
|
||||
<!-----------------------Sub Menu------------------->
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1202,15 +1202,35 @@
|
||||
|
||||
|
||||
<!-- ایجاد بخش فرعی -->
|
||||
<div class="child-check level3">
|
||||
<label class="btn btn-icon waves-effect btn-default m-b-5 open-close">
|
||||
<i class="ion-plus"></i> <i class="ion-minus" style="display: none;"></i><input type="checkbox" style="display: none" class="open-btn" />
|
||||
</label>
|
||||
<label class="btn btn-inverse waves-effect waves-light m-b-5 parentLevel2"> <input type="checkbox" disabled="disabled" value="990111" class="check-btn" data-pm=""> <span style="bottom: 2px;position: relative"> ایجاد بخش فرعی </span> </label>
|
||||
<div class="child-check level3">
|
||||
<label class="btn btn-icon waves-effect btn-default m-b-5 open-close">
|
||||
<i class="ion-plus"></i> <i class="ion-minus" style="display: none;"></i><input type="checkbox" style="display: none" class="open-btn" />
|
||||
</label>
|
||||
<label class="btn btn-inverse waves-effect waves-light m-b-5 parentLevel2"> <input type="checkbox" disabled="disabled" value="990111" class="check-btn" data-pm=""> <span style="bottom: 2px;position: relative"> ایجاد بخش فرعی </span> </label>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- اولویت بندی-->
|
||||
<div class="child-check level3">
|
||||
<label class="btn btn-icon waves-effect btn-default m-b-5 open-close">
|
||||
<i class="ion-plus"></i> <i class="ion-minus" style="display: none;"></i><input type="checkbox" style="display: none" class="open-btn" />
|
||||
</label>
|
||||
<label class="btn btn-inverse waves-effect waves-light m-b-5 parentLevel2"> <input type="checkbox" disabled="disabled" value="990112" class="check-btn" data-pm=""> <span style="bottom: 2px;position: relative"> اولویت بندی </span> </label>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<!-- ایجاد تسک باگ-->
|
||||
<div class="child-check level3">
|
||||
<label class="btn btn-icon waves-effect btn-default m-b-5 open-close">
|
||||
<i class="ion-plus"></i> <i class="ion-minus" style="display: none;"></i><input type="checkbox" style="display: none" class="open-btn" />
|
||||
</label>
|
||||
<label class="btn btn-inverse waves-effect waves-light m-b-5 parentLevel2"> <input type="checkbox" disabled="disabled" value="990113" class="check-btn" data-pm=""> <span style="bottom: 2px;position: relative"> ایجاد تسک باگ </span> </label>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--=================================================-->
|
||||
<!--#### تب اجرا ####-->
|
||||
@@ -1292,17 +1312,36 @@
|
||||
</div>
|
||||
|
||||
<!-- چت -->
|
||||
<div class="child-check level3">
|
||||
<label class="btn btn-icon waves-effect btn-default m-b-5 open-close">
|
||||
<i class="ion-plus"></i> <i class="ion-minus" style="display: none;"></i><input type="checkbox" style="display: none" class="open-btn" />
|
||||
</label>
|
||||
<label class="btn btn-inverse waves-effect waves-light m-b-5 parentLevel2"> <input type="checkbox" disabled="disabled" value="990208" class="check-btn" data-pm=""> <span style="bottom: 2px;position: relative"> چت </span> </label>
|
||||
<div class="child-check level3">
|
||||
<label class="btn btn-icon waves-effect btn-default m-b-5 open-close">
|
||||
<i class="ion-plus"></i> <i class="ion-minus" style="display: none;"></i><input type="checkbox" style="display: none" class="open-btn" />
|
||||
</label>
|
||||
<label class="btn btn-inverse waves-effect waves-light m-b-5 parentLevel2"> <input type="checkbox" disabled="disabled" value="990208" class="check-btn" data-pm=""> <span style="bottom: 2px;position: relative"> چت </span> </label>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- تایید یا رد اتمام اجرا -->
|
||||
<div class="child-check level3">
|
||||
<label class="btn btn-icon waves-effect btn-default m-b-5 open-close">
|
||||
<i class="ion-plus"></i> <i class="ion-minus" style="display: none;"></i><input type="checkbox" style="display: none" class="open-btn" />
|
||||
</label>
|
||||
<label class="btn btn-inverse waves-effect waves-light m-b-5 parentLevel2"> <input type="checkbox" disabled="disabled" value="990209" class="check-btn" data-pm=""> <span style="bottom: 2px;position: relative"> تایید یا رد اتمام اجرا </span> </label>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<!--=================================================-->
|
||||
<!--#### کارپوشه ####-->
|
||||
<div class="child-check level2">
|
||||
<label class="btn btn-icon waves-effect btn-default m-b-5 open-close">
|
||||
<i class="ion-plus"></i> <i class="ion-minus" style="display: none;"></i><input type="checkbox" style="display: none" class="open-btn" />
|
||||
</label>
|
||||
<label class="btn btn-inverse waves-effect waves-light m-b-5 parentLevel2"> <input type="checkbox" disabled="disabled" value="9903" class="check-btn" data-pm=""> <span style="bottom: 2px;position: relative"> کارپوشه </span> </label>
|
||||
<!-----------------------Sub Menu------------------->
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</fieldset>
|
||||
|
||||
Reference in New Issue
Block a user