419 lines
14 KiB
C#
419 lines
14 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using BeWo.Data.Entities;
|
|
using BeWo.Service.DCEntityMapper;
|
|
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.Utils;
|
|
|
|
namespace BeWo.Service.Invoicing
|
|
{
|
|
[IDSpecificClass(Identifier = "LWLNEU")]
|
|
public class LWLSettlementInvoiceCreation : SettlementInvoiceCreation
|
|
{
|
|
public virtual bool SchneideBeiErreichenDesBudgetsAb { get { return false; } }
|
|
|
|
public override bool CanCreateInvoiceTheOldWay(Settlement2DC si, CostBearer2SupportConcept c2s)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
public override void CreateInvoiceDetailsTheOldWay(Settlement2DC si, CostBearer2SupportConcept c2s)
|
|
{
|
|
String id = c2s.CostBearer.ID;
|
|
|
|
var calc = PluginLoader.FindClass<Calculations>(id);
|
|
if (calc == null)
|
|
{
|
|
calc = Calculations.GetInstance(id);
|
|
}
|
|
|
|
var relDc = c2s.MapToNewDC();
|
|
|
|
var apListFehlkontakt = new List<ApprovalPeriod>();
|
|
var apListFLS = CreateApprovalPeriods(si, c2s);
|
|
foreach (var ap in apListFLS)
|
|
{
|
|
var fkAp = new ApprovalPeriod();
|
|
fkAp.PeriodStartDate = ap.PeriodStartDate;
|
|
fkAp.PeriodEndDate = ap.PeriodEndDate;
|
|
fkAp.AmountPerUnit = ap.AmountPerUnit;
|
|
fkAp.BillingInterval = ap.BillingInterval;
|
|
fkAp.RateFactor = 0;
|
|
fkAp.Notice = "Fehlkontakte";
|
|
apListFehlkontakt.Add(fkAp);
|
|
}
|
|
si.ApprovedFLS = CalculateApprovedTotalFLS(apListFLS);
|
|
|
|
DateTime periodStart = si.AccountingPeriodStart.Value.Date;
|
|
DateTime periodEnd = si.AccountingPeriodEnd.Value.Date;
|
|
|
|
//Prüfe ob vorher abgemeldet
|
|
if (c2s.SupportConcept.Customer.TerminationDate.HasValue &&
|
|
c2s.SupportConcept.Customer.TerminationDate.Value < periodEnd)
|
|
{
|
|
periodEnd = c2s.SupportConcept.Customer.TerminationDate.Value;
|
|
si.ErrorNotice =
|
|
String.Format(
|
|
"Achtung: Die Betreuung endete am {0:dd.MM.yyyy}. Alle nachträglich eingegebenen Stunden werden nicht abgerechnet!",
|
|
periodEnd);
|
|
}
|
|
|
|
var hourlyRatesInSpanList = relDc.CostBearer.CostRatePeriods.GetSpans(CostRatePeriodType.HourlyRate, periodStart, periodEnd).ToList();
|
|
|
|
si.DifferentHourlyRateCount = hourlyRatesInSpanList.Count;
|
|
if (si.DifferentHourlyRateCount == 0)
|
|
{
|
|
si.DifferentHourlyRateCount = 1;
|
|
}
|
|
var rateFactorsInSpanList = relDc.CostBearer.CostRatePeriods.GetSpans(CostRatePeriodType.RateFactor, periodStart, periodEnd).ToList();
|
|
if (rateFactorsInSpanList.Count > 0)
|
|
{
|
|
var cdc = rateFactorsInSpanList.Last().Key;
|
|
if (cdc.CostRateValue.HasValue)
|
|
si.RateFactor = cdc.CostRateValue.Value;
|
|
}
|
|
decimal rf = GetRateFactor(si);
|
|
|
|
var records = c2s.ServiceRecords;
|
|
|
|
records = records.Where(i => i.Start.HasValue && i.Start.Value.InBetween(periodStart, periodEnd, true)).ToList();
|
|
|
|
var recordDCS = records.MapToNewDCs();
|
|
|
|
var absenceTimes = MapperFactory.AbsenceTimeDC_AbsenceTime.MapToNewDCs(c2s.SupportConcept.Customer.AbsenceTimes);
|
|
Dictionary<DateTimeSpan, decimal> span2Hours = calc.GetAbsencesTimesNotBillable(absenceTimes);
|
|
|
|
|
|
si.RecordedBillableFLSNotBillableCustomerAbsent
|
|
= calc.GetHoursNotBillableDueToAbsenceTimes(relDc, recordDCS,
|
|
span2Hours, false, false);
|
|
|
|
si.RecordedBillableFLSNotBillableOutOfApprovedSpan = calc.GetHoursNotBillableDueToOutOfSpan(relDc, recordDCS);
|
|
//calc.GetHoursNotBillableDueToMoreThanApproved(relDC, recordDCS, false, false);
|
|
|
|
Dictionary<long, bool> groupBookingDict = new Dictionary<long, bool>();
|
|
//decimal flmCleared = 0;
|
|
|
|
records = records.OrderBy<ServiceRecord, DateTime>(sr => sr.Start.HasValue ? sr.Start.Value : DateTime.MinValue).ToList<ServiceRecord>();
|
|
decimal maxApprovedMinutes = (si.ApprovedFLS ?? 0) * 60;
|
|
decimal totalMinutesGeleistet = 0;
|
|
foreach (var iRecord in records)
|
|
{
|
|
if (iRecord.ServiceDescription.ServiceCategory.IsBillable)
|
|
{
|
|
if (iRecord.GroupOid == null || !groupBookingDict.ContainsKey(iRecord.GroupOid.Value))
|
|
{
|
|
|
|
decimal dauerAbrechenbar = iRecord.RoundedDuration;
|
|
decimal dauerBudget = iRecord.RoundedDuration;
|
|
|
|
if (ReduziereBetragBeiAbwesenheiten())
|
|
{
|
|
//Die Liste recordDCS enthält nach Aufruf von GetHoursNotBillableDueToAbsenceTimes nur noch die Einträge, die auch während Abwesenheiten abgerechnet werden dürfen
|
|
var dcRecord = recordDCS.Where(r => r.ServiceRecordOid == iRecord.Oid).FirstOrDefault();
|
|
if (dcRecord == null)
|
|
{
|
|
dauerAbrechenbar = 0;
|
|
dauerBudget = 0;
|
|
}
|
|
else
|
|
{
|
|
dauerAbrechenbar = dcRecord.RoundedDuration;
|
|
dauerBudget = dcRecord.RoundedDuration;
|
|
}
|
|
}
|
|
|
|
//var prozent = iRecord.ServiceDescription.ServiceCategory.ProzentAbrechnung;
|
|
//if (iRecord.ServiceDescription.ProzentAbrechnung.HasValue)
|
|
//{
|
|
// prozent = iRecord.ServiceDescription.ProzentAbrechnung.Value;
|
|
//}
|
|
//dauerAbrechenbar *= prozent / 100;
|
|
|
|
var prozent = iRecord.ServiceDescription.ServiceCategory.ProzentBudget ?? 100;
|
|
if (iRecord.ServiceDescription.ProzentBudget.HasValue)
|
|
{
|
|
prozent = iRecord.ServiceDescription.ProzentBudget.Value;
|
|
}
|
|
dauerBudget *= prozent / 100;
|
|
|
|
var apList = apListFLS;
|
|
if (iRecord.ServiceDescription.Name.ToLower().Contains("fehlkontakt") || (iRecord.ServiceDescription.ProzentAbrechnung.HasValue && iRecord.ServiceDescription.ProzentAbrechnung.Value < 100))
|
|
{
|
|
apList = apListFehlkontakt;
|
|
}
|
|
|
|
var ap = apList.FirstOrDefault(
|
|
iap =>
|
|
{
|
|
return iap.PeriodStartDate <= iRecord.Start &&
|
|
(iap.PeriodEndDate >= DateTime.MaxValue.AddDays(-1) || iRecord.Start < iap.PeriodEndDate.AddDays(1));
|
|
});
|
|
|
|
if (ap != null)
|
|
{
|
|
//Christian wars:-)
|
|
var dauerBudgetMitRf = dauerBudget * ((ap.RateFactor ?? 0) + 100) / 100;
|
|
if (SchneideBeiErreichenDesBudgetsAb)
|
|
{
|
|
if (totalMinutesGeleistet + dauerBudgetMitRf > maxApprovedMinutes)
|
|
{
|
|
dauerBudget = maxApprovedMinutes - totalMinutesGeleistet;
|
|
dauerBudget /= ((ap.RateFactor ?? 0) + 100) / 100;
|
|
|
|
if (dauerBudget < 0)
|
|
{
|
|
dauerBudget = 0;
|
|
}
|
|
if (dauerAbrechenbar > dauerBudget)
|
|
{
|
|
dauerAbrechenbar = dauerBudget;
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
|
|
totalMinutesGeleistet += dauerBudgetMitRf;
|
|
|
|
ap.RecordedMinutes += dauerAbrechenbar;
|
|
ap.BudgetMinutes += dauerBudget;
|
|
}
|
|
|
|
if (iRecord.GroupOid != null && !groupBookingDict.ContainsKey(iRecord.GroupOid.Value))
|
|
{
|
|
si.HasGroupRecords = true;
|
|
groupBookingDict.Add(iRecord.GroupOid.Value, true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
si.RecordedMinutesTotal = totalMinutesGeleistet;
|
|
|
|
//decimal? maxAmount = CalculateMaxAmount(si, apList);
|
|
var gesamtListe = new List<ApprovalPeriod>();
|
|
gesamtListe.AddRange(apListFLS.Where(a => a.RecordedMinutes > 0));
|
|
gesamtListe.AddRange(apListFehlkontakt.Where(a => a.RecordedMinutes > 0));
|
|
si.InvoiceItems = CreateInvoiceItems(si, gesamtListe);
|
|
|
|
decimal totalAmount = 0;
|
|
decimal rateFactorAmount = 0;
|
|
|
|
foreach (var ii in si.InvoiceItems)
|
|
{
|
|
decimal grossAmount = ii.GrossAmountTotal ?? 0;
|
|
totalAmount += grossAmount;
|
|
|
|
if (ii.RateFactor.HasValue && ii.RateFactor.Value > 0)
|
|
{
|
|
rateFactorAmount += (grossAmount - (grossAmount / ((100 + ii.RateFactor.Value) / 100)));
|
|
}
|
|
}
|
|
|
|
if (!si.RecordedBillableFLSBrutto.HasValue)
|
|
{
|
|
si.RecordedBillableFLSNetto = si.InvoiceItems.Sum(ii => (ii.UnitCount ?? 0)); // * rf; //eigentlich falsch dass netto = brutto ist.
|
|
si.RecordedBillableFLSBrutto = si.RecordedBillableFLSNetto;
|
|
}
|
|
si.AmountBillableTotal = totalAmount;
|
|
si.AmountWithRateFactor = rateFactorAmount;
|
|
|
|
|
|
//AdvancedPayments
|
|
CalculateAdvancedPayments(si, c2s, null, null, hourlyRatesInSpanList);
|
|
//EquityContribution (Eigenanteil)
|
|
CalculateEquityContribution(si, c2s.SupportConcept, null, null);
|
|
|
|
si.Claim = si.AmountBillableTotal - (si.AmountAdvancedPayments ?? 0) - si.EquityContribution;
|
|
}
|
|
|
|
private decimal GetRateFactor(Settlement2DC si)
|
|
{
|
|
decimal rf = 1;
|
|
if (si.RateFactor.HasValue && si.RateFactor.Value != 0)
|
|
{
|
|
rf = si.RateFactor.Value;
|
|
rf = (100 + rf) / 100;
|
|
}
|
|
return rf;
|
|
}
|
|
|
|
public virtual List<InvoiceItemDC> CreateInvoiceItems(Settlement2DC si, IList<ApprovalPeriod> apList)
|
|
{
|
|
return CreateInvoiceItems(si, apList, false);
|
|
}
|
|
|
|
public virtual List<InvoiceItemDC> CreateInvoiceItems(Settlement2DC si, IList<ApprovalPeriod> apList, bool schneideBeiMaxAb)
|
|
{
|
|
List<InvoiceItemDC> iiList = new List<InvoiceItemDC>();
|
|
var apsByHourlyRate = new Dictionary<String, IList<ApprovalPeriod>>();
|
|
|
|
foreach (var ap in apList)
|
|
{
|
|
IList<ApprovalPeriod> list = null;
|
|
String key = String.Format("{0}_{1}", ap.AmountPerUnit, ap.RateFactor);
|
|
// AB, 17.09.2025 doch wieder zurück, der LWL hätte gerne Preise zusammengefasst, z.B. bei mehrern Bewilligungen in einem Preiszeitraum
|
|
// ein Kunde hat aber zweimal den gleichen Preis, da MUSS es getrennt werden. Wenn das Thema wird komplett neu aufdröseln
|
|
//String key = String.Format("{0}_{1}_{2:ddMMyyy}", ap.AmountPerUnit, ap.RateFactor, ap.PeriodStartDate);
|
|
|
|
if (apsByHourlyRate.ContainsKey(key))
|
|
{
|
|
list = apsByHourlyRate[key];
|
|
}
|
|
else
|
|
{
|
|
list = new List<ApprovalPeriod>();
|
|
apsByHourlyRate[key] = list;
|
|
}
|
|
|
|
list.Add(ap);
|
|
}
|
|
|
|
decimal maxApprovedFls = si.ApprovedFLS ?? 0;
|
|
decimal totalHours = 0;
|
|
foreach (var key in apsByHourlyRate.Keys)
|
|
{
|
|
IList<ApprovalPeriod> list = apsByHourlyRate[key];
|
|
var ii = new InvoiceItemDC();
|
|
|
|
decimal minutesTotal = 0;
|
|
decimal minutesTotalBudget = 0;
|
|
|
|
DateTime start = DateTime.MaxValue;
|
|
DateTime end = DateTime.MinValue;
|
|
|
|
decimal rf = si.RateFactor ?? 0;
|
|
decimal hr = 0;
|
|
|
|
foreach (var approvalPeriod in list)
|
|
{
|
|
minutesTotal += approvalPeriod.RecordedMinutes;
|
|
minutesTotalBudget += approvalPeriod.BudgetMinutes;
|
|
if (approvalPeriod.PeriodStartDate < start)
|
|
start = approvalPeriod.PeriodStartDate;
|
|
if (approvalPeriod.PeriodEndDate > end)
|
|
end = approvalPeriod.PeriodEndDate;
|
|
|
|
hr = approvalPeriod.AmountPerUnit;
|
|
if (approvalPeriod.RateFactor.HasValue)
|
|
{
|
|
rf = approvalPeriod.RateFactor.Value;
|
|
}
|
|
if (!String.IsNullOrEmpty(approvalPeriod.Notice) && approvalPeriod.Notice == "Fehlkontakte")
|
|
{
|
|
hr *= 0.8m;
|
|
ii.ItemDescription = "Fehlkontakte";
|
|
}
|
|
}
|
|
ii.ItemPeriodStart = start;
|
|
ii.ItemPeriodEnd = end;
|
|
ii.RateFactor = rf;
|
|
ii.AmountPerUnit = hr;
|
|
decimal hours = Math.Round(minutesTotal / 60m, 2, MidpointRounding.AwayFromZero);
|
|
hours *= (100 + rf) / 100;
|
|
hours = Math.Round(hours, 2, MidpointRounding.AwayFromZero);
|
|
|
|
decimal hoursBudget = Math.Round(minutesTotalBudget / 60m, 2, MidpointRounding.AwayFromZero);
|
|
hoursBudget *= (100 + rf) / 100;
|
|
hoursBudget = Math.Round(hoursBudget, 2, MidpointRounding.AwayFromZero);
|
|
|
|
//if (schneideBeiMaxAb)
|
|
//{
|
|
// if (totalHours + hoursBudget > maxApprovedFls)
|
|
// {
|
|
// hoursBudget = maxApprovedFls - totalHours;
|
|
// if (hoursBudget < 0)
|
|
// {
|
|
// hoursBudget = 0;
|
|
// }
|
|
// if (hours > hoursBudget)
|
|
// {
|
|
// hours = hoursBudget;
|
|
// }
|
|
// }
|
|
//}
|
|
totalHours += hoursBudget;
|
|
|
|
ii.UnitCount = hours;
|
|
ii.UnitCountBudget = hoursBudget;
|
|
ii.AmountTotal = Math.Round(hr * hours, 2, MidpointRounding.AwayFromZero);
|
|
ii.GrossAmountTotal = ii.AmountTotal;
|
|
|
|
iiList.Add(ii);
|
|
}
|
|
|
|
return iiList.OrderBy(ii => ii.ItemPeriodStart).ToList();
|
|
}
|
|
|
|
private decimal? CalculateApprovedTotalFLS(IList<ApprovalPeriod> apList)
|
|
{
|
|
decimal flsTotal = 0;
|
|
foreach (var ap in apList)
|
|
{
|
|
flsTotal += ap.ApprovedTotal;
|
|
}
|
|
return flsTotal;
|
|
}
|
|
|
|
//private decimal? CalculateMaxAmount(Settlement2DC si, IList<ApprovalPeriod> apList)
|
|
//{
|
|
|
|
// var apsByHourlyRate = new Dictionary<decimal, IList<ApprovalPeriod>>();
|
|
|
|
// foreach (var ap in apList)
|
|
// {
|
|
// IList<ApprovalPeriod> list = null;
|
|
// if (apsByHourlyRate.ContainsKey(ap.AmountPerUnit))
|
|
// {
|
|
// list = apsByHourlyRate[ap.AmountPerUnit];
|
|
// }
|
|
// else
|
|
// {
|
|
// list = new List<ApprovalPeriod>();
|
|
// apsByHourlyRate[ap.AmountPerUnit] = list;
|
|
// }
|
|
|
|
// list.Add(ap);
|
|
// }
|
|
|
|
// decimal amount = 0;
|
|
|
|
// foreach (var hr in apsByHourlyRate.Keys)
|
|
// {
|
|
// IList<ApprovalPeriod> list = apsByHourlyRate[hr];
|
|
|
|
// decimal flsTotal = 0;
|
|
|
|
// foreach (var approvalPeriod in list)
|
|
// {
|
|
// var fls = Math.Round(approvalPeriod.Weeks * approvalPeriod.ApprovedPerUnit, 2);
|
|
// flsTotal += fls;
|
|
// }
|
|
// decimal a = Math.Round(flsTotal, 2) * hr;
|
|
// amount += Math.Round(a, 2);
|
|
// }
|
|
|
|
// return amount;
|
|
//}
|
|
|
|
public override void InitApprovalPeriod(Settlement2DC si, ApprovalPeriod approvalPeriod)
|
|
{
|
|
base.InitApprovalPeriod(si, approvalPeriod);
|
|
approvalPeriod.Weeks = Math.Round(approvalPeriod.Days*0.1427m, 2);
|
|
if (approvalPeriod.ApprovalUnit == AccountingIntervalType.Weekly)
|
|
{
|
|
approvalPeriod.ApprovedTotal = Math.Round(approvalPeriod.Weeks*approvalPeriod.ApprovedPerUnit, 2);
|
|
}
|
|
approvalPeriod.ApprovedAmountTotal = Math.Round(approvalPeriod.ApprovedTotal * approvalPeriod.AmountPerUnit, 2);
|
|
}
|
|
}
|
|
}
|