Files
BeWoPlaner/ReportImp/FeidPartnerReports/PersonalplanungRO.cs

334 lines
16 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using BeWo.Data.Access;
using BeWo.Data.Entities;
using BeWo.Report;
using BeWo.Service.DCEntityMapper;
using BeWo.Service.Plugins;
using BS.Shared;
using BS.Shared.Core;
using BS.Shared.Services;
namespace FeidPartnerReports
{
public class PersonalplanungRO
{
public DateTime ReportMonth { get; set; }
public List<PersonalplanungRow> Rows { get; set; } = new List<PersonalplanungRow>();
public static PersonalplanungRO Create(DateTime reportMonth)
{
var ro = new PersonalplanungRO();
ro.ReportMonth = reportMonth;
var monthStart = new DateTime(reportMonth.Year, reportMonth.Month, 1);
var monthEnd = monthStart.AddMonths(1).AddDays(-1);
var nextMonthStart = monthEnd.AddDays(1);
var nextMonthEnd = monthStart.AddMonths(2).AddDays(-1);
var serviceRecordStart = monthStart.AddMonths(-3);
decimal weeksInPeriod = (decimal)(monthEnd - serviceRecordStart).TotalDays / 7m;
var employees = DAOFactory.GenericDAO.GetAllActive<Employee>();
foreach (var emp in employees)
{
// ServiceRecords des MAs für den 3-Monats-Zeitraum gebündelt laden
IList<ServiceRecord> empServiceRecords = null;
try
{
empServiceRecords = DAOFactory.SearchDAO.FindEmployeeServiceRecords(
emp.Oid.Value,
new DateTimeSpan(serviceRecordStart, monthEnd),
null, null);
}
catch { }
var groupMinutesByCb2scOid = empServiceRecords != null
? empServiceRecords
.Where(sr => sr.CostBearer2SupportConceptOid.HasValue && sr.GroupOid.HasValue)
.GroupBy(sr => sr.CostBearer2SupportConceptOid.Value)
.ToDictionary(g => g.Key, g => g.Sum(sr => sr.RoundedDuration))
: new Dictionary<long, decimal>();
var empName = $"{emp.Person.LastName}, {emp.Person.FirstName}";
var contract = emp.GetValidContractForDate(monthStart);
decimal weeklyTotalHours = contract?.WeeklyTotalHours ?? 0m;
decimal weeklyFLS = contract?.WeeklyFLS ?? 0m;
var cb2scEntries = new Dictionary<long, Cb2ScEntry>();
var processedScapOids = new HashSet<long>();
// --- Pfad 1: explizite Betreuungsschlüssel (sp2e) — haben Vorrang ---
var sp2eList = DAOFactory.SearchDAO.FindSupportConceptApprovalPeriod2Employees(emp);
if (sp2eList != null)
{
foreach (var sp2e in sp2eList)
{
SupportConceptApprovalPeriod scap;
CostBearer2SupportConcept cb2sc;
try
{
scap = DAOFactory.GenericDAO.Unproxy(sp2e.SupportConceptApprovalPeriod);
if (scap == null || !scap.Oid.HasValue) continue;
cb2sc = DAOFactory.GenericDAO.Unproxy(scap.CostBearer2SupportConcept);
if (cb2sc == null || !cb2sc.Oid.HasValue) continue;
}
catch { continue; }
processedScapOids.Add(scap.Oid.Value);
if (scap.ServiceCategory?.Name?.IndexOf("Fahrzeit", StringComparison.OrdinalIgnoreCase) >= 0) continue;
bool validThis = IsValidInPeriod(scap, monthStart, monthEnd);
bool validNext = IsValidInPeriod(scap, nextMonthStart, nextMonthEnd);
if (!validThis && !validNext) continue;
long key = cb2sc.Oid.Value;
if (!cb2scEntries.ContainsKey(key))
cb2scEntries[key] = new Cb2ScEntry { Cb2Sc = cb2sc };
var entry = cb2scEntries[key];
if (validThis && entry.ScapThis == null) { entry.ScapThis = scap; entry.Sp2eThis = sp2e; }
if (validNext && entry.ScapNext == null) { entry.ScapNext = scap; entry.Sp2eNext = sp2e; }
}
}
// --- Pfad 2: Hauptbetreuer via Employee2Customer (primäre Zuordnung) ---
if (emp.Employee2CustomerList != null)
{
foreach (var e2c in emp.Employee2CustomerList)
{
bool isMainAttendant = e2c.ValueList != null && e2c.ValueList.Count > 0
&& e2c.ValueList[0].Entry.SystemEntryID == SystemEntryID.EmployeeRoleMainAttendant;
if (!isMainAttendant) continue;
if (e2c.Customer == null || e2c.Customer.IsActive == ActivationTypeId.Deleted) continue;
if (e2c.StartDate.HasValue && e2c.StartDate.Value.Date >= nextMonthEnd.Date) continue;
if (e2c.EndDate.HasValue && e2c.EndDate.Value.Date < monthStart) continue;
if (e2c.Customer.SupportConcepts == null) continue;
foreach (var sc in e2c.Customer.SupportConcepts)
{
if (sc.IsActive == ActivationTypeId.Deleted || sc.IsActive == ActivationTypeId.Archived) continue;
if (sc.CostBearer2SupportConceptList == null) continue;
foreach (var c2s in sc.CostBearer2SupportConceptList)
{
if (c2s.ApprovalPeriodList == null) continue;
foreach (var scap in c2s.ApprovalPeriodList)
{
if (!scap.Oid.HasValue) continue;
if (processedScapOids.Contains(scap.Oid.Value)) continue;
if (scap.ServiceCategory?.Name?.IndexOf("Fahrzeit", StringComparison.OrdinalIgnoreCase) >= 0) continue;
bool validThis = IsValidInPeriod(scap, monthStart, monthEnd);
bool validNext = IsValidInPeriod(scap, nextMonthStart, nextMonthEnd);
if (!validThis && !validNext) continue;
CostBearer2SupportConcept cb2sc;
try
{
cb2sc = DAOFactory.GenericDAO.Unproxy(c2s);
if (cb2sc == null || !cb2sc.Oid.HasValue) continue;
}
catch { continue; }
long key = cb2sc.Oid.Value;
if (!cb2scEntries.ContainsKey(key))
cb2scEntries[key] = new Cb2ScEntry { Cb2Sc = cb2sc };
var entry = cb2scEntries[key];
// sp2e == null signalisiert Hauptbetreuer-Pfad
if (validThis && entry.ScapThis == null) { entry.ScapThis = scap; entry.Sp2eThis = null; }
if (validNext && entry.ScapNext == null) { entry.ScapNext = scap; entry.Sp2eNext = null; }
}
}
}
}
}
// --- Zeilen erzeugen ---
foreach (var entry in cb2scEntries.Values)
{
var cb2sc = entry.Cb2Sc;
var sc = cb2sc.SupportConcept;
if (sc == null || sc.IsActive == ActivationTypeId.Deleted || sc.IsActive == ActivationTypeId.Archived) continue;
var customer = sc.Customer;
if (customer == null || customer.IsActive != ActivationTypeId.Active) continue;
if (customer.TerminationDate.HasValue && customer.TerminationDate.Value < monthStart) continue;
decimal bewilligtThis = entry.Sp2eThis != null
? CalculateBewilligtSp2e(entry.ScapThis, entry.Sp2eThis, cb2sc)
: CalculateBewilligtHauptbetreuer(entry.ScapThis, cb2sc);
decimal bewilligtNext = entry.Sp2eNext != null
? CalculateBewilligtSp2e(entry.ScapNext, entry.Sp2eNext, cb2sc)
: CalculateBewilligtHauptbetreuer(entry.ScapNext, cb2sc);
if (bewilligtThis == 0m && bewilligtNext == 0m) continue;
decimal avgGruppeStdProWoche = 0m;
if (cb2sc.Oid.HasValue
&& groupMinutesByCb2scOid.TryGetValue(cb2sc.Oid.Value, out decimal gruppeMin)
&& weeksInPeriod > 0)
avgGruppeStdProWoche = (gruppeMin / 60m) / weeksInPeriod;
decimal geleistetThis = Math.Max(0m, bewilligtThis - avgGruppeStdProWoche);
decimal geleistetNext = Math.Max(0m, bewilligtNext - avgGruppeStdProWoche);
decimal auslastungThis = bewilligtThis > 0 ? Math.Round((geleistetThis / bewilligtThis) * 100m, 1) : 0m;
decimal auslastungNext = bewilligtNext > 0 ? Math.Round((geleistetNext / bewilligtNext) * 100m, 1) : 0m;
var relevantScap = entry.ScapThis ?? entry.ScapNext;
DateTime? enddatum = null;
enddatum = TakeEarliest(enddatum, relevantScap?.EndDate);
enddatum = TakeEarliest(enddatum, cb2sc.EndDate);
enddatum = TakeEarliest(enddatum, customer.TerminationDate);
var kostentraeger = "";
try { kostentraeger = cb2sc.CostBearer?.Organisation?.Name ?? ""; } catch { }
var wohnort = "";
try { wohnort = customer.Person?.Address?.Town ?? ""; } catch { }
ro.Rows.Add(new PersonalplanungRow
{
MitarbeiterName = empName,
VertragWochenstunden = weeklyTotalHours,
VertragFLSProWoche = weeklyFLS,
KlientName = $"{customer.Person.LastName}, {customer.Person.FirstName}",
Kostentraeger = kostentraeger,
WohnortKlient = wohnort,
BewilligteStdProWoche = Math.Round(bewilligtThis, 2),
GeleisteteStdProWoche = Math.Round(geleistetThis, 2),
AuslastungProWoche = auslastungThis,
BewilligteStdProWocheNaechsterMonat = Math.Round(bewilligtNext, 2),
GeleisteteStdProWocheNaechsterMonat = Math.Round(geleistetNext, 2),
AuslastungProWocheNaechsterMonat = auslastungNext,
Enddatum = enddatum,
});
}
}
ro.Rows.Sort((a, b) =>
{
int cmp = string.Compare(a.MitarbeiterName, b.MitarbeiterName, StringComparison.CurrentCulture);
if (cmp != 0) return cmp;
return string.Compare(a.KlientName, b.KlientName, StringComparison.CurrentCulture);
});
return ro;
}
// Bewilligte Std/Woche für explizite Betreuungsschlüssel-Zuordnung
private static decimal CalculateBewilligtSp2e(SupportConceptApprovalPeriod scap, SupportConceptApprovalPeriod2Employee sp2e, CostBearer2SupportConcept cb2sc)
{
if (scap == null || sp2e == null) return 0m;
try
{
decimal rawBewilligt = GetRawApprovedHoursPerWeek(scap, cb2sc);
decimal anteil = sp2e.Betreuungsschluessel;
if (!sp2e.IsAbsolut)
{
if (anteil == 33m) anteil = 100m / 3m;
else if (anteil == 67m) anteil = (100m / 3m) * 2m;
return rawBewilligt * (anteil / 100m);
}
else
{
return anteil / 60m;
}
}
catch { return 0m; }
}
// Bewilligte Std/Woche für Hauptbetreuer = volle Bewilligung minus Anteile anderer MAs
private static decimal CalculateBewilligtHauptbetreuer(SupportConceptApprovalPeriod scap, CostBearer2SupportConcept cb2sc)
{
if (scap == null) return 0m;
try
{
decimal fullHours = GetRawApprovedHoursPerWeek(scap, cb2sc);
decimal deductions = 0m;
var otherSp2e = DAOFactory.SearchDAO.FindSupportConceptApprovalPeriod2Employees(scap);
foreach (var sp2e in otherSp2e)
{
decimal anteil = sp2e.Betreuungsschluessel;
if (!sp2e.IsAbsolut)
{
if (anteil == 33m) anteil = 100m / 3m;
else if (anteil == 67m) anteil = (100m / 3m) * 2m;
deductions += fullHours * (anteil / 100m);
}
else
{
deductions += anteil / 60m;
}
}
return Math.Max(0m, fullHours - deductions);
}
catch { return 0m; }
}
private static decimal GetRawApprovedHoursPerWeek(SupportConceptApprovalPeriod scap, CostBearer2SupportConcept cb2sc)
{
var calc = PluginLoader.FindClass<Calculations>(cb2sc.CostBearer.ID)
?? Calculations.GetInstance(cb2sc.CostBearer.ID);
var scapDc = MapperFactory.SupportConceptApprovalPeriodDC_SupportConceptApprovalPeriod.MapToNewDC(scap);
var cb2scDc = MapperFactory.SupportConceptCostBearerRelDC_CostBearer2SupportConcept.MapToNewDC(cb2sc);
return calc.GetApprovedHoursPerWeek(scapDc, cb2scDc.CostBearer.CostRatePeriods) ?? 0m;
}
private static bool IsValidInPeriod(SupportConceptApprovalPeriod scap, DateTime periodStart, DateTime periodEnd)
{
return (!scap.StartDate.HasValue || scap.StartDate.Value <= periodEnd)
&& (!scap.EndDate.HasValue || scap.EndDate.Value >= periodStart);
}
private static DateTime? TakeEarliest(DateTime? current, DateTime? candidate)
{
if (!candidate.HasValue) return current;
if (!current.HasValue) return candidate;
return candidate.Value < current.Value ? candidate : current;
}
private class Cb2ScEntry
{
public CostBearer2SupportConcept Cb2Sc { get; set; }
public SupportConceptApprovalPeriod2Employee Sp2eThis { get; set; }
public SupportConceptApprovalPeriod ScapThis { get; set; }
public SupportConceptApprovalPeriod2Employee Sp2eNext { get; set; }
public SupportConceptApprovalPeriod ScapNext { get; set; }
}
}
public class PersonalplanungRow
{
public string MitarbeiterName { get; set; }
public decimal VertragWochenstunden { get; set; }
public decimal VertragFLSProWoche { get; set; }
public string MitarbeiterNameMitVertrag => VertragWochenstunden > 0 || VertragFLSProWoche > 0
? $"{MitarbeiterName} (Std/Wo: {VertragWochenstunden:0.##}, FLS/Wo: {VertragFLSProWoche:0.##})"
: MitarbeiterName;
public string KlientName { get; set; }
public string Kostentraeger { get; set; }
public string WohnortKlient { get; set; }
public decimal BewilligteStdProWoche { get; set; }
public decimal GeleisteteStdProWoche { get; set; }
public decimal AuslastungProWoche { get; set; }
public decimal BewilligteStdProWocheNaechsterMonat { get; set; }
public decimal GeleisteteStdProWocheNaechsterMonat { get; set; }
public decimal AuslastungProWocheNaechsterMonat { get; set; }
public DateTime? Enddatum { get; set; }
public string EnddatumFormatted => Enddatum.HasValue ? Enddatum.Value.ToString("dd.MM.yyyy") : "";
public string AuslastungFormatted => AuslastungProWoche.ToString("0.0") + " %";
public string AuslastungNaechsterMonatFormatted => AuslastungProWocheNaechsterMonat.ToString("0.0") + " %";
}
}