using System; using System.Collections.Generic; using System.Diagnostics; 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 { 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; } 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(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(); if (srdc != null) { srdc.CalculateRoundedDuration(newServiceRecord); roundedDuration = newServiceRecord.RoundedDuration; } decimal? grd = newServiceRecord.GroupRoundedDuration; if (isGroupRecord) { var groupcalc = PluginLoader.FindClass(); 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 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(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 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 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(reldc.CostBearer.CostBearerID) ?? Calculations.GetInstance(reldc.CostBearer.CostBearerID); Dictionary 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(newServiceRecord.CostBearer2SupportConceptOid.Value); //var customer = c2s.SupportConcept.Customer; //var absenceTimes = MapperFactory.AbsenceTimeDC_AbsenceTime.MapToNewDCs(customer.AbsenceTimes); Dictionary span2Geleistet = new Dictionary(); 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(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(); SupportConceptStatisticsDC stats = null; if (factory == null) { factory = new ServiceRecordStatisticFactory(); } Calculations calc = PluginLoader.FindClass(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 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 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 ValidateServiceRecordDeletion(ServiceRecordDC serviceRecord, long employeeOid) { var result = new List(); // 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 {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(serviceRecords.Where(sr => sr.ServiceRecordOid.HasValue).Select(sr => sr.ServiceRecordOid.Value)); var oldVsNew = new Dictionary(); 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; } } }