From bb0a227a0093f82e9f71372c2f2c2ae51d001cd4 Mon Sep 17 00:00:00 2001 From: HirschleM Date: Tue, 21 Jul 2026 08:16:47 +0200 Subject: [PATCH] MH: VSD Hamm Sammelrechnung Kappung --- .../Invoicing/CustomInvoiceCreation.cs | 110 +++++++++++++++++- .../ServiceInvoiceReport.Designer.cs | 89 +------------- 2 files changed, 113 insertions(+), 86 deletions(-) diff --git a/ReportImp/VSDHammReports/Invoicing/CustomInvoiceCreation.cs b/ReportImp/VSDHammReports/Invoicing/CustomInvoiceCreation.cs index 24145b5ce..386dbe0f8 100644 --- a/ReportImp/VSDHammReports/Invoicing/CustomInvoiceCreation.cs +++ b/ReportImp/VSDHammReports/Invoicing/CustomInvoiceCreation.cs @@ -1,4 +1,11 @@ -using BeWo.Service.Invoicing; +using BeWo.Data.Entities; +using BeWo.Service.DCEntityMapper; +using BeWo.Service.Invoicing; +using BS.Shared; +using BS.Shared.Core; +using BS.Shared.DataContracts; +using BS.Shared.Extensions; +using BS.Shared.Services; using System; using System.Collections.Generic; using System.Linq; @@ -9,6 +16,107 @@ namespace VSDHammReports.Invoicing { public class CustomInvoiceCreation : InvoiceCreation { + // Claude: Kappung analog zur CustomSettlementInvoiceCreation: + // Bei Bewilligungen mit Intervall Weekly/Monthly wird nicht auf Gesamt-Ebene gekappt, + // sondern je Kalenderwoche (Mo-So) bzw. Kalendermonat gegen den bewilligten Wert pro Intervall. + // Der RateFactor wird beim Budgetverbrauch mitgerechnet. + // Claude: Kappt die Einzel-Items (eine Position je Leistung) vor der Zusammenfassung, + // 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); + return base.CreateSummaryInvoiceItems(itemList, invoicePeriod, scap, calc, false, true); + } + + // Claude: Die Gesamt-Kappung der Basisklasse entfällt bei Weekly/Monthly, + // weil dort bereits je Intervall gekappt wurde (siehe CreateSummaryInvoiceItems). + public override void CheckMaxApprovedAmount(ServiceInvoice invoice, ServiceInvoicePeriod sip, SupportConceptApprovalPeriodDC scap, CostBearer2SupportConcept costBearer2SupportConcept, DateTimeSpan invoicePeriod, List serviceRecords) + { + if (IstIntervallKappung(scap.ApprovedBEInterval, scap.ApprovedBEPerInterval, scap.IsApprovedBEShifting)) + { + return; + } + base.CheckMaxApprovedAmount(invoice, sip, scap, costBearer2SupportConcept, invoicePeriod, serviceRecords); + } + + private bool IstIntervallKappung(SupportConceptApprovalInterval? approvedBEInterval, decimal? approvedBEPerInterval, bool isApprovedBEShifting) + { + return !isApprovedBEShifting && + approvedBEInterval.HasValue && approvedBEPerInterval.HasValue && + (approvedBEInterval.Value == SupportConceptApprovalInterval.Weekly || + approvedBEInterval.Value == SupportConceptApprovalInterval.Monthly); + } + + private void KappeBeiUeberschreitungDesIntervallBudgets(IList itemList, SupportConceptApprovalPeriod scap, Calculations calc) + { + if (scap == null || itemList == null || itemList.Count == 0) + { + return; + } + + var scapDC = MapperFactory.SupportConceptApprovalPeriodDC_SupportConceptApprovalPeriod.MapToNewDC(scap); + + if (!IstIntervallKappung(scapDC.ApprovedBEInterval, scapDC.ApprovedBEPerInterval, scapDC.IsApprovedBEShifting)) + { + return; + } + + var interval = scapDC.ApprovedBEInterval.Value; + var costRates = MapperFactory.CostRatePeriodDC_CostRatePeriod.MapToNewDCs(scap.CostBearer2SupportConcept.CostBearer.CostRatePeriods); + + // Claude: Je Intervall (Wochenmontag bzw. Monatserster) die verbrauchten Stunden (inkl. RF) sammeln. + var intervalStundenGeleistet = new Dictionary(); + var intervalMaxStunden = new Dictionary(); + + foreach (var ii in itemList.Where(i => i.UnitCount.HasValue && i.ItemPeriodStart.HasValue).OrderBy(i => i.ItemPeriodStart.Value)) + { + DateTime intervalKey = interval == SupportConceptApprovalInterval.Weekly + ? ii.ItemPeriodStart.Value.GetLast(DayOfWeek.Monday).Date + : ii.ItemPeriodStart.Value.GetFirstOfMonth(); + + 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; + } + decimal maxStunden = intervalMaxStunden[intervalKey]; + + decimal stundenGeleistet = intervalStundenGeleistet.ContainsKey(intervalKey) + ? intervalStundenGeleistet[intervalKey] + : 0; + + decimal rfFaktor = ((ii.RateFactor ?? 0) + 100) / 100; + decimal stundenMitRf = ii.UnitCount.Value * rfFaktor; + + if (stundenGeleistet + stundenMitRf > maxStunden) + { + decimal restStunden = (maxStunden - stundenGeleistet) / rfFaktor; + + if (restStunden < 0) + { + restStunden = 0; + } + if (ii.UnitCount.Value > restStunden) + { + ii.UnitCount = restStunden; + // Claude: Beträge nachziehen; die Summenbildung in BerechneSummaryItemsSummen + // rechnet ohnehin nur mit UnitCount weiter. + ii.AmountTotal = (ii.AmountPerUnit ?? 0) * ii.UnitCount; + ii.GrossAmountTotal = ii.AmountTotal; + if (ii.RateFactor.HasValue) + { + ii.GrossAmountTotal *= (ii.RateFactor.Value + 100) / 100; + } + } + } + + intervalStundenGeleistet[intervalKey] = stundenGeleistet + stundenMitRf; + } + } } } diff --git a/ReportImp/VSDHammReports/ServiceInvoiceReport.Designer.cs b/ReportImp/VSDHammReports/ServiceInvoiceReport.Designer.cs index 2b9be990c..35868253c 100644 --- a/ReportImp/VSDHammReports/ServiceInvoiceReport.Designer.cs +++ b/ReportImp/VSDHammReports/ServiceInvoiceReport.Designer.cs @@ -106,8 +106,6 @@ namespace VSDHammReports this.xrTableCell37 = new DevExpress.XtraReports.UI.XRTableCell(); this.xrTableCell35 = new DevExpress.XtraReports.UI.XRTableCell(); this.xrTableCell33 = new DevExpress.XtraReports.UI.XRTableCell(); - this.xrTableCell36 = new DevExpress.XtraReports.UI.XRTableCell(); - this.xrTableCell38 = new DevExpress.XtraReports.UI.XRTableCell(); this.xrTableCell34 = new DevExpress.XtraReports.UI.XRTableCell(); this.xrLabel11 = new DevExpress.XtraReports.UI.XRLabel(); this.xrTable3 = new DevExpress.XtraReports.UI.XRTable(); @@ -129,8 +127,6 @@ namespace VSDHammReports this.xrTableCell42 = new DevExpress.XtraReports.UI.XRTableCell(); this.xrTableCell43 = new DevExpress.XtraReports.UI.XRTableCell(); this.xrTableCell44 = new DevExpress.XtraReports.UI.XRTableCell(); - this.xrTableCell45 = new DevExpress.XtraReports.UI.XRTableCell(); - this.xrTableCell46 = new DevExpress.XtraReports.UI.XRTableCell(); this.xrTableCell47 = new DevExpress.XtraReports.UI.XRTableCell(); this.bindingSource1 = new System.Windows.Forms.BindingSource(this.components); this.ReportFooter = new DevExpress.XtraReports.UI.ReportFooterBand(); @@ -862,16 +858,16 @@ namespace VSDHammReports this.xrLabel11, this.xrTable3, this.xrLabel10}); - this.Detail1.HeightF = 264F; + this.Detail1.HeightF = 264.0001F; this.Detail1.Name = "Detail1"; // // xrTable5 // - this.xrTable5.LocationFloat = new DevExpress.Utils.PointFloat(0F, 239F); + this.xrTable5.LocationFloat = new DevExpress.Utils.PointFloat(0F, 239.0001F); this.xrTable5.Name = "xrTable5"; this.xrTable5.Rows.AddRange(new DevExpress.XtraReports.UI.XRTableRow[] { this.xrTableRow23}); - this.xrTable5.SizeF = new System.Drawing.SizeF(677F, 25F); + this.xrTable5.SizeF = new System.Drawing.SizeF(650.3333F, 25F); // // xrTableRow23 // @@ -880,8 +876,6 @@ namespace VSDHammReports this.xrTableCell37, this.xrTableCell35, this.xrTableCell33, - this.xrTableCell36, - this.xrTableCell38, this.xrTableCell34}); this.xrTableRow23.Name = "xrTableRow23"; this.xrTableRow23.Weight = 1D; @@ -958,42 +952,6 @@ namespace VSDHammReports this.xrTableCell33.TextAlignment = DevExpress.XtraPrinting.TextAlignment.MiddleRight; this.xrTableCell33.Weight = 0.2893277257020575D; // - // xrTableCell36 - // - this.xrTableCell36.BackColor = System.Drawing.Color.Gainsboro; - this.xrTableCell36.BorderColor = System.Drawing.Color.DarkGray; - this.xrTableCell36.Borders = ((DevExpress.XtraPrinting.BorderSide)((((DevExpress.XtraPrinting.BorderSide.Left | DevExpress.XtraPrinting.BorderSide.Top) - | DevExpress.XtraPrinting.BorderSide.Right) - | DevExpress.XtraPrinting.BorderSide.Bottom))); - this.xrTableCell36.Name = "xrTableCell36"; - this.xrTableCell36.Padding = new DevExpress.XtraPrinting.PaddingInfo(2, 2, 2, 2, 100F); - this.xrTableCell36.StylePriority.UseBackColor = false; - this.xrTableCell36.StylePriority.UseBorderColor = false; - this.xrTableCell36.StylePriority.UseBorders = false; - this.xrTableCell36.StylePriority.UsePadding = false; - this.xrTableCell36.StylePriority.UseTextAlignment = false; - this.xrTableCell36.Text = "Betrag"; - this.xrTableCell36.TextAlignment = DevExpress.XtraPrinting.TextAlignment.MiddleRight; - this.xrTableCell36.Weight = 0.26310971709438763D; - // - // xrTableCell38 - // - this.xrTableCell38.BackColor = System.Drawing.Color.Gainsboro; - this.xrTableCell38.BorderColor = System.Drawing.Color.DarkGray; - this.xrTableCell38.Borders = ((DevExpress.XtraPrinting.BorderSide)((((DevExpress.XtraPrinting.BorderSide.Left | DevExpress.XtraPrinting.BorderSide.Top) - | DevExpress.XtraPrinting.BorderSide.Right) - | DevExpress.XtraPrinting.BorderSide.Bottom))); - this.xrTableCell38.Name = "xrTableCell38"; - this.xrTableCell38.Padding = new DevExpress.XtraPrinting.PaddingInfo(2, 2, 2, 2, 100F); - this.xrTableCell38.StylePriority.UseBackColor = false; - this.xrTableCell38.StylePriority.UseBorderColor = false; - this.xrTableCell38.StylePriority.UseBorders = false; - this.xrTableCell38.StylePriority.UsePadding = false; - this.xrTableCell38.StylePriority.UseTextAlignment = false; - this.xrTableCell38.Text = "pausch Fakt."; - this.xrTableCell38.TextAlignment = DevExpress.XtraPrinting.TextAlignment.MiddleRight; - this.xrTableCell38.Weight = 0.42042083402151886D; - // // xrTableCell34 // this.xrTableCell34.BackColor = System.Drawing.Color.Gainsboro; @@ -1128,7 +1086,7 @@ namespace VSDHammReports this.xrTable6.Name = "xrTable6"; this.xrTable6.Rows.AddRange(new DevExpress.XtraReports.UI.XRTableRow[] { this.xrTableRow25}); - this.xrTable6.SizeF = new System.Drawing.SizeF(677F, 25F); + this.xrTable6.SizeF = new System.Drawing.SizeF(650.3333F, 25F); this.xrTable6.StylePriority.UseFont = false; // // xrTableRow25 @@ -1138,8 +1096,6 @@ namespace VSDHammReports this.xrTableCell42, this.xrTableCell43, this.xrTableCell44, - this.xrTableCell45, - this.xrTableCell46, this.xrTableCell47}); this.xrTableRow25.Name = "xrTableRow25"; this.xrTableRow25.Weight = 1D; @@ -1210,39 +1166,6 @@ namespace VSDHammReports this.xrTableCell44.TextFormatString = "{0:0.00}"; this.xrTableCell44.Weight = 0.2893277257020575D; // - // xrTableCell45 - // - this.xrTableCell45.BorderColor = System.Drawing.Color.DarkGray; - this.xrTableCell45.Borders = ((DevExpress.XtraPrinting.BorderSide)(((DevExpress.XtraPrinting.BorderSide.Left | DevExpress.XtraPrinting.BorderSide.Right) - | DevExpress.XtraPrinting.BorderSide.Bottom))); - this.xrTableCell45.DataBindings.AddRange(new DevExpress.XtraReports.UI.XRBinding[] { - new DevExpress.XtraReports.UI.XRBinding("Text", null, "ServiceInvoicePeriods.ServiceInvoiceItems.NetAmount")}); - this.xrTableCell45.Name = "xrTableCell45"; - this.xrTableCell45.Padding = new DevExpress.XtraPrinting.PaddingInfo(2, 2, 2, 2, 100F); - this.xrTableCell45.StylePriority.UseBorderColor = false; - this.xrTableCell45.StylePriority.UseBorders = false; - this.xrTableCell45.StylePriority.UsePadding = false; - this.xrTableCell45.StylePriority.UseTextAlignment = false; - this.xrTableCell45.TextAlignment = DevExpress.XtraPrinting.TextAlignment.MiddleRight; - this.xrTableCell45.TextFormatString = "{0:c}"; - this.xrTableCell45.Weight = 0.26310971709438763D; - // - // xrTableCell46 - // - this.xrTableCell46.BorderColor = System.Drawing.Color.DarkGray; - this.xrTableCell46.Borders = ((DevExpress.XtraPrinting.BorderSide)(((DevExpress.XtraPrinting.BorderSide.Left | DevExpress.XtraPrinting.BorderSide.Right) - | DevExpress.XtraPrinting.BorderSide.Bottom))); - this.xrTableCell46.DataBindings.AddRange(new DevExpress.XtraReports.UI.XRBinding[] { - new DevExpress.XtraReports.UI.XRBinding("Text", null, "ServiceInvoicePeriods.ServiceInvoiceItems.RateFactor")}); - this.xrTableCell46.Name = "xrTableCell46"; - this.xrTableCell46.Padding = new DevExpress.XtraPrinting.PaddingInfo(2, 2, 2, 2, 100F); - this.xrTableCell46.StylePriority.UseBorderColor = false; - this.xrTableCell46.StylePriority.UseBorders = false; - this.xrTableCell46.StylePriority.UsePadding = false; - this.xrTableCell46.StylePriority.UseTextAlignment = false; - this.xrTableCell46.TextAlignment = DevExpress.XtraPrinting.TextAlignment.MiddleRight; - this.xrTableCell46.Weight = 0.42042083402151886D; - // // xrTableCell47 // this.xrTableCell47.BorderColor = System.Drawing.Color.DarkGray; @@ -1387,8 +1310,6 @@ namespace VSDHammReports private DevExpress.XtraReports.UI.XRTableCell xrTableCell37; private DevExpress.XtraReports.UI.XRTableCell xrTableCell35; private DevExpress.XtraReports.UI.XRTableCell xrTableCell33; - private DevExpress.XtraReports.UI.XRTableCell xrTableCell36; - private DevExpress.XtraReports.UI.XRTableCell xrTableCell38; private DevExpress.XtraReports.UI.XRTableCell xrTableCell34; private DevExpress.XtraReports.UI.ReportFooterBand ReportFooter; private DevExpress.XtraReports.UI.DetailReportBand DetailReport2; @@ -1399,8 +1320,6 @@ namespace VSDHammReports private DevExpress.XtraReports.UI.XRTableCell xrTableCell42; private DevExpress.XtraReports.UI.XRTableCell xrTableCell43; private DevExpress.XtraReports.UI.XRTableCell xrTableCell44; - private DevExpress.XtraReports.UI.XRTableCell xrTableCell45; - private DevExpress.XtraReports.UI.XRTableCell xrTableCell46; private DevExpress.XtraReports.UI.XRTableCell xrTableCell47; private DevExpress.XtraReports.UI.XRLabel xrLabel7; private DevExpress.XtraReports.UI.XRTableRow xrTableRow26;