Files
Backend-Api/Company.Domain/RollCallAgg/DomainService/IRollCallDomainService.cs
2025-05-12 21:14:26 +03:30

705 lines
30 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections.Generic;
using System.Linq;
using _0_Framework.Application;
using _0_Framework.Domain.CustomizeCheckoutShared.Base;
using _0_Framework.Domain.CustomizeCheckoutShared.Enums;
using _0_Framework.Domain.CustomizeCheckoutShared.ValueObjects;
using Company.Domain.CustomizeWorkshopEmployeeSettingsAgg;
using Company.Domain.CustomizeWorkshopEmployeeSettingsAgg.Entities;
using Company.Domain.CustomizeWorkshopSettingsAgg;
using System.Collections.Generic;
using System;
using System.Linq;
using System.Threading.Tasks;
using _0_Framework.Application;
using OfficeOpenXml;
using OfficeOpenXml.Drawing.Chart;
using System.Collections;
namespace Company.Domain.RollCallAgg.DomainService;
public interface IRollCallDomainService
{
(WorkshopShiftStatus shiftType, IrregularShift irregularShift, ICollection<CustomizeSifts> regularShifts,
ICollection<CustomizeRotatingShift> rotatingShifts, TimeSpan BreakTime) GetEmployeeShiftDetails(long employeeId,
long workshopId);
TimeOnly GetEmployeeOffSetForRegularSettings(long employeeId, long workshopId);
DateTime GetEmployeeShiftDateByRollCallStartDate(long workshopId, long employeeId, DateTime rollCallStartDate, DateTime rollCallEndDate);
void CalculateTimeDifferences(RollCall rollCall);
(DateTime start, DateTime end) FindRotatingShift(DateTime startRollCall, DateTime endRollCall, ICollection<CustomizeRotatingShift> rotatingShifts);
(DateTime start, DateTime end) FindRotatingShift(List<(DateTime StartDate, DateTime EndDate)> rollcalls, ICollection<CustomizeRotatingShift> rotatingShifts);
BreakTime GetBreakTime(long employeeId, long workshopId);
}
public class RollCallDomainService : IRollCallDomainService
{
private readonly IRollCallRepository _rollCallRepository;
private readonly ICustomizeWorkshopEmployeeSettingsRepository _customizeWorkshopEmployeeSettingsRepository;
private readonly ICustomizeWorkshopSettingsRepository _customizeWorkshopSettingsRepository;
public RollCallDomainService(IRollCallRepository rollCallRepository,
ICustomizeWorkshopEmployeeSettingsRepository customizeWorkshopEmployeeSettingsRepository,
ICustomizeWorkshopSettingsRepository customizeWorkshopSettingsRepository)
{
_rollCallRepository = rollCallRepository;
_customizeWorkshopEmployeeSettingsRepository = customizeWorkshopEmployeeSettingsRepository;
_customizeWorkshopSettingsRepository = customizeWorkshopSettingsRepository;
}
public (WorkshopShiftStatus shiftType, IrregularShift irregularShift, ICollection<CustomizeSifts> regularShifts,
ICollection<CustomizeRotatingShift> rotatingShifts, TimeSpan BreakTime) GetEmployeeShiftDetails(long employeeId,
long workshopId)
{
var employeeSettings =
_customizeWorkshopEmployeeSettingsRepository.GetByEmployeeIdAndWorkshopIdIncludeGroupSettings(workshopId,
employeeId);
var offset = TimeOnly.MinValue;
WorkshopShiftStatus shiftType;
if (employeeSettings == null)
{
var workshopSettings = _customizeWorkshopSettingsRepository.GetBy(workshopId);
shiftType = workshopSettings.WorkshopShiftStatus;
return (shiftType, null,
workshopSettings.CustomizeWorkshopSettingsShifts.Select(x => (CustomizeSifts)x).ToList(),
null, TimeSpan.Zero);
}
else
{
shiftType = employeeSettings.WorkshopShiftStatus;
var breakTimeSpan = employeeSettings.BreakTime.BreakTimeType == BreakTimeType.WithTime
? employeeSettings.BreakTime.BreakTimeValue.ToTimeSpan()
: TimeSpan.Zero;
return (shiftType, employeeSettings.IrregularShift,
employeeSettings.CustomizeWorkshopEmployeeSettingsShifts.Select(x => (CustomizeSifts)x).ToList(),
employeeSettings.CustomizeRotatingShifts, breakTimeSpan);
}
}
public TimeOnly GetEmployeeOffSetForRegularSettings(long employeeId, long workshopId)
{
var workshopSettings = _customizeWorkshopSettingsRepository.GetBy(workshopId);
var employeeSettings =
_customizeWorkshopEmployeeSettingsRepository.GetByEmployeeIdAndWorkshopIdIncludeGroupSettings(workshopId,
employeeId);
if (workshopSettings == null)
return TimeOnly.MinValue;
if (employeeSettings == null && workshopSettings.WorkshopShiftStatus == WorkshopShiftStatus.Regular)
return Tools.CalculateOffset(workshopSettings.CustomizeWorkshopSettingsShifts
.Select(x => (CustomizeSifts)x).ToList());
if (workshopSettings.WorkshopShiftStatus == WorkshopShiftStatus.Regular &&
employeeSettings.WorkshopShiftStatus == WorkshopShiftStatus.Regular)
{
// تعریف بازه‌های زمانی
var workshopStartTime =
workshopSettings.CustomizeWorkshopSettingsShifts.MinBy(x => x.Placement).StartTime; // شروع کارگاه
var workshopEndTime =
workshopSettings.CustomizeWorkshopSettingsShifts.MaxBy(x => x.Placement).EndTime; // پایان کارگاه
var employeeStartTime = employeeSettings.CustomizeWorkshopEmployeeSettingsShifts.MinBy(x => x.Placement)
.StartTime; // شروع بازه پرسنل
var employeeEndTime = employeeSettings.CustomizeWorkshopEmployeeSettingsShifts.MaxBy(x => x.Placement)
.EndTime; // پایان پرسنل
// تبدیل زمان‌ها به TimeSpan برای مقایسه
var workshopStartTimeSpan = workshopStartTime.ToTimeSpan();
var workshopEndTimeSpan = workshopEndTime.ToTimeSpan();
var employeeStartTimeSpan = employeeStartTime.ToTimeSpan();
var employeeEndTimeSpan = employeeEndTime.ToTimeSpan();
// مدیریت زمان‌های بعد از نیمه شب
if (workshopEndTimeSpan < workshopStartTimeSpan)
workshopEndTimeSpan = workshopEndTimeSpan.Add(TimeSpan.FromDays(1)); // افزودن یک روز به پایان بازه اول
if (employeeEndTimeSpan < employeeStartTimeSpan)
employeeEndTimeSpan = employeeEndTimeSpan.Add(TimeSpan.FromDays(1)); // افزودن یک روز به پایان بازه دوم
// محاسبه بزرگ‌ترین زمان شروع و کوچک‌ترین زمان پایان
var overlapStart = workshopStartTimeSpan > employeeStartTimeSpan
? workshopStartTimeSpan
: employeeStartTimeSpan;
var overlapEnd = workshopEndTimeSpan < employeeEndTimeSpan ? workshopEndTimeSpan : employeeEndTimeSpan;
if (overlapStart >= overlapEnd) // اگر بازه هم‌پوشانی وجود ندارد
return Tools.CalculateOffset(employeeSettings.CustomizeWorkshopEmployeeSettingsShifts
.Select(x => (CustomizeSifts)x).ToList());
var overlapDuration = (overlapEnd - overlapStart).TotalMinutes; // مدت زمان هم‌پوشانی بر حسب دقیقه
var duration2 = (employeeEndTime - employeeStartTime).TotalMinutes; // مدت زمان بازه دوم
var overlapPercentage = (overlapDuration / duration2) * 100; // درصد هم‌پوشانی
if (overlapPercentage < 40)
{
return Tools.CalculateOffset(employeeSettings.CustomizeWorkshopEmployeeSettingsShifts
.Select(x => (CustomizeSifts)x).ToList());
}
return Tools.CalculateOffset(workshopSettings.CustomizeWorkshopSettingsShifts
.Select(x => (CustomizeSifts)x).ToList());
}
else if (employeeSettings.WorkshopShiftStatus == WorkshopShiftStatus.Regular)
{
return Tools.CalculateOffset(employeeSettings.CustomizeWorkshopEmployeeSettingsShifts
.Select(x => (CustomizeSifts)x).ToList());
}
else
{
return TimeOnly.MinValue;
}
}
public DateTime GetEmployeeShiftDateByRollCallStartDate(long workshopId, long employeeId,
DateTime rollCallStartDate, DateTime rollCallEndDate)
{
var shiftDetails = GetEmployeeShiftDetails(employeeId, workshopId);
var offset = GetEmployeeOffSetForRegularSettings(employeeId, workshopId);
return shiftDetails.shiftType switch
{
WorkshopShiftStatus.Regular => CalculateRegularShiftDate(rollCallStartDate, offset),
WorkshopShiftStatus.Rotating => FindRotatingShift(rollCallStartDate, rollCallEndDate, shiftDetails.rotatingShifts).start.Date,
WorkshopShiftStatus.Irregular => rollCallStartDate.Date,
_ => throw new ArgumentOutOfRangeException()
};
}
public void CalculateTimeDifferences(RollCall rollCall)
{
var employeeId = rollCall.EmployeeId;
var workshopId = rollCall.WorkshopId;
var shiftDate = rollCall.ShiftDate;
var starDateTime = rollCall.StartDate.Value;
var endDateTime = rollCall.EndDate.Value;
//Todo: Get RollCalls in this shift date
//گرفتن حضور غیاب ها در این شیفت دیت
//در ادامه باید بگردم رول کال اول رو پر کنم رول کال های غیره باید بررسی بشه ببینیم که این رول کال ها کدومش مناسب تره
// و بررسی کنیم چند تا رول کال باید
var shiftDetails = GetEmployeeShiftDetails(employeeId, workshopId);
List<RollCall> rollCalls = GetRollCallsInShiftDate(rollCall.ShiftDate, employeeId, workshopId).GetAwaiter().GetResult();
var deletedRollCall = rollCalls.FirstOrDefault(x => x.id == rollCall.id);
rollCalls.Remove(deletedRollCall);
rollCalls.Add(rollCall);
rollCall.ClearTimeDiff();
switch (shiftDetails.shiftType)
{
case WorkshopShiftStatus.Regular:
var employeeShifts = shiftDetails.regularShifts.Select(x =>
{
var start = new DateTime(DateOnly.FromDateTime(shiftDate), x.StartTime);
var end = new DateTime(DateOnly.FromDateTime(shiftDate), x.EndTime);
if (x.EndTime < x.StartTime)
end = end.AddDays(1);
return new { start, end };
}).ToList();
foreach (var employeeShift in employeeShifts)
{
TimeSpan lateEntryDuration = new TimeSpan();
TimeSpan earlyEntryDuration = new TimeSpan();
TimeSpan lateExitDuration = new TimeSpan();
TimeSpan earlyExitDuration = new TimeSpan();
var shiftStart = employeeShift.start;
var shiftEnd = employeeShift.end;
var rollCallsInShift = rollCalls.Where(a =>
(a.StartDate <= shiftStart && a.EndDate >= shiftStart) ||
(a.StartDate <= shiftEnd && a.EndDate >= shiftEnd) ||
(a.StartDate >= shiftStart && a.StartDate <= shiftEnd) ||
(a.EndDate >= shiftStart && a.EndDate <= shiftEnd) // خروج در شیفت باشد
).ToList();
#region ورود
//RollCall entryRollCall;
// تعجیل در ورود - زود اومدن
var earlyEntryRollCall = rollCallsInShift.OrderBy(x => x.StartDate).FirstOrDefault(x => x.StartDate < employeeShift.start);
var lateEntryRollCall = rollCallsInShift.OrderBy(x => x.StartDate).FirstOrDefault(x => x.StartDate > employeeShift.start);
var previousShift = employeeShifts.OrderByDescending(x => x.start)
.FirstOrDefault(x => x.end < employeeShift.start);
if (earlyEntryRollCall != null)
{
if (previousShift != null)
{
if (earlyEntryRollCall.StartDate > previousShift.end)
{
earlyEntryDuration = shiftStart - earlyEntryRollCall.StartDate!.Value;
earlyEntryRollCall.SetEarlyEnter(earlyEntryDuration);
}
}
else
{
earlyEntryDuration = shiftStart - earlyEntryRollCall.StartDate!.Value;
earlyEntryRollCall.SetEarlyEnter(earlyEntryDuration);
}
}
// تاخیر در ورود - دیر اومدن
else
{
if (lateEntryRollCall != null && (rollCallsInShift.Any(x =>
x.EndDate > shiftStart && x.EndDate < lateEntryRollCall.StartDate) == false))
{
lateEntryDuration = lateEntryRollCall.StartDate!.Value - shiftStart;
lateEntryRollCall.SetLateEnter(lateEntryDuration);
}
}
#endregion
#region خروج
RollCall exitRollCall;
var earlyExitRollCall = rollCallsInShift.OrderByDescending(x => x.EndDate).FirstOrDefault(x => x.EndDate < employeeShift.end);
var lateExitRollCall = rollCallsInShift.OrderBy(x => x.EndDate).FirstOrDefault(x => x.EndDate > employeeShift.end);
// تعجیل در خروج - زود رفتن
var nextShift = employeeShifts.OrderBy(x => x.start)
.FirstOrDefault(x => x.start > employeeShift.end);
if (earlyExitRollCall != null && (rollCallsInShift.Any(x =>
x.StartDate < shiftEnd && x.StartDate > earlyExitRollCall.EndDate) == false))
{
var earlyExit = (shiftEnd - earlyExitRollCall.EndDate!.Value);
earlyExitRollCall.SetEarlyExit(earlyExit);
}
// تاخیر در خروج - دیر رفتن
else
{
if (lateExitRollCall != null)
{
if (nextShift != null)
{
if (lateExitRollCall.EndDate < nextShift.start)
{
lateExitDuration = lateExitRollCall.EndDate!.Value - shiftEnd;
lateExitRollCall.SetLateExit(lateExitDuration);
}
}
else
{
lateExitDuration = lateExitRollCall.EndDate!.Value - shiftEnd;
lateExitRollCall.SetLateExit(lateExitDuration);
}
}
}
#endregion
}
break;
case WorkshopShiftStatus.Rotating:
TimeSpan lateEntryDurationRotating = new TimeSpan();
TimeSpan earlyEntryDurationRotating = new TimeSpan();
TimeSpan lateExitDurationRotating = new TimeSpan();
TimeSpan earlyExitDurationRotating = new TimeSpan();
var rotatingShifts = shiftDetails.rotatingShifts;
var rollCallStartEnds = rollCalls.Select(x => (x.StartDate.Value, x.EndDate.Value)).ToList();
var shift = FindRotatingShift(rollCallStartEnds, rotatingShifts);
var rotatingShiftStart = shift.start;
var rotatingShiftEnd = shift.end;
var rollCallsInRotatingShift = rollCalls.Where(a =>
(a.StartDate <= rotatingShiftStart && a.EndDate >= rotatingShiftStart) ||
(a.StartDate <= rotatingShiftEnd && a.EndDate >= rotatingShiftEnd) ||
(a.StartDate >= rotatingShiftStart && a.StartDate <= rotatingShiftEnd) ||
(a.EndDate >= rotatingShiftStart && a.EndDate <= rotatingShiftEnd) // خروج در شیفت باشد
).ToList();
#region ورود
// تعجیل در ورود - زود اومدن
var earlyEntryRollCallRotating = rollCallsInRotatingShift.OrderBy(x => x.StartDate).FirstOrDefault(x => x.StartDate < shift.start);
var lateEntryRollCallRotating = rollCallsInRotatingShift.OrderBy(x => x.StartDate).FirstOrDefault(x => x.StartDate > shift.start);
//برای خالی کردن اولیه حضور غیاب انتخاب شده
earlyEntryRollCallRotating?.SetEarlyEnter(TimeSpan.Zero);
if (earlyEntryRollCallRotating != null)
{
earlyEntryDurationRotating = rotatingShiftStart - earlyEntryRollCallRotating.StartDate!.Value;
earlyEntryRollCallRotating.SetEarlyEnter(earlyEntryDurationRotating);
}
// تاخیر در ورود - دیر اومدن
else
{
if (lateEntryRollCallRotating != null && (rollCallsInRotatingShift.Any(x =>
x.EndDate > rotatingShiftStart && x.EndDate < lateEntryRollCallRotating.StartDate) == false))
{
lateEntryDurationRotating = lateEntryRollCallRotating.StartDate!.Value - rotatingShiftStart;
lateEntryRollCallRotating.SetLateEnter(lateEntryDurationRotating);
}
}
#endregion
#region خروج
var earlyExitRollCallRotating = rollCallsInRotatingShift.OrderByDescending(x => x.EndDate).FirstOrDefault(x => x.EndDate < shift.end);
var lateExitRollCallRotating = rollCallsInRotatingShift.OrderBy(x => x.EndDate).FirstOrDefault(x => x.EndDate > shift.end);
//برای خالی کردن اولیه حضور غیاب انتخاب شده
earlyExitRollCallRotating?.SetEarlyExit(TimeSpan.Zero);
if (earlyExitRollCallRotating != null && (rollCallsInRotatingShift.Any(x =>
x.StartDate < rotatingShiftEnd && x.StartDate > earlyExitRollCallRotating.EndDate) == false))
{
var earlyExit = (rotatingShiftEnd - earlyExitRollCallRotating.EndDate!.Value);
earlyExitRollCallRotating.SetEarlyExit(earlyExit);
}
// تاخیر در خروج - دیر رفتن
else
{
if (lateExitRollCallRotating != null)
{
lateExitDurationRotating = lateExitRollCallRotating.EndDate!.Value - rotatingShiftEnd;
lateExitRollCallRotating.SetLateExit(lateExitDurationRotating);
}
}
#endregion
break;
case WorkshopShiftStatus.Irregular:
break;
}
_rollCallRepository.SaveChanges();
}
private async Task<List<RollCall>> GetRollCallsInShiftDate(DateTime rollCallShiftDate, long employeeId, long workshopId)
{
return await _rollCallRepository.GetRollCallsInShiftDate(rollCallShiftDate, employeeId, workshopId);
}
public (DateTime start, DateTime end) FindRotatingShift(DateTime startRollCall, DateTime endRollCall,
ICollection<CustomizeRotatingShift> rotatingShifts)
{
DateTime startDate = startRollCall.Date;
DateTime endDate = endRollCall.Date;
var shiftDateTimes = rotatingShifts.SelectMany(shift =>
{
var shifts = new List<(DateTime Start, DateTime End)>();
for (int i = -1; i <= 1; i++)
{
var shiftStart = startDate.AddDays(i).Date;
shiftStart = shiftStart.Add(shift.StartTime.ToTimeSpan());
var shiftEnd = shift.StartTime < shift.EndTime
? startDate.AddDays(i).Date.Add(shift.EndTime.ToTimeSpan())
: startDate.AddDays(i + 1).Date.Add(shift.EndTime.ToTimeSpan());
shifts.Add((shiftStart, shiftEnd));
}
return shifts;
}).ToList();
#region مقایسه شروع حضور غیاب با شیفت
//var startFilteredTimes = shiftDateTimes.Where(shift =>
// (twoHourBeforeStart <= shift.Start && twoHourAfterStart >= shift.Start) ||
// (twoHourBeforeStart <= shift.End && twoHourAfterStart >= shift.End)).ToList();
//if (startFilteredTimes.Count == 0)
//{
// startFilteredTimes = shiftDateTimes;
//}
//else if (startFilteredTimes.Count == 1)
//{
// var startChosenShift = startFilteredTimes.First();
// if (startChosenShift.End < startChosenShift.Start)
// startChosenShift.End = startChosenShift.End.AddDays(1);
// return startChosenShift;
//}
//#endregion
//#region مقایسه پایان حضورغیاب با شیفت
//var endFilteredTimes = shiftDateTimes.Where(shift =>
// (twoHourBeforeEnd <= shift.Start && twoHourAfterEnd >= shift.Start) ||
// (twoHourBeforeEnd <= shift.End && twoHourAfterEnd >= shift.End)).ToList();
//if (endFilteredTimes.Count == 0)
//{
// endFilteredTimes = startFilteredTimes;
//}
//else if (endFilteredTimes.Count == 1)
//{
// var endChosenShift = endFilteredTimes.First();
// return endChosenShift;
//}
#endregion
#region اشتراک حضور غیاب و شیفت
var overlapShifts = shiftDateTimes
.Select(shift => new
{
Shift = shift,
Overlap = new TimeSpan(Math.Max(0,
Math.Min(shift.End.Ticks, endRollCall.Ticks) -
Math.Max(shift.Start.Ticks, startRollCall.Ticks))),
// زمان حضور فرد در شیفت (مجموع Overlap با شیفت)
TotalTimeInShift = new TimeSpan(Math.Max(0,
Math.Min(shift.End.Ticks, endRollCall.Ticks) -
Math.Max(shift.Start.Ticks, startRollCall.Ticks))),
StartDistance = Math.Abs((shift.Start - startRollCall).Ticks),
EndDistance = Math.Abs((shift.End - endRollCall).Ticks),
TotalDistance = Math.Abs((shift.Start - startRollCall).Ticks) + Math.Abs((shift.End - endRollCall).Ticks)
})
.OrderByDescending(s => s.TotalTimeInShift) // 1. بیشترین زمان حضور فرد
.ThenByDescending(s => s.Overlap) // 2. بیشترین Overlap
.ThenBy(s => s.TotalDistance)
.ThenBy(s => s.StartDistance)
.ThenBy(x => x.EndDistance); // 3. اگر برابر بود،
var overlapChosenShift = overlapShifts.First();
var end = overlapChosenShift.Shift.End;
if (overlapChosenShift.Shift.End < overlapChosenShift.Shift.Start)
end = overlapChosenShift.Shift.End.AddDays(1);
return (overlapChosenShift.Shift.Start, end);
#endregion
}
public (DateTime start, DateTime end) FindRotatingShift(List<(DateTime StartDate, DateTime EndDate)> rollCalls, ICollection<CustomizeRotatingShift> rotatingShifts)
{
DateTime startDate = rollCalls.MinBy(x => x.StartDate).StartDate.Date;
DateTime endDate = rollCalls.MaxBy(x => x.EndDate).EndDate.Date;
var shiftDateTimes = rotatingShifts.SelectMany(shift =>
{
var shifts = new List<(DateTime Start, DateTime End)>();
for (int i = -1; i <= 1; i++)
{
var shiftStart = startDate.AddDays(i).Date;
shiftStart = shiftStart.Add(shift.StartTime.ToTimeSpan());
var shiftEnd = shift.StartTime < shift.EndTime
? startDate.AddDays(i).Date.Add(shift.EndTime.ToTimeSpan())
: startDate.AddDays(i + 1).Date.Add(shift.EndTime.ToTimeSpan());
shifts.Add((shiftStart, shiftEnd));
}
return shifts;
}).ToList();
//var shiftScores = shiftDateTimes
// .Select(shift =>
// {
// var totalOverlap = TimeSpan.Zero;
// var firstRollCall = rollCalls.MinBy(x => x.StartDate);
// var lastRollCall = rollCalls.MaxBy(x => x.EndDate);
// long totalStartDistance = Math.Abs((shift.Start - firstRollCall.StartDate).Ticks);
// long totalEndDistance = Math.Abs((shift.End - lastRollCall.EndDate).Ticks);
// foreach (var rollCall in rollCalls)
// {
// var start = rollCall.StartDate;
// var end = rollCall.EndDate;
// var overlapTicks = Math.Max(0,
// Math.Min(shift.End.Ticks, end.Ticks) -
// Math.Max(shift.Start.Ticks, start.Ticks));
// totalOverlap += new TimeSpan(overlapTicks);
// //totalStartDistance += Math.Abs((shift.Start - start).Ticks);
// //totalEndDistance += Math.Abs((shift.End - end).Ticks);
// }
// return new
// {
// Shift = shift,
// TotalOverlap = totalOverlap,
// TotalDistance = totalStartDistance + totalEndDistance,
// StartDistance = totalStartDistance,
// EndDistance = totalEndDistance
// };
// })
// .OrderBy(s => s.StartDistance)
// .ThenBy(s => s.TotalDistance)
// .ThenByDescending(s => s.TotalOverlap)
// .ThenBy(s => s.EndDistance);
// مرحله 1: گروه‌بندی بر اساس نزدیکی زمان شروع به اولین حضور و غیاب
var groupedByStart = shiftDateTimes
.GroupBy(shift =>
{
var firstRollCallStart = rollCalls.Min(x => x.StartDate);
var ticksDiff = Math.Abs((shift.Start - firstRollCallStart).Ticks);
return Math.Round(TimeSpan.FromTicks(ticksDiff).TotalMinutes / 30); // گروه‌بندی هر 30 دقیقه
})
.OrderBy(g => g.Key)
.First(); // نزدیک‌ترین گروه به شروع
// مرحله 2 (جایگزین مرحله ۴): گروه‌بندی بر اساس نزدیکی پایان شیفت به آخرین حضور و غیاب
var filteredByEnd = groupedByStart
.Select(shift =>
{
var lastRollCallEnd = rollCalls.Max(x => x.EndDate);
var endDistance = Math.Abs((shift.End - lastRollCallEnd).Ticks);
return new { Shift = shift, EndDistance = endDistance };
})
.GroupBy(x => x.EndDistance / TimeSpan.FromMinutes(30).Ticks) // گروه‌بندی هر 30 دقیقه
.OrderBy(g => g.Key)
.First() // نزدیک‌ترین پایان
.ToList();
// مرحله 3: فیلتر بر اساس بیشترین همپوشانی با حضور و غیاب‌ها
var filteredByOverlap = filteredByEnd
.Select(shift =>
{
var totalOverlap = TimeSpan.Zero;
foreach (var rollCall in rollCalls)
{
var overlapTicks = Math.Max(0,
Math.Min(shift.Shift.End.Ticks, rollCall.EndDate.Ticks) -
Math.Max(shift.Shift.Start.Ticks, rollCall.StartDate.Ticks));
totalOverlap += new TimeSpan(overlapTicks);
}
return new { Shift = shift.Shift, TotalOverlap = totalOverlap };
})
.GroupBy(x => Math.Round(x.TotalOverlap.TotalMinutes /5)) // گروه‌بندی همپوشانی
.OrderByDescending(g => g.Key) // بیشترین همپوشانی اول
.First()
.ToList();
// مرحله 4 (جایگزین مرحله ۲): فیلتر نهایی بر اساس کمترین مجموع فاصله (TotalDistance)
var bestShift = filteredByOverlap
.Select(shift =>
{
var firstRollCall = rollCalls.MinBy(x => x.StartDate);
var lastRollCall = rollCalls.MaxBy(x => x.EndDate);
var totalDistance =
Math.Abs((shift.Shift.Start - firstRollCall.StartDate).Ticks) +
Math.Abs((shift.Shift.End - lastRollCall.EndDate).Ticks);
return new { Shift = shift.Shift, TotalDistance = totalDistance };
})
.OrderBy(x => x.TotalDistance) // کمترین فاصله کلی
.First(); // بهترین شیفت نهایی
var overlapChosenShift = bestShift;
var end = overlapChosenShift.Shift.End;
if (overlapChosenShift.Shift.End < overlapChosenShift.Shift.Start)
end = overlapChosenShift.Shift.End.AddDays(1);
return (overlapChosenShift.Shift.Start, end);
}
public BreakTime GetBreakTime(long employeeId, long workshopId)
{
var employeeSettings = _customizeWorkshopEmployeeSettingsRepository.GetByEmployeeIdAndWorkshopIdIncludeGroupSettings(workshopId,
employeeId);
if (employeeSettings == null)
{
return new BreakTime(false, TimeOnly.MinValue);
}
return employeeSettings.BreakTime;
}
private DateTime CalculateRegularShiftDate(DateTime startDate, TimeOnly offset)
{
DateTime nextOffSetDateTime;
if (startDate.TimeOfDay >= offset.ToTimeSpan())
{
nextOffSetDateTime = startDate.AddDays(1).Date + offset.ToTimeSpan();
}
else
nextOffSetDateTime = startDate.Date + offset.ToTimeSpan();
if (nextOffSetDateTime.TimeOfDay >= TimeSpan.FromHours(12))
return nextOffSetDateTime.Date;
return nextOffSetDateTime.AddDays(-1).Date;
}
}