334 lines
16 KiB
C#
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") + " %";
|
|
}
|
|
}
|