Compare commits

...

7 Commits

15 changed files with 793 additions and 102 deletions

3
.gitignore vendored
View File

@@ -368,3 +368,6 @@ MigrationBackup/
# Storage folder - ignore all uploaded files, thumbnails, and temporary files
ServiceHost/Storage
.env
.env.*

View File

@@ -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>
/// این تاریخ در جدول اکانت لفت ورک به این معنیست

View File

@@ -66,4 +66,9 @@ public class File1 : EntityBase
Description = description;
Status = status;
}
public void ChangeStatus(int status)
{
Status = status;
}
}

View File

@@ -531,6 +531,11 @@ namespace Company.Domain.RollCallAgg
FridayWorkTimeSpan = CalculateFridayWorkDuration(StartDate.Value, EndDate.Value);
}
public void SetStartDate(DateTime start)
{
StartDate = start;
}
/// <summary>
/// جیزه
/// </summary>

View File

@@ -7,6 +7,7 @@ public class InstitutionContractExtensionCompleteRequest
public Guid TemporaryId { get; set; }
public bool IsInstallment { get; set; }
public long LawId { get; set; }
public bool CancelSendVerificationSms { get; set; }
}
public class InstitutionContractCreationCompleteRequest

View File

@@ -2269,7 +2269,8 @@ public class InstitutionContractRepository : RepositoryBase<long, InstitutionCon
extenstionTemp
);
var workshopIds = prevInstitutionContracts.WorkshopGroup?.CurrentWorkshops?.Select(x => x.WorkshopId.Value)??[];
var workshopIds = prevInstitutionContracts.WorkshopGroup?.CurrentWorkshops?.Select(x => x.WorkshopId.Value) ??
[];
var workshopsNotInInstitution = employerWorkshopIds.Where(x => !workshopIds.Contains(x)).ToList();
@@ -2317,7 +2318,7 @@ public class InstitutionContractRepository : RepositoryBase<long, InstitutionCon
WorkshopId = workshop?.id ?? 0,
RollCallInPerson = service.RollCallInPerson
};
}).ToList()??[];
}).ToList() ?? [];
var notIncludeWorskhopsLeftWork = await _context.LeftWorkList
.Where(x => workshopsNotInInstitution.Contains(x.WorkshopId) && x.StartWorkDate <= DateTime.Now &&
x.LeftWorkDate >= DateTime.Now)
@@ -2959,8 +2960,11 @@ public class InstitutionContractRepository : RepositoryBase<long, InstitutionCon
await SaveChangesAsync();
await _smsService.SendInstitutionCreationVerificationLink(contractingParty.Phone, contractingPartyFullName,
entity.PublicId, contractingParty.id, entity.id);
if (!request.CancelSendVerificationSms)
{
await _smsService.SendInstitutionCreationVerificationLink(contractingParty.Phone, contractingPartyFullName,
entity.PublicId, contractingParty.id, entity.id);
}
await SaveChangesAsync();
@@ -3363,10 +3367,10 @@ public class InstitutionContractRepository : RepositoryBase<long, InstitutionCon
institution.VerificationStatus == InstitutionContractVerificationStatus.PendingForVerify
? null
: institution.VerifierFullName,
VerifierPhoneNumber =
VerifierPhoneNumber =
institution.VerificationStatus == InstitutionContractVerificationStatus.PendingForVerify
? null
: institution.VerifierPhoneNumber,
? null
: institution.VerifierPhoneNumber,
VerifyCode = institution.VerificationStatus == InstitutionContractVerificationStatus.PendingForVerify
? null
: institution.VerifyCode,
@@ -4366,8 +4370,8 @@ public class InstitutionContractRepository : RepositoryBase<long, InstitutionCon
bool tempCreated = false;
if (creationTemp == null)
{
creationTemp = new InstitutionContractCreationTemp();
await _institutionContractCreationTemp.InsertOneAsync(creationTemp);
creationTemp = new InstitutionContractCreationTemp();
await _institutionContractCreationTemp.InsertOneAsync(creationTemp);
}
List<WorkshopTempViewModel> workshopDetails = [];
@@ -5109,9 +5113,11 @@ public class InstitutionContractRepository : RepositoryBase<long, InstitutionCon
await SaveChangesAsync();
await _smsService.SendInstitutionCreationVerificationLink(contractingParty.Phone, contractingPartyFullName,
entity.PublicId, contractingParty.id, entity.id);
if (!request.CancelSendVerificationSms)
{
await _smsService.SendInstitutionCreationVerificationLink(contractingParty.Phone, contractingPartyFullName,
entity.PublicId, contractingParty.id, entity.id);
}
await SaveChangesAsync();
await transaction.CommitAsync();

View File

@@ -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;
}
}
}

View File

@@ -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
};
}
}

View File

@@ -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>();

View File

@@ -92,6 +92,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"));

View File

@@ -0,0 +1,231 @@
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 = category.ToString();
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;
}
}

View File

@@ -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=""> &nbsp;<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=""> &nbsp;<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=""> &nbsp;<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=""> &nbsp;<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=""> &nbsp;<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=""> &nbsp;<span style="bottom: 2px;position: relative"> کارپوشه </span> </label>
<!-----------------------Sub Menu------------------->
</div>
</div>

View File

@@ -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=""> &nbsp;<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=""> &nbsp;<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=""> &nbsp;<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=""> &nbsp;<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=""> &nbsp;<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=""> &nbsp;<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=""> &nbsp;<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=""> &nbsp;<span style="bottom: 2px;position: relative"> کارپوشه </span> </label>
<!-----------------------Sub Menu------------------->
</div>
</div>
</fieldset>

View File

@@ -0,0 +1,157 @@
@page
@model ServiceHost.Areas.AdminNew.Pages.Company.ExcelUpload.IndexModel
@{
ViewData["Title"] = "بارگذاری فایل اکسل";
}
<div class="container-fluid">
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-header">
<h3 class="card-title">بارگذاری و بررسی فایل اکسل</h3>
</div>
<div class="card-body">
<form method="post" enctype="multipart/form-data">
<div class="form-group">
<label for="ExcelFile">انتخاب فایل اکسل (ستون B و C از ردیف 2 به بعد خوانده می‌شود):</label>
<input type="file" class="form-control" id="ExcelFile" name="ExcelFile" accept=".xlsx,.xls" required />
</div>
<button type="submit" asp-page-handler="UploadExcel" class="btn btn-primary">بارگذاری و پردازش</button>
</form>
@if (!string.IsNullOrEmpty(Model.Message))
{
<div class="alert alert-info mt-3" role="alert">
@Model.Message
</div>
}
</div>
</div>
</div>
</div>
@if (Model.HasProcessed)
{
<div class="row mt-4">
<!-- دسته اول: فایل‌هایی که در اکسل هستند -->
<div class="col-md-6">
<div class="card">
<div class="card-header bg-success text-white">
<h4 class="card-title mb-0">فایل‌های موجود در اکسل (@Model.FoundInExcel.Count مورد)</h4>
</div>
<div class="card-body" style="max-height: 600px; overflow-y: auto;">
@if (Model.FoundInExcel.Any())
{
<table class="table table-striped table-hover">
<thead>
<tr>
<th>ردیف</th>
<th>شناسه</th>
<th>شماره بایگانی</th>
<th>رده پرونده</th>
<th>وضعیت</th>
<th>عملیات</th>
</tr>
</thead>
<tbody>
@{
int index = 1;
}
@foreach (var item in Model.FoundInExcel)
{
<tr>
<td>@index</td>
<td>@item.ArchiveNo</td>
<td>@item.FileClass</td>
<td>@(item.Status == 2 ? "فعال" : "غیرفعال")</td>
<td>
<button class="btn btn-sm btn-primary">ویرایش</button>
</td>
</tr>
index++;
}
</tbody>
</table>
}
else
{
<p class="text-muted">هیچ موردی یافت نشد.</p>
}
</div>
</div>
</div>
<!-- دسته دوم: فایل‌هایی که در اکسل نیستند -->
<div class="col-md-6">
<div class="card">
<div class="card-header bg-warning text-dark">
<h4 class="card-title mb-0">فایل‌های غیرموجود در اکسل (@Model.NotFoundInExcel.Count مورد)</h4>
</div>
<div class="card-body" style="max-height: 600px; overflow-y: auto;">
@if (Model.NotFoundInExcel.Any())
{
<table class="table table-striped table-hover">
<thead>
<tr>
<th>ردیف</th>
<th>شناسه</th>
<th>شماره بایگانی</th>
<th>رده پرونده</th>
<th>وضعیت</th>
<th>عملیات</th>
</tr>
</thead>
<tbody>
@{
int index2 = 1;
}
@foreach (var item in Model.NotFoundInExcel)
{
<tr>
<td>@index2</td>
<td>@item.ArchiveNo</td>
<td>@item.FileClass</td>
<td>@(item.Status == 2 ? "فعال" : "غیرفعال")</td>
<td>
<button class="btn btn-sm btn-primary">ویرایش</button>
</td>
</tr>
index2++;
}
</tbody>
</table>
}
else
{
<p class="text-muted">هیچ موردی یافت نشد.</p>
}
</div>
</div>
</div>
</div>
}
</div>
<style>
.card {
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
border-radius: 8px;
}
.card-header {
border-radius: 8px 8px 0 0;
}
.table {
margin-bottom: 0;
}
.table thead th {
position: sticky;
top: 0;
background-color: #f8f9fa;
z-index: 10;
}
</style>

View File

@@ -0,0 +1,156 @@
using Company.Domain.File1;
using Company.Domain.RollCallAgg;
using Company.Domain.RollCallAgg.DomainService;
using CompanyManagment.EFCore;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using OfficeOpenXml;
namespace ServiceHost.Areas.AdminNew.Pages.Company.ExcelUpload;
public class ExcelRowData
{
public string ColumnB { get; set; }
public string ColumnC { get; set; }
}
public class IndexModel : PageModel
{
private readonly CompanyContext _dbContext;
private readonly IRollCallDomainService _rollCallDomainService;
[BindProperty]
public IFormFile ExcelFile { get; set; }
public string Message { get; set; }
public List<File1> FoundInExcel { get; set; } = new List<File1>();
public List<File1> NotFoundInExcel { get; set; } = new List<File1>();
public List<ExcelRowData> ExcelData { get; set; } = new List<ExcelRowData>();
public bool HasProcessed { get; set; }
public IndexModel(CompanyContext dbContext, IRollCallDomainService rollCallDomainService)
{
_dbContext = dbContext;
_rollCallDomainService = rollCallDomainService;
}
public void OnGet()
{
}
public async Task<IActionResult> OnPostUploadExcel()
{
if (ExcelFile == null || ExcelFile.Length == 0)
{
Message = "لطفا یک فایل اکسل انتخاب کنید.";
return Page();
}
//await NewMethod();
// var rollcall = _dbContext.RollCalls.Where(x => x.id > 361628 && x.id < 362119).ToList();
//
// var diffTime = TimeSpan.FromHours(8) + TimeSpan.FromMinutes(30);
// var NoneRollCalls = rollcall
// .Where(x => x.RollCallModifyType == RollCallModifyType.None && x.EndDate == null).ToList();
// foreach (var noneRollCall in NoneRollCalls)
// {
// noneRollCall.SetStartDate(noneRollCall.StartDate!.Value.Add(diffTime));
// }
// var rollcallIds = NoneRollCalls.Select(x => x.id).ToList();
// var editedRollCall = rollcall.Where(x=>!rollcallIds.Contains(x.id) && x.RollCallModifyType != RollCallModifyType.EditByEmployer).ToList();
//
// foreach (var rollCall in editedRollCall)
// {
// rollCall.Edit(rollCall.StartDate.Value.Add(diffTime),
// rollCall.EndDate.Value.Add(diffTime),_rollCallDomainService);
// }
//
// await _dbContext.SaveChangesAsync();
return Page();
}
private async System.Threading.Tasks.Task NewMethod()
{
try
{
ExcelPackage.License.SetNonCommercialOrganization("Gozareshgir Noncommercial organization");
using (var stream = new MemoryStream())
{
await ExcelFile.CopyToAsync(stream);
stream.Position = 0;
using (var package = new ExcelPackage(stream))
{
var worksheet = package.Workbook.Worksheets[0];
int rowCount = worksheet.Dimension?.Rows ?? 0;
// خواندن مقادیر از ستون B و C از ردیف 2 به بعد
for (int row = 2; row <= rowCount; row++)
{
var columnB = worksheet.Cells[row, 2].Value?.ToString()?.Trim();
var columnC = worksheet.Cells[row, 3].Value?.ToString()?.Trim();
if (!string.IsNullOrWhiteSpace(columnB) || !string.IsNullOrWhiteSpace(columnC))
{
ExcelData.Add(new ExcelRowData
{
ColumnB = columnB ?? "",
ColumnC = columnC ?? ""
});
}
}
}
}
// ساخت HashSet برای سرچ سریع - هر جفت (B, C) رو با هم نگه داری
var excelPairs = new HashSet<(string B, string C)>(
ExcelData.Select(x => (
B: (x.ColumnB ?? "").Trim(),
C: (x.ColumnC ?? "").Trim()
)));
// دریافت تمام داده‌های موجود در دیتابیس
var allFiles = _dbContext.Files.ToList();
// دسته‌بندی: فایل‌هایی که در اکسل هستند و نیستند
foreach (var file in allFiles)
{
var fileB = file.ArchiveNo.ToString();
var fileC = (file.FileClass ?? "").Trim();
// بررسی آیا جفت (B, C) این فایل در اکسل وجود دارد
bool foundInExcel = excelPairs.Contains((fileB, fileC));
if (foundInExcel)
{
FoundInExcel.Add(file);
}
else
{
NotFoundInExcel.Add(file);
}
}
foreach (var file1 in FoundInExcel)
{
file1.ChangeStatus(2);
}
foreach (var file1 in NotFoundInExcel)
{
file1.ChangeStatus(1);
}
await _dbContext.SaveChangesAsync();
HasProcessed = true;
Message = $"پردازش با موفقیت انجام شد. تعداد ردیف‌های اکسل: {ExcelData.Count} | فایل‌های موجود در اکسل: {FoundInExcel.Count} | فایل‌های غیرموجود در اکسل: {NotFoundInExcel.Count}";
}
catch (Exception ex)
{
Message = $"خطا در پردازش فایل: {ex.Message}";
}
}
}