Files
BeWoPlaner/Service/Plugins/ServiceRecordValidator.cs

1185 lines
50 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.WebSockets;
using BeWo.Data.Access;
using BeWo.Data.Entities;
using BeWo.Service.DCEntityMapper;
using BS.Shared;
using BS.Shared.Core;
using BS.Shared.DataContracts;
using BS.Shared.DataContracts.Compact;
using BS.Shared.Extensions;
using BS.Shared.Services;
using BeWo.Service.Configuration;
using BeWo.Data.Security;
using BeWo.Service.Security;
namespace BeWo.Service.Plugins
{
public class ServiceRecordValidator : AbstractIDSpecificDefaultClass<ServiceRecordValidator>
{
public virtual Dictionary<ServiceRecordDC, List<ServiceRecordValidationResult>> ValidateGroupServiceRecordEntry(ServiceRecordDC pServiceRecord, List<ServiceRecordDC> groupServiceRecords, List<CompactEmployeeDC> employees, DateTime? date, int maxDaysEditServiceRecordsAllowed, decimal flsTotalRounded, out Dictionary<long, string> overlappingRecords, out Dictionary<long, string> overlappingEmployees)
{
var result = new Dictionary<ServiceRecordDC, List<ServiceRecordValidationResult>>();
overlappingRecords = new Dictionary<long, string>();
overlappingEmployees = new Dictionary<long, string>();
long gid = pServiceRecord.GroupOid ?? -1;
foreach (ServiceRecordDC groupRecord in groupServiceRecords)
{
DateTime start = groupRecord.Start.Value;
bool noTime = start.Hour == 0 && start.Minute == 0 && start.Second == 1; //Keine Zeit angegeben
if(!noTime)
{
IList<ServiceRecord> records =
DAOFactory.SearchDAO.FindServiceRecordsDetailsForLastDays(
groupRecord.CostBearer2SupportConceptOid.Value, null);
// 1. ServiceRecordOverlapping
try
{
records = records.Where(r =>
{
bool valid = true;
if (r.GroupOid.HasValue)
valid = r.GroupOid.Value != gid;
return valid;
}).ToList();
List<ServiceRecord> belongsToSupportConcept = records.ToList();
bool overlapping =
belongsToSupportConcept.Any(
rec =>
TimeSpanOverlapsWithOtherTimeSpan(groupRecord.Start, rec.Start,
groupRecord.GroupRoundedDuration ?? groupRecord.RoundedDuration,
rec.GroupRoundedDuration ?? rec.RoundedDuration));
if (overlapping)
{
result.AddOrUpdateValueInDictionary(groupRecord,
new List<ServiceRecordValidationResult> { ServiceRecordValidationResult.ServiceRecordOverlapping });
ServiceRecord rec =
belongsToSupportConcept.First(
re =>
TimeSpanOverlapsWithOtherTimeSpan(groupRecord.Start, re.Start,
groupRecord.GroupRoundedDuration ?? groupRecord.RoundedDuration,
re.GroupRoundedDuration ?? re.RoundedDuration));
overlappingRecords.Add(groupRecord.ServiceRecordOid.Value,
rec.Start.Value.ToShortTimeString() + ", Dauer " +
string.Format("{0:0}", rec.GroupRoundedDuration ?? rec.RoundedDuration) + ", " +
groupRecord.Customer.FullName);
}
}
catch (Exception)
{
result.AddOrUpdateValueInDictionary(groupRecord,
new List<ServiceRecordValidationResult> { ServiceRecordValidationResult.Error });
}
// 2. EmployeeOverlapping
try
{
foreach (CompactEmployeeDC employee in employees)
{
var span = new DateTimeSpan();
span.StartDate = start.Date.AddDays(-1);
span.EndDate = groupRecord.End.Value.Date.AddDays(1);
IList<ServiceRecord> empRecords = DAOFactory.SearchDAO.FindEmployeeServiceRecords(employee.EmployeeOid, span, null, null);
if (groupRecord.ServiceRecordOid.HasValue)
{
empRecords = empRecords.Where(r => r.Oid != groupRecord.ServiceRecordOid.Value).ToList();
}
empRecords = empRecords.Where(r => r.Start.HasValue && r.Start.Value.Second == 0).ToList();
bool overlapping =
empRecords.Any(
record =>
TimeSpanOverlapsWithOtherTimeSpan(pServiceRecord.Start, record.Start, pServiceRecord.RoundedDuration,
record.RoundedDuration));
if (overlapping)
{
result.AddOrUpdateValueInDictionary(groupRecord,
new List<ServiceRecordValidationResult> { ServiceRecordValidationResult.EmployeeOverlapping });
overlappingEmployees.Add(groupRecord.ServiceRecordOid.Value,
string.Format("{1}, {0}", employee.FirstName, employee.LastName));
}
}
}
catch (Exception)
{
result.AddOrUpdateValueInDictionary(groupRecord,
new List<ServiceRecordValidationResult> { ServiceRecordValidationResult.Error });
}
}
// 4. ApprovedFLSOverspending
try
{
SupportConceptCostBearerRelDC reldc = MapperFactory.SupportConceptCostBearerRelDC_CostBearer2SupportConcept.MapToNewDC(DAOFactory.GenericDAO.GetByID<CostBearer2SupportConcept>(groupRecord.CostBearer2SupportConceptOid.Value));
Calculations calc = PluginLoader.FindClass<Calculations>(reldc.CostBearer.CostBearerID) ?? Calculations.GetInstance(reldc.CostBearer.CostBearerID);
decimal totalFLSApproved = calc.GetApprovedHoursTotal(reldc, true) ?? 0m;
if (totalFLSApproved > 0)
{
decimal totalFLSNew = flsTotalRounded + (groupRecord.RoundedDuration / 60);
if (pServiceRecord.ServiceDescription.Category.IsBillable && totalFLSNew > totalFLSApproved)
result.AddOrUpdateValueInDictionary(groupRecord, new List<ServiceRecordValidationResult> { ServiceRecordValidationResult.OutsideOfSupportConcept });
}
// 3. OutsideOfSupportConcept
if (date.HasValue && reldc.StartDate.HasValue && reldc.EndDate.HasValue)
if (!date.Value.InBetween(reldc.StartDate.Value, reldc.EndDate.Value, true))
result.AddOrUpdateValueInDictionary(groupRecord, new List<ServiceRecordValidationResult> { ServiceRecordValidationResult.OutsideOfSupportConcept });
}
catch (Exception)
{
result.AddOrUpdateValueInDictionary(groupRecord, new List<ServiceRecordValidationResult> { ServiceRecordValidationResult.Error });
}
// 6. SettlementInvoiceAlreadyExisting
try
{
IList<SettlementInvoice> allSettlementInvoices = DAOFactory.GenericDAO.GetAll<SettlementInvoice>();
if (allSettlementInvoices.Where(rechnung => rechnung.InvoiceBase.CostBearer2SupportConcept.Oid.HasValue).Any(rechnung => rechnung.InvoiceBase.CostBearer2SupportConcept.Oid.Value.Equals(groupRecord.CostBearer2SupportConceptOid.Value) && rechnung.IsActive.Equals(ActivationTypeId.Active)))
result.AddOrUpdateValueInDictionary(groupRecord, new List<ServiceRecordValidationResult> { ServiceRecordValidationResult.SettlementInvoiceAlreadyExisting });
}
catch (Exception)
{
result.AddOrUpdateValueInDictionary(groupRecord, new List<ServiceRecordValidationResult> { ServiceRecordValidationResult.Error });
}
try
{
// 5. Frist
var settings = AppSettings.CreateSettings();
if (settings.AnzTageZeiterfassErfolgt > 0)
{
var dt = new DateTime(date.Value.Year, date.Value.Month, 1);
dt = dt.AddDays(settings.AnzTageZeiterfassErfolgt);
if (DateTime.Now >= dt)
result.AddOrUpdateValueInDictionary(groupRecord, new List<ServiceRecordValidationResult> { ServiceRecordValidationResult.FristAbgelaufen });
}
}
catch (Exception)
{
result.AddOrUpdateValueInDictionary(groupRecord, new List<ServiceRecordValidationResult> { ServiceRecordValidationResult.Error });
}
if (maxDaysEditServiceRecordsAllowed <= 0)
return result;
try
{
// 6. OutOfEditLimit
var dt = new DateTime(date.Value.Year, date.Value.Month, 1);
dt = dt.AddMonths(1);
dt = dt.AddDays(maxDaysEditServiceRecordsAllowed + 1);
if (DateTime.Now >= dt)
result.AddOrUpdateValueInDictionary(groupRecord, new List<ServiceRecordValidationResult> { ServiceRecordValidationResult.OutOfEditLimit });
}
catch (Exception)
{
result.AddOrUpdateValueInDictionary(groupRecord, new List<ServiceRecordValidationResult> { ServiceRecordValidationResult.Error });
}
}
return result;
}
public virtual List<ServiceRecordValidationResultDC> ValidateServiceRecord(ServiceRecordDC newServiceRecord, SupportConceptStatisticsDC statistics, int maxDaysEditServiceRecordsAllowed, IList<long> employeeOids, IList<long> cb2scOids)
{
var result = new List<ServiceRecordValidationResultDC>();
if (!newServiceRecord.Start.HasValue)
{
return result;
}
if (!ValidateSignature(newServiceRecord))
{
result.Add(new ServiceRecordValidationResultDC
{
ResultType = ServiceRecordValidationResult.Custom,
Message = "Der Eintrag wurde bereits unterschrieben. Das Verändern von Datum und Uhrzeit ist nicht möglich."
});
}
//// TODO: CheckForExistingSignature an den Stellen einbauen an denen ValidateServiceRecord aufgerufen wird
//if (!ValidateSignature(newServiceRecord))
//{
// result.Add(new ServiceRecordValidationResultDC
// {
// ResultType = ServiceRecordValidationResult.SignatureExists,
// Message = "Der Eintrag wurde bereits unterschrieben. Das Verändern von Datum und Uhrzeit führt zur Löschen der Unterschrift!"
// });
//}
if(newServiceRecord.ServiceRecordOid.HasValue)
{
var original = DAOFactory.GenericDAO.LoadByID<ServiceRecord>(newServiceRecord.ServiceRecordOid.Value);
var startEqual = Equals(original.Start, newServiceRecord.Start);
var endEqual = Equals(original.End, newServiceRecord.End);
var areTimesEqual = startEqual && endEqual;
var confirmationReceiptSignatures = DAOFactory.SearchDAO.GetConfirmationReceiptSignaturesByServiceRecord(newServiceRecord.ServiceRecordOid.Value);
if(!areTimesEqual && confirmationReceiptSignatures.Any())
{
var message = "Für diesen Eintrag existiert bereits eine Monatsunterschrift. Das Verändern von Datum oder Uhrzeit führt zu einer ungültigen Monatsunterschrift, die erneut geleistet werden muss, um auf dem Quittierungsbeleg angezeigt zu werden.";
result.Add(new ServiceRecordValidationResultDC
{
ResultType = ServiceRecordValidationResult.SignatureExists,
Message = message,
Allow = true
});
}
}
bool isGroupRecord = (cb2scOids != null && cb2scOids.Count > 1) ||
(employeeOids != null && employeeOids.Count > 1);
DateTime start = newServiceRecord.Start.Value;
bool noTime = (start.Hour == 0 && start.Minute == 0 && start.Second == 1); //Keine Zeit angegeben
if (!noTime)
{
noTime = newServiceRecord.ServiceDescription.DoNotCheckOverlapping;
}
var duration = (decimal)newServiceRecord.End.Value.Subtract(newServiceRecord.Start.Value).TotalMinutes;
if (!noTime && duration == 0)
{
noTime = true;
}
newServiceRecord.RoundedDuration = duration;
decimal roundedDuration = duration;
var srdc = PluginLoader.FindClass<ServiceRecordDurationCalculator>();
if (srdc != null)
{
srdc.CalculateRoundedDuration(newServiceRecord);
roundedDuration = newServiceRecord.RoundedDuration;
}
decimal? grd = newServiceRecord.GroupRoundedDuration;
if (isGroupRecord)
{
var groupcalc = PluginLoader.FindClass<GroupDurationCalculator>();
if (groupcalc != null)
{
var gd = groupcalc.CalculateGroupDuration(cb2scOids.Count, employeeOids.Count, duration, cb2scOids.ToArray());
if (gd == null)
{
grd = roundedDuration;
roundedDuration = Math.Round(roundedDuration / cb2scOids.Count, 0, MidpointRounding.AwayFromZero);
}
else
{
grd = gd.TotalDuration;
roundedDuration = gd.SingleDuration;
}
}
}
//var minuteInterval = cbDC.ActualMinuteIntervall;
//if (minuteInterval > 0)
newServiceRecord.RoundedDuration = roundedDuration;
decimal rd = newServiceRecord.RoundedDuration;
long? cb2ScOid = null;
if (newServiceRecord.SupportConcept != null && newServiceRecord.CostBearer != null && newServiceRecord.CostBearer2SupportConceptOid.HasValue)
{
cb2ScOid = newServiceRecord.CostBearer2SupportConceptOid.Value;
}
IList<ServiceRecord> allCustomerRecords = null;
if (!noTime)
{
bool overlapsCustomer = false;
if (cb2ScOid.HasValue)
{
// 1. Customer ServiceRecordOverlapping
try
{
allCustomerRecords = DAOFactory.SearchDAO.FindServiceRecordsDetailsForLastDays(cb2ScOid.Value, null);
if (newServiceRecord.ServiceRecordOid.HasValue)
{
allCustomerRecords = allCustomerRecords.Where(r => r.Oid != newServiceRecord.ServiceRecordOid.Value).ToList();
}
ServiceRecordValidationResultDC resultDc = CheckOverlappingCustomerServiceRecords(newServiceRecord, start, allCustomerRecords, isGroupRecord, grd ?? rd);
if (resultDc != null)
{
overlapsCustomer = true;
result.Add(resultDc);
}
}
catch (Exception)
{
result.Add(new ServiceRecordValidationResultDC { ResultType = ServiceRecordValidationResult.Error });
}
}
// 2. EmployeeOverlapping
if (!overlapsCustomer)
{
if (!isGroupRecord)
{
ServiceRecordValidationResultDC resultdc = CheckOverlappingEmployeeServiceRecords(newServiceRecord, start, newServiceRecord.Employee.EmployeeOid, rd);
if (resultdc != null)
result.Add(resultdc);
}
else if (employeeOids != null)
{
foreach (long employeeOid in employeeOids)
{
ServiceRecordValidationResultDC resultdc = null;
if (resultdc == null)
{
resultdc = CheckOverlappingEmployeeServiceRecords(newServiceRecord, start, employeeOid, grd ?? rd);
if (resultdc != null)
result.Add(resultdc);
}
}
}
}
}
try
{
// 5. Frist
var settings = AppSettings.CreateSettings();
if (settings.AnzTageZeiterfassErfolgt > 0)
{
var dt = new DateTime(start.Year, start.Month, start.Day);
dt = dt.AddDays(settings.AnzTageZeiterfassErfolgt + 1);
if (DateTime.Now >= dt)
result.Add(new ServiceRecordValidationResultDC { ResultType = ServiceRecordValidationResult.FristAbgelaufen });
}
}
catch (Exception)
{
result.Add(new ServiceRecordValidationResultDC { ResultType = ServiceRecordValidationResult.Error });
}
if (cb2ScOid.HasValue)
{
SupportConceptCostBearerRelDC reldc =
MapperFactory.SupportConceptCostBearerRelDC_CostBearer2SupportConcept.MapToNewDC(
DAOFactory.GenericDAO.GetByID<CostBearer2SupportConcept>(cb2ScOid.Value));
// 4. ApprovedFLSOverspending
try
{
var resultOverspending = CheckApprovedOverspending(reldc, newServiceRecord);
if (resultOverspending != null)
{
result.Add(resultOverspending);
}
// 3. OutsideOfSupportConcept
if (reldc.StartDate.HasValue && reldc.EndDate.HasValue)
if (!start.InBetween(reldc.StartDate.Value, reldc.EndDate.Value, true))
{
String handlungsOption = "Das Speichern ist nicht möglich.";
bool hatDasRechtTrotzdemZuSpeichern = false;
if (UserRightHelper.LoggedInUserHasRight(UserRightType.BookServiceRecordOutOfSupportConcept))
{
handlungsOption = "Möchten Sie trotzdem Speichern?";
hatDasRechtTrotzdemZuSpeichern = true;
}
var dc = new ServiceRecordValidationResultDC
{
ResultType = ServiceRecordValidationResult.Custom,
Message = $"Das gewählte Datum '{start:dd.MM.yyyy}' liegt außerhalb des Hilfeplanzeitraums ('{reldc.StartDate.Value.ToShortDateString()}' bis '{reldc.EndDate.Value.ToShortDateString()}').\n" + handlungsOption,
StartDate = reldc.StartDate.Value,
EndDate = reldc.EndDate.Value,
Allow = hatDasRechtTrotzdemZuSpeichern
};
result.Add(dc);
}
}
catch (Exception)
{
result.Add(new ServiceRecordValidationResultDC { ResultType = ServiceRecordValidationResult.Error });
}
// 6. SettlementInvoiceAlreadyExisting
try
{
IList<SettlementInvoice> allSettlementInvoices =
DAOFactory.SearchDAO.GetSettlementInvoiceByCostBearer2SupportConceptOid(cb2ScOid.Value);
if (allSettlementInvoices.Count > 0)
{
var r = new ServiceRecordValidationResultDC
{
ResultType = ServiceRecordValidationResult.SettlementInvoiceAlreadyExisting
};
r.CustomerName = reldc.SupportConcept.CustomerFullName;
result.Add(r);
}
}
catch (Exception)
{
result.Add(new ServiceRecordValidationResultDC { ResultType = ServiceRecordValidationResult.Error });
}
//7. Prüfe ob Datum in Zukunft liegt
if (CheckZukunft())
{
if (start.Date > DateTime.Now)
{
result.Add(new ServiceRecordValidationResultDC
{
ResultType = ServiceRecordValidationResult.Custom,
Message = "Das gewählte Datum darf nicht in der Zukunft liegen."
});
}
}
// 8. OutOfEditLimit
try
{
if (maxDaysEditServiceRecordsAllowed > 0)
{
var dt = new DateTime(start.Year, start.Month, 1);
dt = dt.AddMonths(1);
dt = dt.AddDays(maxDaysEditServiceRecordsAllowed);
if (DateTime.Now >= dt)
result.Add(new ServiceRecordValidationResultDC
{
ResultType = ServiceRecordValidationResult.OutOfEditLimit
});
}
}
catch (Exception)
{
result.Add(new ServiceRecordValidationResultDC { ResultType = ServiceRecordValidationResult.Error });
}
// 9. Prüfe Abwesenheiten
var resultdc = CheckAbwesenheiten(newServiceRecord, allCustomerRecords, reldc, rd, isGroupRecord);
if (resultdc != null)
{
result.Add(resultdc);
}
// 10. Prüfe Fehlkontakte > 2 Std.
if (duration > 120 && (newServiceRecord.ServiceDescription.Name.ToLower().Contains("fehlkontakt") || newServiceRecord.ServiceDescription.Category.Name.ToLower().Contains("fehlkontakt")))
{
String message = String.Format("Die eingegebene Dauer des Fehlkontakts überschreitet die maximal abrechenbare Dauer von 2 Stunden.\nMöchten Sie trotzdem speichern?");
result.Add(new ServiceRecordValidationResultDC
{
ResultType = ServiceRecordValidationResult.Custom,
Message = message,
Allow = true
});
}
}
long? userOid = SecurityUtils.GetLoggedInUser().Oid;
foreach (var r in result)
{
ValidationMessage validationMessage = new ValidationMessage();
validationMessage.Message = r.Message;
validationMessage.UserOid = userOid;
validationMessage.EmployeeOid = newServiceRecord.Employee?.EmployeeOid;
validationMessage.SupportConceptOid = newServiceRecord.SupportConcept?.SupportConceptOid;
validationMessage.CustomerName = r.CustomerName;
validationMessage.ServiceCategory = newServiceRecord.ServiceDescription.CategoryName;
validationMessage.ServiceDescription = newServiceRecord.ServiceDescription.Name;
validationMessage.ServiceRecordStart = newServiceRecord.Start;
validationMessage.ServiceRecordEnd = newServiceRecord.End;
validationMessage.ServiceRecordValidationResult = (int)r.ResultType;
DAOFactory.GenericDAO.Insert(validationMessage);
}
return result;
}
public virtual ServiceRecordValidationResultDC CheckAbwesenheiten(ServiceRecordDC newServiceRecord, IList<ServiceRecord> allRecords, SupportConceptCostBearerRelDC reldc, decimal rd, bool isGroupRecord)
{
if (reldc != null && newServiceRecord.ServiceDescription.Category.IsBillable)
{
if (reldc.SupportConcept.Customer.AbsenceTimes != null &&
reldc.SupportConcept.Customer.AbsenceTimes.Count > 0)
{
Calculations calc = PluginLoader.FindClass<Calculations>(reldc.CostBearer.CostBearerID) ??
Calculations.GetInstance(reldc.CostBearer.CostBearerID);
Dictionary<DateTimeSpan, decimal> span2MaxHours =
calc.GetAbsencesTimesNotBillable(reldc.SupportConcept.Customer.AbsenceTimes);
if (allRecords == null)
{
allRecords =
DAOFactory.SearchDAO.FindServiceRecordsDetailsForLastDays(
reldc.CostBearer2SupportConceptOid.Value, null);
if (newServiceRecord.ServiceRecordOid.HasValue)
{
allRecords = allRecords.Where(r => r.Oid != newServiceRecord.ServiceRecordOid.Value)
.ToList();
}
if (isGroupRecord && newServiceRecord.GroupOid.HasValue)
{
allRecords = allRecords.Where(r => !r.GroupOid.HasValue || r.GroupOid != newServiceRecord.GroupOid).ToList();
}
}
//var c2s = DAOFactory.GenericDAO.LoadByID<CostBearer2SupportConcept>(newServiceRecord.CostBearer2SupportConceptOid.Value);
//var customer = c2s.SupportConcept.Customer;
//var absenceTimes = MapperFactory.AbsenceTimeDC_AbsenceTime.MapToNewDCs(customer.AbsenceTimes);
Dictionary<DateTimeSpan, decimal> span2Geleistet = new Dictionary<DateTimeSpan, decimal>();
foreach (var span in span2MaxHours.Keys)
{
if (!span2Geleistet.ContainsKey(span))
{
span2Geleistet.Add(span, 0);
}
}
foreach (var sr in allRecords)
{
if (sr.Start.HasValue && sr.End.HasValue)
{
foreach (var span in span2MaxHours.Keys)
{
if (sr.End.Value.Date >= span.StartDateTime &&
sr.Start.Value.Date <= span.EndDateTime)
{
if (sr.ServiceDescription.ServiceCategory.IsBillable)
{
if (!span2Geleistet.ContainsKey(span))
{
span2Geleistet.Add(span, 0);
}
var duration = sr.RoundedDuration;
span2Geleistet[span] += duration;
}
}
}
}
}
foreach (var span in span2MaxHours.Keys)
{
if (newServiceRecord.End.Value.Date >= span.StartDateTime &&
newServiceRecord.Start.Value.Date <= span.EndDateTime)
{
var max = span2MaxHours[span];
if (span2Geleistet.ContainsKey(span))
{
max -= span2Geleistet[span];
}
if (max < 0 || max - rd < 0)
{
String message = String.Format(
"In der Woche vom {0:dd.MM.yyyy}-{1:dd.MM.yyyy} wurden bereits {2:0.##} Stunden geleistet.\nDie eingegebene Zeit kann nicht komplett abgerechnet werden, da der/die gewählte KlientIn in dem angegebenen Zeitraum abwesend ist und die maximal abrechenbaren Stunden in dieser Woche überschritten werden.\nMöchten Sie trotzdem speichern?",
span.StartDateTime, span.EndDateTime, span2Geleistet[span] / 60);
if (span2Geleistet[span] == 0)
{
message = "Die eingegebene Zeit kann nicht komplett abgerechnet werden, da der/die gewählte KlientIn in dem angegebenen Zeitraum abwesend ist und die maximal abrechenbaren Stunden in dieser Woche überschritten werden.\nMöchten Sie trotzdem speichern?";
}
return new ServiceRecordValidationResultDC
{
ResultType = ServiceRecordValidationResult.Custom,
Message = message,
Allow = true
};
}
}
}
}
}
return null;
}
public virtual bool ValidateSignature(ServiceRecordDC sr)
{
if (sr.ServiceRecordOid.HasValue && sr.SignatureOid.HasValue)
{
var existingRecord = DAOFactory.GenericDAO.LoadByID<ServiceRecord>(sr.ServiceRecordOid.Value);
if (existingRecord.Start != sr.Start || existingRecord.End != sr.End)
{
return false;
}
}
return true;
}
public virtual ServiceRecordValidationResultDC CheckApprovedOverspending(SupportConceptCostBearerRelDC reldc, ServiceRecordDC newServiceRecord)
{
decimal totalFLSApproved = 0;
var factory = PluginLoader.FindClass<ServiceRecordStatisticFactory>();
SupportConceptStatisticsDC stats = null;
if (factory == null)
{
factory = new ServiceRecordStatisticFactory();
}
Calculations calc = PluginLoader.FindClass<Calculations>(reldc.CostBearer.CostBearerID) ?? Calculations.GetInstance(reldc.CostBearer.CostBearerID);
//if (reldc.ApprovalPeriodList != null && reldc.ApprovalPeriodList.Count > 1)
//{
// stats = factory.CreateSupportConceptStatisticsImpl(reldc.CostBearer2SupportConceptOid.Value,
// DateTime.Now, newServiceRecord.ServiceRecordOid);
// }
//else
//{
totalFLSApproved = calc.GetApprovedHoursTotal(reldc, true) ?? 0m;
//}
if (totalFLSApproved > 0)
{
stats = factory.CreateSupportConceptStatisticsImpl(reldc.CostBearer2SupportConceptOid.Value,
newServiceRecord.Start ?? DateTime.Now, newServiceRecord.ServiceRecordOid);
decimal flmTotalRounded = 0;
decimal flmApprovedForPeriod = 0;
SupportConceptPeriodStatisticsDC periodStat = GetStatisticsForServiceRecord(stats, newServiceRecord);
if (periodStat != null)
{
flmTotalRounded = periodStat.MinutesProvidedTotalRounded;
flmApprovedForPeriod = periodStat.MinutesApprovedTotal;
//Prüfe nicht übertragbare Stunden
if (periodStat.SupportConceptApprovalPeriod != null && !periodStat.SupportConceptApprovalPeriod.IsApprovedBEShifting && (newServiceRecord.ServiceDescription.Category.IsBillable || periodStat.SupportConceptApprovalPeriod.ServiceCategory != null))
{
if (periodStat.SupportConceptApprovalPeriod.ApprovedBEInterval.HasValue && periodStat.SupportConceptApprovalPeriod.ApprovedBEPerInterval.HasValue)
{
decimal totalfls = (newServiceRecord.RoundedDuration) / 60;
decimal maxAllowed = periodStat.SupportConceptApprovalPeriod.ApprovedBEPerInterval.Value;
if (periodStat.SupportConceptApprovalPeriod.ApprovedBEInterval.Value == SupportConceptApprovalInterval.Weekly)
{
totalfls += periodStat.MinutesProvidedThisWeek / 60;
}
else if (periodStat.SupportConceptApprovalPeriod.ApprovedBEInterval.Value == SupportConceptApprovalInterval.Monthly)
{
totalfls += periodStat.MinutesProvidedThisMonth / 60;
}
if (maxAllowed > 0 && totalfls > maxAllowed)
{
var dc = new ServiceRecordValidationResultDC
{
ResultType = ServiceRecordValidationResult.ApprovedFLSOverspending,
ApprovedHours = Math.Round(maxAllowed, 2, MidpointRounding.AwayFromZero),
HoursNew = Math.Round(totalfls, 2, MidpointRounding.AwayFromZero)
};
dc.CustomerName = reldc.SupportConcept.CustomerFullName;
return dc;
}
}
}
}
else
{
flmTotalRounded = stats.MinutesProvidedTotalRounded;
flmApprovedForPeriod = stats.MinutesApprovedTotal;
}
decimal totalFLSNew = (flmTotalRounded + newServiceRecord.RoundedDuration) / 60;
decimal approvedFlsForPeriod = (flmApprovedForPeriod / 60);
approvedFlsForPeriod = Math.Round(approvedFlsForPeriod, 2, MidpointRounding.AwayFromZero);
if (approvedFlsForPeriod > 0 && totalFLSNew > approvedFlsForPeriod)
{
if (newServiceRecord.ServiceDescription.Category.IsBillable ||
(periodStat != null && periodStat.SupportConceptApprovalPeriod != null && periodStat.SupportConceptApprovalPeriod.ServiceCategory != null))
{
var dc = new ServiceRecordValidationResultDC
{
ResultType = ServiceRecordValidationResult.ApprovedFLSOverspending,
ApprovedHours = Math.Round((flmApprovedForPeriod / 60), 2, MidpointRounding.AwayFromZero),
HoursNew = Math.Round(totalFLSNew, 2, MidpointRounding.AwayFromZero)
};
dc.CustomerName = reldc.SupportConcept.CustomerFullName;
return dc;
}
}
}
return null;
}
public virtual bool CheckZukunft()
{
//#if DEBUG
//return false;
//#endif
return true;
}
public virtual ServiceRecordValidationResultDC CheckOverlappingCustomerServiceRecords(ServiceRecordDC newServiceRecord, DateTime start, IList<ServiceRecord> allCustomerRecords, bool isGroupRecord, decimal roundedDuration)
{
//Nur für abrechenbare Leistungen prüfen.
if (newServiceRecord.ServiceDescription.Category.IsBillable)
{
//if (isGroupRecord && newServiceRecord.GroupOid.HasValue)
//{
// records = records.Where(r => !r.GroupOid.HasValue || r.GroupOid != newServiceRecord.GroupOid).ToList();
// }
var customerRecordsWithTime = allCustomerRecords.Where(r => r.Start.HasValue && r.Start.Value.Second == 0).ToList();
if (isGroupRecord && newServiceRecord.GroupOid.HasValue)
{
customerRecordsWithTime =
customerRecordsWithTime.Where(r => !r.GroupOid.HasValue || r.GroupOid != newServiceRecord.GroupOid).ToList();
}
ServiceRecord overlappingRecord =
customerRecordsWithTime.FirstOrDefault(
record => record.ServiceDescription.ServiceCategory.IsBillable &&
TimeSpanOverlapsWithOtherTimeSpan(start, record.Start,
roundedDuration,
record.GroupRoundedDuration ?? record.RoundedDuration));
if (overlappingRecord != null)
{
var rdc = new ServiceRecordValidationResultDC
{
ResultType = ServiceRecordValidationResult.ServiceRecordOverlapping,
Message = String.Format("{0}, Dauer {1:0} Minuten", overlappingRecord.Start.Value.ToShortTimeString(),
overlappingRecord.GroupRoundedDuration ?? overlappingRecord.RoundedDuration)
};
Customer c = overlappingRecord.Customer;
if (c != null)
{
rdc.CustomerName = String.Format("{0}, {1}", c.Person.LastName, c.Person.FirstName);
}
Employee e = overlappingRecord.Employee;
if (e != null)
{
rdc.EmployeeName = String.Format("{0} {1}", e.Person.FirstName, e.Person.LastName);
}
return rdc;
}
}
return null;
}
public virtual ServiceRecordValidationResultDC CheckOverlappingEmployeeServiceRecords(ServiceRecordDC newServiceRecord, DateTime start, long employeeOid, decimal roundedDuration)
{
var span = new DateTimeSpan();
span.StartDate = start.Date.AddDays(-1);
span.EndDate = newServiceRecord.End.Value.Date.AddDays(1);
IList<ServiceRecord> empRecords = DAOFactory.SearchDAO.FindEmployeeServiceRecords(employeeOid, span, null, null);
if (newServiceRecord.ServiceRecordOid.HasValue)
{
empRecords = empRecords.Where(r => r.Oid != newServiceRecord.ServiceRecordOid.Value).ToList();
}
if (newServiceRecord.GroupOid.HasValue)
{
empRecords = empRecords.Where(r => !r.GroupOid.HasValue || r.GroupOid != newServiceRecord.GroupOid).ToList();
}
empRecords = empRecords.Where(r => r.Start.HasValue && r.Start.Value.Second == 0).ToList();
String[] ignoreNames = GetIgnoreCategoriesForOverlappingCheck();
if (ignoreNames != null)
{
bool ignore = ignoreNames.Contains(newServiceRecord.ServiceDescription.Category.Name);
if (ignore)
{
empRecords =
empRecords.Where(r => ignoreNames.Contains(r.ServiceDescription.ServiceCategory.Name)).ToList();
}
else
{
empRecords =
empRecords.Where(r => !ignoreNames.Contains(r.ServiceDescription.ServiceCategory.Name)).ToList();
}
}
ignoreNames = GetIgnoreServiceDescriptionsForOverlappingCheck();
if (ignoreNames != null)
{
bool ignore = ignoreNames.Contains(newServiceRecord.ServiceDescription.Name);
if (ignore)
{
empRecords =
empRecords.Where(r => ignoreNames.Contains(r.ServiceDescription.Name)).ToList();
}
else
{
empRecords =
empRecords.Where(r => !ignoreNames.Contains(r.ServiceDescription.Name)).ToList();
}
}
try
{
ServiceRecord overlappingRecord =
empRecords.FirstOrDefault(
record =>
TimeSpanOverlapsWithOtherTimeSpan(start, record.Start, roundedDuration, record.GroupRoundedDuration ?? record.RoundedDuration));
if (overlappingRecord != null)
{
var rdc = new ServiceRecordValidationResultDC
{
ResultType = ServiceRecordValidationResult.EmployeeOverlapping,
Message = String.Format("{0}, Dauer {1:0} Minuten", overlappingRecord.Start.Value.ToShortTimeString(),
overlappingRecord.GroupRoundedDuration ?? overlappingRecord.RoundedDuration)
};
Customer c = overlappingRecord.Customer;
if (c != null)
{
rdc.Message += String.Format(" bei Klient/in {0}, {1}", c.Person.LastName, c.Person.FirstName);
}
Employee e = overlappingRecord.Employee;
if (e != null)
{
rdc.EmployeeName = String.Format("{0} {1}", e.Person.FirstName, e.Person.LastName);
}
return rdc;
}
}
catch (Exception)
{
return new ServiceRecordValidationResultDC { ResultType = ServiceRecordValidationResult.Error };
}
return null;
}
public virtual String[] GetIgnoreCategoriesForOverlappingCheck()
{
return null;
}
public virtual String[] GetIgnoreServiceDescriptionsForOverlappingCheck()
{
return null;
}
public SupportConceptPeriodStatisticsDC GetStatisticsForServiceRecord(SupportConceptStatisticsDC statistics, ServiceRecordDC newServiceRecord)
{
if (statistics != null && statistics.PeriodStatistics.Count > 0)
{
//Prüfe erst Bewilligungen die nicht übertragbar sind
foreach (SupportConceptPeriodStatisticsDC pstat in statistics.PeriodStatistics)
{
if (pstat.SupportConceptApprovalPeriod != null &&
!pstat.SupportConceptApprovalPeriod.IsApprovedBEShifting)
{
if (BelongsToPeriodStatistic(newServiceRecord, pstat))
return pstat;
}
}
foreach (SupportConceptPeriodStatisticsDC pstat in statistics.PeriodStatistics)
{
if (pstat.SupportConceptApprovalPeriod != null && !pstat.SupportConceptApprovalPeriod.IsApprovedBEShifting && pstat.SupportConceptApprovalPeriod.ServiceCategory == null)
{
if (pstat.SupportConceptApprovalPeriod.StartDate <= newServiceRecord.Start)
{
if (!pstat.SupportConceptApprovalPeriod.EndDate.HasValue ||
(newServiceRecord.Start.Value.Date <= pstat.SupportConceptApprovalPeriod.EndDate.Value.Date))
{
return pstat;
}
}
}
}
//Prüfen, ob es Bewilligungen mit Kategorie gibt:
if (statistics.PeriodStatistics.Count > 1)
{
bool mindestensEinerHatKategorie = false;
foreach (SupportConceptPeriodStatisticsDC pstat in statistics.PeriodStatistics)
{
if (pstat.SupportConceptApprovalPeriod != null &&
pstat.SupportConceptApprovalPeriod.ServiceCategory != null)
{
mindestensEinerHatKategorie = true;
}
}
if (!mindestensEinerHatKategorie)
{
return null;
}
//Prüfen, ob es Bewilligungen mit unterschiedlicher Kategorie gibt:
bool mindestensZweiHabenUnterschiedlicheKategorie = false;
ServiceCategoryDC cat = null;
foreach (SupportConceptPeriodStatisticsDC pstat in statistics.PeriodStatistics)
{
if (pstat.SupportConceptApprovalPeriod != null &&
pstat.SupportConceptApprovalPeriod.ServiceCategory != null)
{
if (cat != null)
{
if (pstat.SupportConceptApprovalPeriod.ServiceCategory.ServiceCategoryOid !=
cat.ServiceCategoryOid)
{
mindestensZweiHabenUnterschiedlicheKategorie = true;
}
}
else
{
cat = pstat.SupportConceptApprovalPeriod.ServiceCategory;
}
}
}
if (!mindestensZweiHabenUnterschiedlicheKategorie)
{
return null;
}
}
foreach (SupportConceptPeriodStatisticsDC pstat in statistics.PeriodStatistics)
{
if (BelongsToPeriodStatistic(newServiceRecord, pstat))
return pstat;
}
foreach (SupportConceptPeriodStatisticsDC pstat in statistics.PeriodStatistics)
{
if (pstat.SupportConceptApprovalPeriod != null && pstat.SupportConceptApprovalPeriod.ServiceCategory == null)
{
if (pstat.SupportConceptApprovalPeriod.StartDate <= newServiceRecord.Start)
{
if (!pstat.SupportConceptApprovalPeriod.EndDate.HasValue ||
(newServiceRecord.Start.Value.Date <= pstat.SupportConceptApprovalPeriod.EndDate.Value.Date))
{
return pstat;
}
}
}
}
}
return null;
}
public static bool TimeSpanOverlapsWithOtherTimeSpan(DateTime? pStart, DateTime? rStart, decimal pRoundedDuration, decimal rRoundedDuration)
{
DateTime? pEnd = pStart.Value.AddMinutes(Convert.ToDouble(pRoundedDuration));
DateTime? rEnd = rStart.Value.AddMinutes(Convert.ToDouble(rRoundedDuration));
bool overlaps = pStart == rStart || rEnd == pEnd || pEnd == rStart || pStart == rEnd ||
rStart > pStart && rStart < pEnd || rEnd > pStart && rEnd < pEnd ||
pStart > rStart && pStart < rEnd || pEnd > rStart && pEnd < rEnd;
overlaps = !(pEnd <= rStart || pStart >= rEnd);
return overlaps;
}
private bool BelongsToPeriodStatistic(ServiceRecordDC newServiceRecord, SupportConceptPeriodStatisticsDC pstat)
{
if (pstat.SupportConceptApprovalPeriod != null && pstat.SupportConceptApprovalPeriod.ServiceCategory != null)
if (pstat.SupportConceptApprovalPeriod.ServiceCategory.ServiceCategoryOid ==
newServiceRecord.ServiceDescription.Category.ServiceCategoryOid)
{
if (pstat.SupportConceptApprovalPeriod.StartDate <= newServiceRecord.Start)
{
if (!pstat.SupportConceptApprovalPeriod.EndDate.HasValue ||
(newServiceRecord.Start.Value.Date <= pstat.SupportConceptApprovalPeriod.EndDate.Value.Date))
{
return true;
}
}
}
return false;
}
public List<ServiceRecordValidationResultDC> ValidateServiceRecordDeletion(ServiceRecordDC serviceRecord, long employeeOid)
{
var result = new List<ServiceRecordValidationResultDC>();
// 1. Prüfe Unterschrift vorhanden
if (serviceRecord.SignatureOid.HasValue)
{
result.Add(new ServiceRecordValidationResultDC
{
Allow = true,
ResultType = ServiceRecordValidationResult.Custom,
Message = "Dieser Eintrag wurde bereits unterschrieben.\n\nMöchten Sie ihn trotzdem löschen?"
});
}
// TODO: Flag beachten
// 2. Prüfe, ob eine Mitarbeiterunterschrift mit diesem Eintrag verknüpft ist
//if(serviceRecord.ServiceRecordOid.HasValue)
//{
// var hasEmployeeSignature = DAOFactory.SearchDAO.HasEmployeeSignaturesForServiceRecord(serviceRecord.ServiceRecordOid.Value);
// if(hasEmployeeSignature)
// {
// result.Add(new ServiceRecordValidationResultDC
// {
// ResultType = ServiceRecordValidationResult.HasEmployeeSignature
// });
// }
//}
// 3. Prüfe, ob eine Monatsunterschrift vorhanden ist.
if(serviceRecord.ServiceRecordOid is null)
{
return result;
}
var confirmationReceiptSignatures = DAOFactory.SearchDAO.GetConfirmationReceiptSignaturesByServiceRecord(serviceRecord.ServiceRecordOid.Value);
if(confirmationReceiptSignatures.Any(crs => crs.IsActive == ActivationTypeId.Active))
{
result.Add(new ServiceRecordValidationResultDC
{
ResultType = ServiceRecordValidationResult.Custom,
Message = "Für diesen Eintrag existiert bereits eine Monatsunterschrift. Das Löschen dieses Eintrags führt zu einer ungültigen Monatsunterschrift, die erneut geleistet werden muss, um auf dem Quittierungsbeleg angezeigt zu werden.\n\nMöchten Sie ihn trotzdem löschen?",
Allow = true
});
}
return result;
}
// Nur bei Updates von ServiceRecords
public virtual ServiceRecordValidationResultDC CheckForExistingSignature(ServiceRecordDC serviceRecord)
{
if(serviceRecord == null)
{
return null;
}
var serviceRecords = new List<ServiceRecordDC> {serviceRecord};
if (serviceRecord.GroupOid.HasValue)
{
var serviceRecordGroup = DAOFactory.SearchDAO.FindServiceRecordGroup(serviceRecord.GroupOid.Value);
if(serviceRecordGroup?.ServiceRecordList.Any() ?? false)
{
serviceRecords.AddRange(MapperFactory.ServiceRecordDC_ServiceRecord.MapToNewDCs(serviceRecordGroup.ServiceRecordList.Where(sr => sr.Oid != serviceRecord.ServiceRecordOid)));
}
}
var message = serviceRecords.Count > 1
? "Es existiert bereits eine Unterschrift. Das Ändern des Datums oder der Uhrzeiten führt zur Löschung der vorhandenen Unterschrift"
: "Es existieren bereits Unterschriften. Das Ändern des Datums oder der Uhrzeiten führt zur Löschung der vorhandenen Unterschriften";
var result = new ServiceRecordValidationResultDC
{
ResultType = ServiceRecordValidationResult.SignatureExists,
Message = message
};
var originals = DAOFactory.GenericDAO.LoadByIDs<ServiceRecord>(serviceRecords.Where(sr => sr.ServiceRecordOid.HasValue).Select(sr => sr.ServiceRecordOid.Value));
var oldVsNew = new Dictionary<ServiceRecord, ServiceRecordDC>();
var orderedOldOnes = originals.Where(x => x.Oid.HasValue).OrderBy(x => x.Oid.Value).ToList();
var orderedNewOnes = serviceRecords.Where(x => x.ServiceRecordOid.HasValue).OrderBy(x => x.ServiceRecordOid.Value).ToList();
foreach (var oldSr in orderedOldOnes)
{
var newSr = orderedNewOnes.FirstOrDefault(f => f.ServiceRecordOid == oldSr.Oid);
if (newSr != null)
{
oldVsNew.Add(oldSr, newSr);
}
}
foreach(var kvp in oldVsNew)
{
if ((kvp.Key.Start != kvp.Value.Start || kvp.Key.End != kvp.Value.End) && kvp.Key.SignatureOid.HasValue)
{
return result;
}
}
return null;
}
}
}