Files
BeWoPlaner/Service/Plugins/ServiceRecordValidator.cs
2017-05-18 21:47:09 +02:00

681 lines
24 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
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;
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;
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;
}
decimal roundedDuration = duration;
var srdc = PluginLoader.FindClass<ServiceRecordDurationCalculator>();
if (srdc != null)
{
srdc.CalculateRoundedDuration(newServiceRecord);
roundedDuration = newServiceRecord.RoundedDuration;
}
if (isGroupRecord)
{
var groupcalc = PluginLoader.FindClass<GroupDurationCalculator>();
if (groupcalc != null)
{
var gd = groupcalc.CalculateGroupDuration(cb2scOids.Count, employeeOids.Count, duration, cb2scOids.ToArray());
if (gd == null)
{
roundedDuration = Math.Round(roundedDuration / cb2scOids.Count, 0, MidpointRounding.AwayFromZero);
}
else
{
roundedDuration = gd.SingleDuration;
}
}
}
//var minuteInterval = cbDC.ActualMinuteIntervall;
//if (minuteInterval > 0)
newServiceRecord.RoundedDuration = roundedDuration;
decimal rd = newServiceRecord.RoundedDuration;
decimal? grd = newServiceRecord.GroupRoundedDuration;
long? cb2ScOid = null;
if (newServiceRecord.CostBearer2SupportConceptOid.HasValue)
{
cb2ScOid = newServiceRecord.CostBearer2SupportConceptOid.Value;
}
if (!noTime)
{
bool overlapsCustomer = false;
if (cb2ScOid.HasValue)
{
// 1. Customer ServiceRecordOverlapping
try
{
//Nur für abrechenbare Leistungen prüfen.
if (newServiceRecord.ServiceDescription.Category.IsBillable)
{
IList<ServiceRecord> records = DAOFactory.SearchDAO.FindServiceRecordsDetailsForLastDays(cb2ScOid.Value,
null);
if (newServiceRecord.ServiceRecordOid.HasValue)
{
records = records.Where(r => r.Oid != newServiceRecord.ServiceRecordOid.Value).ToList();
}
records = records.Where(r => r.Start.HasValue && r.Start.Value.Second == 0).ToList();
if (isGroupRecord && newServiceRecord.GroupOid.HasValue)
{
records =
records.Where(r => !r.GroupOid.HasValue || !r.GroupOid.Equals(newServiceRecord.GroupOid.Value)).ToList();
}
ServiceRecord overlappingRecord =
records.FirstOrDefault(
record => record.ServiceDescription.ServiceCategory.IsBillable &&
TimeSpanOverlapsWithOtherTimeSpan(start, record.Start,
grd ?? rd,
record.GroupRoundedDuration ?? record.RoundedDuration));
if (overlappingRecord != null)
{
overlapsCustomer = true;
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);
}
result.Add(rdc);
}
}
}
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, 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
{
Calculations calc = PluginLoader.FindClass<Calculations>(reldc.CostBearer.CostBearerID) ?? Calculations.GetInstance(reldc.CostBearer.CostBearerID);
decimal totalFLSApproved = calc.GetApprovedHoursTotal(reldc, true) ?? 0m;
if (totalFLSApproved > 0)
{
//SupportConceptStatisticsDC stats = statistics;
//if (stats == null)
//{
var factory = PluginLoader.FindClass<ServiceRecordStatisticFactory>();
if (factory == null)
{
factory = new ServiceRecordStatisticFactory();
}
var stats = factory.CreateSupportConceptStatisticsImpl(cb2ScOid.Value, DateTime.Now, newServiceRecord.ServiceRecordOid);
//}
decimal flmTotalRounded = 0;
SupportConceptPeriodStatisticsDC periodStat = GetStatisticsForServiceRecord(stats, newServiceRecord);
if (periodStat != null)
{
flmTotalRounded = periodStat.MinutesProvidedTotalRounded;
}
else
{
flmTotalRounded = stats.MinutesProvidedTotalRounded;
}
decimal totalFLSNew = (flmTotalRounded + rd) / 60;
if (totalFLSNew > totalFLSApproved)
{
if (newServiceRecord.ServiceDescription.Category.IsBillable ||
(periodStat != null && periodStat.SupportConceptApprovalPeriod != null && periodStat.SupportConceptApprovalPeriod.ServiceCategory != null))
{
var dc = new ServiceRecordValidationResultDC
{
ResultType = ServiceRecordValidationResult.ApprovedFLSOverspending,
ApprovedHours = Math.Round(totalFLSApproved, 2, MidpointRounding.AwayFromZero),
HoursNew = Math.Round(totalFLSNew, 2, MidpointRounding.AwayFromZero)
};
dc.CustomerName = reldc.SupportConcept.CustomerFullName;
result.Add(dc);
}
}
}
// 3. OutsideOfSupportConcept
if (reldc.StartDate.HasValue && reldc.EndDate.HasValue)
if (!start.InBetween(reldc.StartDate.Value, reldc.EndDate.Value, true))
{
var dc = new ServiceRecordValidationResultDC
{
ResultType = ServiceRecordValidationResult.OutsideOfSupportConcept,
StartDate = reldc.StartDate.Value,
EndDate = reldc.EndDate.Value
};
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())
{
//7. Prüfe ob Datum in Zukunft liegt
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 });
}
}
return result;
}
public virtual bool CheckZukunft()
{
return true;
}
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();
}
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.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;
}
private SupportConceptPeriodStatisticsDC GetStatisticsForServiceRecord(SupportConceptStatisticsDC statistics, ServiceRecordDC newServiceRecord)
{
if (statistics != null && statistics.PeriodStatistics.Count > 0)
{
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)
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)
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.\nMöchten Sie ihn trotzdem löschen?"
});
}
return result;
}
}
}