From d110afa1473392b95be1e4fd560fd6e6aec2f123 Mon Sep 17 00:00:00 2001 From: HirschleM Date: Tue, 21 Jul 2026 09:28:04 +0200 Subject: [PATCH] MH: VSD Hamm Kappung in der Sammelrechnung SupportOID=168627 --- .../Invoicing/CustomInvoiceCreation.cs | 93 +++++++++++++++++-- .../VSDHammReports/LeistungsnachweisJAHamm.cs | 2 +- .../VSDHammReports/ServiceInvoiceReport.cs | 4 +- 3 files changed, 88 insertions(+), 11 deletions(-) diff --git a/ReportImp/VSDHammReports/Invoicing/CustomInvoiceCreation.cs b/ReportImp/VSDHammReports/Invoicing/CustomInvoiceCreation.cs index 386dbe0f8..8dd3fdd66 100644 --- a/ReportImp/VSDHammReports/Invoicing/CustomInvoiceCreation.cs +++ b/ReportImp/VSDHammReports/Invoicing/CustomInvoiceCreation.cs @@ -25,7 +25,7 @@ namespace VSDHammReports.Invoicing // da nur hier noch die Wochen-/Monatszuordnung über ItemPeriodStart möglich ist. public override IList CreateSummaryInvoiceItems(IList 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 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 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 itemList, SupportConceptApprovalPeriod scap, Calculations calc) + private void KappeBeiUeberschreitungDesIntervallBudgets(IList 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]; diff --git a/ReportImp/VSDHammReports/LeistungsnachweisJAHamm.cs b/ReportImp/VSDHammReports/LeistungsnachweisJAHamm.cs index f7c312deb..f753fd417 100644 --- a/ReportImp/VSDHammReports/LeistungsnachweisJAHamm.cs +++ b/ReportImp/VSDHammReports/LeistungsnachweisJAHamm.cs @@ -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; } diff --git a/ReportImp/VSDHammReports/ServiceInvoiceReport.cs b/ReportImp/VSDHammReports/ServiceInvoiceReport.cs index 1eb203683..e7c7baf05 100644 --- a/ReportImp/VSDHammReports/ServiceInvoiceReport.cs +++ b/ReportImp/VSDHammReports/ServiceInvoiceReport.cs @@ -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; } }