Files
BeWoPlaner/ReportImp/BeWoAuxilio/CustomMitarbeiterstundenkontoBerechnung.cs

643 lines
29 KiB
C#

using System;
using System.Collections.Generic;
using BeWo.Data.Entities;
using BeWo.Report.ReportObjects;
using BS.Shared.Core;
using System.Linq;
using BeWo.Data.Access;
using BeWo.Service.DCEntityMapper;
using BS.Shared;
using BS.Shared.Extensions;
using BeWo.Service.Dienstplanung;
namespace BeWo.BeWoAuxilio
{
public class CustomMitarbeiterstundenkontoBerechnung : MitarbeiterstundenkontoBerechnung
{
// die haben in 2024 ein recht komplexes Bonussystem eingeführt, dafür wurden folgende Customfelder besetzt:
// Custom1 = geleistete FLS, Custom2 = Monatssoll, Custom3 = FLS-Monatssoll , Custom4 = Wochensoll,
// Custom5 = besondere Aufgaben (werden neutral bewertet), Custom6 = Fehlkontakte (neutral), Custom7 = StundenGes bereinigt, Custom8 = FLS bereinigt
// Custom15 = vertragliches Quotensoll, Custom14 = FLS QuoteMonat, Custom13 = Quoten Diff zum Soll, Custom12 = FLS über FLS-Soll pro Monat,
// Custom11 = Bonusbetrag in €, Custom10 = BonusVormonate des Quartals, Custom9 = BonusQuartal,
private decimal maxWochenstunden = 40;
private const int ANZ_WOCHENTAGE = 5;
public decimal bonusVormonate = 0;
public DateTime reportStart;
decimal FLS1 = 0;
decimal bes1 = 0;
decimal fk1 = 0;
decimal FLS2 = 0;
decimal bes2 = 0;
decimal fk2 = 0;
public override decimal GetFeiertagsFaktor(DateTime start)
{
// 24.12. und 31.12. halbe Tage
if ((start.Day == 24 || start.Day == 31) && start.Month == 12)
return 0.5m;
if (IstFeiertag(start))
{
return 1;
}
return 0;
}
public override void CreateEmployeeDetails(MitarbeiterstundenkontoRO ro)
{
reportStart = ro.ReportStartDate;
base.CreateEmployeeDetails(ro);
foreach (var ed in ro.EmployeeDetailList)
{
ed.FlsDiff = ed.Custom1 - ed.FlsSoll;
var urlaubKrankheit = 0m;
if (ed.TageUrlaub.HasValue)
urlaubKrankheit += ed.TageUrlaub.Value;
if (ed.TageKrankheit.HasValue)
urlaubKrankheit += ed.TageKrankheit.Value;
ed.UrlaubUndKrankheit = urlaubKrankheit;
// Bonusberechnung
ed.Custom10 = bonusVormonate;
ed.Custom15 = 0.75m; // Standard, Rentner mit Vertragsart Renter müssen 80 % leisten
var preis = GetPreis(ro.ReportStartDate);
Contract c = ed.Employee.GetValidContractForDate(ro.ReportStartDate);
if (c != null)
{
if (c.WeeklyTotalHours.HasValue)
{
ed.Custom4 = c.WeeklyTotalHours.Value; //Wochensoll
}
if (c.ContractTypeValueListEntry != null && c.ContractTypeValueListEntry.Value.ToLower().Contains("rent"))
{
ed.Custom15 = 0.8m;
}
}
else if (ed.Employee.LastContract != null)
{
if (ed.Employee.LastContract.WeeklyTotalHours.HasValue)
{
ed.Custom4 = ed.Employee.LastContract.WeeklyTotalHours.Value; //Wochensoll
}
if (ed.Employee.LastContract.ContractTypeValueListEntry != null && ed.Employee.LastContract.ContractTypeValueListEntry.Value.ToLower().Contains("rent"))
{
ed.Custom15 = 0.8m;
}
}
ed.Custom2 = ed.StundenSollOhneUrlaubKrankheit - ed.Custom5 - ed.Custom6; //Monatssoll - bereinigt um besondere Aufgaben u Fehlkontakte
ed.Custom3 = Math.Round(ed.Custom15 * ed.Custom2, 2, MidpointRounding.AwayFromZero); //FLSMonatsSoll
if (ed.StundenGesamtIst != 0)
{
ed.Custom7 = ed.StundenGesamtIst - ed.Custom5 - ed.Custom6; //Gesamtstunden bereinigt um bes.Aufgaben & Fehlkontakte
ed.Custom8 = ed.Custom1 - ed.Custom6; //FLS bereinigt um Fehlkontakte
ed.Custom14 = ed.Custom8 / ed.Custom7; //FLS Quote Monat
}
if (ed.UrlaubUndKrankheit < 3 && ed.Custom7 > ed.Custom2)
{
ed.Custom3 = Math.Round(ed.Custom15 * ed.Custom7, 2, MidpointRounding.AwayFromZero);
ed.Custom13 = ed.Custom14 - ed.Custom15; // Quoten Diff zum Soll
if (ed.Custom13 > 0)
{
ed.Custom12 = Math.Round(ed.Custom8 - ed.Custom3, 2, MidpointRounding.AwayFromZero); //FLS über FLS-Soll pro Monat
ed.Custom11 = ed.Custom12 * preis;
if (ed.Custom11 > 150) // Deckelung bei 150 € im Monat
{
ed.Custom11 = 150;
}
}
else
{
ed.Custom13 = 0;
}
}
if (reportStart.Month == 3 || reportStart.Month == 6 || reportStart.Month == 9 || reportStart.Month == 12)
{
ed.Custom10 = bonusVormonate;
ed.Custom9 = ed.Custom11 + ed.Custom10;
}
}
}
protected override decimal? BerechneUeberstunden(MitarbeiterstundenkontoRO.EmployeeDetail ed, decimal? ueberstundenStd, Contract contract, DateTime start, DateTime ende)
{
// Bonusberechnung fürs Quartal funktioniert fast, mir fehlt nur das ed.Custom1 bzw. HandleServiceRecord für Vormonate
base.BerechneUeberstunden(ed, ueberstundenStd, contract, start, ende);
if ((reportStart.Month == 3 || reportStart.Month == 6 || reportStart.Month == 9 || reportStart.Month == 12)
&& (start == reportStart.AddMonths(-2) || start == reportStart.AddMonths(-1)))
{
decimal ratio = 0.75m;
if (contract.ContractTypeValueListEntry != null && contract.ContractTypeValueListEntry.Value.ToLower().Contains("rent"))
{
ratio = 0.8m;
}
var urlaubKrankheit = 0m;
if (ed.AbsenceTimeDays != null && ed.AbsenceTimeDays.TageGesamt.HasValue)
urlaubKrankheit += ed.AbsenceTimeDays.TageGesamt.Value;
var preis = GetPreis(start);
if (start == reportStart.AddMonths(-2))
{
ed.Custom2 = ed.StundenSollOhneUrlaubKrankheit - bes1 - fk1; // Monatssoll
ed.Custom3 = Math.Round(ratio * ed.Custom2, 2, MidpointRounding.AwayFromZero); //FLSMonatsSoll
if (urlaubKrankheit < 3 && (ed.StundenGesamtIst - bes1 - fk1) > ed.Custom2)
{
ed.Custom3 = Math.Round(ratio * (ed.StundenGesamtIst - bes1 - fk1), 2, MidpointRounding.AwayFromZero);
ed.Custom14 = (FLS1 - fk1) / (ed.StundenGesamtIst - bes1 - fk1); //FLS Quote Monat
ed.Custom13 = ed.Custom14 - ratio; // Quoten Diff zum Soll
if (ed.Custom13 > 0)
{
ed.Custom12 = Math.Round(FLS1 - fk1 - ed.Custom3, 2, MidpointRounding.AwayFromZero);
}
}
}
else if (start == reportStart.AddMonths(-1))
{
ed.Custom2 = ed.StundenSollOhneUrlaubKrankheit - bes2 - fk2; // Monatssoll
ed.Custom3 = Math.Round(ratio * ed.Custom2, 2, MidpointRounding.AwayFromZero); //FLSMonatsSoll
if (urlaubKrankheit < 3 && (ed.StundenGesamtIst - bes2 - fk2) > ed.Custom2)
{
ed.Custom3 = Math.Round(ratio * (ed.StundenGesamtIst - bes2 - fk2), 2, MidpointRounding.AwayFromZero);
ed.Custom14 = (FLS2 - fk2) / (ed.StundenGesamtIst - bes2 - fk2); //FLS Quote Monat
ed.Custom13 = ed.Custom14 - ratio; // Quoten Diff zum Soll
if (ed.Custom13 > 0)
{
ed.Custom12 = Math.Round(FLS2 - fk2 - ed.Custom3, 2, MidpointRounding.AwayFromZero);
}
}
}
var bonus = ed.Custom12 * preis;
if (bonus > 150) // Deckelung bei 150 € im Monat
{
bonus = 150;
}
bonusVormonate += bonus;
}
return ueberstundenStd;
}
public override MitarbeiterstundenkontoRO.EmployeeDetail BerechneUeberstundenBis(MitarbeiterstundenkontoRO.EmployeeDetail edOrig, IList<StundenkontoServiceRecord> records, DateTime monat, out decimal? ueberstunden, out decimal alteExtraUeberstunden)
{
//Minimale Änderungen zum Original, aber ich brauche den Pausenabzug und Fehltage für die Boni -> Änderungen markiert mit //Änderung
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);
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;
}
}
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;
decimal ah = 0;
if (ShouldCreateDayDetails())
{
var days = CreateDayDetails(start, ende, ed, records);
ah = GetAdditionalHours(days, start, ende, ed, records);
ueberstunden += ah;
}
ed.StundenSollOhneUrlaubKrankheit = sollMitarbeiter;
ed.FlsIst = flsIst / 60;
ed.MittelbarIst = mittelbarIst / 60;
ed.StundenGesamtIst = stundenMonat + ah;
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;
}
public virtual decimal GetPreis(DateTime datum)
{
var preise = DAOFactory.GenericDAO.GetAllActive<Preis>();
var preis = preise.Where(p => p.Name.Contains("Bonus")).FirstOrDefault();
if (preis != null)
{
var crlist = MapperFactory.CostRatePeriodDC_CostRatePeriod.MapToNewDCs(preis.CostRatePeriods);
var cr = crlist.GetCostRatePeriodForDate(CostRatePeriodType.AmountOfMoney, datum);
if (cr != null && cr.CostRateValue.HasValue)
{
return cr.CostRateValue.Value;
}
}
return 13;
}
public override bool ShouldCreateDayDetails()
{
return true;
}
public override MitarbeiterstundenkontoRO.DayDetail CreateDayDetail(DateTime date, MitarbeiterstundenkontoRO.EmployeeDetail ed)
{
if (date < new DateTime(2022, 10, 01))
{
MitarbeiterstundenkontoRO.DayDetail dd = new MitarbeiterstundenkontoRO.DayDetail();
dd.ZusammenhaengendeZeitraeume = new List<DateTimeSpan>();
dd.Date = date.Date;
CalculateAbsenceTimesForDay(dd, ed);
return dd;
}
else
{
var dd = base.CreateDayDetail(date, ed);
dd.MaxHoursUntilBreak = 6;
dd.MaxHoursUntilBreak2 = 9;
dd.BreakMinutes = 30;
dd.BreakMinutes2 = 15;
return dd;
}
}
public override decimal CalculateBreakMinutes(MitarbeiterstundenkontoRO.DayDetail dayDetail, int maxHoursUntilBreak, int breakMinutes)
{
decimal addHours = 0;
if (maxHoursUntilBreak != 9 && breakMinutes != 15)
{
addHours = base.CalculateBreakMinutes(dayDetail, maxHoursUntilBreak, breakMinutes);
}
else
{
if (dayDetail.ZusammenhaengendeZeitraeume != null && dayDetail.ZusammenhaengendeZeitraeume.Count > 0)
{
foreach (var span in dayDetail.ZusammenhaengendeZeitraeume)
{
if (span.EndDateTime.Subtract(span.StartDateTime).TotalHours - 0.5 > maxHoursUntilBreak)
{
addHours -= (breakMinutes / 60m);
dayDetail.MinutesTotal -= breakMinutes;
dayDetail.PauseInMinuten += breakMinutes;
}
}
return addHours;
}
if (dayDetail.EndDateTime1.HasValue && dayDetail.StartDateTime1.HasValue)
{
if (dayDetail.EndDateTime1.Value.Subtract(dayDetail.StartDateTime1.Value).TotalHours - 0.5 > maxHoursUntilBreak)
{
addHours -= (breakMinutes / 60m);
dayDetail.MinutesTotal -= breakMinutes;
dayDetail.PauseInMinuten += breakMinutes;
}
}
if (dayDetail.EndDateTime2.HasValue && dayDetail.StartDateTime2.HasValue)
{
if (dayDetail.EndDateTime2.Value.Subtract(dayDetail.StartDateTime2.Value).TotalHours - 0.5 > maxHoursUntilBreak)
{
addHours -= (breakMinutes / 60m);
dayDetail.MinutesTotal -= breakMinutes;
dayDetail.PauseInMinuten += breakMinutes;
}
}
if (dayDetail.EndDateTime3.HasValue && dayDetail.StartDateTime3.HasValue)
{
if (dayDetail.EndDateTime3.Value.Subtract(dayDetail.StartDateTime3.Value).TotalHours - 0.5 > maxHoursUntilBreak)
{
addHours -= (breakMinutes / 60m);
dayDetail.MinutesTotal -= breakMinutes;
dayDetail.PauseInMinuten += breakMinutes;
}
}
if (dayDetail.EndDateTime4.HasValue && dayDetail.StartDateTime4.HasValue)
{
if (dayDetail.EndDateTime4.Value.Subtract(dayDetail.StartDateTime4.Value).TotalHours - 0.5 > maxHoursUntilBreak)
{
addHours -= (breakMinutes / 60m);
dayDetail.MinutesTotal -= breakMinutes;
dayDetail.PauseInMinuten += breakMinutes;
}
}
}
return addHours;
}
public override bool AddServiceRecordToDuration(StundenkontoServiceRecord item)
{
if (item.ServiceDescription != null && item.ServiceDescription.Category != null)
{
if (item.ServiceDescription.Category.Name == "Arbeitszeit")
{
return true;
}
}
return false;
}
public override void HandleServiceRecords(MitarbeiterstundenkontoRO.EmployeeDetail empDetail, IList<StundenkontoServiceRecord> groupFilteredRecords, Contract contract)
{
if (reportStart.Month == 3 || reportStart.Month == 6 || reportStart.Month == 9 || reportStart.Month == 12)
//&& (start == reportStart.AddMonths(-2) || start == reportStart.AddMonths(-1)))
{
foreach (var item in groupFilteredRecords)
{
if (item.Start >= reportStart.AddMonths(-2) && item.Start < reportStart.AddMonths(-1))
{
decimal duration = 0;
if (item.GroupRoundedDuration.HasValue)
{
duration = item.GroupRoundedDuration.Value;
}
else
{
duration = item.RoundedDuration;
}
if (item.ServiceDescription != null && item.ServiceDescription.Category != null && item.ServiceDescription.Category.IsBillable)
{
FLS1 += duration / 60;
}
if (item.ServiceDescription != null && item.ServiceDescription.Category != null && item.ServiceDescription.Category.Name.ToLower().Contains("besondere aufgaben"))
{
bes1 += duration / 60;
}
if (item.ServiceDescription != null && item.ServiceDescription.Name != null && item.ServiceDescription.Name.ToLower().Contains("fehlkontakt"))
{
fk1 += duration / 60;
}
}
if (item.Start >= reportStart.AddMonths(-1) && item.Start < reportStart)
{
decimal duration = 0;
if (item.GroupRoundedDuration.HasValue)
{
duration = item.GroupRoundedDuration.Value;
}
else
{
duration = item.RoundedDuration;
}
if (item.ServiceDescription != null && item.ServiceDescription.Category != null && item.ServiceDescription.Category.IsBillable)
{
FLS2 += duration / 60;
}
if (item.ServiceDescription != null && item.ServiceDescription.Category != null && item.ServiceDescription.Category.Name.ToLower().Contains("besondere aufgaben"))
{
bes2 += duration / 60;
}
if (item.ServiceDescription != null && item.ServiceDescription.Name != null && item.ServiceDescription.Name.ToLower().Contains("fehlkontakt"))
{
fk2 += duration / 60;
}
}
}
}
}
public override void HandleServiceRecord(MitarbeiterstundenkontoRO.EmployeeDetail emp, StundenkontoServiceRecord item)
{
decimal duration = 0;
if (item.GroupRoundedDuration.HasValue)
{
duration = item.GroupRoundedDuration.Value;
}
else
{
duration = item.RoundedDuration;
}
if (item.ServiceDescription != null && item.ServiceDescription.Category != null && item.ServiceDescription.Category.IsBillable)
{
emp.Custom1 += duration / 60;
}
if (item.ServiceDescription != null && item.ServiceDescription.Category != null && item.ServiceDescription.Category.Name.ToLower().Contains("besondere aufgaben"))
{
emp.Custom5 += duration / 60;
}
if (item.ServiceDescription != null && item.ServiceDescription.Name != null && item.ServiceDescription.Name.ToLower().Contains("fehlkontakt"))
{
emp.Custom6 += duration / 60;
}
}
public override bool IstAnrechenbareAbsenceTime(AbsenceTime item)
{
if (item.AbsenceReason.Description != null && item.AbsenceReason.Description.ToLower().Contains("freizeitausgleich"))
return false;
else
return base.IstAnrechenbareAbsenceTime(item);
}
}
}