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 { public virtual Dictionary> ValidateGroupServiceRecordEntry(ServiceRecordDC pServiceRecord, List groupServiceRecords, List employees, DateTime? date, int maxDaysEditServiceRecordsAllowed, decimal flsTotalRounded, out Dictionary overlappingRecords, out Dictionary overlappingEmployees) { var result = new Dictionary>(); overlappingRecords = new Dictionary(); overlappingEmployees = new Dictionary(); 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 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 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.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.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 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.EmployeeOverlapping }); overlappingEmployees.Add(groupRecord.ServiceRecordOid.Value, string.Format("{1}, {0}", employee.FirstName, employee.LastName)); } } } catch (Exception) { result.AddOrUpdateValueInDictionary(groupRecord, new List { ServiceRecordValidationResult.Error }); } } // 4. ApprovedFLSOverspending try { SupportConceptCostBearerRelDC reldc = MapperFactory.SupportConceptCostBearerRelDC_CostBearer2SupportConcept.MapToNewDC(DAOFactory.GenericDAO.GetByID(groupRecord.CostBearer2SupportConceptOid.Value)); Calculations calc = PluginLoader.FindClass(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.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.OutsideOfSupportConcept }); } catch (Exception) { result.AddOrUpdateValueInDictionary(groupRecord, new List { ServiceRecordValidationResult.Error }); } // 6. SettlementInvoiceAlreadyExisting try { IList allSettlementInvoices = DAOFactory.GenericDAO.GetAll(); 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.SettlementInvoiceAlreadyExisting }); } catch (Exception) { result.AddOrUpdateValueInDictionary(groupRecord, new List { 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.FristAbgelaufen }); } } catch (Exception) { result.AddOrUpdateValueInDictionary(groupRecord, new List { 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.OutOfEditLimit }); } catch (Exception) { result.AddOrUpdateValueInDictionary(groupRecord, new List { ServiceRecordValidationResult.Error }); } } return result; } public virtual List ValidateServiceRecord(ServiceRecordDC newServiceRecord, SupportConceptStatisticsDC statistics, int maxDaysEditServiceRecordsAllowed, IList employeeOids, IList cb2scOids) { var result = new List(); 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; decimal roundedDuration = duration; if (newServiceRecord.ServiceDescription.Category != null && newServiceRecord.ServiceDescription.Category.IsBillable && newServiceRecord.CostBearer != null) { Calculations calc = Calculations.GetInstance(newServiceRecord.CostBearer.CostBearerID); roundedDuration = calc.GetRoundedDuration(newServiceRecord.CostBearer, roundedDuration); } if (isGroupRecord) { var groupcalc = PluginLoader.FindClass(); 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 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) { // 4. ApprovedFLSOverspending try { SupportConceptCostBearerRelDC reldc = MapperFactory.SupportConceptCostBearerRelDC_CostBearer2SupportConcept.MapToNewDC( DAOFactory.GenericDAO.GetByID(cb2ScOid.Value)); Calculations calc = PluginLoader.FindClass(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(); 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) }; 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 allSettlementInvoices = DAOFactory.SearchDAO.GetSettlementInvoiceByCostBearer2SupportConceptOid(cb2ScOid.Value); if (allSettlementInvoices.Count > 0) { result.Add(new ServiceRecordValidationResultDC { ResultType = ServiceRecordValidationResult.SettlementInvoiceAlreadyExisting }); } } catch (Exception) { result.Add(new ServiceRecordValidationResultDC { ResultType = ServiceRecordValidationResult.Error }); } //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." }); } if (maxDaysEditServiceRecordsAllowed <= 0) return result; // 8. OutOfEditLimit try { 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 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 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(); 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; } 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; } } }