MH: VSD Hamm Kappung in der Sammelrechnung

SupportOID=168627
This commit is contained in:
2026-07-21 09:28:04 +02:00
parent bb0a227a00
commit d110afa147
3 changed files with 88 additions and 11 deletions

View File

@@ -25,7 +25,7 @@ namespace VSDHammReports.Invoicing
// da nur hier noch die Wochen-/Monatszuordnung über ItemPeriodStart möglich ist.
public override IList<InvoiceItem> CreateSummaryInvoiceItems(IList<InvoiceItem> itemList, DateTimeSpan invoicePeriod, SupportConceptApprovalPeriod scap, Calculations calc)
{
KappeBeiUeberschreitungDesIntervallBudgets(itemList, scap, calc);
KappeBeiUeberschreitungDesIntervallBudgets(itemList, scap);
return base.CreateSummaryInvoiceItems(itemList, invoicePeriod, scap, calc, false, true);
}
@@ -40,6 +40,79 @@ namespace VSDHammReports.Invoicing
base.CheckMaxApprovedAmount(invoice, sip, scap, costBearer2SupportConcept, invoicePeriod, serviceRecords);
}
// Claude: Die im Report angezeigten Werte "bewilligte Stunden/Betrag im Zeitraum"
// (xrTableCell18 = [ApprovedHours]/[ApprovedBE], xrTableCell29 = [ApprovedAmount])
// an die Kappung angleichen. Die Basisklasse setzt sie über GetApprovedHoursForPeriod,
// das bei Weekly/Monthly Std/Tag * Tage rechnet und den bewilligten Intervallwert verfehlt
// (z.B. 5 h/Monat -> 4,93 h bei 30 Tagen). Hier wird stattdessen je vollem Kalenderintervall
// der bewilligte Wert angesetzt - konsistent zur Kappung. Die Korrektur erfolgt auf der
// Entity vor dem Mapping ins ReportObject, daher stimmen automatisch auch die Summen.
public override void SetServiceInvoicePeriods(ServiceInvoice invoice, CostBearer2SupportConcept costBearer2SupportConcept, DateTimeSpan invoicePeriod, List<ServiceRecordDC> serviceRecords)
{
// Claude: Nur die in diesem Aufruf (= für diesen c2s) neu hinzugefügten Perioden nachziehen,
// damit bei Sammelrechnungen keine Perioden anderer c2s mit falschen CostRates überschrieben werden.
int startIndex = invoice.ServiceInvoicePeriodList.Count;
base.SetServiceInvoicePeriods(invoice, costBearer2SupportConcept, invoicePeriod, serviceRecords);
var costRates = MapperFactory.CostRatePeriodDC_CostRatePeriod.MapToNewDCs(costBearer2SupportConcept.CostBearer.CostRatePeriods);
for (int i = startIndex; i < invoice.ServiceInvoicePeriodList.Count; i++)
{
ZieheBewilligtImZeitraumGleich(invoice.ServiceInvoicePeriodList[i], costRates);
}
}
private void ZieheBewilligtImZeitraumGleich(ServiceInvoicePeriod sip, List<CostRatePeriodDC> costRates)
{
if (sip.SupportConceptApprovalPeriod == null || !sip.Start.HasValue || !sip.End.HasValue)
{
return;
}
var scapDC = MapperFactory.SupportConceptApprovalPeriodDC_SupportConceptApprovalPeriod.MapToNewDC(sip.SupportConceptApprovalPeriod);
if (!IstIntervallKappung(scapDC.ApprovedBEInterval, scapDC.ApprovedBEPerInterval, scapDC.IsApprovedBEShifting))
{
return;
}
var serviceUnit = costRates.GetCostRatePeriodForDate(CostRatePeriodType.MinutesPerServiceUnit, sip.Start.Value);
if (serviceUnit == null)
{
return;
}
// Claude: bewilligte Stunden je vollem Intervall (z.B. 5 h/Monat), unabhängig von der Kalenderlänge -
// dieselbe Basis wie die Kappung in KappeBeiUeberschreitungDesIntervallBudgets.
decimal stundenProIntervall = serviceUnit.GetBEInHours(scapDC.ApprovedBEPerInterval) ?? 0;
// Claude: Anzahl der vom Zeitraum berührten Kalenderintervalle (Wochen/Monate) - wie bei der Kappung,
// wo jedes berührte Kalenderintervall sein volles Budget erhält.
int anzahlIntervalle = ZaehleIntervalle(scapDC.ApprovedBEInterval.Value, sip.Start.Value, sip.End.Value);
decimal bewilligteStunden = stundenProIntervall * anzahlIntervalle;
sip.ApprovedHours = bewilligteStunden;
sip.ApprovedBE = serviceUnit.GetHoursInBE(bewilligteStunden);
var hourlyRate = costRates.GetCostRatePeriodForDate(CostRatePeriodType.HourlyRate, sip.Start.Value);
if (hourlyRate != null && hourlyRate.CostRateValue.HasValue)
{
sip.ApprovedAmountDefaultHourlyRate = bewilligteStunden * hourlyRate.CostRateValue.Value;
}
}
private int ZaehleIntervalle(SupportConceptApprovalInterval interval, DateTime start, DateTime end)
{
if (interval == SupportConceptApprovalInterval.Monthly)
{
return (end.Year * 12 + end.Month) - (start.Year * 12 + start.Month) + 1;
}
// Claude: Weekly - Anzahl berührter Kalenderwochen (Montag als Wochenstart).
DateTime ersterMontag = start.GetLast(DayOfWeek.Monday).Date;
return ((end.Date - ersterMontag).Days / 7) + 1;
}
private bool IstIntervallKappung(SupportConceptApprovalInterval? approvedBEInterval, decimal? approvedBEPerInterval, bool isApprovedBEShifting)
{
return !isApprovedBEShifting &&
@@ -48,7 +121,7 @@ namespace VSDHammReports.Invoicing
approvedBEInterval.Value == SupportConceptApprovalInterval.Monthly);
}
private void KappeBeiUeberschreitungDesIntervallBudgets(IList<InvoiceItem> itemList, SupportConceptApprovalPeriod scap, Calculations calc)
private void KappeBeiUeberschreitungDesIntervallBudgets(IList<InvoiceItem> itemList, SupportConceptApprovalPeriod scap)
{
if (scap == null || itemList == null || itemList.Count == 0)
{
@@ -77,12 +150,16 @@ namespace VSDHammReports.Invoicing
if (!intervalMaxStunden.ContainsKey(intervalKey))
{
// Claude: Bewilligte Stunden für das volle Intervall über dieselbe Berechnung
// ermitteln, mit der die Basisklasse auch ApprovedHours bestimmt (inkl. BE-Umrechnung).
DateTime intervalEnde = interval == SupportConceptApprovalInterval.Weekly
? intervalKey.AddDays(6)
: intervalKey.AddMonths(1).AddDays(-1);
intervalMaxStunden[intervalKey] = calc.GetApprovedHoursForPeriod(scapDC, costRates, intervalKey, intervalEnde) ?? 0;
// Claude: Budget = die je Intervall bewilligten Stunden direkt aus der bewilligten BE-Menge.
// NICHT über GetApprovedHoursForPeriod (= Stunden-pro-Tag * Tage), denn das schwankt mit der
// Monats-/Wochenlaenge (z.B. 30 Tage -> 4,93 h, 31 Tage -> 5,10 h) und trifft die bewilligten
// 5 h fast nie exakt, wodurch bei der Kappung ein Rest-Euro fehlt oder zu viel entsteht.
// GetBEInHours liefert fuer ein volles Intervall exakt den bewilligten Wert (z.B. 5 h) -
// genau wie die Basisklasse es in GetApprovedHoursPerWeek fuer Weekly macht.
var serviceUnit = costRates.GetCostRatePeriodForDate(CostRatePeriodType.MinutesPerServiceUnit, ii.ItemPeriodStart.Value);
intervalMaxStunden[intervalKey] = serviceUnit != null
? (serviceUnit.GetBEInHours(scapDC.ApprovedBEPerInterval) ?? 0)
: 0;
}
decimal maxStunden = intervalMaxStunden[intervalKey];

View File

@@ -78,7 +78,7 @@ namespace VSDHammReports
double total = 0;
foreach (var value in dict.Values)
{
if (!value.ServiceDescription.Contains("80"))
if (!value.ServiceDescription.Contains("80") && !value.ServiceDescription.Contains("fehlkontakt ohne"))
{
value.Minutes *= (double)rateFactor;
}

View File

@@ -30,9 +30,9 @@ namespace VSDHammReports
}
if (item.ServiceDescription.ToLower().Contains("fehlkontakt"))
summeFehlkontakte += item.UnitCount.Value * item.AmountPerUnit.Value * faktor;
summeFehlkontakte += item.UnitCount.Value * item.AmountPerUnit.Value;
else
summeFachleistungen += item.UnitCount.Value * item.AmountPerUnit.Value * faktor;
summeFachleistungen += item.UnitCount.Value * item.AmountPerUnit.Value;
}
}