3284 lines
133 KiB
C#
3284 lines
133 KiB
C#
using BeWo.Data.Access;
|
|
using BeWo.Data.Entities;
|
|
using BeWo.Report.ReportObjects;
|
|
using BeWo.Service.Configuration;
|
|
using BeWo.Service.DCEntityMapper;
|
|
using BeWo.Service.Plugins;
|
|
using BeWo.Service.ServiceImplementations;
|
|
using BS.Shared;
|
|
using BS.Shared.Core;
|
|
using BS.Shared.DataContracts;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
using System.Windows.Forms;
|
|
using BeWo.Report;
|
|
using BS.Shared.Extensions;
|
|
using BeWo.Service.Dienstplanung;
|
|
|
|
namespace ClaudeStundenkontoTest
|
|
{
|
|
public class CustomMitarbeiterstundenkontoBerechnung : MitarbeiterstundenkontoBerechnung
|
|
{
|
|
private decimal maxWochenstunden = 40;
|
|
private AppSettings appSettings;
|
|
private const int ANZ_WOCHENTAGE = 5;
|
|
protected new VacationService vacationService;
|
|
|
|
// Claude: Caches für Performance-Optimierung (Instanz-scoped, eine Instanz pro Report-Lauf)
|
|
// Claude: Feiertagsliste pro Jahr cachen, damit vacationService.GetFeiertage nicht mehrfach aufgerufen wird
|
|
private Dictionary<int, List<DateTime>> _feiertageListeCache;
|
|
// Claude: HashSet pro Jahr für O(1)-Lookup in IstFeiertag statt linearer Suche
|
|
private Dictionary<int, HashSet<DateTime>> _feiertageSetCache;
|
|
// Claude: Cache für FeiertagDC-Objekt pro Datum (verwendet in CreateDayDetail)
|
|
private Dictionary<DateTime, FeiertagDC> _feiertagDCByDateCache;
|
|
// Claude: Cache der gesamten DienstEintraege pro Mitarbeiter, einmal pro Report-Lauf geladen
|
|
private Dictionary<long, List<DienstEintrag>> _allDienstEintraegeByEmpCache;
|
|
// Claude: Cache der gesamten Dienstvertretungen pro Mitarbeiter, einmal pro Report-Lauf geladen
|
|
private Dictionary<long, List<Dienstvertretung>> _allDienstvertretungenByEmpCache;
|
|
// Claude: Cache von HatArbeitszeitImZeitraum (nur start==ende-Fall) pro Arbeitszeiten-Liste und Datum
|
|
// Claude: Wir nutzen einen ReferenceEqualityComparer um sicherzustellen, dass NHibernate-Proxy-Collections nicht via überschriebenes Equals kollidieren
|
|
private Dictionary<IList<Arbeitszeit>, Dictionary<DateTime, bool>> _hatArbeitszeitImZeitraumCache;
|
|
|
|
// Claude: Comparer, der ausschließlich Referenz-Gleichheit nutzt (verhindert unbeabsichtigte Treffer bei NHibernate-Collections mit überschriebenem Equals)
|
|
private sealed class ReferenceEqualityComparer<T> : IEqualityComparer<T> where T : class
|
|
{
|
|
public static readonly ReferenceEqualityComparer<T> Default = new ReferenceEqualityComparer<T>();
|
|
public bool Equals(T x, T y) { return ReferenceEquals(x, y); }
|
|
public int GetHashCode(T obj) { return System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(obj); }
|
|
}
|
|
|
|
protected new AppSettings Settings
|
|
{
|
|
get
|
|
{
|
|
return appSettings;
|
|
}
|
|
|
|
}
|
|
|
|
public new bool CheckIst { get; set; }
|
|
|
|
public override void CreateReport(MitarbeiterstundenkontoRO ro)
|
|
{
|
|
appSettings = AppSettings.CreateSettings();
|
|
vacationService = PluginLoader.FindClass<VacationService>();
|
|
|
|
CreateEmployeeDetails(ro);
|
|
|
|
CreateTeamDetails(ro);
|
|
|
|
CreateTotals(ro);
|
|
}
|
|
|
|
public override void CreateTotals(MitarbeiterstundenkontoRO ro)
|
|
{
|
|
foreach (var empDetail in ro.EmployeeDetailList)
|
|
{
|
|
|
|
ro.SollGesamt += empDetail.StundenSollOhneUrlaubKrankheit;
|
|
ro.FlsSollGesamt += empDetail.FlsSoll;
|
|
ro.FlsIstGesamt += empDetail.FlsIst;
|
|
ro.MittelbarIst += empDetail.MittelbarIst;
|
|
ro.FlsDiffGesamt += empDetail.FlsDiff;
|
|
ro.StundenGesamt += empDetail.StundenGesamtIst;
|
|
}
|
|
|
|
if (ro.FlsSollGesamt != 0)
|
|
ro.RelationIstSollGesamt = (ro.FlsIstGesamt / ro.FlsSollGesamt) * 100m;
|
|
|
|
if (ro.FlsIstGesamt + ro.MittelbarIst != 0)
|
|
ro.RelationFlsZuMittelbar = (ro.FlsIstGesamt / (ro.FlsIstGesamt + ro.MittelbarIst)) * 100;
|
|
}
|
|
|
|
public override void CreateTeamDetails(MitarbeiterstundenkontoRO ro)
|
|
{
|
|
foreach (var teamDetail in ro.TeamDetailList)
|
|
{
|
|
teamDetail.EmployeeDetailList = FilterEmployeeList(teamDetail.EmployeeDetailList);
|
|
foreach (var empDetail in teamDetail.EmployeeDetailList)
|
|
{
|
|
teamDetail.SollGesamt += empDetail.StundenSollOhneUrlaubKrankheit;
|
|
teamDetail.FlsSollGesamt += empDetail.FlsSoll;
|
|
teamDetail.FlsIstGesamt += empDetail.FlsIst;
|
|
teamDetail.MittelbarIst += empDetail.MittelbarIst;
|
|
teamDetail.FlsDiffGesamt += empDetail.FlsDiff;
|
|
teamDetail.StundenGesamt += empDetail.StundenGesamtIst;
|
|
}
|
|
if (teamDetail.FlsSollGesamt != 0)
|
|
teamDetail.RelationIstSollGesamt = (teamDetail.FlsIstGesamt / teamDetail.FlsSollGesamt) * 100m;
|
|
|
|
if (teamDetail.FlsIstGesamt + teamDetail.MittelbarIst != 0)
|
|
teamDetail.RelationFlsZuMittelbar = (teamDetail.FlsIstGesamt / (teamDetail.FlsIstGesamt + teamDetail.MittelbarIst)) * 100;
|
|
}
|
|
|
|
|
|
}
|
|
|
|
public override void CreateEmployeeDetails(MitarbeiterstundenkontoRO ro)
|
|
{
|
|
foreach (var empDetail in ro.EmployeeDetailList)
|
|
{
|
|
|
|
DateTime monatsAnfang = ro.ReportStartDate;
|
|
DateTime monatsEnde = monatsAnfang.AddMonths(1);
|
|
DateTime berichtsAnfang = monatsAnfang;
|
|
DateTime berichtsEnde = monatsEnde;
|
|
|
|
Employee emp = empDetail.Employee;
|
|
|
|
decimal bu = 0;
|
|
|
|
var c = GetContractForDate(emp, ro.ReportStartDate, ro.ReportEndDate);
|
|
|
|
if (c != null)
|
|
{
|
|
if (c.EntryDate.HasValue && c.EntryDate > monatsAnfang)
|
|
{
|
|
berichtsAnfang = c.EntryDate.Value;
|
|
}
|
|
if (c.ContractEndDate.HasValue && c.ContractEndDate < monatsEnde)
|
|
{
|
|
berichtsEnde = c.ContractEndDate.Value.Date.AddDays(1);
|
|
}
|
|
empDetail.HasContract = true;
|
|
}
|
|
|
|
if (BerechnungsStartdatum > berichtsAnfang)
|
|
{
|
|
berichtsAnfang = BerechnungsStartdatum;
|
|
}
|
|
|
|
ServiceRecordSummary summaryTotal = new ServiceRecordSummary();
|
|
summaryTotal.RecordsImBerichtszeitraum = new List<StundenkontoServiceRecord>();
|
|
|
|
decimal pause1 = 0;
|
|
decimal pause2 = 0;
|
|
decimal weeklyTotalHours = 0;
|
|
decimal flsSollGesamt = 0;
|
|
decimal? fehlZeitenGesamt = null;
|
|
decimal? tageUrlaubGesamt = null;
|
|
decimal? tageKrankheitGesamt = null;
|
|
decimal? abwesenheitTageGesamt = null;
|
|
decimal workdaysTotal = 0;
|
|
decimal stundenSollGesamt = 0;
|
|
decimal tatSollStdGesamt = 0;
|
|
decimal azDurchFeiertage = 0;
|
|
|
|
IList<StundenkontoServiceRecord> groupFilteredRecords = empDetail.AllServiceRecords;
|
|
if (groupFilteredRecords == null)
|
|
{
|
|
IList<ServiceRecord> recordsAll = DAOFactory.SearchDAO.FindServiceRecords(emp.Oid, null);
|
|
recordsAll = AnalysisServiceImp.FilterActiveServiceRecords(recordsAll);
|
|
|
|
//
|
|
groupFilteredRecords = new List<StundenkontoServiceRecord>();
|
|
Dictionary<long, bool> groupBookingDict = new Dictionary<long, bool>();
|
|
foreach (var item in recordsAll)
|
|
{
|
|
|
|
if (!item.GroupOid.HasValue || !groupBookingDict.ContainsKey(item.GroupOid.Value))
|
|
{
|
|
groupFilteredRecords.Add(item);
|
|
if (item.GroupOid.HasValue)
|
|
{
|
|
groupBookingDict.Add(item.GroupOid.Value, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
empDetail.AllServiceRecords = groupFilteredRecords;
|
|
}
|
|
|
|
while (berichtsAnfang < berichtsEnde)
|
|
{
|
|
|
|
|
|
|
|
StarteBerechnung(berichtsAnfang, berichtsEnde, empDetail);
|
|
|
|
empDetail.ArbeitstageImBerichtszeitraum = GetArbeitstageImBereich(berichtsAnfang, berichtsEnde, emp);
|
|
empDetail.ArbeitstageProWoche = ANZ_WOCHENTAGE;
|
|
|
|
|
|
workdaysTotal += empDetail.ArbeitstageImBerichtszeitraum;
|
|
weeklyTotalHours = 0;
|
|
|
|
if (c != null)
|
|
{
|
|
if (c.WeeklyTotalHours.HasValue)
|
|
{
|
|
weeklyTotalHours = c.WeeklyTotalHours.Value;
|
|
bu = c.WeeklyTotalHours.Value / this.maxWochenstunden;
|
|
}
|
|
if (c.WeeklyDays.HasValue && c.WeeklyDays.Value > 0)
|
|
{
|
|
empDetail.ArbeitstageProWoche = c.WeeklyDays.Value;
|
|
}
|
|
|
|
empDetail.WeeklyFLS = c.WeeklyFLS;
|
|
empDetail.HourlyRate = c.HourlyRate;
|
|
empDetail.EmploymentType = c.EmploymentType;
|
|
|
|
|
|
}
|
|
empDetail.SollProTag = (weeklyTotalHours / empDetail.ArbeitstageProWoche);
|
|
empDetail.SollProWoche = BerechneSollProWoche(c, empDetail);
|
|
if (empDetail.SollProTag == 0 && empDetail.SollProWoche > 0 && empDetail.ArbeitstageProWoche > 0)
|
|
{
|
|
empDetail.SollProTag = empDetail.SollProWoche / empDetail.ArbeitstageProWoche;
|
|
|
|
}
|
|
HandleServiceRecords(empDetail, groupFilteredRecords, c);
|
|
|
|
if (CheckIst)
|
|
{
|
|
|
|
var summary = BerechneStunden(empDetail, groupFilteredRecords, berichtsAnfang, berichtsEnde);
|
|
summaryTotal.IstAbrechenbar += summary.IstAbrechenbar;
|
|
summaryTotal.IstNichtAbrechenbar += summary.IstNichtAbrechenbar;
|
|
summaryTotal.RecordsImBerichtszeitraum.AddRange(summary.RecordsImBerichtszeitraum);
|
|
|
|
if (SollPauseBerechnen())
|
|
{
|
|
pause1 += BerechnePauseMinuten(summary.RecordsImBerichtszeitraum);
|
|
pause2 += BerechneAutoPausen(summary.RecordsImBerichtszeitraum);
|
|
|
|
}
|
|
}
|
|
|
|
|
|
decimal? stundenSollProMonat = null;
|
|
decimal? stundenSollImVertragsUndBerichtzeitraum = null;
|
|
if (empDetail.HasContract)
|
|
{
|
|
stundenSollImVertragsUndBerichtzeitraum = GetSollNachArbeitszeiten(empDetail.Employee, berichtsAnfang, berichtsEnde);
|
|
}
|
|
|
|
if (!stundenSollImVertragsUndBerichtzeitraum.HasValue)
|
|
{
|
|
stundenSollProMonat = BerechneSollProMonat(emp, berichtsAnfang, berichtsEnde);
|
|
stundenSollImVertragsUndBerichtzeitraum = stundenSollProMonat;
|
|
}
|
|
|
|
if (!stundenSollImVertragsUndBerichtzeitraum.HasValue)
|
|
{
|
|
if (!stundenSollImVertragsUndBerichtzeitraum.HasValue)
|
|
{
|
|
if (empDetail.ArbeitstageProWoche <= 5)
|
|
{
|
|
stundenSollImVertragsUndBerichtzeitraum =
|
|
empDetail.ArbeitstageImBerichtszeitraum * (weeklyTotalHours / 5);
|
|
}
|
|
else
|
|
{
|
|
stundenSollImVertragsUndBerichtzeitraum =
|
|
empDetail.ArbeitstageImBerichtszeitraum * (weeklyTotalHours / empDetail.ArbeitstageProWoche);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
stundenSollGesamt += stundenSollImVertragsUndBerichtzeitraum ?? 0;
|
|
|
|
|
|
empDetail.StundenSollMax = stundenSollGesamt;
|
|
|
|
|
|
var tatSollStdImVertragsUndBerichtzeitraum = stundenSollImVertragsUndBerichtzeitraum ?? 0;
|
|
|
|
|
|
decimal? fehlZeiten = GetFehlzeiten(groupFilteredRecords, berichtsAnfang, berichtsEnde);
|
|
if (fehlZeiten.HasValue)
|
|
{
|
|
if (!fehlZeitenGesamt.HasValue)
|
|
fehlZeitenGesamt = 0;
|
|
fehlZeitenGesamt += fehlZeiten;
|
|
|
|
tatSollStdImVertragsUndBerichtzeitraum -= fehlZeiten.Value;
|
|
}
|
|
|
|
AktuellBearbeitetesEmployeeDetail = empDetail;
|
|
var ath = GetAbsenceTimeDaysWithWorkWeeks(emp, berichtsAnfang, berichtsEnde, empDetail.ArbeitstageProWoche);
|
|
empDetail.AbsenceTimeDays = ath;
|
|
|
|
if (ath.TageUrlaub.HasValue)
|
|
{
|
|
if (!tageUrlaubGesamt.HasValue)
|
|
tageUrlaubGesamt = 0;
|
|
tageUrlaubGesamt += ath.TageUrlaub;
|
|
}
|
|
if (ath.TageKrankheit.HasValue)
|
|
{
|
|
if (!tageKrankheitGesamt.HasValue)
|
|
tageKrankheitGesamt = 0;
|
|
tageKrankheitGesamt += ath.TageKrankheit;
|
|
}
|
|
|
|
|
|
if (ath.TageGesamt.HasValue)
|
|
{
|
|
if (!abwesenheitTageGesamt.HasValue)
|
|
abwesenheitTageGesamt = 0;
|
|
abwesenheitTageGesamt += ath.TageGesamt;
|
|
|
|
var minusStunden = BerechneStundenDurchAbwesenheiten(ath, berichtsAnfang, berichtsEnde, c, empDetail);
|
|
tatSollStdImVertragsUndBerichtzeitraum -= minusStunden;
|
|
if (tatSollStdImVertragsUndBerichtzeitraum < 0 && !StundenDurchAbwesenheitenDuerfenNegativSein())
|
|
{
|
|
|
|
tatSollStdImVertragsUndBerichtzeitraum = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
var ath2 = GetAbwesenheitenDurchServiceRecords(empDetail, c, groupFilteredRecords, berichtsAnfang, berichtsEnde);
|
|
|
|
if (ath2 != null)
|
|
{
|
|
|
|
var stunden = ath2.StundenKrankheit + ath2.StundenUrlaub + ath2.StundenSonstiges;
|
|
|
|
tatSollStdImVertragsUndBerichtzeitraum -= stunden;
|
|
|
|
if (tatSollStdImVertragsUndBerichtzeitraum < 0 && !StundenDurchAbwesenheitenDuerfenNegativSein())
|
|
{
|
|
tatSollStdImVertragsUndBerichtzeitraum = 0;
|
|
}
|
|
|
|
if (empDetail.SollProTag > 0)
|
|
{
|
|
if (!abwesenheitTageGesamt.HasValue)
|
|
abwesenheitTageGesamt = 0;
|
|
abwesenheitTageGesamt += stunden / empDetail.SollProTag;
|
|
|
|
if (ath2.StundenKrankheit > 0)
|
|
{
|
|
if (!tageKrankheitGesamt.HasValue)
|
|
{
|
|
tageKrankheitGesamt = 0;
|
|
}
|
|
tageKrankheitGesamt += ath2.StundenKrankheit / empDetail.SollProTag;
|
|
}
|
|
|
|
if (ath2.StundenUrlaub > 0)
|
|
{
|
|
if (!tageUrlaubGesamt.HasValue)
|
|
{
|
|
tageUrlaubGesamt = 0;
|
|
}
|
|
tageUrlaubGesamt += ath2.StundenUrlaub / empDetail.SollProTag;
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
|
|
tatSollStdGesamt += tatSollStdImVertragsUndBerichtzeitraum;
|
|
|
|
|
|
flsSollGesamt += BerechneFlsSoll(emp, c, berichtsAnfang, berichtsEnde, tatSollStdImVertragsUndBerichtzeitraum, stundenSollProMonat);
|
|
|
|
if (ath.StundenAlsIst > 0)
|
|
{
|
|
tatSollStdGesamt += ath.StundenAlsIst;
|
|
summaryTotal.IstNichtAbrechenbar += ath.StundenAlsIst * 60;
|
|
}
|
|
|
|
azDurchFeiertage += BerechneGesamtArbeitszeitDurchFeiertage(empDetail, berichtsAnfang, berichtsEnde);
|
|
|
|
|
|
berichtsAnfang = berichtsEnde;
|
|
berichtsEnde = monatsEnde;
|
|
c = GetNextValidContractForDate(berichtsAnfang, emp);
|
|
|
|
if (c != null)
|
|
{
|
|
if (c.EntryDate.HasValue && c.EntryDate >= berichtsAnfang)
|
|
berichtsAnfang = c.EntryDate.Value;
|
|
if (c.ContractEndDate.HasValue && c.ContractEndDate < monatsEnde)
|
|
{
|
|
berichtsEnde = c.ContractEndDate.Value.Date.AddDays(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (AddRateFactorToIst() != 1)
|
|
{
|
|
summaryTotal.IstAbrechenbar *= AddRateFactorToIst();
|
|
}
|
|
summaryTotal.IstAbrechenbar /= 60m;
|
|
summaryTotal.IstNichtAbrechenbar /= 60m;
|
|
|
|
empDetail.WorkDaysThisMonth = workdaysTotal;
|
|
//decimal stundenSollMax = arbeitstage * (maxWochenstunden / xxx) * bu;
|
|
|
|
|
|
|
|
empDetail.Fehlzeiten = fehlZeitenGesamt;
|
|
empDetail.TageUrlaub = tageUrlaubGesamt;
|
|
empDetail.TageKrankheit = tageKrankheitGesamt;
|
|
empDetail.UrlaubUndKrankheit = abwesenheitTageGesamt;
|
|
|
|
empDetail.StundenSollOhneUrlaubKrankheit = tatSollStdGesamt;
|
|
|
|
empDetail.FlsSoll = flsSollGesamt;
|
|
|
|
|
|
|
|
empDetail.MittelbarIst = summaryTotal.IstNichtAbrechenbar;
|
|
empDetail.Bu = bu;
|
|
empDetail.FlsIst = summaryTotal.IstAbrechenbar;
|
|
empDetail.FlsDiff = summaryTotal.IstAbrechenbar - empDetail.FlsSoll;
|
|
empDetail.StundenMittelbarIst = summaryTotal.IstNichtAbrechenbar;
|
|
empDetail.StundenGesamtIst = summaryTotal.IstAbrechenbar + summaryTotal.IstNichtAbrechenbar;
|
|
|
|
empDetail.StundenGesamtIst -= pause1 / 60;
|
|
empDetail.StundenGesamtIst -= pause2 / 60;
|
|
|
|
empDetail.StundenGesamtIst += azDurchFeiertage;
|
|
|
|
if (empDetail.FlsSoll != 0)
|
|
empDetail.RelationIstSoll = (empDetail.FlsIst / empDetail.FlsSoll) * 100m;
|
|
|
|
|
|
decimal? ueberstunden = empDetail.StundenGesamtIst - empDetail.StundenSollOhneUrlaubKrankheit;
|
|
decimal? ueberstundenVormonat;
|
|
|
|
|
|
decimal alteExtraUeberstunden = 0;
|
|
BerechneUeberstundenBis(empDetail, groupFilteredRecords, ro.ReportStartDate, out ueberstundenVormonat,
|
|
out alteExtraUeberstunden);
|
|
|
|
// Claude: Globaler Cache für DienstEintraege/Dienstvertretungen, nur einmal pro Report-Lauf aus dem DAO holen statt pro Mitarbeiter
|
|
//EnsureDienstplanDataLoaded();
|
|
//var empOidNullable = empDetail.Employee.Oid;
|
|
//var alleDiensteintraege = new List<DienstEintrag>();
|
|
//var alleDienstvertretungen = new List<Dienstvertretung>();
|
|
//if (empOidNullable.HasValue)
|
|
//{
|
|
// List<DienstEintrag> deList;
|
|
// if (_allDienstEintraegeByEmpCache.TryGetValue(empOidNullable.Value, out deList))
|
|
// {
|
|
// alleDiensteintraege.AddRange(deList);
|
|
// }
|
|
// List<Dienstvertretung> dvList;
|
|
// if (_allDienstvertretungenByEmpCache.TryGetValue(empOidNullable.Value, out dvList))
|
|
// {
|
|
// alleDienstvertretungen.AddRange(dvList);
|
|
// }
|
|
//}
|
|
|
|
//decimal? ueberstundenVormonatFuerDienstplanung;
|
|
//decimal? alteExtraUeberstundenDienstplanung;
|
|
//empDetail.DienstplanIst += BerechneDienstplanIst(empDetail, alleDiensteintraege, alleDienstvertretungen, ro.ReportStartDate,
|
|
//out ueberstundenVormonatFuerDienstplanung, out alteExtraUeberstundenDienstplanung); // ABCDEF
|
|
|
|
//var dienstplanIstAlsStunden = empDetail.DienstplanIst / 60;
|
|
|
|
decimal extraStunden = 0;
|
|
|
|
BerechneExtraUeberstunden(emp, monatsAnfang, monatsEnde, out extraStunden); // OverTimes
|
|
|
|
if (ShouldCreateDayDetails())
|
|
{
|
|
|
|
empDetail.Days = CreateDayDetails(monatsAnfang, monatsEnde, empDetail, summaryTotal.RecordsImBerichtszeitraum);
|
|
var ah = GetAdditionalHours(empDetail.Days, monatsAnfang, monatsEnde, empDetail, summaryTotal.RecordsImBerichtszeitraum);
|
|
ueberstunden += ah;
|
|
empDetail.StundenGesamtIst += ah;
|
|
}
|
|
|
|
if (empDetail.StundenGesamtIst != 0)
|
|
empDetail.RelationFlsZuMittelbar = (summaryTotal.IstAbrechenbar / empDetail.StundenGesamtIst) * 100;
|
|
|
|
empDetail.Ueberstunden = ueberstunden;
|
|
empDetail.UeberstundenVormonat = ueberstundenVormonat;
|
|
if (!empDetail.UeberstundenGesamt.HasValue)
|
|
empDetail.UeberstundenGesamt = ((ueberstundenVormonat ?? 0) + (ueberstunden ?? 0)) + extraStunden;
|
|
else
|
|
empDetail.UeberstundenGesamt += ((ueberstundenVormonat ?? 0) + (ueberstunden ?? 0)) + extraStunden;
|
|
empDetail.UeberstundenAusbezahlt = extraStunden;
|
|
|
|
//Urlaubskonto (todo)
|
|
empDetail.UrlaubGesamtImAktuellenJahr =
|
|
GetAbsenceTimeDaysWithWorkWeeks(emp, new DateTime(monatsAnfang.Year, 1, 1), monatsEnde, empDetail.ArbeitstageProWoche).TageUrlaub;
|
|
|
|
// Aktuellen Resturlaub berechnen
|
|
var vs = PluginLoader.FindClass<VacationService>();
|
|
// Darstellungsvariante, da das normale Urlaubskonto nur den Resturlaubsstand vom Ende des Jahres anzeigt.
|
|
var konto = vs.BerechneUrlaubskontoZurDarstellung(MapperFactory.EmployeeDC_Employee.MapToNewDC(emp), ro.ReportStartDate.Year, berichtsEnde.AddDays(-1));
|
|
if (konto != null && konto.Count > 0)
|
|
{
|
|
var alleVertraglichenBudgets = konto
|
|
.Where(k => k.DatumZurSortierung == null && ZaehltZuUrlaubsbudget(k.Erlaeuterung))
|
|
.ToList();
|
|
|
|
empDetail.Resturlaub = alleVertraglichenBudgets.Sum(b => Convert.ToDecimal(b.AnzahlTage));
|
|
|
|
// Jeder Entry enthält den Zeitraum und die abzuziehenden Tage vom Gesamtbudget.
|
|
foreach (var entry in konto)
|
|
{
|
|
// Hat entry kein Datum, dann ist es nur eine Überschrift in der Darstellung und hat hier keinen Nutzen
|
|
if (entry.DatumZurSortierung != null || entry.Erlaeuterung.Contains("Resturlaub aus"))
|
|
{
|
|
// Ist das Datum nach dem Enddatum des Stundenkontos braucht nicht weitergerechnet werden
|
|
if (entry.DatumZurSortierung > ro.ReportEndDate.AddTicks(-1))
|
|
break;
|
|
|
|
empDetail.Resturlaub += Convert.ToDecimal(entry.AnzahlTage); // += weil Tage immer als Minus dargestellt sind
|
|
}
|
|
}
|
|
}
|
|
|
|
CalculateEmployeeSalary(empDetail, berichtsAnfang, berichtsEnde);
|
|
}
|
|
|
|
ro.EmployeeDetailList = FilterEmployeeList(ro.EmployeeDetailList);
|
|
}
|
|
|
|
public override bool ZaehltZuUrlaubsbudget(string erlaueterung)
|
|
{
|
|
if (erlaueterung.Contains("Urlaubstage laut Vertrag") ||
|
|
erlaueterung.Contains("Sonderurlaub") ||
|
|
erlaueterung.Contains("Regeneration"))
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
#region Ist-Berechnung für Dienstplanung
|
|
public decimal BerechneDienstplanIst(MitarbeiterstundenkontoRO.EmployeeDetail empDetail, List<DienstEintrag> eintraege, List<Dienstvertretung> vertretungen, DateTime monat, out decimal? ueberstunden, out decimal? alteExtraUeberstunden) // ABCDEF
|
|
{
|
|
ueberstunden = null;
|
|
alteExtraUeberstunden = 0;
|
|
|
|
decimal dpIst = 0;
|
|
|
|
|
|
MitarbeiterstundenkontoRO.EmployeeDetail edTotal = new MitarbeiterstundenkontoRO.EmployeeDetail();
|
|
Employee emp = empDetail.Employee;
|
|
Contract c = emp.FirstContract;
|
|
|
|
// Der Einfachheit halber: Vertretungen in Einträge umwandeln
|
|
eintraege.AddRange(VertretungenUmwandeln(vertretungen.ToList()));
|
|
|
|
if (c != null)
|
|
{
|
|
Dictionary<string, List<DienstEintrag>> eintragDict = CreateDiensteintragDictByDate(eintraege); // Anzahl der Einträge korrekt
|
|
|
|
DateTime start = c.EntryDate.HasValue ? c.EntryDate.Value : new DateTime(2011, 1, 1);
|
|
while (c != null && start < monat)
|
|
{
|
|
DateTime contractEnd = c.ContractEndDate.HasValue ? c.ContractEndDate.Value : DateTime.MaxValue;
|
|
decimal bu = 0;
|
|
decimal weeklyTotalHours = this.maxWochenstunden;
|
|
int anzWochentage = ANZ_WOCHENTAGE;
|
|
|
|
if (c.WeeklyTotalHours.HasValue)
|
|
{
|
|
weeklyTotalHours = c.WeeklyTotalHours.Value;
|
|
bu = c.WeeklyTotalHours.Value / maxWochenstunden;
|
|
}
|
|
|
|
while (start < monat && start < contractEnd)
|
|
{
|
|
decimal flsIst = 0;
|
|
decimal mittelbarIst = 0;
|
|
decimal minutenMonat = 0;
|
|
DateTime ende = new DateTime(start.Year, start.Month, 1).AddMonths(1);
|
|
if (contractEnd < DateTime.MaxValue && contractEnd.AddDays(1) < ende)
|
|
ende = contractEnd.AddDays(1);
|
|
|
|
List<DienstEintrag> entriesThisMonth = new List<DienstEintrag>();
|
|
string key = string.Format("{0:yyyyMM}", start);
|
|
|
|
if (eintragDict.ContainsKey(key))
|
|
{
|
|
foreach (var item in eintragDict[key])
|
|
{
|
|
minutenMonat += Convert.ToDecimal(item.DauerInStunden * 60);
|
|
entriesThisMonth.Add(item);
|
|
}
|
|
}
|
|
dpIst += minutenMonat;
|
|
|
|
decimal arbeitstage = GetArbeitstageImBereich(start, ende, emp);
|
|
|
|
var arbeitstageProWoche = anzWochentage;
|
|
if (c != null && c.WeeklyDays.HasValue && c.WeeklyDays.Value > 0)
|
|
arbeitstageProWoche = c.WeeklyDays.Value;
|
|
|
|
var soll = GetSollNachArbeitszeiten(emp, start, ende);
|
|
if (!soll.HasValue)
|
|
{
|
|
var stundenSollProMonat = BerechneSollProMonat(emp, start, ende);
|
|
soll = stundenSollProMonat;
|
|
}
|
|
|
|
if (!soll.HasValue)
|
|
{
|
|
|
|
if (!soll.HasValue)
|
|
{
|
|
decimal maxSollMonat = arbeitstage * (maxWochenstunden / arbeitstageProWoche);
|
|
if (arbeitstageProWoche <= 5)
|
|
{
|
|
maxSollMonat = arbeitstage * (maxWochenstunden / 5);
|
|
}
|
|
|
|
soll = maxSollMonat * bu;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
decimal sollMitarbeiter = soll ?? 0;
|
|
|
|
MitarbeiterstundenkontoRO.EmployeeDetail ed = new MitarbeiterstundenkontoRO.EmployeeDetail();
|
|
ed.SollProTag = weeklyTotalHours / arbeitstageProWoche;
|
|
ed.ArbeitstageProWoche = arbeitstageProWoche;
|
|
ed.ArbeitstageImBerichtszeitraum = arbeitstage;
|
|
ed.SollProWoche = BerechneSollProWoche(c, ed);
|
|
ed.Employee = emp;
|
|
ed.StundenSollMax = sollMitarbeiter;
|
|
|
|
AktuellBearbeitetesEmployeeDetail = ed;
|
|
var ath = GetAbsenceTimeDaysWithWorkWeeks(emp, start, ende, arbeitstageProWoche);
|
|
|
|
|
|
|
|
ed.AbsenceTimeDays = ath;
|
|
|
|
|
|
if (ath.TageGesamt.HasValue)
|
|
{
|
|
var minusStunden = BerechneStundenDurchAbwesenheiten(ath, start, ende, c, ed);
|
|
sollMitarbeiter -= minusStunden;
|
|
if (sollMitarbeiter < 0 && !StundenDurchAbwesenheitenDuerfenNegativSein())
|
|
{
|
|
sollMitarbeiter = 0;
|
|
}
|
|
}
|
|
|
|
decimal stundenMonat = minutenMonat / 60m;
|
|
stundenMonat = ((flsIst * AddRateFactorToIst()) + mittelbarIst) / 60m;
|
|
|
|
|
|
if (!ueberstunden.HasValue)
|
|
ueberstunden = 0;
|
|
ueberstunden += stundenMonat - sollMitarbeiter;
|
|
|
|
decimal extraUeberstunden = 0;
|
|
BerechneExtraUeberstunden(emp, start, ende, out extraUeberstunden);
|
|
ueberstunden += extraUeberstunden;
|
|
|
|
alteExtraUeberstunden = extraUeberstunden;
|
|
|
|
ed.StundenSollOhneUrlaubKrankheit = sollMitarbeiter;
|
|
ed.StundenGesamtIst = stundenMonat;
|
|
ed.Ueberstunden = ueberstunden;
|
|
ed.UeberstundenVormonat = edTotal.Ueberstunden;
|
|
|
|
edTotal.StundenSollOhneUrlaubKrankheit += sollMitarbeiter;
|
|
edTotal.StundenSollMax += soll ?? 0;
|
|
edTotal.FlsIst += flsIst / 60;
|
|
edTotal.MittelbarIst += mittelbarIst / 60;
|
|
edTotal.StundenGesamtIst += stundenMonat;
|
|
edTotal.Ueberstunden = ueberstunden;
|
|
|
|
start = ende;
|
|
}
|
|
|
|
if (start >= contractEnd)
|
|
{
|
|
var ctemp = GetNextValidContractForDate(start, emp);
|
|
if (ctemp != null && ctemp.Oid != c.Oid)
|
|
{
|
|
c = ctemp;
|
|
}
|
|
else
|
|
{
|
|
c = null;
|
|
}
|
|
|
|
if (c != null && c.EntryDate.HasValue && c.EntryDate >= start)
|
|
start = c.EntryDate.Value;
|
|
else
|
|
{
|
|
start = start.AddMonths(1);
|
|
c = GetNextValidContractForDate(start, emp);
|
|
if (c != null && c.EntryDate.HasValue && c.EntryDate >= start)
|
|
start = c.EntryDate.Value;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return dpIst;
|
|
}
|
|
|
|
// Claude: Lädt DienstEintraege und Dienstvertretungen einmalig per DAO und gruppiert nach EmployeeOid (statt pro Mitarbeiter den DAO erneut zu fragen)
|
|
private void EnsureDienstplanDataLoaded()
|
|
{
|
|
if (_allDienstEintraegeByEmpCache == null)
|
|
{
|
|
_allDienstEintraegeByEmpCache = new Dictionary<long, List<DienstEintrag>>();
|
|
foreach (var d in DAOFactory.GenericDAO.GetAllActive<DienstEintrag>())
|
|
{
|
|
List<DienstEintrag> list;
|
|
if (!_allDienstEintraegeByEmpCache.TryGetValue(d.EmployeeOid, out list))
|
|
{
|
|
list = new List<DienstEintrag>();
|
|
_allDienstEintraegeByEmpCache[d.EmployeeOid] = list;
|
|
}
|
|
list.Add(d);
|
|
}
|
|
}
|
|
if (_allDienstvertretungenByEmpCache == null)
|
|
{
|
|
_allDienstvertretungenByEmpCache = new Dictionary<long, List<Dienstvertretung>>();
|
|
foreach (var d in DAOFactory.GenericDAO.GetAllActive<Dienstvertretung>())
|
|
{
|
|
List<Dienstvertretung> list;
|
|
if (!_allDienstvertretungenByEmpCache.TryGetValue(d.EmployeeOid, out list))
|
|
{
|
|
list = new List<Dienstvertretung>();
|
|
_allDienstvertretungenByEmpCache[d.EmployeeOid] = list;
|
|
}
|
|
list.Add(d);
|
|
}
|
|
}
|
|
}
|
|
|
|
private Dictionary<string, List<DienstEintrag>> CreateDiensteintragDictByDate(List<DienstEintrag> eintraege)
|
|
{
|
|
var dict = new Dictionary<string, List<DienstEintrag>>();
|
|
|
|
foreach (var item in eintraege)
|
|
{
|
|
//if (!IsServiceRecordFehlzeit(item))
|
|
//{
|
|
|
|
string key = string.Format("{0:yyyyMM}", item.Datum);
|
|
|
|
List<DienstEintrag> list;
|
|
if (!dict.ContainsKey(key))
|
|
{
|
|
list = new List<DienstEintrag>();
|
|
dict.Add(key, list);
|
|
}
|
|
else
|
|
{
|
|
list = dict[key];
|
|
}
|
|
list.Add(item);
|
|
|
|
//}
|
|
}
|
|
return dict;
|
|
}
|
|
|
|
private List<DienstEintrag> VertretungenUmwandeln(List<Dienstvertretung> vertretungen)
|
|
{
|
|
List<DienstEintrag> eintraege = new List<DienstEintrag>();
|
|
|
|
foreach (var v in vertretungen)
|
|
{
|
|
eintraege.Add(new DienstEintrag()
|
|
{
|
|
Datum = v.Datum,
|
|
DauerInStunden = v.DauerInStunden,
|
|
DienstOid = v.DienstOid,
|
|
EmployeeOid = v.EmployeeOid,
|
|
Endzeit = v.Endzeit,
|
|
Notice = v.Notice,
|
|
Startzeit = v.Startzeit,
|
|
WohnheimOid = v.WohnheimOid,
|
|
Zeile = v.Zeile
|
|
});
|
|
}
|
|
|
|
return eintraege;
|
|
}
|
|
#endregion
|
|
|
|
|
|
protected override void StarteBerechnung(DateTime berichtsAnfang, DateTime berichtsEnde, MitarbeiterstundenkontoRO.EmployeeDetail empDetail)
|
|
{
|
|
//do nothing
|
|
}
|
|
|
|
|
|
protected override AbsenceTimeHours GetAbwesenheitenDurchServiceRecords(MitarbeiterstundenkontoRO.EmployeeDetail empDetail, Contract c, IList<StundenkontoServiceRecord> groupFilteredRecords, DateTime berichtsAnfang, DateTime berichtsEnde)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
protected override decimal BerechneFlsSoll(Employee emp, Contract contract, DateTime berichtsAnfang, DateTime berichtsEnde, decimal tatSollStdImVertragsUndBerichtzeitraum, decimal? stundenSollProMonat)
|
|
{
|
|
var festeflsSollProMonat = BerechneFlsSollProMonat(emp, berichtsAnfang, berichtsEnde);
|
|
decimal flsSollImVertragsUndBerichtzeitraum = 0;
|
|
|
|
if (festeflsSollProMonat.HasValue)
|
|
{
|
|
return festeflsSollProMonat.Value;
|
|
//flsSollImVertragsUndBerichtzeitraum = tatSollStdImVertragsUndBerichtzeitraum;
|
|
|
|
//if (stundenSollProMonat.HasValue)
|
|
//{
|
|
// flsSollImVertragsUndBerichtzeitraum *= (festeflsSollProMonat.Value / stundenSollProMonat.Value);
|
|
//}
|
|
}
|
|
else
|
|
{
|
|
if (contract != null && contract.WeeklyFLS.HasValue)
|
|
{
|
|
flsSollImVertragsUndBerichtzeitraum = tatSollStdImVertragsUndBerichtzeitraum;
|
|
|
|
if (HatArbeitszeitImZeitraum(emp.Arbeitszeiten, berichtsAnfang, berichtsEnde))
|
|
{
|
|
var wochenArbeitszeit = GetWochenArbeitszeit(emp.Arbeitszeiten, berichtsAnfang, berichtsEnde);
|
|
|
|
if (wochenArbeitszeit > 0)
|
|
{
|
|
flsSollImVertragsUndBerichtzeitraum *= contract.WeeklyFLS.Value / wochenArbeitszeit;
|
|
}
|
|
|
|
}
|
|
else if (contract.WeeklyTotalHours.HasValue && contract.WeeklyTotalHours.Value != 0)
|
|
{
|
|
flsSollImVertragsUndBerichtzeitraum *= contract.WeeklyFLS.Value / contract.WeeklyTotalHours.Value;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
return flsSollImVertragsUndBerichtzeitraum;
|
|
}
|
|
|
|
private decimal GetWochenArbeitszeit(IList<Arbeitszeit> arbeitszeiten, DateTime start, DateTime ende)
|
|
{
|
|
if (arbeitszeiten != null)
|
|
{
|
|
foreach (var az in arbeitszeiten)
|
|
{
|
|
if ((!az.GueltigVon.HasValue || az.GueltigVon.Value < ende) &&
|
|
(!az.GueltigBis.HasValue || az.GueltigBis.Value >= start))
|
|
{
|
|
|
|
DateTime wochenStart = start;
|
|
while (wochenStart.DayOfWeek != DayOfWeek.Monday)
|
|
{
|
|
wochenStart = wochenStart.AddDays(1);
|
|
}
|
|
|
|
decimal stunden = 0;
|
|
for (int i = 0; i < 7; i++)
|
|
{
|
|
stunden += GetArbeitszeitStundenFuerTag(az, wochenStart);
|
|
|
|
wochenStart = wochenStart.AddDays(1);
|
|
}
|
|
|
|
return stunden;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
protected override bool StundenDurchAbwesenheitenDuerfenNegativSein()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
protected override bool SchneideZuvieleAbwesenheitsstundenAb()
|
|
{
|
|
// sorgt dafür dass bei Überschreitung des Tagessolls nicht die Abwesenheitszeit verwendet wird
|
|
// wenn auch Überschreitung Wochensoll möglich sein soll muss zusätzlich GetSollStundenForAbsenceTime angepasst werden
|
|
return true;
|
|
}
|
|
|
|
protected override decimal? GetSollNachArbeitszeiten(Employee emp, DateTime start, DateTime ende)
|
|
{
|
|
if (BerechneSollNurBisZumAktuellenTag())
|
|
{
|
|
if (ende > DateTime.Now.Date.AddDays(1))
|
|
{
|
|
ende = DateTime.Now.Date.AddDays(1);
|
|
}
|
|
}
|
|
|
|
if (emp.Arbeitszeiten != null && emp.Arbeitszeiten.Count > 0)
|
|
{
|
|
if (HatArbeitszeitImZeitraum(emp.Arbeitszeiten, start, ende))
|
|
{
|
|
decimal soll = 0;
|
|
|
|
DateTime startTemp = start;
|
|
|
|
while (startTemp < ende)
|
|
{
|
|
soll += GetSollNachArbeitszeitenFuerTag(emp, startTemp);
|
|
startTemp = startTemp.AddDays(1);
|
|
}
|
|
|
|
return soll;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
protected override bool HatArbeitszeitImZeitraum(IList<Arbeitszeit> arbeitszeiten, DateTime start, DateTime ende)
|
|
{
|
|
// Claude: Cache für den häufigen (date == date)-Aufruf aus CalculateAbsenceTimesForDay/BerechneSollStundenFuerTag
|
|
if (arbeitszeiten != null && start == ende)
|
|
{
|
|
if (_hatArbeitszeitImZeitraumCache == null)
|
|
{
|
|
_hatArbeitszeitImZeitraumCache = new Dictionary<IList<Arbeitszeit>, Dictionary<DateTime, bool>>(ReferenceEqualityComparer<IList<Arbeitszeit>>.Default);
|
|
}
|
|
Dictionary<DateTime, bool> innerCache;
|
|
if (!_hatArbeitszeitImZeitraumCache.TryGetValue(arbeitszeiten, out innerCache))
|
|
{
|
|
innerCache = new Dictionary<DateTime, bool>();
|
|
_hatArbeitszeitImZeitraumCache[arbeitszeiten] = innerCache;
|
|
}
|
|
bool cachedResult;
|
|
if (innerCache.TryGetValue(start, out cachedResult))
|
|
{
|
|
return cachedResult;
|
|
}
|
|
cachedResult = HatArbeitszeitImZeitraumIntern(arbeitszeiten, start, ende);
|
|
innerCache[start] = cachedResult;
|
|
return cachedResult;
|
|
}
|
|
return HatArbeitszeitImZeitraumIntern(arbeitszeiten, start, ende);
|
|
}
|
|
|
|
// Claude: Ursprüngliche Logik, ohne Cache - aus HatArbeitszeitImZeitraum extrahiert
|
|
private static bool HatArbeitszeitImZeitraumIntern(IList<Arbeitszeit> arbeitszeiten, DateTime start, DateTime ende)
|
|
{
|
|
if (arbeitszeiten != null)
|
|
{
|
|
foreach (var az in arbeitszeiten)
|
|
{
|
|
if ((!az.GueltigVon.HasValue || az.GueltigVon.Value < ende) && (!az.GueltigBis.HasValue || az.GueltigBis.Value >= start) && az.ArbeitszeitEintraege != null && az.ArbeitszeitEintraege.Count > 0)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
protected override decimal GetSollNachArbeitszeitenFuerTag(Employee emp, DateTime tag)
|
|
{
|
|
decimal soll = 0;
|
|
var fak = GetFeiertagsFaktor(tag);
|
|
|
|
if (BerechneFeiertageAlsArbeitszeit())
|
|
{
|
|
if (fak == 1)
|
|
{
|
|
fak = 0;
|
|
}
|
|
}
|
|
|
|
if (fak < 1)
|
|
{
|
|
if (emp.Arbeitszeiten != null)
|
|
{
|
|
foreach (var az in emp.Arbeitszeiten)
|
|
{
|
|
if ((!az.GueltigVon.HasValue || az.GueltigVon.Value <= tag) &&
|
|
(!az.GueltigBis.HasValue || az.GueltigBis.Value >= tag))
|
|
{
|
|
var std = GetArbeitszeitStundenFuerTag(az, tag);
|
|
soll += (std * (1 - fak));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return soll;
|
|
}
|
|
|
|
// Claude: Zurückgesetzt auf Original-Implementierung. Cache hat in Einzelfällen abweichende Werte erzeugt.
|
|
protected decimal GetArbeitszeitStundenFuerTag(Arbeitszeit az, DateTime tag)
|
|
{
|
|
decimal stunden = 0;
|
|
foreach (var ae in az.ArbeitszeitEintraege)
|
|
{
|
|
if (ae.IstAmGleichenWochentag(tag))
|
|
{
|
|
String dateStr1 = String.Format("{0:dd.MM.yyyy} {1}", tag, ae.UhrzeitVon);
|
|
String dateStr2 = String.Format("{0:dd.MM.yyyy} {1}", tag, ae.UhrzeitBis);
|
|
|
|
DateTime date1;
|
|
DateTime date2;
|
|
|
|
if (DateTime.TryParse(dateStr1, out date1) && DateTime.TryParse(dateStr2, out date2))
|
|
{
|
|
|
|
stunden += (decimal)date2.Subtract(date1).TotalHours;
|
|
}
|
|
}
|
|
}
|
|
|
|
return stunden;
|
|
}
|
|
|
|
protected override void CalculateEmployeeSalary(MitarbeiterstundenkontoRO.EmployeeDetail empDetail, DateTime start, DateTime ende)
|
|
{
|
|
decimal maxpayment = 0;
|
|
|
|
Contract cstart = empDetail.Employee.GetValidContractForDate(start);
|
|
Contract cend = empDetail.Employee.GetValidContractForDate(ende);
|
|
if (cstart != null && cstart.HourlyRate.HasValue && cstart.WeeklyTotalHours.HasValue && cend != null)
|
|
{
|
|
if (cstart.Oid.Value == cend.Oid.Value || !cend.EntryDate.HasValue)
|
|
{
|
|
maxpayment = cstart.HourlyRate.Value * cstart.WeeklyTotalHours.Value * 4;
|
|
}
|
|
else
|
|
{
|
|
decimal totalHoursPerDay = cstart.WeeklyTotalHours.Value / 7m;
|
|
DateTime end = cend.EntryDate.Value;
|
|
TimeSpan ts = end.Subtract(start);
|
|
int days = (int)ts.TotalDays;
|
|
|
|
maxpayment = totalHoursPerDay * cstart.HourlyRate.Value * days;
|
|
|
|
Contract c2 = empDetail.Employee.GetValidContractForDate(end.AddDays(1));
|
|
if (c2 != null && c2.HourlyRate.HasValue && c2.WeeklyTotalHours.HasValue)
|
|
{
|
|
decimal totalHoursPerDay2 = c2.WeeklyTotalHours.Value / 7m;
|
|
TimeSpan ts2 = ende.Subtract(end);
|
|
int days2 = (int)ts2.TotalDays + 1;
|
|
|
|
maxpayment += totalHoursPerDay2 * c2.HourlyRate.Value * days2;
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
empDetail.EmployeeSalaryByContract = maxpayment;
|
|
|
|
decimal salary = 0;
|
|
|
|
if (empDetail.ServiceRecordsInSelectedTimeSpan != null)
|
|
{
|
|
foreach (var sr in empDetail.ServiceRecordsInSelectedTimeSpan)
|
|
{
|
|
if (sr.ServiceDescription.Name != "Freizeit" || sr.ServiceDescription.Category.Name != "Selbstzahler Freizeit")
|
|
{
|
|
Contract con = empDetail.Employee.GetValidContractForDate(sr.Start);
|
|
if (con != null && con.HourlyRate.HasValue)
|
|
{
|
|
salary += con.HourlyRate.Value * (sr.RoundedDuration / 60);
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
empDetail.EmployeeSalaryByHours = salary;
|
|
}
|
|
|
|
protected override ServiceRecordSummary BerechneStunden(MitarbeiterstundenkontoRO.EmployeeDetail empDetail, IList<StundenkontoServiceRecord> groupFilteredRecords, DateTime berichtsAnfang, DateTime berichtsEnde)
|
|
{
|
|
decimal flsGesamt = 0;
|
|
decimal arbeitszeit = 0;
|
|
|
|
var sum = new ServiceRecordSummary();
|
|
sum.RecordsImBerichtszeitraum = new List<StundenkontoServiceRecord>();
|
|
|
|
empDetail.ServiceRecordsInSelectedTimeSpan = new List<StundenkontoServiceRecord>();
|
|
|
|
foreach (var item in groupFilteredRecords)
|
|
{
|
|
if (!IsServiceRecordFehlzeit(item))
|
|
{
|
|
if (item.Start >= berichtsAnfang && item.Start < berichtsEnde)
|
|
{
|
|
//String test = String.Format("{0:dd.MM.yyyy HH:mm}, {1:0} Minuten, {2}, {3}", item.Start, item.RoundedDuration, item.ServiceDescription.Name, item.Notice);
|
|
|
|
HandleServiceRecord(empDetail, item);
|
|
|
|
if (Settings.ShowWorktime && Settings.EnableArbeitszeiterfassung)
|
|
{
|
|
empDetail.ServiceRecordsInSelectedTimeSpan.Add(item);
|
|
|
|
decimal duration = GetDuration(item, empDetail);
|
|
|
|
if (IstArbeitszeit(item))
|
|
{
|
|
arbeitszeit += duration;
|
|
}
|
|
else if (item.ServiceDescription != null && item.ServiceDescription.Category != null && item.ServiceDescription.Category.IsBillable)
|
|
{
|
|
flsGesamt += duration;
|
|
}
|
|
}
|
|
else if (AddServiceRecordToDuration(item))
|
|
{
|
|
empDetail.ServiceRecordsInSelectedTimeSpan.Add(item);
|
|
|
|
decimal duration = GetDuration(item, empDetail);
|
|
|
|
if (item.ServiceDescription != null &&
|
|
item.ServiceDescription.Category != null &&
|
|
item.ServiceDescription.Category.IsBillable)
|
|
sum.IstAbrechenbar += duration;
|
|
else
|
|
sum.IstNichtAbrechenbar += duration;
|
|
|
|
|
|
//Prüfe ProzentStundenkonto, wenn der Wert != 100 ist, dann packe die prozentuale Differenz von abbrechenbar auf nicht abbrechenbar
|
|
decimal prozent = 100;
|
|
if (item.ServiceDescription.ProzentStundenkonto.HasValue)
|
|
{
|
|
prozent = item.ServiceDescription.ProzentStundenkonto.Value;
|
|
}
|
|
else if (item.ServiceDescription.Category.ProzentStundenkonto.HasValue)
|
|
{
|
|
prozent = item.ServiceDescription.Category.ProzentStundenkonto.Value;
|
|
}
|
|
if (prozent == 0)
|
|
{
|
|
int test = 0;
|
|
}
|
|
if (prozent != 100 && prozent > 0)
|
|
{
|
|
var ohneProzent = duration / prozent * 100;
|
|
|
|
var diff = duration - ohneProzent;
|
|
|
|
sum.IstAbrechenbar -= diff;
|
|
sum.IstNichtAbrechenbar += diff;
|
|
}
|
|
|
|
|
|
|
|
}
|
|
sum.RecordsImBerichtszeitraum.Add(item);
|
|
}
|
|
}
|
|
}
|
|
if (Settings.ShowWorktime && Settings.EnableArbeitszeiterfassung)
|
|
{
|
|
sum.IstAbrechenbar = flsGesamt;
|
|
sum.IstNichtAbrechenbar = arbeitszeit - flsGesamt;
|
|
}
|
|
return sum;
|
|
}
|
|
|
|
public override bool UseServiceDescriptionPercentage()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
public override decimal BerechneAutoPausen(IList<StundenkontoServiceRecord> recordsThisMonth)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
public override decimal BerechnePauseMinuten(IList<StundenkontoServiceRecord> srList)
|
|
{
|
|
decimal pausenZeit = 0;
|
|
|
|
List<StundenkontoServiceRecord> pausen = new List<StundenkontoServiceRecord>();
|
|
List<StundenkontoServiceRecord> nichtpausen = new List<StundenkontoServiceRecord>();
|
|
|
|
foreach (var sr in srList)
|
|
{
|
|
if (sr.ServiceDescription.Name == "Pause")
|
|
{
|
|
pausen.Add(sr);
|
|
}
|
|
else
|
|
{
|
|
nichtpausen.Add(sr);
|
|
}
|
|
}
|
|
|
|
foreach (var pause in pausen)
|
|
{
|
|
foreach (var arbeitszeit in nichtpausen)
|
|
{
|
|
var pauseSpan = new DateTimeSpan
|
|
{
|
|
StartDateTime = pause.Start,
|
|
EndDateTime = pause.Start.AddMinutes((double)pause.RoundedDuration)
|
|
};
|
|
|
|
var arbeitszeitSpan = new DateTimeSpan
|
|
{
|
|
StartDateTime = arbeitszeit.Start,
|
|
EndDateTime = arbeitszeit.Start.AddMinutes((double)arbeitszeit.RoundedDuration)
|
|
};
|
|
|
|
if (arbeitszeit.GroupOid.HasValue)
|
|
{
|
|
arbeitszeitSpan.EndDateTime =
|
|
arbeitszeit.Start.AddMinutes((double)(arbeitszeit.GroupRoundedDuration ?? 0));
|
|
}
|
|
|
|
|
|
if (pauseSpan.Overlaps(arbeitszeitSpan))
|
|
{
|
|
DateTime startUeberlappung = pauseSpan.StartDateTime;
|
|
if (arbeitszeitSpan.StartDateTime > startUeberlappung)
|
|
startUeberlappung = arbeitszeitSpan.StartDateTime;
|
|
|
|
DateTime endUeberlappung = pauseSpan.EndDateTime;
|
|
|
|
if (arbeitszeitSpan.EndDateTime < endUeberlappung)
|
|
endUeberlappung = arbeitszeitSpan.EndDateTime;
|
|
|
|
var overlapping = (decimal)endUeberlappung.Subtract(startUeberlappung).TotalMinutes;
|
|
|
|
pausenZeit += overlapping;
|
|
}
|
|
}
|
|
}
|
|
return pausenZeit;
|
|
}
|
|
|
|
public override bool SollPauseBerechnen()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
public override bool BerechneSollNurBisZumAktuellenTag()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
public override List<MitarbeiterstundenkontoRO.EmployeeDetail> FilterEmployeeList(List<MitarbeiterstundenkontoRO.EmployeeDetail> employeeDetailList)
|
|
{
|
|
List<MitarbeiterstundenkontoRO.EmployeeDetail> newEmpList = new List<MitarbeiterstundenkontoRO.EmployeeDetail>();
|
|
|
|
foreach (var ed in employeeDetailList)
|
|
{
|
|
if (ed.Employee.IsActive == ActivationTypeId.Active)
|
|
{
|
|
newEmpList.Add(ed);
|
|
}
|
|
else if (ed.Employee.IsActive == ActivationTypeId.Archived)
|
|
{
|
|
if (ed.StundenGesamtIst != 0)
|
|
{
|
|
newEmpList.Add(ed);
|
|
}
|
|
}
|
|
}
|
|
|
|
return newEmpList;
|
|
}
|
|
|
|
public override void HandleServiceRecords(MitarbeiterstundenkontoRO.EmployeeDetail empDetail, IList<StundenkontoServiceRecord> groupFilteredRecords, Contract contract)
|
|
{
|
|
//do nothing
|
|
}
|
|
|
|
public override void CorrectOverlappingTimes(IList<StundenkontoServiceRecord> serviceRecords)
|
|
{
|
|
List<StundenkontoServiceRecord> oldRecords = new List<StundenkontoServiceRecord>();
|
|
|
|
foreach (var newRecord in serviceRecords)
|
|
{
|
|
if (!CorrectOverlappingTime(newRecord))
|
|
{
|
|
continue;
|
|
}
|
|
if (newRecord.Start.Second > 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
bool isGroup = false;
|
|
var minuten = newRecord.RoundedDuration;
|
|
if (newRecord.GroupRoundedDuration.HasValue)
|
|
{
|
|
minuten = newRecord.GroupRoundedDuration.Value;
|
|
isGroup = true;
|
|
}
|
|
var newSpan = new DateTimeSpan
|
|
{
|
|
StartDateTime = newRecord.Start,
|
|
EndDateTime = newRecord.Start.AddMinutes((double)minuten)
|
|
};
|
|
|
|
foreach (var oldRecord in oldRecords)
|
|
{
|
|
if (!CorrectOverlappingTime(oldRecord, newRecord))
|
|
{
|
|
continue;
|
|
}
|
|
var oldSpan = new DateTimeSpan
|
|
{
|
|
StartDateTime = oldRecord.Start,
|
|
EndDateTime = oldRecord.Start.AddMinutes((double)oldRecord.RoundedDuration)
|
|
};
|
|
if (oldRecord.GroupRoundedDuration.HasValue)
|
|
{
|
|
oldSpan.EndDateTime = oldRecord.Start.AddMinutes((double)oldRecord.GroupRoundedDuration);
|
|
}
|
|
|
|
if (oldSpan.Overlaps(newSpan))
|
|
{
|
|
DateTime start = newSpan.StartDateTime;
|
|
if (oldSpan.StartDateTime > start)
|
|
start = oldSpan.StartDateTime;
|
|
|
|
DateTime end = newSpan.EndDateTime;
|
|
|
|
if (oldSpan.EndDateTime < end)
|
|
end = oldSpan.EndDateTime;
|
|
|
|
|
|
if (newSpan.StartDateTime >= oldSpan.StartDateTime)
|
|
{
|
|
newSpan.StartDateTime = oldSpan.EndDateTime;
|
|
if (newSpan.StartDateTime > newSpan.EndDateTime)
|
|
{
|
|
newSpan.StartDateTime = newSpan.EndDateTime;
|
|
}
|
|
newRecord.Start = newSpan.StartDateTime;
|
|
newRecord.End = newSpan.EndDateTime;
|
|
|
|
if (!isGroup)
|
|
{
|
|
newRecord.RoundedDuration =
|
|
(decimal)newRecord.End.Subtract(newRecord.Start).TotalMinutes;
|
|
}
|
|
else
|
|
{
|
|
newRecord.GroupRoundedDuration = (decimal)newRecord.End.Subtract(newRecord.Start).TotalMinutes;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
oldSpan.StartDateTime = newSpan.EndDateTime;
|
|
if (oldSpan.StartDateTime > oldSpan.EndDateTime)
|
|
{
|
|
oldSpan.StartDateTime = oldSpan.EndDateTime;
|
|
}
|
|
oldRecord.Start = oldSpan.StartDateTime;
|
|
oldRecord.End = oldSpan.EndDateTime;
|
|
|
|
|
|
if (!oldRecord.GroupRoundedDuration.HasValue)
|
|
//if (!isGroup)
|
|
{
|
|
oldRecord.RoundedDuration = (decimal)oldRecord.End.Subtract(oldRecord.Start).TotalMinutes;
|
|
}
|
|
else
|
|
{
|
|
oldRecord.GroupRoundedDuration = (decimal)oldRecord.End.Subtract(oldRecord.Start).TotalMinutes;
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
}
|
|
oldRecords.Add(newRecord);
|
|
|
|
}
|
|
}
|
|
|
|
public override bool CorrectOverlappingTime(StundenkontoServiceRecord newRecord)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
public override bool CorrectOverlappingTime(StundenkontoServiceRecord record1, StundenkontoServiceRecord record2)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
public override decimal? BerechneSollProMonat(Employee emp, DateTime start, DateTime ende)
|
|
{
|
|
Contract c = GetContractForDate(emp, start, ende);
|
|
|
|
|
|
if (c != null && c.MonthlyTotalHours.HasValue && c.MonthlyTotalHours.Value > 0)
|
|
{
|
|
return c.MonthlyTotalHours.Value;
|
|
|
|
DateTime monatsAnfang = start;
|
|
DateTime monatsEnde = ende;
|
|
|
|
if (c.EntryDate.HasValue && c.EntryDate > monatsAnfang)
|
|
{
|
|
monatsAnfang = c.EntryDate.Value;
|
|
}
|
|
if (c.ContractEndDate.HasValue && c.ContractEndDate < monatsEnde)
|
|
{
|
|
monatsEnde = c.ContractEndDate.Value.Date.AddDays(1);
|
|
}
|
|
|
|
if (monatsAnfang < monatsEnde)
|
|
{
|
|
//if (monatsAnfang.Day == 1 && monatsEnde.Day == 1)
|
|
//{
|
|
return c.MonthlyTotalHours.Value;
|
|
//}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public override decimal? BerechneFlsSollProMonat(Employee emp, DateTime start, DateTime ende)
|
|
{
|
|
Contract c = GetContractForDate(emp, start, ende);
|
|
|
|
|
|
if (c != null && c.MonthlyFLS.HasValue && c.MonthlyFLS.Value > 0)
|
|
{
|
|
return c.MonthlyFLS.Value;
|
|
|
|
DateTime monatsAnfang = start;
|
|
DateTime monatsEnde = ende;
|
|
|
|
if (c.EntryDate.HasValue && c.EntryDate > monatsAnfang)
|
|
{
|
|
monatsAnfang = c.EntryDate.Value;
|
|
}
|
|
if (c.ContractEndDate.HasValue && c.ContractEndDate < monatsEnde)
|
|
{
|
|
monatsEnde = c.ContractEndDate.Value.Date.AddDays(1);
|
|
}
|
|
|
|
if (monatsAnfang < monatsEnde)
|
|
{
|
|
//if (monatsAnfang.Day == 1 && monatsEnde.Day == 1)
|
|
//{
|
|
return c.MonthlyFLS.Value;
|
|
//}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public override void HandleServiceRecord(MitarbeiterstundenkontoRO.EmployeeDetail emp, StundenkontoServiceRecord item)
|
|
{
|
|
//do nothing
|
|
}
|
|
|
|
public override bool ShouldCreateDayDetails()
|
|
{
|
|
//if (Settings.ShowWorktime && Settings.EnableArbeitszeiterfassung)
|
|
//{
|
|
// return true;
|
|
//}
|
|
//CB ab 04.09.2024 immer true
|
|
return true;
|
|
}
|
|
|
|
public override bool ShouldCreateDayDetailsForEachServiceRecord()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
public override IList<MitarbeiterstundenkontoRO.DayDetail> CreateDayDetails(DateTime berichtStart, DateTime berichtEnde, MitarbeiterstundenkontoRO.EmployeeDetail ed, IList<StundenkontoServiceRecord> recordsAll)
|
|
{
|
|
if (ShouldCreateDayDetailsForEachServiceRecord())
|
|
{
|
|
return CreateDayDetailsForEachServiceRecord(berichtStart, berichtEnde, ed, recordsAll);
|
|
}
|
|
|
|
Dictionary<int, MitarbeiterstundenkontoRO.DayDetail> dayToDetail = new Dictionary<int, MitarbeiterstundenkontoRO.DayDetail>();
|
|
|
|
for (int i = berichtStart.Day; i <= berichtEnde.AddDays(-1).Day; i++)
|
|
{
|
|
MitarbeiterstundenkontoRO.DayDetail dd = CreateDayDetail(new DateTime(berichtStart.Year, berichtStart.Month, i), ed);
|
|
dayToDetail.Add(i, dd);
|
|
}
|
|
|
|
foreach (var sr in recordsAll)
|
|
{
|
|
if (sr.Start >= berichtStart && sr.Start < berichtEnde)
|
|
{
|
|
if (sr.Start.Year == 2023 && sr.Start.Month == 10 && sr.Start.Day == 3)
|
|
{
|
|
int test = 0;
|
|
}
|
|
var dd = dayToDetail[sr.Start.Day];
|
|
|
|
if (!IsServiceRecordFehlzeit(sr) && AddServiceRecordToDuration(sr))
|
|
{
|
|
decimal duration = GetDuration(sr, ed);
|
|
dd.MinutesTotal += duration;
|
|
TimeSpan tspan = TimeSpan.FromMinutes((double)dd.MinutesTotal);
|
|
dd.TimeTotal = String.Format("{0:00}:{1:00}", tspan.Hours, tspan.Minutes);
|
|
if (dd.ServiceRecords == null)
|
|
dd.ServiceRecords = new List<StundenkontoServiceRecord>();
|
|
dd.ServiceRecords.Add(sr);
|
|
|
|
CalculateSpecialHours(dd, sr, duration);
|
|
}
|
|
}
|
|
}
|
|
|
|
//Berechne von bis Zeiten
|
|
foreach (var ddKeyValue in dayToDetail)
|
|
{
|
|
var dd = ddKeyValue.Value;
|
|
if (dd.ZusammenhaengendeZeitraeume == null)
|
|
{
|
|
dd.ZusammenhaengendeZeitraeume = new List<DateTimeSpan>();
|
|
}
|
|
|
|
if (dd.ServiceRecords != null)
|
|
{
|
|
var orderedList = dd.ServiceRecords.OrderBy(i => i.Start);
|
|
|
|
DateTime lastEnd = DateTime.MinValue;
|
|
var gapCount = -1;
|
|
|
|
foreach (var sr in orderedList)
|
|
{
|
|
if (sr.Start.Hour == 0 && sr.Start.Second > 0)
|
|
{
|
|
continue;
|
|
}
|
|
decimal duration = GetDuration(sr, ed);
|
|
|
|
|
|
DateTime dtEnd = sr.Start.AddMinutes((double)duration);
|
|
if (sr.Start > lastEnd)
|
|
{
|
|
gapCount++;
|
|
|
|
}
|
|
if (dtEnd > lastEnd)
|
|
{
|
|
lastEnd = dtEnd;
|
|
}
|
|
|
|
DateTimeSpan currentSpan = null;
|
|
if (gapCount + 1 > dd.ZusammenhaengendeZeitraeume.Count)
|
|
{
|
|
currentSpan = new DateTimeSpan();
|
|
currentSpan.StartDateTime = DateTime.MaxValue;
|
|
currentSpan.EndDateTime = DateTime.MinValue;
|
|
dd.ZusammenhaengendeZeitraeume.Add(currentSpan);
|
|
}
|
|
else
|
|
{
|
|
currentSpan = dd.ZusammenhaengendeZeitraeume[gapCount];
|
|
}
|
|
|
|
if (sr.Start < currentSpan.StartDateTime)
|
|
{
|
|
currentSpan.StartDateTime = sr.Start;
|
|
}
|
|
if (dtEnd > currentSpan.EndDateTime)
|
|
{
|
|
currentSpan.EndDateTime = dtEnd;
|
|
}
|
|
|
|
if (gapCount == 0)
|
|
{
|
|
if (!dd.StartDateTime1.HasValue || sr.Start < dd.StartDateTime1)
|
|
{
|
|
dd.StartDateTime1 = sr.Start;
|
|
}
|
|
if (!dd.EndDateTime1.HasValue || dtEnd > dd.EndDateTime1)
|
|
{
|
|
dd.EndDateTime1 = dtEnd;
|
|
}
|
|
}
|
|
else if (gapCount == 1)
|
|
{
|
|
if (!dd.StartDateTime2.HasValue || sr.Start < dd.StartDateTime2)
|
|
{
|
|
dd.StartDateTime2 = sr.Start;
|
|
}
|
|
if (!dd.EndDateTime2.HasValue || dtEnd > dd.EndDateTime2)
|
|
{
|
|
dd.EndDateTime2 = dtEnd;
|
|
}
|
|
}
|
|
else if (gapCount == 2)
|
|
{
|
|
if (!dd.StartDateTime3.HasValue || sr.Start < dd.StartDateTime3)
|
|
{
|
|
dd.StartDateTime3 = sr.Start;
|
|
}
|
|
if (!dd.EndDateTime3.HasValue || dtEnd > dd.EndDateTime3)
|
|
{
|
|
dd.EndDateTime3 = dtEnd;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!dd.StartDateTime4.HasValue || sr.Start < dd.StartDateTime4)
|
|
{
|
|
dd.StartDateTime4 = sr.Start;
|
|
}
|
|
if (!dd.EndDateTime4.HasValue || dtEnd > dd.EndDateTime4)
|
|
{
|
|
dd.EndDateTime4 = dtEnd;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
return dayToDetail.Values.OrderBy(dd => dd.Date).ToList();
|
|
}
|
|
|
|
public override IList<MitarbeiterstundenkontoRO.DayDetail> CreateDayDetailsForEachServiceRecord(DateTime berichtStart, DateTime berichtEnde, MitarbeiterstundenkontoRO.EmployeeDetail ed, IList<StundenkontoServiceRecord> recordsAll)
|
|
{
|
|
var details = new List<MitarbeiterstundenkontoRO.DayDetail>();
|
|
|
|
foreach (var sr in recordsAll)
|
|
{
|
|
if (sr.Start >= berichtStart && sr.Start < berichtEnde)
|
|
{
|
|
if (sr.Start.Day == 8)
|
|
{
|
|
int test = 0;
|
|
}
|
|
if (!IsServiceRecordFehlzeit(sr) && AddServiceRecordToDuration(sr))
|
|
{
|
|
var dd = CreateDayDetail(sr.Start, ed);
|
|
details.Add(dd);
|
|
|
|
decimal duration = GetDuration(sr, ed);
|
|
|
|
|
|
dd.MinutesTotal += duration;
|
|
TimeSpan tspan = TimeSpan.FromMinutes((double)dd.MinutesTotal);
|
|
dd.TimeTotal = String.Format("{0:00}:{1:00}", tspan.Hours, tspan.Minutes);
|
|
if (dd.ServiceRecords == null)
|
|
dd.ServiceRecords = new List<StundenkontoServiceRecord>();
|
|
dd.ServiceRecords.Add(sr);
|
|
|
|
dd.Date = sr.Start;
|
|
|
|
if (sr.Start.Second == 0)
|
|
{
|
|
DateTime dtEnd = sr.Start.AddMinutes((double)duration);
|
|
dd.StartDateTime1 = sr.Start;
|
|
|
|
dd.EndDateTime1 = dtEnd;
|
|
|
|
}
|
|
|
|
CalculateSpecialHours(dd, sr, duration);
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
return details.OrderBy(dd => dd.Date).ToList();
|
|
}
|
|
|
|
protected override decimal GetDuration(StundenkontoServiceRecord sr, MitarbeiterstundenkontoRO.EmployeeDetail ed)
|
|
{
|
|
|
|
foreach (var at in ed.Employee.AbsenceTimes)
|
|
{
|
|
if (!StundenBeiAbwesenheitBerechnen(at))
|
|
{
|
|
DateTime start = DateTime.MinValue;
|
|
DateTime end = DateTime.MaxValue;
|
|
|
|
if (at.Start.HasValue)
|
|
{
|
|
start = at.Start.Value;
|
|
}
|
|
if (at.End.HasValue)
|
|
{
|
|
end = at.End.Value;
|
|
if (end.Hour == 0)
|
|
{
|
|
end = end.AddDays(1);
|
|
}
|
|
}
|
|
|
|
if (start <= sr.Start && sr.Start < end)
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
decimal duration = 0;
|
|
|
|
if (sr.GroupRoundedDuration.HasValue)
|
|
{
|
|
duration = sr.GroupRoundedDuration.Value;
|
|
}
|
|
else
|
|
{
|
|
if (appSettings.ShowRoundedDurationInEmployeeAnalysis)
|
|
{
|
|
duration = sr.RoundedDuration;
|
|
}
|
|
else
|
|
{
|
|
duration = (decimal)sr.End.Subtract(sr.Start).TotalMinutes;
|
|
}
|
|
}
|
|
|
|
if (UseServiceDescriptionPercentage())
|
|
{
|
|
decimal prozent = sr.ServiceDescription.Category.ProzentStundenkonto ?? sr.ServiceDescription.Category.ProzentAbrechnung;
|
|
|
|
if (sr.ServiceDescription.ProzentStundenkonto.HasValue)
|
|
{
|
|
prozent = sr.ServiceDescription.ProzentStundenkonto.Value;
|
|
}
|
|
duration = (duration * prozent) / 100;
|
|
}
|
|
else
|
|
{
|
|
decimal prozent = 100;
|
|
if (sr.ServiceDescription.ProzentStundenkonto.HasValue)
|
|
{
|
|
prozent = sr.ServiceDescription.ProzentStundenkonto.Value;
|
|
}
|
|
else if (sr.ServiceDescription.Category.ProzentStundenkonto.HasValue)
|
|
{
|
|
prozent = sr.ServiceDescription.Category.ProzentStundenkonto.Value;
|
|
}
|
|
|
|
|
|
duration *= (prozent / 100);
|
|
}
|
|
|
|
|
|
return duration;
|
|
}
|
|
|
|
//Mit "SpecialHours" sind Sonderzeiten wie Nachtzeiten oder Arbeitszeiten sonn- und feiertags gemeint. Da diese gesondert abgerechnet werden können.
|
|
public override void CalculateSpecialHours(MitarbeiterstundenkontoRO.DayDetail dd, StundenkontoServiceRecord sr, decimal duration)
|
|
{
|
|
//Prüfe, ob Start oder Ende des Eintrags an einem Sonntag sind, dann die am Sonntag geleisteten Minuten berechnen
|
|
//Todo: Bei mehrtägigen Einträgen prüfen, ob sie vor einem Sonntag anfangen und nach einem Sonntag enden. Unklar, ob das vorkommen kann und ob dann Sonntagszuschläge berechnet werden müssen
|
|
if (sr.Start.DayOfWeek == DayOfWeek.Sunday || sr.End.DayOfWeek == DayOfWeek.Sunday)
|
|
{
|
|
DateTime sonntag = sr.Start.Date;
|
|
if (sr.Start.DayOfWeek != DayOfWeek.Sunday && sr.End.DayOfWeek == DayOfWeek.Sunday)
|
|
{
|
|
sonntag = sr.End.Date;
|
|
}
|
|
|
|
var sonnatgsMinuten = GetOverlappingMinutes(sr.Start, sr.End, sonntag, sonntag.AddDays(1));
|
|
dd.HoursSunday += (decimal)sonnatgsMinuten / 60;
|
|
}
|
|
//Prüfe, ob Start oder Ende des Eintrags an einem Feiertag sind, dann die am Feiertag geleisteten Minuten berechnen
|
|
//Todo: Bei mehrtägigen Einträgen prüfen, ob sie vor einem Feiertag anfangen und nach einem Feiertag enden. Unklar, ob das vorkommen kann
|
|
else if (IstFeiertag(sr.Start) || IstFeiertag(sr.End))
|
|
{
|
|
var feiertagsMinuten = duration;
|
|
DateTime feiertag = sr.Start.Date;
|
|
if (IstFeiertag(sr.Start) && !IstFeiertag(sr.End))
|
|
{
|
|
feiertagsMinuten = (decimal)GetOverlappingMinutes(sr.Start, sr.End, feiertag, feiertag.AddDays(1));
|
|
}
|
|
else if (!IstFeiertag(sr.Start) && IstFeiertag(sr.End))
|
|
{
|
|
feiertag = sr.End.Date;
|
|
feiertagsMinuten = (decimal)GetOverlappingMinutes(sr.Start, sr.End, feiertag, feiertag.AddDays(1));
|
|
}
|
|
dd.HoursHoliday += feiertagsMinuten / 60;
|
|
}
|
|
|
|
if (sr.Start.Second == 0 && dd.NightEnd.HasValue && dd.NightStart.HasValue)
|
|
{
|
|
var minuten = CheckNachtzeiten(sr.Start, dd.NightStart.Value, dd.NightEnd.Value, (double)duration);
|
|
|
|
if (minuten > 0)
|
|
{
|
|
dd.SpecialHours += (decimal)(Math.Round(minuten, 0) / 60);
|
|
}
|
|
|
|
//DateTime dtEnd = sr.Start.Value.AddMinutes((double)duration);
|
|
|
|
//if (dtEnd.Hour >= dd.NightStart.Value)
|
|
//{
|
|
// DateTime dtStart = dtEnd.Date.AddHours(dd.NightStart.Value);
|
|
// if (sr.Start.Value > dtStart)
|
|
// dtStart = sr.Start.Value;
|
|
|
|
// TimeSpan ts = dtEnd.Subtract(dtStart);
|
|
// dd.SpecialHours += (decimal)ts.TotalMinutes / 60;
|
|
//}
|
|
|
|
//if (sr.Start.Value.Hour < dd.NightEnd)
|
|
//{
|
|
// DateTime dtEnd2 = sr.Start.Value.Date.AddHours(dd.NightEnd.Value);
|
|
// if (dtEnd < dtEnd2)
|
|
// dtEnd2 = dtEnd;
|
|
// TimeSpan ts = dtEnd2.Subtract(sr.Start.Value);
|
|
// dd.SpecialHours += (decimal)(Math.Round(ts.TotalMinutes, 0) / 60);
|
|
//}
|
|
}
|
|
}
|
|
|
|
private double CheckNachtzeiten(DateTime start, int nightStart, int nightEnd, double minuten)
|
|
{
|
|
DateTime nightStartDt = start.Date.AddHours(nightStart);
|
|
DateTime nightEndDt = start.Date.AddDays(1).AddHours(nightEnd);
|
|
|
|
|
|
DateTime dtEnd = start.AddMinutes(minuten);
|
|
|
|
double minutes = GetOverlappingMinutes(start, dtEnd, nightStartDt, nightEndDt);
|
|
minutes += GetOverlappingMinutes(start, dtEnd, nightStartDt.AddDays(-1), nightEndDt.AddDays(-1));
|
|
minutes += GetOverlappingMinutes(start, dtEnd, nightStartDt.AddDays(1), nightEndDt.AddDays(1));
|
|
|
|
return minutes;
|
|
|
|
}
|
|
|
|
public double GetOverlappingMinutes(DateTime start1, DateTime end1, DateTime start2, DateTime end2)
|
|
{
|
|
if (end1 > start2 && start1 < end2)
|
|
{
|
|
DateTime dtStart = start1;
|
|
|
|
if (dtStart < start2)
|
|
dtStart = start2;
|
|
|
|
DateTime dtEnd = end1;
|
|
|
|
if (dtEnd > end2)
|
|
dtEnd = end2;
|
|
TimeSpan ts = dtEnd.Subtract(dtStart);
|
|
|
|
return ts.TotalMinutes;
|
|
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
public override MitarbeiterstundenkontoRO.DayDetail CreateDayDetail(DateTime date, MitarbeiterstundenkontoRO.EmployeeDetail ed)
|
|
{
|
|
MitarbeiterstundenkontoRO.DayDetail dd = new MitarbeiterstundenkontoRO.DayDetail();
|
|
dd.ZusammenhaengendeZeitraeume = new List<DateTimeSpan>();
|
|
dd.Date = date.Date;
|
|
dd.NightStart = 20;
|
|
dd.NightEnd = 6;
|
|
dd.TimeTotal = "00:00";
|
|
dd.TimeInVacation = "00:00";
|
|
dd.TimeSick = "00:00";
|
|
dd.TimeOtherAbsenceTimes = "00:00";
|
|
ConfigurePauseSettings(dd);
|
|
CalculateAbsenceTimesForDay(dd, ed);
|
|
// Claude: vacationService.GetFeiertag pro Datum cachen, da dieselbe Datum-Abfrage über viele Mitarbeiter wiederholt wird
|
|
// Claude: Vollständiges DateTime als Key (nicht .Date), damit das Verhalten bei evtl. nicht-mitternächtigen Aufrufen identisch bleibt
|
|
if (_feiertagDCByDateCache == null)
|
|
{
|
|
_feiertagDCByDateCache = new Dictionary<DateTime, FeiertagDC>();
|
|
}
|
|
FeiertagDC feiertagDC;
|
|
if (!_feiertagDCByDateCache.TryGetValue(date, out feiertagDC))
|
|
{
|
|
feiertagDC = vacationService.GetFeiertag(date);
|
|
_feiertagDCByDateCache[date] = feiertagDC;
|
|
}
|
|
dd.Feiertag = feiertagDC;
|
|
BerechneSollStundenFuerDayDetail(ed, dd);
|
|
|
|
if (BerechneFeiertageAlsArbeitszeit())
|
|
{
|
|
if (dd.Feiertag != null)
|
|
{
|
|
dd.MinutesTotal = dd.SollStunden * 60;// * GetFeiertagsFaktor(dd.Date);
|
|
TimeSpan tspan = TimeSpan.FromMinutes((double)dd.MinutesTotal);
|
|
dd.TimeTotal = String.Format("{0:00}:{1:00}", tspan.Hours, tspan.Minutes);
|
|
}
|
|
}
|
|
|
|
return dd;
|
|
}
|
|
|
|
public override void ConfigurePauseSettings(MitarbeiterstundenkontoRO.DayDetail dd)
|
|
{
|
|
if (Settings.ShowWorktime && Settings.EnableArbeitszeiterfassung)
|
|
{
|
|
if (Settings.EnableAutomaticPauses)
|
|
{
|
|
dd.MaxHoursUntilBreak = 6;
|
|
dd.BreakMinutes = 30;
|
|
dd.MaxHoursUntilBreak2 = 9;
|
|
dd.BreakMinutes2 = 15;
|
|
}
|
|
}
|
|
//nicht mehr nötig. Im Default werden keine Pausen mehr berechnet
|
|
//else
|
|
// {
|
|
// //Alter Default
|
|
// dd.MaxHoursUntilBreak = null;
|
|
// dd.BreakMinutes = 30;
|
|
//}
|
|
}
|
|
|
|
public override void BerechneSollStundenFuerDayDetail(MitarbeiterstundenkontoRO.EmployeeDetail ed, MitarbeiterstundenkontoRO.DayDetail dd)
|
|
{
|
|
var sollMax = BerechneSollStundenFuerTag(ed, dd.Date);
|
|
|
|
if (BerechneSollNurBisZumAktuellenTag())
|
|
{
|
|
if (dd.Date > DateTime.Now.Date)
|
|
{
|
|
sollMax = 0;
|
|
}
|
|
}
|
|
|
|
|
|
dd.SollStunden = sollMax;
|
|
|
|
}
|
|
|
|
public override decimal BerechneSollStundenFuerTag(MitarbeiterstundenkontoRO.EmployeeDetail ed, DateTime date)
|
|
{
|
|
decimal sollDiesenTag = 0;
|
|
var faktor = GetFeiertagsFaktor(date);
|
|
if (BerechneFeiertageAlsArbeitszeit())
|
|
{
|
|
if (faktor == 1)
|
|
{
|
|
faktor = 0;
|
|
}
|
|
}
|
|
if (faktor < 1)
|
|
{
|
|
//Berechne Soll, entweder aus Vertrag oder Arbeitszeiten
|
|
if (HatArbeitszeitImZeitraum(ed.Employee.Arbeitszeiten, date, date))
|
|
{
|
|
sollDiesenTag = GetSollNachArbeitszeitenFuerTag(ed.Employee, date);
|
|
|
|
}
|
|
else
|
|
{
|
|
//sollDiesenTag = ed.SollProTag;
|
|
|
|
var c = ed.Employee.GetValidContractForDate(date);
|
|
if (c != null)
|
|
{
|
|
decimal wth = 0;
|
|
int apw = ANZ_WOCHENTAGE;
|
|
if (c.WeeklyDays.HasValue && c.WeeklyDays.Value > 0)
|
|
{
|
|
apw = c.WeeklyDays.Value;
|
|
}
|
|
if (c.WeeklyTotalHours.HasValue)
|
|
{
|
|
wth = c.WeeklyTotalHours.Value;
|
|
}
|
|
else if (c.MonthlyTotalHours.HasValue && ed.ArbeitstageImBerichtszeitraum > 0)
|
|
{
|
|
wth = c.MonthlyTotalHours.Value / ed.ArbeitstageImBerichtszeitraum;
|
|
wth *= apw;
|
|
}
|
|
|
|
|
|
sollDiesenTag = wth / apw;
|
|
|
|
bool istWochenende = date.DayOfWeek == DayOfWeek.Saturday || date.DayOfWeek == DayOfWeek.Sunday;
|
|
if (apw == 6)
|
|
{
|
|
istWochenende = date.DayOfWeek == DayOfWeek.Sunday;
|
|
}
|
|
else if (apw > 6)
|
|
{
|
|
istWochenende = false;
|
|
}
|
|
if (istWochenende)
|
|
{
|
|
sollDiesenTag = 0;
|
|
}
|
|
}
|
|
if (faktor != 0)
|
|
{
|
|
sollDiesenTag *= faktor; //halbe Feiertage etc. berücksichtigen
|
|
}
|
|
}
|
|
}
|
|
|
|
return sollDiesenTag;
|
|
|
|
}
|
|
|
|
public override void CalculateAbsenceTimesForDay(MitarbeiterstundenkontoRO.DayDetail dd, MitarbeiterstundenkontoRO.EmployeeDetail ed)
|
|
{
|
|
var absenceTimes = ed.Employee.AbsenceTimes;
|
|
|
|
if (absenceTimes != null)
|
|
{
|
|
foreach (var absenceTime in absenceTimes)
|
|
{
|
|
|
|
//if (IsUrlaubAbsenceTime(absenceTime) || IsKrankheitAbsenceTime(absenceTime))
|
|
//{
|
|
DateTimeSpan span = new DateTimeSpan();
|
|
|
|
span.StartDate = dd.Date;
|
|
|
|
if (absenceTime.Start.HasValue && absenceTime.Start.Value > span.StartDate)
|
|
{
|
|
span.StartDate = absenceTime.Start.Value;
|
|
}
|
|
|
|
if (absenceTime.End.HasValue && absenceTime.End.Value.Date == span.StartDate.Date &&
|
|
absenceTime.End.Value.Hour > 0)
|
|
{
|
|
span.EndDate = absenceTime.End.Value;
|
|
}
|
|
else
|
|
{
|
|
span.EndDate = dd.Date.AddDays(1);
|
|
if (absenceTime.End.HasValue && absenceTime.End.Value.Date.AddDays(1) < span.EndDate)
|
|
{
|
|
span.EndDate = absenceTime.End.Value.Date.AddDays(1);
|
|
}
|
|
}
|
|
|
|
if (span.EndDate > dd.Date && span.StartDate < dd.Date.AddDays(1))
|
|
{
|
|
DateTime day = span.StartDate;
|
|
var faktor = GetFeiertagsFaktor(day);
|
|
|
|
|
|
if (faktor < 1 && (day.DayOfWeek != DayOfWeek.Saturday || ed.ArbeitstageProWoche == 6) && (day.DayOfWeek != DayOfWeek.Sunday || ed.ArbeitstageProWoche > 6)) // Prüfung auf 6 + bzw 7 Tage Woche
|
|
{
|
|
//Berechne Soll, entweder aus Vertrag oder Arbeitszeiten
|
|
var sollDiesenTag = ed.SollProTag;
|
|
|
|
bool checkMaxWochenSoll = true;
|
|
|
|
if (!IstAnrechenbareAbsenceTime(absenceTime))
|
|
{
|
|
sollDiesenTag = 0;
|
|
}
|
|
else if (HatArbeitszeitImZeitraum(ed.Employee.Arbeitszeiten, dd.Date, dd.Date))
|
|
{
|
|
sollDiesenTag = BerechneAlleAbwesenheitsStundenFuerArbeitszeiten(ed.Employee, dd.Date); // Hier wird der Feiertagsfaktor schon applied
|
|
|
|
}
|
|
else
|
|
{
|
|
var ssa = GetSollStundenForAbsenceTime(ed, absenceTime, dd.Date);
|
|
if (ssa.HasValue)
|
|
{
|
|
sollDiesenTag = ssa.Value;
|
|
checkMaxWochenSoll = false;
|
|
|
|
}
|
|
else
|
|
{
|
|
var c = ed.Employee.GetValidContractForDate(dd.Date);
|
|
if (c != null)
|
|
{
|
|
decimal wth = 0;
|
|
int apw = ANZ_WOCHENTAGE;
|
|
if (c.WeeklyDays.HasValue && c.WeeklyDays.Value > 0)
|
|
{
|
|
apw = c.WeeklyDays.Value;
|
|
}
|
|
|
|
if (c.WeeklyTotalHours.HasValue)
|
|
{
|
|
wth = c.WeeklyTotalHours.Value;
|
|
}
|
|
else if (c.MonthlyTotalHours.HasValue)
|
|
{
|
|
|
|
sollDiesenTag = c.MonthlyTotalHours.Value / ed.ArbeitstageImBerichtszeitraum;
|
|
wth = sollDiesenTag * apw;
|
|
}
|
|
|
|
|
|
sollDiesenTag = wth / apw;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//Dauer der Abwesenheit = Soll an diesem Tag
|
|
bool istGanztaegig = true;
|
|
var abwesenheitsDauer = sollDiesenTag;
|
|
|
|
if (span.StartDate.Hour > 0 && span.EndDate.Hour > 0)
|
|
{
|
|
istGanztaegig = false;
|
|
|
|
var ts = span.EndDate.Subtract(span.StartDate);
|
|
abwesenheitsDauer = (decimal)ts.TotalHours;
|
|
if (SchneideZuvieleAbwesenheitsstundenAb() && abwesenheitsDauer > sollDiesenTag)
|
|
{
|
|
abwesenheitsDauer = sollDiesenTag;
|
|
}
|
|
|
|
sollDiesenTag -= abwesenheitsDauer;
|
|
if (sollDiesenTag < 0)
|
|
{
|
|
sollDiesenTag = 0;
|
|
}
|
|
|
|
faktor = 1; // Keine halben Feiertag berücksichtigen, wenn die Abwesenheit in Stunden angegeben wurde
|
|
}
|
|
|
|
if (faktor != 0 && !HatArbeitszeitImZeitraum(ed.Employee.Arbeitszeiten, dd.Date, dd.Date))
|
|
{
|
|
abwesenheitsDauer *= faktor; //halbe Feiertage etc. berücksichtigen
|
|
}
|
|
|
|
if (ed.AbsenceTimeDays != null && ed.AbsenceTimeDays.TageProWocheDict != null)
|
|
{
|
|
if (ed.AbsenceTimeDays.RestStundenProWocheDict == null)
|
|
{
|
|
ed.AbsenceTimeDays.RestStundenProWocheDict = new Dictionary<DateTimeSpan, decimal>();
|
|
}
|
|
foreach (var week in ed.AbsenceTimeDays.TageProWocheDict.Keys)
|
|
{
|
|
if (!ed.AbsenceTimeDays.RestStundenProWocheDict.ContainsKey(week))
|
|
{
|
|
ed.AbsenceTimeDays.RestStundenProWocheDict.Add(week, ed.SollProWoche);
|
|
}
|
|
|
|
if (day.Date >= week.StartDate && day.Date <= week.EndDate && checkMaxWochenSoll)
|
|
{
|
|
|
|
var rest = ed.AbsenceTimeDays.RestStundenProWocheDict[week];
|
|
if (rest < abwesenheitsDauer)
|
|
{
|
|
abwesenheitsDauer = rest;
|
|
}
|
|
rest -= abwesenheitsDauer;
|
|
if (rest < 0)
|
|
{
|
|
rest = 0;
|
|
}
|
|
|
|
ed.AbsenceTimeDays.RestStundenProWocheDict[week] = rest;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if (!AbsenceTimeAufIstStundenAddieren(absenceTime) && IsUrlaubAbsenceTime(absenceTime))
|
|
{ // Vermutlich diese Methode in CustomMitarbeiterStundenKontoBerechnung überschreiben und hier Prüfung machen, ob absenceReason ohne Fortzahlung ist
|
|
//und wenn ja, HoursSick oder Soll oder so für den Tag auf 0 setzen
|
|
|
|
dd.HoursInVacation = abwesenheitsDauer;
|
|
var tspan = TimeSpan.FromHours((double)abwesenheitsDauer);
|
|
dd.TimeInVacation = String.Format("{0:00}:{1:00}", tspan.Hours, tspan.Minutes);
|
|
}
|
|
else if (!AbsenceTimeAufIstStundenAddieren(absenceTime) && IsKrankheitAbsenceTime(absenceTime))
|
|
{
|
|
dd.HoursSick = abwesenheitsDauer;
|
|
var tspan = TimeSpan.FromHours((double)abwesenheitsDauer);
|
|
dd.TimeSick = String.Format("{0:00}:{1:00}", tspan.Hours, tspan.Minutes);
|
|
}
|
|
else
|
|
{
|
|
if (!AbsenceTimeAufIstStundenAddieren(absenceTime))
|
|
{
|
|
dd.HoursOtherAbsenceTimes = abwesenheitsDauer;
|
|
var tspan = TimeSpan.FromHours((double)abwesenheitsDauer);
|
|
dd.TimeOtherAbsenceTimes = String.Format("{0:00}:{1:00}", tspan.Hours, tspan.Minutes);
|
|
}
|
|
if (istGanztaegig)
|
|
{
|
|
//Bei ganztägigen Abwesenheiten darf nicht noch zusätzlich gearbeitet werden
|
|
dd.MinutesTotal = 0;
|
|
dd.TimeTotal = "00:00";
|
|
}
|
|
dd.MinutesTotal += abwesenheitsDauer * 60;
|
|
TimeSpan tspan2 = TimeSpan.FromMinutes((double)dd.MinutesTotal);
|
|
dd.TimeTotal = String.Format("{0:00}:{1:00}", tspan2.Hours, tspan2.Minutes);
|
|
}
|
|
|
|
if (dd.AbsenceTimes == null)
|
|
{
|
|
dd.AbsenceTimes = new List<AbsenceTime>();
|
|
}
|
|
dd.AbsenceTimes.Add(absenceTime);
|
|
//dd.MinutesTotal += sollDiesenTag * 60;
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
public override bool IstAnrechenbareAbsenceTime(AbsenceTime item)
|
|
{
|
|
//CB Erst ab 2026 Überstundenfrei berücksichtigen
|
|
if (item.Start.HasValue && item.Start.Value.Date >= new DateTime(2026, 1, 1) && item.AbsenceReason != null && (item.AbsenceReason.Description.ToLower().Contains("überstundenfrei") || item.AbsenceReason.Description.ToLower().Contains("überstundenabbau") ||
|
|
item.AbsenceReason.Description.ToLower().Contains("ü-frei")))
|
|
return false;
|
|
else
|
|
return true;
|
|
}
|
|
|
|
public override bool IsKrankheitAbsenceTime(AbsenceTime absenceTime)
|
|
{
|
|
return !String.IsNullOrEmpty(absenceTime.AbsenceReason.Description) &&
|
|
(absenceTime.AbsenceReason.Description.ToLower().IndexOf("arbeitsunfähig") >= 0
|
|
|| absenceTime.AbsenceReason.Description.ToLower().IndexOf("krank") >= 0 || absenceTime.AbsenceReason.Description.ToLower() == "au");
|
|
}
|
|
|
|
public override bool IsUrlaubAbsenceTime(AbsenceTime absenceTime)
|
|
{
|
|
return !String.IsNullOrEmpty(absenceTime.AbsenceReason.Description) &&
|
|
absenceTime.AbsenceReason.Description.ToLower().IndexOf("urlaub") >= 0;
|
|
}
|
|
|
|
public override decimal? GetSollStundenForAbsenceTime(MitarbeiterstundenkontoRO.EmployeeDetail ed, AbsenceTime at, DateTime datum)
|
|
{
|
|
return null;
|
|
|
|
}
|
|
|
|
public override bool AddAbsenceTimeToIst()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
|
|
public override bool AddServiceRecordToDuration(StundenkontoServiceRecord item)
|
|
{
|
|
if (Settings.ShowWorktime && Settings.EnableArbeitszeiterfassung)
|
|
{
|
|
return IstArbeitszeit(item);
|
|
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public override bool IstArbeitszeit(StundenkontoServiceRecord item)
|
|
{
|
|
if (item.ServiceDescription != null && item.ServiceDescription.Category != null)
|
|
{
|
|
if (item.ServiceDescription.Name.StartsWith("Arbeitszeit") || item.ServiceDescription.Category.Name.StartsWith("Arbeitszeit")) //Arbeitszeit und Arbeitszeiterfassung etc.
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public override decimal AddRateFactorToIst()
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
public override MitarbeiterstundenkontoRO.EmployeeDetail BerechneUeberstundenBis(MitarbeiterstundenkontoRO.EmployeeDetail edOrig, IList<StundenkontoServiceRecord> records, DateTime monat, out decimal? ueberstunden, out decimal alteExtraUeberstunden)
|
|
{
|
|
MitarbeiterstundenkontoRO.EmployeeDetail edTotal = new MitarbeiterstundenkontoRO.EmployeeDetail();
|
|
|
|
ueberstunden = null;
|
|
alteExtraUeberstunden = 0;
|
|
|
|
var emp = edOrig.Employee;
|
|
|
|
Contract c = emp.FirstContract;
|
|
|
|
|
|
|
|
if (c != null)
|
|
{
|
|
|
|
Dictionary<String, IList<StundenkontoServiceRecord>> srDict = CreateServiceRecordDictByDate(records);
|
|
|
|
DateTime start = c.EntryDate.HasValue ? c.EntryDate.Value : new DateTime(2011, 1, 1);
|
|
if (BerechnungsStartdatum > start)
|
|
{
|
|
start = BerechnungsStartdatum;
|
|
c = GetNextValidContractForDate(start, emp);
|
|
}
|
|
while (c != null && start < monat)
|
|
{
|
|
DateTime contractEnd = c.ContractEndDate.HasValue ? c.ContractEndDate.Value : DateTime.MaxValue;
|
|
if (contractEnd < DateTime.MaxValue)
|
|
{
|
|
contractEnd = contractEnd.Date.AddDays(1).AddTicks(-1);
|
|
}
|
|
decimal bu = 0;
|
|
decimal weeklyTotalHours = this.maxWochenstunden;
|
|
int anzWochentage = ANZ_WOCHENTAGE;
|
|
|
|
|
|
if (c.WeeklyTotalHours.HasValue)
|
|
{
|
|
weeklyTotalHours = c.WeeklyTotalHours.Value;
|
|
bu = c.WeeklyTotalHours.Value / maxWochenstunden;
|
|
}
|
|
|
|
|
|
//while (start < monat && start <= contractEnd)
|
|
while (start < monat && start < contractEnd)
|
|
{
|
|
if (start >= new DateTime(2024, 8, 1))
|
|
{
|
|
int test = 0;
|
|
}
|
|
|
|
decimal flsIst = 0;
|
|
decimal mittelbarIst = 0;
|
|
decimal minutenMonat = 0;
|
|
DateTime ende = new DateTime(start.Year, start.Month, 1).AddMonths(1);
|
|
if (contractEnd < DateTime.MaxValue && contractEnd.Date.AddDays(1) < ende)
|
|
ende = contractEnd.Date.AddDays(1);
|
|
|
|
IList<StundenkontoServiceRecord> recordsThisMonth = new List<StundenkontoServiceRecord>();
|
|
String key = String.Format("{0:yyyyMM}", start);
|
|
|
|
if (srDict.ContainsKey(key))
|
|
{
|
|
foreach (var item in srDict[key])
|
|
{
|
|
if (item.Start >= start && item.Start < ende)
|
|
{
|
|
if (AddServiceRecordToDuration(item))
|
|
{
|
|
|
|
decimal duration = GetDuration(item, edOrig);
|
|
|
|
|
|
minutenMonat += duration;
|
|
|
|
if (item.ServiceDescription != null &&
|
|
item.ServiceDescription.Category != null &&
|
|
item.ServiceDescription.Category.IsBillable)
|
|
flsIst += duration;
|
|
else
|
|
mittelbarIst += duration;
|
|
|
|
}
|
|
recordsThisMonth.Add(item);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
decimal arbeitstage = GetArbeitstageImBereich(start, ende, emp);
|
|
var arbeitstageProWoche = anzWochentage;
|
|
if (c != null && c.WeeklyDays.HasValue && c.WeeklyDays.Value > 0)
|
|
{
|
|
arbeitstageProWoche = c.WeeklyDays.Value;
|
|
}
|
|
|
|
//if (anzWochentage != 5)
|
|
//{
|
|
// arbeitstage *= anzWochentage / 5m;
|
|
//}
|
|
var soll = GetSollNachArbeitszeiten(emp, start, ende);
|
|
|
|
if (!soll.HasValue)
|
|
{
|
|
var stundenSollProMonat = BerechneSollProMonat(emp, start, ende);
|
|
soll = stundenSollProMonat;
|
|
}
|
|
|
|
if (!soll.HasValue)
|
|
{
|
|
|
|
if (!soll.HasValue)
|
|
{
|
|
decimal maxSollMonat = arbeitstage * (maxWochenstunden / arbeitstageProWoche);
|
|
if (arbeitstageProWoche <= 5)
|
|
{
|
|
maxSollMonat = arbeitstage * (maxWochenstunden / 5);
|
|
}
|
|
|
|
soll = maxSollMonat * bu;
|
|
}
|
|
}
|
|
|
|
decimal sollMitarbeiter = soll ?? 0;
|
|
|
|
MitarbeiterstundenkontoRO.EmployeeDetail ed = new MitarbeiterstundenkontoRO.EmployeeDetail();
|
|
ed.SollProTag = weeklyTotalHours / arbeitstageProWoche;
|
|
ed.ArbeitstageProWoche = arbeitstageProWoche;
|
|
ed.ArbeitstageImBerichtszeitraum = arbeitstage;
|
|
ed.SollProWoche = BerechneSollProWoche(c, ed);
|
|
|
|
ed.Employee = emp;
|
|
|
|
AktuellBearbeitetesEmployeeDetail = ed;
|
|
var ath = GetAbsenceTimeDaysWithWorkWeeks(emp, start, ende, arbeitstageProWoche);
|
|
ed.AbsenceTimeDays = ath;
|
|
|
|
decimal? fehlzeit = GetFehlzeiten(records, start, ende);
|
|
|
|
if (fehlzeit.HasValue)
|
|
{
|
|
sollMitarbeiter -= fehlzeit.Value;
|
|
|
|
}
|
|
|
|
ed.StundenSollMax = sollMitarbeiter;
|
|
|
|
|
|
if (ath.TageGesamt.HasValue)
|
|
{
|
|
var minusStunden = BerechneStundenDurchAbwesenheiten(ath, start, ende, c, ed);
|
|
sollMitarbeiter -= minusStunden;
|
|
if (sollMitarbeiter < 0 && !StundenDurchAbwesenheitenDuerfenNegativSein())
|
|
{
|
|
sollMitarbeiter = 0;
|
|
}
|
|
//if (stundenSollProMonat.HasValue)
|
|
//{
|
|
// decimal stundenProTag = stundenSollProMonat.Value;
|
|
// if (arbeitstage != 0)
|
|
// {
|
|
// stundenProTag = stundenSollProMonat.Value/arbeitstage;
|
|
// }
|
|
// sollMitarbeiter -= (ath.TageGesamt.Value * stundenProTag);
|
|
//}
|
|
//else
|
|
//{
|
|
// decimal sollProWoche = maxWochenstunden*bu;
|
|
|
|
|
|
// var minusStunden = BerechneStundenDurchAbwesenheiten(ath, start, ende, c, sollProWoche);
|
|
|
|
|
|
//}
|
|
}
|
|
|
|
var ath2 = GetAbwesenheitenDurchServiceRecords(ed, c, records, start, ende);
|
|
|
|
if (ath2 != null)
|
|
{
|
|
|
|
var stunden = ath2.StundenKrankheit + ath2.StundenUrlaub + ath2.StundenSonstiges;
|
|
|
|
sollMitarbeiter -= stunden;
|
|
|
|
if (sollMitarbeiter < 0 && !StundenDurchAbwesenheitenDuerfenNegativSein())
|
|
{
|
|
sollMitarbeiter = 0;
|
|
}
|
|
|
|
|
|
}
|
|
|
|
var azDurchFeiertage = BerechneGesamtArbeitszeitDurchFeiertage(ed, start, ende);
|
|
sollMitarbeiter -= azDurchFeiertage;
|
|
|
|
decimal stundenMonat = minutenMonat / 60m;
|
|
stundenMonat = ((flsIst * AddRateFactorToIst()) + mittelbarIst) / 60m;
|
|
|
|
|
|
if (!ueberstunden.HasValue)
|
|
ueberstunden = 0;
|
|
ueberstunden += stundenMonat - sollMitarbeiter;
|
|
|
|
if (SollPauseBerechnen())
|
|
{
|
|
var pause1 = BerechnePauseMinuten(recordsThisMonth);
|
|
var pause2 = BerechneAutoPausen(recordsThisMonth);
|
|
|
|
ueberstunden -= pause1 / 60;
|
|
ueberstunden -= pause2 / 60;
|
|
}
|
|
|
|
decimal extraUeberstunden = 0;
|
|
|
|
BerechneExtraUeberstunden(emp, start, ende, out extraUeberstunden);
|
|
ueberstunden += extraUeberstunden;
|
|
|
|
alteExtraUeberstunden += extraUeberstunden;
|
|
|
|
if (ShouldCreateDayDetails())
|
|
{
|
|
var days = CreateDayDetails(start, ende, ed, records);
|
|
var ah = GetAdditionalHours(days, start, ende, ed, records);
|
|
ueberstunden += ah;
|
|
}
|
|
|
|
|
|
ed.StundenSollOhneUrlaubKrankheit = sollMitarbeiter;
|
|
ed.FlsIst = flsIst / 60;
|
|
ed.MittelbarIst = mittelbarIst / 60;
|
|
ed.StundenGesamtIst = stundenMonat;
|
|
ed.Ueberstunden = ueberstunden;
|
|
ed.UeberstundenVormonat = edTotal.Ueberstunden;
|
|
|
|
ueberstunden = BerechneUeberstunden(ed, ueberstunden, c, start, ende);
|
|
|
|
edTotal.StundenSollOhneUrlaubKrankheit += sollMitarbeiter;
|
|
edTotal.StundenSollMax += soll ?? 0;
|
|
edTotal.FlsIst += flsIst / 60;
|
|
edTotal.MittelbarIst += mittelbarIst / 60;
|
|
edTotal.StundenGesamtIst += stundenMonat;
|
|
edTotal.Ueberstunden = ueberstunden;
|
|
|
|
start = ende;
|
|
}
|
|
|
|
//if (start > contractEnd)
|
|
if (start >= contractEnd)
|
|
{
|
|
var ctemp = GetNextValidContractForDate(start, emp);
|
|
if (ctemp != null && ctemp.Oid != c.Oid)
|
|
{
|
|
c = ctemp;
|
|
}
|
|
else
|
|
{
|
|
c = null;
|
|
}
|
|
|
|
if (c != null && c.EntryDate.HasValue && c.EntryDate >= start)
|
|
start = c.EntryDate.Value;
|
|
else
|
|
{
|
|
start = start.AddMonths(1);
|
|
c = GetNextValidContractForDate(start, emp);
|
|
if (c != null && c.EntryDate.HasValue && c.EntryDate >= start)
|
|
start = c.EntryDate.Value;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
return edTotal;
|
|
}
|
|
|
|
protected override decimal? BerechneUeberstunden(MitarbeiterstundenkontoRO.EmployeeDetail ed, decimal? ueberstundenStd, Contract contract, DateTime start, DateTime ende)
|
|
{
|
|
return ueberstundenStd;
|
|
}
|
|
|
|
public override decimal BerechneSollAmTag(MitarbeiterstundenkontoRO.EmployeeDetail ed, DateTime date)
|
|
{
|
|
decimal soll = 0;
|
|
var c = ed.Employee.GetValidContractForDate(date);
|
|
|
|
if (c != null)
|
|
{
|
|
int arbeitstageProWoche = ANZ_WOCHENTAGE;
|
|
|
|
if (c.WeeklyDays.HasValue && c.WeeklyDays > 0)
|
|
{
|
|
arbeitstageProWoche = c.WeeklyDays.Value;
|
|
}
|
|
if (c.WeeklyTotalHours.HasValue)
|
|
{
|
|
return c.WeeklyTotalHours.Value / arbeitstageProWoche;
|
|
}
|
|
}
|
|
return soll;
|
|
}
|
|
public override decimal BerechneSollProWoche(Contract c, MitarbeiterstundenkontoRO.EmployeeDetail ed)
|
|
{
|
|
if (c != null)
|
|
{
|
|
if (c.MonthlyTotalHours.HasValue && c.MonthlyTotalHours.Value > 0)
|
|
{
|
|
if (ed.ArbeitstageImBerichtszeitraum > 0)
|
|
{
|
|
|
|
decimal stundenProTag = GetSollArbeitszeitProArbeitstag(c, ed);
|
|
|
|
return stundenProTag * 5;
|
|
//return stundenProTag * ed.ArbeitstageProWoche;
|
|
//sollStundenProWoche = stundenProTag * ed.ArbeitstageProWoche;
|
|
|
|
//return (ath.TageGesamt.Value * stundenProTag);
|
|
}
|
|
}
|
|
else if (c.WeeklyTotalHours.HasValue)
|
|
{
|
|
return c.WeeklyTotalHours.Value;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
|
|
public override decimal BerechneAbwesenheitsStundenDurchArbeitszeiten(Employee emp, DateTime start, DateTime ende)
|
|
{
|
|
decimal stunden = 0;
|
|
|
|
DateTime startTemp = start;
|
|
|
|
while (startTemp < ende)
|
|
{
|
|
stunden += BerechneAlleAbwesenheitsStundenFuerArbeitszeiten(emp, startTemp);
|
|
|
|
startTemp = startTemp.AddDays(1);
|
|
}
|
|
|
|
return stunden;
|
|
}
|
|
|
|
// Claude: Zurückgesetzt auf Original-Implementierung. Die TimeSpan-Optimierung hat in Einzelfällen abweichende Werte erzeugt.
|
|
public override decimal BerechneAlleAbwesenheitsStundenFuerArbeitszeiten(Employee emp, DateTime tag)
|
|
{
|
|
decimal stunden = 0;
|
|
|
|
foreach (var az in emp.Arbeitszeiten)
|
|
{
|
|
if ((!az.GueltigVon.HasValue || az.GueltigVon.Value <= tag) &&
|
|
(!az.GueltigBis.HasValue || az.GueltigBis.Value >= tag))
|
|
{
|
|
foreach (var ae in az.ArbeitszeitEintraege)
|
|
{
|
|
if (ae.IstAmGleichenWochentag(tag))
|
|
{
|
|
|
|
String dateStr1 = String.Format("{0:dd.MM.yyyy} {1}", tag, ae.UhrzeitVon);
|
|
String dateStr2 = String.Format("{0:dd.MM.yyyy} {1}", tag, ae.UhrzeitBis);
|
|
|
|
DateTime date1;
|
|
DateTime date2;
|
|
|
|
if (DateTime.TryParse(dateStr1, out date1) && DateTime.TryParse(dateStr2, out date2))
|
|
{
|
|
stunden += BerechneAbwesenheitsStundenFuerArbeitstag(emp, date1, date2);
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return stunden;
|
|
}
|
|
|
|
public override decimal BerechneAbwesenheitsStundenFuerArbeitstag(Employee emp, DateTime arbeitstagStart, DateTime arbeitstagEnde)
|
|
{
|
|
foreach (var at in emp.AbsenceTimes)
|
|
{
|
|
if (IstAnrechenbareAbsenceTime(at))
|
|
{
|
|
if ((!at.Start.HasValue || at.Start.Value <= arbeitstagEnde) && (!at.End.HasValue || at.End.Value >= arbeitstagStart.Date))
|
|
{
|
|
var faktor = GetFeiertagsFaktor(arbeitstagStart);
|
|
|
|
if (faktor < 1)
|
|
{
|
|
var hours = (decimal)arbeitstagEnde.Subtract(arbeitstagStart).TotalHours;
|
|
|
|
// Prüfen, ob halber Tag
|
|
if ((at.Start.HasValue && at.Start.Value.Date != at.Start.Value) || (at.End.HasValue && at.End.Value.Date != at.End.Value))
|
|
{
|
|
var start = at.Start;
|
|
var end = at.End;
|
|
if (!at.End.HasValue || at.End.HasValue && at.End.Value > arbeitstagEnde)
|
|
end = arbeitstagEnde;
|
|
if (!at.Start.HasValue || at.Start.HasValue && at.Start.Value < arbeitstagStart)
|
|
start = arbeitstagStart;
|
|
|
|
hours = Convert.ToDecimal((end.Value - start.Value).TotalHours);
|
|
}
|
|
|
|
if (faktor != 0)
|
|
{
|
|
hours *= faktor;
|
|
}
|
|
|
|
return hours;
|
|
}
|
|
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
//CB 12.4.18: Die Abwesenheitsstunden wurden bei Arbeitstagen < 5 früher falsch berechnet. Damit bereits erstellte Stundenkonto nicht zu veränderten Werten führen, habe
|
|
//ich einen Schalter eingebaut, mit dem bestimmt werden kann, ob die alte Berechnung angewendet werden soll oder nicht (Standard ist ja)
|
|
public override bool BerechneAlteAbwesenheitsStunden(DateTime monatAnfang)
|
|
{
|
|
if (monatAnfang < new DateTime(2018, 4, 1))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private decimal BerechneStundenDurchAbwesenheitenAlt(AbsenceTimeDays ath, DateTime start, DateTime ende, Contract c, MitarbeiterstundenkontoRO.EmployeeDetail ed)
|
|
{
|
|
|
|
decimal? sollProMonat = BerechneSollProMonat(ed.Employee, start, ende);
|
|
|
|
if (sollProMonat.HasValue)
|
|
{
|
|
decimal stundenProTag = sollProMonat.Value / ed.ArbeitstageImBerichtszeitraum;
|
|
return (ath.TageGesamt.Value * stundenProTag);
|
|
}
|
|
else
|
|
{
|
|
decimal sollProWoche = BerechneSollProWoche(c, ed);
|
|
decimal stunden = 0;
|
|
|
|
if (ath.TageGesamt.HasValue)
|
|
{
|
|
if (ed.ArbeitstageProWoche == 5)
|
|
{
|
|
stunden = ath.TageGesamt.Value * (sollProWoche / ed.ArbeitstageProWoche);
|
|
}
|
|
else if (ath.TageProWocheDict != null && ed.ArbeitstageProWoche > 0)
|
|
{
|
|
foreach (var week in ath.TageProWocheDict.Keys)
|
|
{
|
|
var tage = ath.TageProWocheDict[week];
|
|
|
|
if (tage > 0)
|
|
{
|
|
if (tage > ed.ArbeitstageProWoche)
|
|
{
|
|
tage = ed.ArbeitstageProWoche;
|
|
|
|
}
|
|
stunden += tage * (sollProWoche / ed.ArbeitstageProWoche);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return stunden;
|
|
}
|
|
}
|
|
|
|
public override decimal GetSollArbeitszeitProArbeitstag(Contract contract, MitarbeiterstundenkontoRO.EmployeeDetail ed)
|
|
{
|
|
if (contract.MonthlyTotalHours.HasValue && contract.MonthlyTotalHours.Value > 0 && ed.ArbeitstageImBerichtszeitraum > 0)
|
|
{
|
|
return contract.MonthlyTotalHours.Value / ed.ArbeitstageImBerichtszeitraum;
|
|
}
|
|
else if (contract.WeeklyTotalHours.HasValue && ed.ArbeitstageProWoche > 0)
|
|
{
|
|
return contract.WeeklyTotalHours.Value / ed.ArbeitstageProWoche;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
public override Dictionary<string, IList<StundenkontoServiceRecord>> CreateServiceRecordDictByDate(IList<StundenkontoServiceRecord> records)
|
|
{
|
|
var dict = new Dictionary<string, IList<StundenkontoServiceRecord>>();
|
|
foreach (var item in records)
|
|
{
|
|
if (!IsServiceRecordFehlzeit(item))
|
|
{
|
|
String key = String.Format("{0:yyyyMM}", item.Start);
|
|
|
|
IList<StundenkontoServiceRecord> list;
|
|
if (!dict.ContainsKey(key))
|
|
{
|
|
list = new List<StundenkontoServiceRecord>();
|
|
dict.Add(key, list);
|
|
}
|
|
else
|
|
{
|
|
list = dict[key];
|
|
}
|
|
list.Add(item);
|
|
}
|
|
}
|
|
return dict;
|
|
}
|
|
|
|
public override decimal GetAdditionalHours(IList<MitarbeiterstundenkontoRO.DayDetail> days, DateTime berichtStart, DateTime berichtEnde, MitarbeiterstundenkontoRO.EmployeeDetail ed, IList<StundenkontoServiceRecord> recordsAll)
|
|
{
|
|
decimal addHours = 0;
|
|
|
|
foreach (var dayDetail in days)
|
|
{
|
|
if (dayDetail.Date >= new DateTime(2023, 1, 1))
|
|
{
|
|
addHours += CalculateBreakMinutesNeu(dayDetail);
|
|
}
|
|
else
|
|
{
|
|
if (dayDetail.BreakMinutes2.HasValue && dayDetail.MaxHoursUntilBreak2.HasValue)
|
|
{
|
|
addHours += CalculateBreakMinutes(dayDetail, dayDetail.MaxHoursUntilBreak2.Value, dayDetail.BreakMinutes2.Value);
|
|
}
|
|
if (dayDetail.BreakMinutes.HasValue && dayDetail.MaxHoursUntilBreak.HasValue)
|
|
{
|
|
addHours += CalculateBreakMinutes(dayDetail, dayDetail.MaxHoursUntilBreak.Value, dayDetail.BreakMinutes.Value);
|
|
}
|
|
}
|
|
}
|
|
|
|
return addHours;
|
|
}
|
|
|
|
public override decimal CalculateBreakMinutesNeu(MitarbeiterstundenkontoRO.DayDetail dayDetail)
|
|
{
|
|
decimal addHours = 0;
|
|
|
|
if (dayDetail.ZusammenhaengendeZeitraeume != null && dayDetail.ZusammenhaengendeZeitraeume.Count > 0)
|
|
{
|
|
foreach (var span in dayDetail.ZusammenhaengendeZeitraeume)
|
|
{
|
|
addHours -= BerechneGesetzlichePauseInMinuten(dayDetail, span.StartDateTime, span.EndDateTime) / 60;
|
|
}
|
|
|
|
return addHours;
|
|
}
|
|
|
|
if (dayDetail.EndDateTime1.HasValue && dayDetail.StartDateTime1.HasValue)
|
|
{
|
|
addHours -= BerechneGesetzlichePauseInMinuten(dayDetail, dayDetail.StartDateTime1.Value, dayDetail.EndDateTime1.Value) / 60;
|
|
}
|
|
if (dayDetail.EndDateTime2.HasValue && dayDetail.StartDateTime2.HasValue)
|
|
{
|
|
addHours -= BerechneGesetzlichePauseInMinuten(dayDetail, dayDetail.StartDateTime2.Value, dayDetail.EndDateTime2.Value) / 60;
|
|
}
|
|
if (dayDetail.EndDateTime3.HasValue && dayDetail.StartDateTime3.HasValue)
|
|
{
|
|
addHours -= BerechneGesetzlichePauseInMinuten(dayDetail, dayDetail.StartDateTime3.Value, dayDetail.EndDateTime3.Value) / 60;
|
|
}
|
|
if (dayDetail.EndDateTime4.HasValue && dayDetail.StartDateTime4.HasValue)
|
|
{
|
|
addHours -= BerechneGesetzlichePauseInMinuten(dayDetail, dayDetail.StartDateTime4.Value, dayDetail.EndDateTime4.Value) / 60;
|
|
}
|
|
|
|
return addHours;
|
|
}
|
|
|
|
public override decimal BerechneGesetzlichePauseInMinuten(MitarbeiterstundenkontoRO.DayDetail dayDetail, DateTime startDateTime, DateTime endDateTime)
|
|
{
|
|
decimal arbeitsstunden = (decimal)endDateTime.Subtract(startDateTime).TotalHours;
|
|
decimal gesetzlichePauseMinuten = 0;
|
|
if (dayDetail.MaxHoursUntilBreak.HasValue && arbeitsstunden > dayDetail.MaxHoursUntilBreak)
|
|
{
|
|
gesetzlichePauseMinuten += dayDetail.BreakMinutes ?? 0;
|
|
}
|
|
arbeitsstunden -= gesetzlichePauseMinuten / 60;
|
|
|
|
if (dayDetail.MaxHoursUntilBreak2.HasValue && arbeitsstunden > dayDetail.MaxHoursUntilBreak2)
|
|
{
|
|
gesetzlichePauseMinuten += dayDetail.BreakMinutes2 ?? 0;
|
|
}
|
|
|
|
dayDetail.MinutesTotal -= gesetzlichePauseMinuten;
|
|
TimeSpan tspan = TimeSpan.FromMinutes((double)dayDetail.MinutesTotal);
|
|
dayDetail.TimeTotal = String.Format("{0:00}:{1:00}", tspan.Hours, tspan.Minutes);
|
|
dayDetail.PauseInMinuten += gesetzlichePauseMinuten;
|
|
|
|
return gesetzlichePauseMinuten;
|
|
}
|
|
|
|
public override decimal CalculateBreakMinutes(MitarbeiterstundenkontoRO.DayDetail dayDetail, int maxHoursUntilBreak, int breakMinutes)
|
|
{
|
|
decimal addHours = 0;
|
|
|
|
if (dayDetail.ZusammenhaengendeZeitraeume != null && dayDetail.ZusammenhaengendeZeitraeume.Count > 0)
|
|
{
|
|
foreach (var span in dayDetail.ZusammenhaengendeZeitraeume)
|
|
{
|
|
if (span.EndDateTime.Subtract(span.StartDateTime).TotalHours > maxHoursUntilBreak)
|
|
{
|
|
addHours -= (breakMinutes / 60m);
|
|
dayDetail.MinutesTotal -= breakMinutes;
|
|
TimeSpan tspan = TimeSpan.FromMinutes((double)dayDetail.MinutesTotal);
|
|
dayDetail.TimeTotal = String.Format("{0:00}:{1:00}", tspan.Hours, tspan.Minutes);
|
|
dayDetail.PauseInMinuten += breakMinutes;
|
|
}
|
|
}
|
|
|
|
return addHours;
|
|
}
|
|
|
|
if (dayDetail.EndDateTime1.HasValue && dayDetail.StartDateTime1.HasValue)
|
|
{
|
|
if (dayDetail.EndDateTime1.Value.Subtract(dayDetail.StartDateTime1.Value).TotalHours > maxHoursUntilBreak)
|
|
{
|
|
addHours -= (breakMinutes / 60m);
|
|
dayDetail.MinutesTotal -= breakMinutes;
|
|
TimeSpan tspan = TimeSpan.FromMinutes((double)dayDetail.MinutesTotal);
|
|
dayDetail.TimeTotal = String.Format("{0:00}:{1:00}", tspan.Hours, tspan.Minutes);
|
|
dayDetail.PauseInMinuten += breakMinutes;
|
|
}
|
|
}
|
|
if (dayDetail.EndDateTime2.HasValue && dayDetail.StartDateTime2.HasValue)
|
|
{
|
|
if (dayDetail.EndDateTime2.Value.Subtract(dayDetail.StartDateTime2.Value).TotalHours > maxHoursUntilBreak)
|
|
{
|
|
addHours -= (breakMinutes / 60m);
|
|
dayDetail.MinutesTotal -= breakMinutes;
|
|
TimeSpan tspan = TimeSpan.FromMinutes((double)dayDetail.MinutesTotal);
|
|
dayDetail.TimeTotal = String.Format("{0:00}:{1:00}", tspan.Hours, tspan.Minutes);
|
|
dayDetail.PauseInMinuten += breakMinutes;
|
|
}
|
|
}
|
|
if (dayDetail.EndDateTime3.HasValue && dayDetail.StartDateTime3.HasValue)
|
|
{
|
|
if (dayDetail.EndDateTime3.Value.Subtract(dayDetail.StartDateTime3.Value).TotalHours > maxHoursUntilBreak)
|
|
{
|
|
addHours -= (breakMinutes / 60m);
|
|
dayDetail.MinutesTotal -= breakMinutes;
|
|
TimeSpan tspan = TimeSpan.FromMinutes((double)dayDetail.MinutesTotal);
|
|
dayDetail.TimeTotal = String.Format("{0:00}:{1:00}", tspan.Hours, tspan.Minutes);
|
|
dayDetail.PauseInMinuten += breakMinutes;
|
|
}
|
|
}
|
|
if (dayDetail.EndDateTime4.HasValue && dayDetail.StartDateTime4.HasValue)
|
|
{
|
|
if (dayDetail.EndDateTime4.Value.Subtract(dayDetail.StartDateTime4.Value).TotalHours > maxHoursUntilBreak)
|
|
{
|
|
addHours -= (breakMinutes / 60m);
|
|
dayDetail.MinutesTotal -= breakMinutes;
|
|
TimeSpan tspan = TimeSpan.FromMinutes((double)dayDetail.MinutesTotal);
|
|
dayDetail.TimeTotal = String.Format("{0:00}:{1:00}", tspan.Hours, tspan.Minutes);
|
|
dayDetail.PauseInMinuten += breakMinutes;
|
|
}
|
|
}
|
|
|
|
return addHours;
|
|
}
|
|
|
|
public override void BerechneExtraUeberstunden(Employee emp, DateTime start, DateTime ende, out decimal stunden)
|
|
{
|
|
stunden = 0;
|
|
|
|
if (emp.Overtimes != null)
|
|
{
|
|
foreach (var ot in emp.Overtimes)
|
|
{
|
|
if (ot.Date >= start && ot.Date < ende)
|
|
{
|
|
stunden += ot.Betrag;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public override Contract GetNextValidContractForDate(DateTime dt, Employee emp)
|
|
{
|
|
Contract contractNextToStartDate = emp.GetValidContractForDate(dt);
|
|
|
|
if (contractNextToStartDate == null)
|
|
{
|
|
|
|
foreach (var item in emp.Contracts)
|
|
{
|
|
if (item.EntryDate.HasValue && item.EntryDate.Value >= dt)
|
|
{
|
|
if (contractNextToStartDate == null || item.EntryDate.Value < contractNextToStartDate.EntryDate.Value)
|
|
{
|
|
contractNextToStartDate = item;
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
}
|
|
return contractNextToStartDate;
|
|
}
|
|
|
|
public override decimal? GetFehlzeiten(IList<StundenkontoServiceRecord> records, DateTime monat)
|
|
{
|
|
|
|
return GetFehlzeiten(records, monat, monat.AddMonths(1));
|
|
}
|
|
|
|
public override decimal? GetFehlzeiten(IList<StundenkontoServiceRecord> records, DateTime start, DateTime ende)
|
|
{
|
|
decimal? stunden = null;
|
|
decimal minuten = 0;
|
|
if (records != null)
|
|
{
|
|
foreach (var item in records)
|
|
{
|
|
if (IsServiceRecordFehlzeit(item))
|
|
{
|
|
if (item.Start >= start && item.Start < ende)
|
|
{
|
|
if (item.GroupRoundedDuration.HasValue)
|
|
{
|
|
minuten += item.GroupRoundedDuration.Value;
|
|
}
|
|
else
|
|
{
|
|
if (appSettings.ShowRoundedDurationInEmployeeAnalysis)
|
|
{
|
|
minuten += item.RoundedDuration;
|
|
}
|
|
else
|
|
{
|
|
minuten += (decimal)item.End.Subtract(item.Start).TotalMinutes;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (minuten != 0)
|
|
stunden = minuten / 60;
|
|
return stunden;
|
|
}
|
|
|
|
|
|
public override decimal GetArbeitstageImMonat(DateTime datum, Employee emp)
|
|
{
|
|
Contract c = GetNextValidContractForDate(datum, emp);
|
|
if (c == null)
|
|
return 0;
|
|
|
|
DateTime start = new DateTime(datum.Year, datum.Month, 1);
|
|
|
|
DateTime end = start.AddMonths(1);
|
|
|
|
if (c.EntryDate.HasValue)
|
|
{
|
|
if (c.EntryDate >= end)
|
|
return 0;
|
|
|
|
if (c.EntryDate > start)
|
|
start = c.EntryDate.Value;
|
|
}
|
|
if (c.ContractEndDate.HasValue && c.ContractEndDate.Value.AddDays(1) < end)
|
|
end = c.ContractEndDate.Value.AddDays(1);
|
|
|
|
return GetArbeitstageImBereich(start, end, emp);
|
|
|
|
}
|
|
|
|
public override decimal GetArbeitstageImBereich(DateTime von, DateTime bis, Employee emp)
|
|
{
|
|
return GetArbeitstageImBereich(von, bis, emp, false);
|
|
|
|
}
|
|
|
|
// Wenn das Flag berechneAnhandArbeitszeiten gesetzt ist, werden nur die Wochentage als Arbeitstage berechent, für die auch im Reiter Arbeitszeiten ein entsprechender Arbeitstag hinterlegt wurde.
|
|
// Nur relevant bei < 5 Wochenarbeitstagen
|
|
// Aus Kompatibilitätsgründen ist das Flag standardmäßig auf false
|
|
public override decimal GetArbeitstageImBereich(DateTime von, DateTime bis, Employee emp, bool berechneAnhandArbeitszeiten)
|
|
{
|
|
DateTime start = von;
|
|
DateTime end = bis;
|
|
|
|
if (BerechneSollNurBisZumAktuellenTag())
|
|
{
|
|
if (end > DateTime.Now.Date.AddDays(1))
|
|
{
|
|
end = DateTime.Now.Date.AddDays(1);
|
|
}
|
|
}
|
|
|
|
TimeSpan ts = end.Subtract(start);
|
|
|
|
var maxDays = (decimal)ts.TotalDays;
|
|
|
|
while (start < end)
|
|
{
|
|
int arbeitstageProWoche = ANZ_WOCHENTAGE;
|
|
|
|
var c = GetContractForDate(emp, start, start);
|
|
|
|
if (c != null && c.WeeklyDays.HasValue && c.WeeklyDays > 0)
|
|
{
|
|
arbeitstageProWoche = c.WeeklyDays.Value;
|
|
}
|
|
|
|
|
|
if (arbeitstageProWoche <= 5 && (start.DayOfWeek == DayOfWeek.Saturday || start.DayOfWeek == DayOfWeek.Sunday))
|
|
{
|
|
maxDays -= 1;
|
|
}
|
|
else if (arbeitstageProWoche == 6 && start.DayOfWeek == DayOfWeek.Sunday)
|
|
{
|
|
maxDays -= 1;
|
|
}
|
|
else
|
|
{
|
|
if (berechneAnhandArbeitszeiten && arbeitstageProWoche < 5)
|
|
{
|
|
if (GetSollNachArbeitszeitenFuerTag(emp, start) == 0)
|
|
{
|
|
maxDays--; // Kein Arbeitstag, also Anzahl um eins verringern
|
|
}
|
|
}
|
|
else if (!BerechneFeiertageAlsArbeitszeit())
|
|
{
|
|
maxDays -= GetFeiertagsFaktor(start);
|
|
}
|
|
}
|
|
start = start.AddDays(1);
|
|
}
|
|
return maxDays;
|
|
|
|
}
|
|
|
|
public override Contract GetContractForDate(Employee emp, DateTime start, DateTime ende)
|
|
{
|
|
Contract c = emp.GetValidContractForDate(start);
|
|
if (c == null) // Kein Vertrag vom 1. gefunden, gucken, ob es einen gibt, der mitten im Monat beginnt.
|
|
{
|
|
var c2 = GetNextValidContractForDate(start, emp);
|
|
if (c2 != null && c2.EntryDate.HasValue && c2.EntryDate.Value < ende)
|
|
c = c2;
|
|
}
|
|
return c;
|
|
}
|
|
|
|
//gibt 1 zurück wenn ganzer Feiertag und z.B. 0.5 bei halben etc.
|
|
public override decimal GetFeiertagsFaktor(DateTime start)
|
|
{
|
|
//CB 26.02.2024: Darf nicht über den VacationService gehen, sonst werden die im CustomMitarbeiterstundenkontoBerechnung hinterlegten Feiertage ignoriert
|
|
//return vacationService.GetFeiertagsFaktor(start);
|
|
if (IstFeiertag(start))
|
|
{
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
public override bool IstFeiertag(DateTime start)
|
|
{
|
|
// Claude: HashSet-Lookup statt linearer Suche; pro Jahr nur einmal aufgebaut
|
|
if (_feiertageSetCache == null)
|
|
{
|
|
_feiertageSetCache = new Dictionary<int, HashSet<DateTime>>();
|
|
}
|
|
HashSet<DateTime> set;
|
|
if (!_feiertageSetCache.TryGetValue(start.Year, out set))
|
|
{
|
|
set = new HashSet<DateTime>(GetFeiertage(start.Year));
|
|
_feiertageSetCache[start.Year] = set;
|
|
}
|
|
return set.Contains(start.Date);
|
|
}
|
|
|
|
public override List<FeiertagDC> GetFeiertageDCs(int year)
|
|
{
|
|
// Hier muss das Bundesland überprüft werden und für z.B. Hessen Allerheiligen raus
|
|
return vacationService.GetFeiertage(year, vacationService.Bundesland);
|
|
}
|
|
|
|
public override List<DateTime> GetFeiertage(int year)
|
|
{
|
|
// Claude: Feiertagsliste pro Jahr cachen, damit vacationService.GetFeiertage nicht wiederholt aufgerufen wird
|
|
if (_feiertageListeCache == null)
|
|
{
|
|
_feiertageListeCache = new Dictionary<int, List<DateTime>>();
|
|
}
|
|
List<DateTime> list;
|
|
if (!_feiertageListeCache.TryGetValue(year, out list))
|
|
{
|
|
list = vacationService.GetFeiertage(year);
|
|
_feiertageListeCache[year] = list;
|
|
}
|
|
return list;
|
|
}
|
|
|
|
|
|
protected DateTime GetOsterSonntag(int year)
|
|
{
|
|
return VacationService.GetOsterSonntag(year);
|
|
}
|
|
|
|
public override bool IsServiceRecordFehlzeit(StundenkontoServiceRecord sr)
|
|
{
|
|
if (sr.ServiceDescription != null && sr.ServiceDescription.Category != null && !String.IsNullOrEmpty(sr.ServiceDescription.Category.Name) && sr.ServiceDescription.Category.Name.ToLower().IndexOf("fehlzeit") >= 0)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
public override bool BerechneFeiertageAlsArbeitszeit()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
public override bool AbsenceTimeAufIstStundenAddieren(AbsenceTime at)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
public override bool StundenBeiAbwesenheitBerechnen(AbsenceTime at)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
public override decimal BerechneGesamtArbeitszeitDurchFeiertage(MitarbeiterstundenkontoRO.EmployeeDetail ed, DateTime start, DateTime ende)
|
|
{
|
|
decimal az = 0;
|
|
if (BerechneFeiertageAlsArbeitszeit() && ed.Employee.IsActive == ActivationTypeId.Active)
|
|
{
|
|
DateTime dt = start;
|
|
while (dt < ende)
|
|
{
|
|
if (GetFeiertagsFaktor(dt) > 0)
|
|
{
|
|
az += BerechneSollStundenFuerTag(ed, dt);
|
|
}
|
|
dt = dt.AddDays(1);
|
|
}
|
|
}
|
|
return az;
|
|
}
|
|
|
|
public AbsenceTime GetAbsenceTimeForDate(MitarbeiterstundenkontoRO.EmployeeDetail ed, DateTime date)
|
|
{
|
|
var absenceTimes = ed.Employee.AbsenceTimes;
|
|
|
|
if (absenceTimes != null)
|
|
{
|
|
foreach (var absenceTime in absenceTimes)
|
|
{
|
|
if (absenceTime.Start < date.AddDays(1) && (!absenceTime.End.HasValue || absenceTime.End >= date))
|
|
{
|
|
return absenceTime;
|
|
}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public class AbsenceTimeDays
|
|
{
|
|
public decimal? TageUrlaub;
|
|
public decimal? TageKrankheit;
|
|
public decimal? TageGesamt;
|
|
public decimal TageAlsIst;
|
|
public decimal? StundenUrlaub;
|
|
public decimal? StundenKrankheit;
|
|
public decimal? StundenGesamt;
|
|
public decimal StundenAlsIst;
|
|
|
|
public Dictionary<DateTimeSpan, decimal> TageProWocheDict;
|
|
public Dictionary<DateTime, decimal> StundenProTagDict;
|
|
public Dictionary<DateTimeSpan, decimal> RestStundenProWocheDict;
|
|
}
|
|
|
|
}
|
|
|