Merge branch 'master' of https://pm.gozareshgir.ir/gozareshgir/OriginalGozareshgir into Feature/SmsRepoetApi

This commit is contained in:
gozareshgir
2026-01-11 12:58:36 +03:30
14 changed files with 388 additions and 61 deletions

View File

@@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Company.Domain.InstitutionContractSendFlagAgg;
/// <summary>
/// Interface برای Repository مربوط به فلگ ارسال قرارداد
/// </summary>
public interface IInstitutionContractSendFlagRepository
{
/// <summary>
/// ایجاد یک رکورد جدید برای فلگ ارسال قرارداد
/// </summary>
Task Create(InstitutionContractSendFlag flag);
/// <summary>
/// بازیابی فلگ بر اساس شناسه قرارداد
/// </summary>
Task<InstitutionContractSendFlag> GetByContractId(long contractId);
/// <summary>
/// به‌روزرسانی فلگ ارسال
/// </summary>
Task Update(InstitutionContractSendFlag flag);
/// <summary>
/// بررسی اینکه آیا قرارداد ارسال شده است
/// </summary>
Task<bool> IsContractSent(long contractId);
}

View File

@@ -0,0 +1,82 @@
using System;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
namespace Company.Domain.InstitutionContractSendFlagAgg;
/// <summary>
/// نمایندگی فلگ ارسال قرارداد در MongoDB
/// این موجودیت برای ردیابی اینکه آیا قرارداد ارسال شده است استفاده می‌شود
/// </summary>
public class InstitutionContractSendFlag
{
public InstitutionContractSendFlag(long institutionContractId,bool isSent)
{
Id = Guid.NewGuid();
InstitutionContractId = institutionContractId;
IsSent = isSent;
CreatedDate = DateTime.Now;
}
/// <summary>
/// شناسه یکتای MongoDB
/// </summary>
[BsonId]
[BsonRepresentation(BsonType.String)]
public Guid Id { get; set; }
/// <summary>
/// شناسه قرارداد در SQL
/// </summary>
public long InstitutionContractId { get; set; }
/// <summary>
/// آیا قرارداد ارسال شده است
/// </summary>
public bool IsSent { get; set; }
/// <summary>
/// تاریخ و زمان ارسال
/// </summary>
public DateTime? SentDate { get; set; }
/// <summary>
/// تاریخ و زمان ایجاد رکورد
/// </summary>
public DateTime CreatedDate { get; set; }
/// <summary>
/// تاریخ و زمان آخرین به‌روزرسانی
/// </summary>
public DateTime? LastModifiedDate { get; set; }
/// <summary>
/// علامت‌گذاری قرارداد به عنوان ارسال‌شده
/// </summary>
public void MarkAsSent()
{
IsSent = true;
SentDate = DateTime.Now;
LastModifiedDate = DateTime.Now;
}
/// <summary>
/// بازگردانی علامت ارسال
/// </summary>
public void MarkAsNotSent()
{
IsSent = false;
SentDate = null;
LastModifiedDate = DateTime.Now;
}
/// <summary>
/// به‌روزرسانی علامت آخری اصلاح
/// </summary>
public void UpdateLastModified()
{
LastModifiedDate = DateTime.Now;
}
}

View File

@@ -0,0 +1,58 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Company.Domain.InstitutionContractSendFlagAgg;
using MongoDB.Driver;
namespace CompanyManagement.Infrastructure.Mongo.InstitutionContractSendFlagRepo;
/// <summary>
/// Repository برای مدیریت فلگ ارسال قرارداد در MongoDB
/// </summary>
public class InstitutionContractSendFlagRepository : IInstitutionContractSendFlagRepository
{
private readonly IMongoCollection<InstitutionContractSendFlag> _collection;
public InstitutionContractSendFlagRepository(IMongoDatabase database)
{
_collection = database.GetCollection<InstitutionContractSendFlag>("InstitutionContractSendFlag");
}
public async Task Create(InstitutionContractSendFlag flag)
{
await _collection.InsertOneAsync(flag);
}
public async Task<InstitutionContractSendFlag> GetByContractId(long contractId)
{
var filter = Builders<InstitutionContractSendFlag>.Filter
.Eq(x => x.InstitutionContractId, contractId);
return await _collection.Find(filter).FirstOrDefaultAsync();
}
public async Task Update(InstitutionContractSendFlag flag)
{
var filter = Builders<InstitutionContractSendFlag>.Filter
.Eq(x => x.InstitutionContractId, flag.InstitutionContractId);
await _collection.ReplaceOneAsync(filter, flag);
}
public async Task<bool> IsContractSent(long contractId)
{
var flag = await GetByContractId(contractId);
return flag != null && flag.IsSent;
}
public async Task Remove(long contractId)
{
var filter = Builders<InstitutionContractSendFlag>.Filter
.Eq(x => x.InstitutionContractId, contractId);
await _collection.DeleteOneAsync(filter);
}
}

View File

@@ -96,6 +96,8 @@ public class GetInstitutionContractListItemsViewModel
/// مبلغ قسط
/// </summary>
public double InstallmentAmount { get; set; }
public bool InstitutionContractIsSentFlag { get; set; }
}
public class InstitutionContractListWorkshop

View File

@@ -148,7 +148,7 @@ public interface IInstitutionContractApplication
/// <param name="id">شناسه قرارداد</param>
/// <returns>نتیجه عملیات</returns>
OperationResult UnSign(long id);
/// <summary>
/// ایجاد حساب کاربری برای طرف قرارداد
/// </summary>
@@ -305,6 +305,14 @@ public interface IInstitutionContractApplication
Task<InstitutionContractDiscountResponse> SetDiscountForCreation(InstitutionContractSetDiscountForCreationRequest request);
Task<InstitutionContractDiscountResponse> ResetDiscountForCreation(InstitutionContractResetDiscountForExtensionRequest request);
Task<OperationResult> CreationComplete(InstitutionContractExtensionCompleteRequest request);
/// <summary>
/// تعیین فلگ ارسال قرارداد در MongoDB
/// اگر فلگ وجود نداشتن‌د ایجاد می‌کند
/// </summary>
/// <param name="request">درخواست تعیین فلگ</param>
/// <returns>نتیجه عملیات</returns>
Task<OperationResult> SetContractSendFlag(SetInstitutionContractSendFlagRequest request);
}
public class CreationSetContractingPartyResponse

View File

@@ -0,0 +1,19 @@
namespace CompanyManagment.App.Contracts.InstitutionContract;
/// <summary>
/// درخواست برای تعیین فلگ ارسال قرارداد
/// </summary>
public class SetInstitutionContractSendFlagRequest
{
/// <summary>
/// شناسه قرارداد
/// </summary>
public long InstitutionContractId { get; set; }
/// <summary>
/// آیا قرارداد ارسال شده است
/// </summary>
public bool IsSent { get; set; }
}

View File

@@ -19,6 +19,7 @@ using Company.Domain.PaymentTransactionAgg;
using Company.Domain.RepresentativeAgg;
using Company.Domain.RollCallServiceAgg;
using Company.Domain.WorkshopAgg;
using Company.Domain.InstitutionContractSendFlagAgg;
using CompanyManagment.App.Contracts.FinancialInvoice;
using CompanyManagment.App.Contracts.FinancialStatment;
using CompanyManagment.App.Contracts.InstitutionContract;
@@ -51,6 +52,7 @@ public class InstitutionContractApplication : IInstitutionContractApplication
private readonly IPaymentTransactionRepository _paymentTransactionRepository;
private readonly IRollCallServiceRepository _rollCallServiceRepository;
private readonly ISepehrPaymentGatewayService _sepehrPaymentGatewayService;
private readonly IInstitutionContractSendFlagRepository _institutionContractSendFlagRepository;
public InstitutionContractApplication(IInstitutionContractRepository institutionContractRepository,
@@ -62,7 +64,8 @@ public class InstitutionContractApplication : IInstitutionContractApplication
IAccountApplication accountApplication, ISmsService smsService,
IFinancialInvoiceRepository financialInvoiceRepository, IHttpClientFactory httpClientFactory,
IPaymentTransactionRepository paymentTransactionRepository, IRollCallServiceRepository rollCallServiceRepository,
ISepehrPaymentGatewayService sepehrPaymentGatewayService,ILogger<SepehrPaymentGateway> sepehrGatewayLogger)
ISepehrPaymentGatewayService sepehrPaymentGatewayService,ILogger<SepehrPaymentGateway> sepehrGatewayLogger,
IInstitutionContractSendFlagRepository institutionContractSendFlagRepository)
{
_institutionContractRepository = institutionContractRepository;
_contractingPartyRepository = contractingPartyRepository;
@@ -80,6 +83,7 @@ public class InstitutionContractApplication : IInstitutionContractApplication
_rollCallServiceRepository = rollCallServiceRepository;
_sepehrPaymentGatewayService = sepehrPaymentGatewayService;
_paymentGateway = new SepehrPaymentGateway(httpClientFactory,sepehrGatewayLogger);
_institutionContractSendFlagRepository = institutionContractSendFlagRepository;
}
public OperationResult Create(CreateInstitutionContract command)
@@ -894,6 +898,7 @@ public class InstitutionContractApplication : IInstitutionContractApplication
return opration.Succcedded();
}
public void CreateContractingPartyAccount(long contractingPartyid, long accountId)
{
_institutionContractRepository.CreateContractingPartyAccount(contractingPartyid, accountId);
@@ -1820,7 +1825,60 @@ public class InstitutionContractApplication : IInstitutionContractApplication
installments.Add(lastInstallment);
return installments;
}
}
/// <summary>
/// تعیین فلگ ارسال قرارداد
/// اگر فلگ وجود نداشتن‌د ایجاد می‌کند
/// </summary>
public async Task<OperationResult> SetContractSendFlag(SetInstitutionContractSendFlagRequest request)
{
var operationResult = new OperationResult();
try
{
// بازیابی قرارداد از SQL
var contract = _institutionContractRepository.Get(request.InstitutionContractId);
if (contract == null)
return operationResult.Failed("قرارداد مورد نظر یافت نشد");
// بررسی اینکه آیا فلگ در MongoDB وجود دارد
var existingFlag = await _institutionContractSendFlagRepository
.GetByContractId(request.InstitutionContractId);
if (existingFlag != null)
{
// اگر فلگ وجود داشتن‌د، آن را اپدیت کنیم
if (request.IsSent)
{
existingFlag.MarkAsSent();
}
else
{
existingFlag.MarkAsNotSent();
}
existingFlag.UpdateLastModified();
await _institutionContractSendFlagRepository.Update(existingFlag);
}
else
{
// اگر فلگ وجود ندارد، آن را ایجاد کنیم
var newFlag = new InstitutionContractSendFlag(
request.InstitutionContractId,
request.IsSent
);
await _institutionContractSendFlagRepository.Create(newFlag);
}
return operationResult.Succcedded();
}
catch (Exception ex)
{
return operationResult.Failed($"خطا در تعیین فلگ ارسال: {ex.Message}");
}
}
}
#region CustomViewModels

View File

@@ -11,6 +11,7 @@ using Company.Domain.InstitutionContractAgg;
using Company.Domain.InstitutionContractAmendmentTempAgg;
using Company.Domain.InstitutionContractContactInfoAgg;
using Company.Domain.InstitutionContractExtensionTempAgg;
using Company.Domain.InstitutionContractSendFlagAgg;
using Company.Domain.InstitutionPlanAgg;
using Company.Domain.SmsResultAgg;
using Company.Domain.WorkshopAgg;
@@ -42,6 +43,7 @@ using AccountManagement.Application.Contracts.Account;
using Company.Domain.InstitutionContractCreationTempAgg;
using Company.Domain.RepresentativeAgg;
using Company.Domain.TemporaryClientRegistrationAgg;
using Company.Domain.InstitutionContractSendFlagAgg;
using ContractingPartyAccount = Company.Domain.ContractingPartyAccountAgg.ContractingPartyAccount;
using FinancialStatment = Company.Domain.FinancialStatmentAgg.FinancialStatment;
using String = System.String;
@@ -57,6 +59,7 @@ public class InstitutionContractRepository : RepositoryBase<long, InstitutionCon
private readonly IMongoCollection<InstitutionContractExtensionTemp> _institutionExtensionTemp;
private readonly IMongoCollection<InstitutionContractAmendmentTemp> _institutionAmendmentTemp;
private readonly IMongoCollection<InstitutionContractCreationTemp> _institutionContractCreationTemp;
private readonly IMongoCollection<InstitutionContractSendFlag> _institutionContractSendFlag;
private readonly IPlanPercentageRepository _planPercentageRepository;
private readonly ISmsService _smsService;
private readonly ISmsResultRepository _smsResultRepository;
@@ -114,6 +117,8 @@ public class InstitutionContractRepository : RepositoryBase<long, InstitutionCon
database.GetCollection<InstitutionContractAmendmentTemp>("InstitutionContractAmendmentTemp");
_institutionContractCreationTemp =
database.GetCollection<InstitutionContractCreationTemp>("InstitutionContractCreationTemp");
_institutionContractSendFlag =
database.GetCollection<InstitutionContractSendFlag>("InstitutionContractSendFlag");
}
public EditInstitutionContract GetDetails(long id)
@@ -1353,6 +1358,12 @@ public class InstitutionContractRepository : RepositoryBase<long, InstitutionCon
.Where(x => contractIds.Contains(x.InstitutionContractId))
.ToDictionaryAsync(x => x.InstitutionContractId, x => x);
// بارگذاری وضعیت ارسال قراردادها از MongoDB - کوئری مستقیم
var filter = Builders<InstitutionContractSendFlag>.Filter
.In(x => x.InstitutionContractId, contractIds);
var sendFlagsList = await _institutionContractSendFlag.Find(filter).ToListAsync();
var sendFlags = sendFlagsList.ToDictionary(x => x.InstitutionContractId, x => x.IsSent);
var financialStatements = _context.FinancialStatments.Include(x => x.FinancialTransactionList)
.Where(x => contractingPartyIds.Contains(x.ContractingPartyId)).ToList();
var res = new PagedResult<GetInstitutionContractListItemsViewModel>()
@@ -1462,7 +1473,8 @@ public class InstitutionContractRepository : RepositoryBase<long, InstitutionCon
Workshops = workshopDetails,
IsInPersonContract = workshopGroup?.CurrentWorkshops
.Any(y => y.Services.ContractInPerson) ?? true,
IsOldContract = x.contract.SigningType == InstitutionContractSigningType.Legacy
IsOldContract = x.contract.SigningType == InstitutionContractSigningType.Legacy,
InstitutionContractIsSentFlag = sendFlags.ContainsKey(x.contract.id) ? sendFlags[x.contract.id] : false
};
}).ToList()
};

View File

@@ -61,6 +61,7 @@ using Company.Domain.HolidayItemAgg;
using Company.Domain.InstitutionContractAgg;
using Company.Domain.InstitutionContractContactInfoAgg;
using Company.Domain.InstitutionContractExtensionTempAgg;
using Company.Domain.InstitutionContractSendFlagAgg;
using Company.Domain.InstitutionPlanAgg;
using Company.Domain.InsuranceAgg;
using Company.Domain.InsuranceEmployeeInfoAgg;
@@ -123,6 +124,7 @@ using Company.Domain.ZoneAgg;
using CompanyManagement.Infrastructure.Excel.SalaryAid;
using CompanyManagement.Infrastructure.Mongo.EmployeeFaceEmbeddingRepo;
using CompanyManagement.Infrastructure.Mongo.InstitutionContractInsertTempRepo;
using CompanyManagement.Infrastructure.Mongo.InstitutionContractSendFlagRepo;
using CompanyManagment.App.Contracts.AdminMonthlyOverview;
using CompanyManagment.App.Contracts.AndroidApkVersion;
using CompanyManagment.App.Contracts.AuthorizedPerson;
@@ -659,6 +661,9 @@ public class PersonalBootstrapper
services.AddTransient<ICameraBugReportApplication, CameraBugReportApplication>();
services.AddTransient<ICameraBugReportRepository, CameraBugReportRepository>(); // MongoDB Implementation
// InstitutionContractSendFlag - MongoDB
services.AddTransient<IInstitutionContractSendFlagRepository, InstitutionContractSendFlagRepository>();
services.AddDbContext<CompanyContext>(x => x.UseSqlServer(connectionString));
}
}

View File

@@ -6,5 +6,6 @@ namespace GozareshgirProgramManager.Application.Modules.Projects.Queries.Project
public record ProjectBoardListQuery: IBaseQuery<List<ProjectBoardListResponse>>
{
public long? UserId { get; set; }
public TaskSectionStatus? Status { get; set; }
}

View File

@@ -40,6 +40,11 @@ public class ProjectBoardListQueryHandler : IBaseQueryHandler<ProjectBoardListQu
{
queryable = queryable.Where(x => x.Status == request.Status);
}
if (request.UserId is > 0)
{
queryable = queryable.Where(x => x.CurrentAssignedUserId == request.UserId);
}
var data = await queryable.ToListAsync(cancellationToken);

View File

@@ -21,7 +21,7 @@ public record ProjectDeployBoardDetailTaskItem(
TimeSpan DoneTimeSpan,
int Percentage,
List<ProjectDeployBoardDetailItemSkill> Skills)
: ProjectDeployBoardDetailPhaseItem(Name, TotalTimeSpan, DoneTimeSpan,Percentage);
: ProjectDeployBoardDetailPhaseItem(Name, TotalTimeSpan, DoneTimeSpan, Percentage);
public record ProjectDeployBoardDetailItemSkill(string OriginalUserFullName, string SkillName, int TimePercentage);
@@ -73,6 +73,7 @@ public class
var doneTime = t.Sections.Aggregate(TimeSpan.Zero,
(sum, next) => sum.Add(next.GetTotalTimeSpent()));
var skills = t.Sections
.Select(s =>
{
@@ -82,13 +83,23 @@ public class
var skillName = s.Skill?.Name ?? "بدون مهارت";
var timePercentage = (int)s.GetProgressPercentage();
return new ProjectDeployBoardDetailItemSkill(
originalUserFullName,
skillName,
timePercentage);
}).ToList();
var taskPercentage = (int)Math.Round(skills.Average(x => x.TimePercentage));
int taskPercentage;
if (skills.Count == 0)
{
taskPercentage = 0;
}
else
{
taskPercentage = (int)Math.Round(skills.Average(x => x.TimePercentage));
}
return new ProjectDeployBoardDetailTaskItem(
t.Name,
@@ -105,7 +116,7 @@ public class
(sum, next) => sum.Add(next.DoneTimeSpan));
var phasePercentage = tasksRes.Average(x => x.Percentage);
var phaseRes = new ProjectDeployBoardDetailPhaseItem(phase.Name, totalTimeSpan, doneTimeSpan,
(int)phasePercentage);

View File

@@ -1,7 +1,6 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
using GozareshgirProgramManager.Application._Common.Models;
using GozareshgirProgramManager.Application.Modules.TaskChat.DTOs;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace GozareshgirProgramManager.Application.Modules.TaskChat.Queries.GetMessages;
@@ -25,6 +24,39 @@ public class GetMessagesQueryHandler : IBaseQueryHandler<GetMessagesQuery, Pagin
_authHelper = authHelper;
}
private List<MessageDto> CreateAdditionalTimeNotes(
IEnumerable<Domain.ProjectAgg.Entities.TaskSectionAdditionalTime> additionalTimes,
Dictionary<long, string> users,
Guid taskId)
{
var notes = new List<MessageDto>();
foreach (var additionalTime in additionalTimes)
{
var addedByUserName = additionalTime.AddedByUserId.HasValue && users.TryGetValue(additionalTime.AddedByUserId.Value, out var user)
? user
: "سیستم";
var noteContent = $"⏱️ زمان اضافی: {additionalTime.Hours.TotalHours.ToString("F2")} ساعت - {(string.IsNullOrWhiteSpace(additionalTime.Reason) ? "بدون علت" : additionalTime.Reason)} - توسط {addedByUserName}";
var noteDto = new MessageDto
{
Id = Guid.NewGuid(),
TaskId = taskId,
SenderUserId = 0,
SenderName = "سیستم",
MessageType = "Note",
TextContent = noteContent,
CreationDate = additionalTime.CreationDate,
IsMine = false
};
notes.Add(noteDto);
}
return notes;
}
public async Task<OperationResult<PaginationResult<MessageDto>>> Handle(GetMessagesQuery request, CancellationToken cancellationToken)
{
var currentUserId = _authHelper.GetCurrentUserId();
@@ -44,36 +76,52 @@ public class GetMessagesQueryHandler : IBaseQueryHandler<GetMessagesQuery, Pagin
.ToListAsync(cancellationToken);
// ✅ گرفتن تمامی کاربران برای نمایش نام کامل فرستنده به جای "کاربر"
// این بخش تمام UserId هایی که در پیام‌ها استفاده شده را جمع‌آوری می‌کند
// و یک Dictionary ایجاد می‌کند که UserId را به FullName نگاشت می‌کند
var senderUserIds = messages.Select(m => m.SenderUserId).Distinct().ToList();
var users = await _context.Users
.Where(u => senderUserIds.Contains(u.Id))
.ToDictionaryAsync(u => u.Id, u => u.FullName, cancellationToken);
// ✅ گرفتن تمامی زمان‌های اضافی (Additional Times) برای نمایش به صورت نوت
// در اینجا تمامی TaskSections مربوط به این تسک را می‌گیریم
// و برای هر کدام تمام AdditionalTimes آن را بارگذاری می‌کنیم
var taskSections = await _context.TaskSections
.Where(ts => ts.TaskId == request.TaskId)
.Include(ts => ts.AdditionalTimes)
.ToListAsync(cancellationToken);
// ✅ تمام زمان‌های اضافی را یکجا بگیر و مرتب کن
var allAdditionalTimes = taskSections
.SelectMany(ts => ts.AdditionalTimes)
.OrderBy(at => at.CreationDate)
.ToList();
var messageDtos = new List<MessageDto>();
// ✅ ابتدا زمان‌های اضافی قبل از اولین پیام را اضافه کن (اگر پیامی وجود داشته باشد)
if (messages.Any())
{
var firstMessageDate = messages.First().CreationDate;
var additionalTimesBeforeFirstMessage = allAdditionalTimes
.Where(at => at.CreationDate < firstMessageDate)
.ToList();
messageDtos.AddRange(CreateAdditionalTimeNotes(additionalTimesBeforeFirstMessage, users, request.TaskId));
}
else
{
// ✅ اگر هیچ پیامی وجود ندارد، همه زمان‌های اضافی را نمایش بده
messageDtos.AddRange(CreateAdditionalTimeNotes(allAdditionalTimes, users, request.TaskId));
}
foreach (var message in messages)
{
// ✅ نام فرستنده را از Dictionary Users بگیر، در صورت عدم وجود "کاربر ناشناس" نمایش بده
var senderName = users.ContainsKey(message.SenderUserId)
? users[message.SenderUserId]
: "کاربر ناشناس";
var senderName = users.GetValueOrDefault(message.SenderUserId, "کاربر ناشناس");
var dto = new MessageDto
{
Id = message.Id,
TaskId = message.TaskId,
SenderUserId = message.SenderUserId,
SenderName = senderName, // ✅ از User واقعی استفاده می‌کنیم
SenderName = senderName,
MessageType = message.MessageType.ToString(),
TextContent = message.TextContent,
ReplyToMessageId = message.ReplyToMessageId,
@@ -88,10 +136,7 @@ public class GetMessagesQueryHandler : IBaseQueryHandler<GetMessagesQuery, Pagin
if (message.ReplyToMessage != null)
{
// ✅ برای پیام‌های Reply نیز نام فرستنده را درست نمایش بده
var replySenderName = users.ContainsKey(message.ReplyToMessage.SenderUserId)
? users[message.ReplyToMessage.SenderUserId]
: "کاربر ناشناس";
var replySenderName = users.GetValueOrDefault(message.ReplyToMessage.SenderUserId, "کاربر ناشناس");
dto.ReplyToMessage = new MessageDto
{
@@ -125,55 +170,31 @@ public class GetMessagesQueryHandler : IBaseQueryHandler<GetMessagesQuery, Pagin
messageDtos.Add(dto);
// ✅ اینجا بخش جدید است: نوت‌های زمان اضافی را بین پیام‌ها اضافه کن
// این بخش تمام AdditionalTimes را که بعد از این پیام اضافه شده‌اند را پیدا می‌کند
var additionalTimesAfterMessage = taskSections
.SelectMany(ts => ts.AdditionalTimes)
.Where(at => at.AddedAt > message.CreationDate) // ✅ تغییر به AddedAt (زمان واقعی اضافه شدن)
.OrderBy(at => at.AddedAt)
.FirstOrDefault();
// ✅ پیدا کردن پیام بعدی (اگر وجود داشته باشد)
var currentIndex = messages.IndexOf(message);
var nextMessage = currentIndex < messages.Count - 1 ? messages[currentIndex + 1] : null;
if (additionalTimesAfterMessage != null)
if (nextMessage != null)
{
// ✅ تمام AdditionalTimes بین این پیام و پیام قبلی را بگیر
var additionalTimesByDate = taskSections
.SelectMany(ts => ts.AdditionalTimes)
.Where(at => at.AddedAt <= message.CreationDate &&
(messageDtos.Count == 1 || at.AddedAt > messageDtos[messageDtos.Count - 2].CreationDate))
.OrderBy(at => at.AddedAt)
// ✅ زمان‌های اضافی بین این پیام و پیام بعدی
var additionalTimesBetween = allAdditionalTimes
.Where(at => at.CreationDate > message.CreationDate && at.CreationDate < nextMessage.CreationDate)
.ToList();
foreach (var additionalTime in additionalTimesByDate)
{
// ✅ نام کاربری که این زمان اضافی را اضافه کرد
var addedByUserName = additionalTime.AddedByUserId.HasValue && users.TryGetValue(additionalTime.AddedByUserId.Value, out var user)
? user
: "سیستم";
messageDtos.AddRange(CreateAdditionalTimeNotes(additionalTimesBetween, users, request.TaskId));
}
else
{
// ✅ این آخرین پیام است، زمان‌های اضافی بعد از آن را اضافه کن
var additionalTimesAfterLastMessage = allAdditionalTimes
.Where(at => at.CreationDate > message.CreationDate)
.ToList();
// ✅ محتوای نوت را با اطلاعات کامل ایجاد کن
// نمایش می‌دهد: مقدار زمان + علت + نام کسی که اضافه کرد
var noteContent = $"⏱️ زمان اضافی: {additionalTime.Hours.TotalHours:F2} ساعت - {(string.IsNullOrWhiteSpace(additionalTime.Reason) ? "بدون علت" : additionalTime.Reason)} - توسط {addedByUserName}";
// ✅ نوت را به عنوان MessageDto خاصی ایجاد کن
var noteDto = new MessageDto
{
Id = Guid.NewGuid(),
TaskId = request.TaskId,
SenderUserId = 0, // ✅ سیستم برای نشان دادن اینکه یک پیام خودکار است
SenderName = "سیستم",
MessageType = "Note", // ✅ نوع پیام: Note (یادداشت سیستم)
TextContent = noteContent,
CreationDate = additionalTime.AddedAt, // ✅ تاریخ اضافه شدن زمان اضافی
IsMine = false
};
messageDtos.Add(noteDto);
}
messageDtos.AddRange(CreateAdditionalTimeNotes(additionalTimesAfterLastMessage, users, request.TaskId));
}
}
// ✅ مرتب کردن نهایی تمام پیام‌ها (معمولی + نوت‌ها) بر اساس زمان ایجاد
// اینطور که نوت‌های زمان اضافی در جای درست خود قرار می‌گیرند
messageDtos = messageDtos.OrderBy(m => m.CreationDate).ToList();
var response = new PaginationResult<MessageDto>()

View File

@@ -916,6 +916,17 @@ public class institutionContractController : AdminBaseController
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
$"قرارداد های مالی.xlsx");
}
/// <summary>
/// تنظیم وضعیت ارسال قرارداد
/// </summary>
[HttpPost("set-is-sent")]
public async Task<ActionResult<OperationResult>> SetIsSent([FromBody] SetInstitutionContractSendFlagRequest request)
{
var result = await _institutionContractApplication.SetContractSendFlag(request);
return result;
}
}
public class InstitutionContractCreationGetRepresentativeIdResponse
@@ -969,4 +980,4 @@ public class VerifyCodeRequest
{
public long ContractingPartyId { get; set; }
public string verifyCode { get; set; }
}
}