356 lines
19 KiB
C#
356 lines
19 KiB
C#
using BeWo.Data.Access;
|
|
using BeWo.Data.Entities;
|
|
using BeWo.Service.DCEntityMapper;
|
|
using BeWo.Service.Invoicing;
|
|
using BeWo.Service.Plugins;
|
|
using BS.Shared;
|
|
using BS.Shared.Core;
|
|
using BS.Shared.DataContracts;
|
|
using BS.Shared.Extensions;
|
|
using BS.Shared.Services;
|
|
using DevExpress.Data.Helpers;
|
|
using DevExpress.Utils.About;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Security.Cryptography;
|
|
using System.Text;
|
|
using System.Windows.Markup.Localizer;
|
|
using System.Windows.Media.Media3D;
|
|
using TeamPfeil.Korridorberechnung;
|
|
|
|
namespace TeamPfeil.Invoicing
|
|
{
|
|
public class CustomInvoiceCreation : InvoiceCreation
|
|
{
|
|
// Noch die Prüfung auf Übererbringung wieder einfügen
|
|
|
|
public override void SetInvoiceId(ServiceInvoice invoice)
|
|
{
|
|
invoice.InvoiceBase.InvoiceId = "Qualifikation";
|
|
invoice.InvoiceBase.InvoiceTypeText = "RechnungQualifikation";
|
|
}
|
|
|
|
public override void SetInvoiceItems(ServiceInvoice invoice,
|
|
ServiceInvoicePeriod serviceInvoicePeriod,
|
|
SupportConceptApprovalPeriodDC supportConceptApprovalPeriod,
|
|
CostBearer2SupportConcept cb2sc,
|
|
DateTimeSpan invoicePeriod,
|
|
List<ServiceRecordDC> serviceRecords)
|
|
{
|
|
Calculations calc = PluginLoader.FindClass<Calculations>(_SpecificID) ?? Calculations.GetInstance(_SpecificID);
|
|
|
|
List<ServiceRecordDC> serviceRecordsInPeriod = serviceRecords.Where(
|
|
i =>
|
|
{
|
|
bool valid = i.Start.Value.InBetween(serviceInvoicePeriod.Start.Value,
|
|
serviceInvoicePeriod.End.Value, true);
|
|
|
|
//if (valid && serviceInvoicePeriod.SupportConceptApprovalPeriod != null && serviceInvoicePeriod.SupportConceptApprovalPeriod.ServiceCategory != null)
|
|
// valid = serviceInvoicePeriod.SupportConceptApprovalPeriod.ServiceCategory.Oid ==
|
|
// i.ServiceDescription.Category.ServiceCategoryOid;
|
|
return valid;
|
|
}).ToList();
|
|
|
|
if (serviceRecordsInPeriod.Count > 0)
|
|
{
|
|
var absenceTimes = MapperFactory.AbsenceTimeDC_AbsenceTime.MapToNewDCs(cb2sc.SupportConcept.Customer.AbsenceTimes);
|
|
Dictionary<DateTimeSpan, decimal> span2Hours = calc.GetAbsencesTimesNotBillable(absenceTimes);
|
|
|
|
serviceInvoicePeriod.HoursNotBillableAbsence +=
|
|
calc.GetHoursNotBillableDueToAbsenceTimes(supportConceptApprovalPeriod,
|
|
cb2sc.CostBearer.CostRatePeriods.MapToNewDCs(),
|
|
serviceRecordsInPeriod,
|
|
span2Hours,
|
|
true, true);
|
|
|
|
var itemList = CreateInvoiceItems(serviceRecordsInPeriod, invoicePeriod, serviceInvoicePeriod.SupportConceptApprovalPeriod, calc);
|
|
serviceInvoicePeriod.InvoiceItemList.AddRange(itemList);
|
|
}
|
|
}
|
|
|
|
public override IList<InvoiceItem> CreateInvoiceItems(List<ServiceRecordDC> serviceRecordsInPeriod, DateTimeSpan invoicePeriod, SupportConceptApprovalPeriod scap, Calculations calc)
|
|
{
|
|
var list = new List<InvoiceItem>();
|
|
foreach (ServiceRecordDC iServiceRecord in serviceRecordsInPeriod)
|
|
{
|
|
if (iServiceRecord.ServiceDescription.Category.IsBillable)
|
|
{
|
|
if (!iServiceRecord.AlreadyAccounted)
|
|
{
|
|
InvoiceItem invoiceItem = CreateInvoiceItem(iServiceRecord, scap, calc);
|
|
if (invoiceItem != null)
|
|
{
|
|
// Wenn das Item ganz normal abgerechnet wird, einfach normal zur Liste
|
|
if (invoiceItem.CustomString5 != "Mischitem")
|
|
list.Add(invoiceItem);
|
|
else
|
|
{
|
|
// Ist es ein Mischitem, müssen zwei Items erstellt werden. Eins mit dem Mitarbeiterstundensatz und eins mit dem Leistungsstundensatz
|
|
// invoiceItem.CustomNumber1: Stunden für den MA-Stundensatz
|
|
// invoiceItem.CustomNumber2: MA-Stundensatz
|
|
// invoiceItem.CustomString2: Leistungsname für MA-Stundensatz
|
|
// invoiceItem.CustomNumber3: Stunden für den Leistungs-Stundensatz
|
|
// invoiceItem.CustomNumber4: Leistungs-Stundensatz
|
|
// invoiceItem.CustomString4: Leistungsname für Leistungsstundensatz
|
|
var item1 = invoiceItem;
|
|
var item2 = CopyInvoiceItem(invoiceItem);
|
|
|
|
item1.UnitCount = invoiceItem.CustomNumber1;
|
|
item1.AmountPerUnit = invoiceItem.CustomNumber2;
|
|
item1.AmountTotal = item1.UnitCount * invoiceItem.AmountPerUnit;
|
|
item1.GrossAmountTotal = item1.AmountTotal;
|
|
item1.ItemDescription = item1.CustomString2;
|
|
list.Add(item1);
|
|
|
|
item2.UnitCount = invoiceItem.CustomNumber3;
|
|
item2.AmountPerUnit = invoiceItem.CustomNumber4;
|
|
item2.AmountTotal = item2.UnitCount * invoiceItem.AmountPerUnit;
|
|
item2.GrossAmountTotal = item2.AmountTotal;
|
|
item2.ItemDescription = item1.CustomString4;
|
|
list.Add(item2);
|
|
}
|
|
}
|
|
iServiceRecord.AlreadyAccounted = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return CreateSummaryInvoiceItems(list, invoicePeriod, scap, calc);
|
|
}
|
|
|
|
|
|
|
|
public override InvoiceItem CreateInvoiceItem(ServiceRecordDC iServiceRecord, SupportConceptApprovalPeriod scap, Calculations calc)
|
|
{
|
|
var invoiceItem = new InvoiceItem();
|
|
//Dictionary<long, string> empQuali = new Dictionary<long, string>();
|
|
|
|
|
|
BillingData bd = calc.GetBillingData(iServiceRecord);
|
|
decimal stundensatz = 0;
|
|
// Bis Ende 2025 richtet sich die erbrachte Kategorie nach dem, was dokumentiert wurde.
|
|
|
|
long qualiOid;
|
|
if (iServiceRecord.Start.HasValue && iServiceRecord.Start.Value.Date < new DateTime(2026, 01, 01))
|
|
{
|
|
|
|
if (iServiceRecord.ServiceDescription.CategoryName.Contains("1"))
|
|
{
|
|
qualiOid = 2118;
|
|
stundensatz = GetStundensatz(scap.CostBearer2SupportConcept, qualiOid, iServiceRecord.Start);
|
|
invoiceItem.ItemDescription = "Qualifikationsniveau 1: ";
|
|
}
|
|
else if (iServiceRecord.ServiceDescription.CategoryName.Contains("2"))
|
|
{
|
|
qualiOid = 2119;
|
|
stundensatz = GetStundensatz(scap.CostBearer2SupportConcept, qualiOid, iServiceRecord.Start);
|
|
invoiceItem.ItemDescription = "Qualifikationsniveau 2: ";
|
|
}
|
|
else if (iServiceRecord.ServiceDescription.CategoryName.Contains("3"))
|
|
{
|
|
qualiOid = 2120;
|
|
stundensatz = GetStundensatz(scap.CostBearer2SupportConcept, qualiOid, iServiceRecord.Start);
|
|
invoiceItem.ItemDescription = "Qualifikationsniveau 3: ";
|
|
}
|
|
else if (iServiceRecord.ServiceDescription.CategoryName.Contains("4"))
|
|
{
|
|
qualiOid = 2121;
|
|
stundensatz = GetStundensatz(scap.CostBearer2SupportConcept, qualiOid, iServiceRecord.Start);
|
|
invoiceItem.ItemDescription = "Qualifikationsniveau 4: ";
|
|
}
|
|
|
|
}
|
|
|
|
// Ab 2026 wird das abgerechnet, was dokumentiert wird.
|
|
// Sonderfall: Korridor
|
|
// Korridor wird Thema, wenn die bewilligte Leistungsform sich von der Qualifikation der dokumentierenden Person unterscheidet.
|
|
// Es wird dann erstmal der Stundensatz der dokumentierten Leistung genommen, solange ein Schwellwert an Stunden nicht überschritten wird.
|
|
// Wenn qual.MA unterstützend dokumentiert, dürfen es 30 % der bew.Stunden sein
|
|
// Wenn unt.MA qualifiziert dokumentiert, dürfen es 20 % der bew.Stunden sein
|
|
// Wenn diese %-Werte überschritten sind, darf in der Abrechnung nur noch der Stundensatz genommen werden,
|
|
// den der Korridormitarbeiter eigentlich hat (also qualfi.abhäng. Stundensatz im Kostenträger, der der Qualifikation des Mitarbeiters entspricht)
|
|
else
|
|
{
|
|
|
|
bool korridorAufgebraucht = false; // Wenn true, wird statt dem Leistungsstundensatz der MA-Stundensatz genommen
|
|
// Mischitem = ServiceRecord kann nur teilweise mit Leistungsstundensatz abgerechnet werden, der Rest muss aber mit MA-Qual--Std.satz abgerechnet werden
|
|
// In dem Fall werden im Item beide Teilstunden und beide jeweiligem Stundensätze gesetzt und im Nachgang wird ein weiteres Item erzeugt (in CreateInvoiceItems)
|
|
bool mischitem = false;
|
|
|
|
// KORRIDOR ERMITTELN
|
|
var scapDC = MapperFactory.SupportConceptApprovalPeriodDC_SupportConceptApprovalPeriod.MapToNewDC(scap);
|
|
var gesamtBewilligt = KorridorberechnungHelper.BerechneStundenBewilligtGesamt(scapDC);
|
|
double korridorbudget = 0;
|
|
double genutztesKorridorbudget = 0;
|
|
double rest = 0;
|
|
if (scap.ServiceCategory.Name.Contains("Qualifizierte Assistenz"))
|
|
{
|
|
korridorbudget = KorridorberechnungHelper.BerechneBudgetFuerQualAss(gesamtBewilligt);
|
|
genutztesKorridorbudget = KorridorberechnungHelper.BerechneGenutzteKorridorstunden(iServiceRecord.SupportConcept.SupportConceptOid, scapDC.ServiceCategory,
|
|
korridorbudget, iServiceRecord.Start.Value.AddTicks(-1), true);
|
|
rest = korridorbudget - genutztesKorridorbudget;
|
|
}
|
|
else if (scap.ServiceCategory.Name.Contains("Unterstützende Assistenz"))
|
|
{
|
|
korridorbudget = KorridorberechnungHelper.BerechneBudgetFuerUnterstAss(gesamtBewilligt);
|
|
genutztesKorridorbudget = KorridorberechnungHelper.BerechneGenutzteKorridorstunden(iServiceRecord.SupportConcept.SupportConceptOid, scapDC.ServiceCategory,
|
|
korridorbudget, iServiceRecord.Start.Value.AddTicks(-1), false);
|
|
rest = korridorbudget - genutztesKorridorbudget;
|
|
}
|
|
|
|
// Wenn das Item noch etwas Restbudget hat, aber es nicht ganz ausreicht, um alles abzudecken, muss ein Mischitem erzeugt werden.
|
|
// Es muss also der Teil, der noch verfügbar ist, mit dem Leistungsstundensatz verrechnet werden und der Rest mit der Qualifikation
|
|
// Dazu werden der jeweilige UnitCount und der jeweilige Stundensatz in CustomNumbers gespeichert, um in CreateInvoiceItems ein zweites Item zu machen
|
|
if (rest > 0 && rest - Convert.ToDouble(iServiceRecord.RoundedDuration) / 60 < 0)
|
|
{
|
|
decimal zeitNormalAbrechenbar = Convert.ToDecimal(rest);
|
|
decimal zeitMitMitarbeiterStundensatz = iServiceRecord.RoundedDuration / 60 - Convert.ToDecimal(rest);
|
|
|
|
// Wenn Mischitem, dann UnitCounts zwischenspeichern, um später aufzuteilen
|
|
invoiceItem.CustomNumber1 = zeitMitMitarbeiterStundensatz;
|
|
invoiceItem.CustomNumber3 = zeitNormalAbrechenbar;
|
|
|
|
mischitem = true;
|
|
invoiceItem.CustomString5 = "Mischitem";
|
|
}
|
|
// Wenn der Rest eh schon unter 0 ist, wird einfach mit dem MA-Stundensatz abgerechnet
|
|
else if (rest < 0)
|
|
korridorAufgebraucht = true;
|
|
|
|
if (korridorAufgebraucht || mischitem) // Stundensatz der Mitarbeitendenqualifikation
|
|
{
|
|
var qualifikation = iServiceRecord.Employee.ValueListEntries.FirstOrDefault(v => v.Value == ValueListEntryType.StaffQualificationsType);
|
|
if (qualifikation.Key != null)
|
|
{
|
|
qualiOid = qualifikation.Key;
|
|
if (qualiOid == 2118) // QA 1
|
|
{
|
|
stundensatz = GetStundensatz(scap.CostBearer2SupportConcept, qualiOid, iServiceRecord.Start);
|
|
invoiceItem.ItemDescription = "QA 1: ";
|
|
}
|
|
else if (qualiOid == 2119) // QA 2
|
|
{
|
|
stundensatz = GetStundensatz(scap.CostBearer2SupportConcept, qualiOid, iServiceRecord.Start);
|
|
invoiceItem.ItemDescription = "QA 2: ";
|
|
}
|
|
else if (qualiOid == 2120) // UA 3
|
|
{
|
|
stundensatz = GetStundensatz(scap.CostBearer2SupportConcept, qualiOid, iServiceRecord.Start);
|
|
invoiceItem.ItemDescription = "UA 3: ";
|
|
}
|
|
else if (qualiOid == 2121) // UA 4
|
|
{
|
|
stundensatz = GetStundensatz(scap.CostBearer2SupportConcept, qualiOid, iServiceRecord.Start);
|
|
invoiceItem.ItemDescription = "UA 4: ";
|
|
}
|
|
|
|
if (mischitem) // Wenn Mischitem, dann Stundensatz der Person + dessen Qualifikation zwischenspeichern, um später aufzuteilen
|
|
{
|
|
invoiceItem.CustomNumber2 = stundensatz;
|
|
invoiceItem.CustomString2 = invoiceItem.ItemDescription;
|
|
}
|
|
}
|
|
}
|
|
if (!korridorAufgebraucht || mischitem) // Stundensatz, der der dokumentierten Leistung entspricht
|
|
{
|
|
var leistung = iServiceRecord.ServiceDescription.Name;
|
|
switch (leistung)
|
|
{
|
|
case "QA 1": stundensatz = GetStundensatz(scap.CostBearer2SupportConcept, 2118, iServiceRecord.Start); invoiceItem.ItemDescription = "QA 1: "; break;
|
|
case "QA 2": stundensatz = GetStundensatz(scap.CostBearer2SupportConcept, 2119, iServiceRecord.Start); invoiceItem.ItemDescription = "QA 2: "; break;
|
|
case "UA 3": stundensatz = GetStundensatz(scap.CostBearer2SupportConcept, 2120, iServiceRecord.Start); invoiceItem.ItemDescription = "UA 3: "; break;
|
|
case "UA 4": stundensatz = GetStundensatz(scap.CostBearer2SupportConcept, 2121, iServiceRecord.Start); invoiceItem.ItemDescription = "UA 4: "; break;
|
|
}
|
|
if (mischitem) // Wenn Mischitem, dann Stundensatz der Leistung + Leistungsbezeichnung zwischenspeichern, um später aufzuteilen
|
|
{
|
|
invoiceItem.CustomNumber4 = stundensatz;
|
|
invoiceItem.CustomString4 = invoiceItem.ItemDescription;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if (stundensatz != 0)
|
|
{
|
|
invoiceItem.AmountPerUnit = stundensatz;
|
|
invoiceItem.AmountTotal = stundensatz * bd.UnitCount;
|
|
invoiceItem.GrossAmountTotal = stundensatz * bd.UnitCount;
|
|
}
|
|
else
|
|
{
|
|
invoiceItem.AmountPerUnit = bd.AmountPerUnit;
|
|
invoiceItem.AmountTotal = bd.AmountTotal;
|
|
invoiceItem.GrossAmountTotal = bd.GrossAmountTotal;
|
|
invoiceItem.ItemDescription = iServiceRecord.ServiceDescription.CategoryName;
|
|
}
|
|
|
|
invoiceItem.UnitCount = bd.UnitCount;
|
|
invoiceItem.ItemPeriodStart = bd.BillingPeriodStart;
|
|
invoiceItem.ItemPeriodEnd = bd.BillingPeriodEnd;
|
|
invoiceItem.RateFactor = bd.RateFactor;
|
|
invoiceItem.AccountingInterval = bd.AccountingInterval;
|
|
invoiceItem.ServiceRecord = DAOFactory.GenericDAO.LoadByID<ServiceRecord>(iServiceRecord.ServiceRecordOid.Value);
|
|
|
|
return invoiceItem;
|
|
}
|
|
|
|
private decimal GetStundensatz(CostBearer2SupportConcept c2s, long vleOid, DateTime? date)
|
|
{
|
|
|
|
if (c2s.CostBearer.CostRatePeriods != null)
|
|
{
|
|
//Valuelistentryoid:
|
|
//2118 Q 1 Sozialpädagoge
|
|
//2119 Q 2 Fachkraft
|
|
//2120 Qualifizierte Hilfskraft
|
|
//2121 Unqualifiziert Hilfskraft
|
|
|
|
var crpList = MapperFactory.CostRatePeriodDC_CostRatePeriod.MapToNewDCs(c2s.CostBearer.CostRatePeriods);
|
|
var cp = crpList.GetCostRatePeriodForDate(CostRatePeriodType.HourlyRate, (DateTime)date, vleOid);
|
|
|
|
if (cp != null && cp.CostRateValue.HasValue)
|
|
{
|
|
return cp.CostRateValue.Value;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
private InvoiceItem CopyInvoiceItem(InvoiceItem invoiceItem)
|
|
{
|
|
return new InvoiceItem()
|
|
{
|
|
AccountingInterval = invoiceItem.AccountingInterval,
|
|
AmountPerUnit = invoiceItem.AmountPerUnit,
|
|
AmountTotal = invoiceItem.AmountTotal,
|
|
ApprovalUnit = invoiceItem.ApprovalUnit,
|
|
ApprovedPerUnit = invoiceItem.ApprovedPerUnit,
|
|
GrossAmountTotal = invoiceItem.GrossAmountTotal,
|
|
ItemDescription = invoiceItem.ItemDescription,
|
|
IsActive = invoiceItem.IsActive,
|
|
ItemPeriodEnd = invoiceItem.ItemPeriodEnd,
|
|
ItemPeriodStart = invoiceItem.ItemPeriodStart,
|
|
MaxApprovedAmount = invoiceItem.MaxApprovedAmount,
|
|
MaxApprovedUnitCount = invoiceItem.MaxApprovedAmount,
|
|
RateFactor = invoiceItem.RateFactor,
|
|
ServiceRecord = invoiceItem.ServiceRecord,
|
|
SupportConceptApprovalPeriodOid = invoiceItem.SupportConceptApprovalPeriodOid,
|
|
UnitCount = invoiceItem.UnitCount,
|
|
UnitDescription = invoiceItem.UnitDescription,
|
|
};
|
|
}
|
|
public override string GetSummaryItemKey(SupportConceptApprovalPeriod scap, InvoiceItem iitem, bool summaryPerMonth)
|
|
{
|
|
String key = String.Format("{0}_{1}", iitem.AmountPerUnit, iitem.ItemDescription);
|
|
if (summaryPerMonth)
|
|
{
|
|
key = String.Format("{0}_{1}_{2:yyyyMM}", iitem.AmountPerUnit, iitem.ItemDescription, iitem.ItemPeriodStart);
|
|
}
|
|
|
|
return key;
|
|
}
|
|
}
|
|
}
|