diff --git a/ReportImp/BeWoAuxilioRitter/Validation/ServiceRecordValidator.cs b/ReportImp/BeWoAuxilioRitter/Validation/ServiceRecordValidator.cs index eb8f06333..5d22856ea 100644 --- a/ReportImp/BeWoAuxilioRitter/Validation/ServiceRecordValidator.cs +++ b/ReportImp/BeWoAuxilioRitter/Validation/ServiceRecordValidator.cs @@ -4,18 +4,25 @@ using System.Linq; using BeWo.Data; using BeWo.Data.Access; using BeWo.Data.Entities; +using BeWo.Data.Security; +using BeWo.Service.Configuration; using BeWo.Service.DCEntityMapper; using BeWo.Service.Plugins; using BS.Shared; using BS.Shared.Core; using BS.Shared.DataContracts; using BS.Shared.Extensions; +using SecurityUtils = BeWo.Service.Security.SecurityUtils; namespace BeWoAuxilioRitter.Validation { + public class CustomServiceRecordValidator : ServiceRecordValidator { - public override string[] GetIgnoreCategoriesForOverlappingCheck() + private IList _EmployeeRecords = null; + private AppSettings _AppSettings = AppSettings.CreateSettings(); + + public override string[] GetIgnoreCategoriesForOverlappingCheck() { return new[] { "Arbeitszeit" }; } @@ -25,31 +32,414 @@ namespace BeWoAuxilioRitter.Validation return false; } - public override List ValidateServiceRecord(ServiceRecordDC newServiceRecord, SupportConceptStatisticsDC statistics, - int maxDaysEditServiceRecordsAllowed, IList employeeOids, IList cb2scOids) + + // TODO auch bei Arbeitszeit auf Frist prüfen ("// 5. Frist") + public override List ValidateServiceRecord(ServiceRecordDC newServiceRecord, SupportConceptStatisticsDC statisticsXX, int maxDaysEditServiceRecordsAllowed, IList employeeOids, IList cb2scOids) { - var list = base.ValidateServiceRecord(newServiceRecord, statistics, maxDaysEditServiceRecordsAllowed, employeeOids, cb2scOids); + #region Standard + var result = new List(); - // Warnung bei Fehlkontakt, wenn bereits eine abrechenbare Leistung an dem Tag gebucht wurde - if (newServiceRecord.Start.HasValue && newServiceRecord.SupportConcept != null && newServiceRecord.SupportConcept.CostBearer.Contains("LVR") && newServiceRecord.ServiceDescription.Name.ToLower().Contains("fehlkontakt")) + if (!newServiceRecord.Start.HasValue) + { + return result; + } + + _EmployeeRecords = null; + _AppSettings = AppSettings.CreateSettings(); + + 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." + }); + } + + /* + * Meldung anzeigen bei: + * 1. Neuem ServiceRecord + * 2. Ändern der Zeitangaben eines bestehenden ServiceRecord + */ + + if (newServiceRecord.ServiceDescription.Category.IsBillable) + { + var startDate = newServiceRecord.Start.Value; + var endDate = newServiceRecord.End.Value; + + var areTimesEqual = true; + var isNew = newServiceRecord.ServiceRecordOid is null; + + if (newServiceRecord.ServiceRecordOid.HasValue) + { + var originalServiceRecord = DAOFactory.GenericDAO.LoadByID(newServiceRecord.ServiceRecordOid.Value); + + var isStartUnchanged = Equals(originalServiceRecord.Start, newServiceRecord.Start); + var isEndUnchanged = Equals(originalServiceRecord.End, newServiceRecord.End); + + areTimesEqual = isStartUnchanged && isEndUnchanged; + } + + var klientenoid = newServiceRecord.SupportConcept?.Customer?.CustomerOid; + var mitarbeiteroid = newServiceRecord.Employee?.EmployeeOid; + + if (klientenoid is object && mitarbeiteroid is object) + { + var hasMonatsunterschrift = DAOFactory.SearchDAO.HasConfirmationReceiptSignatureByDateAndEmployeeAndCustomer(klientenoid.Value, mitarbeiteroid.Value, startDate, endDate, newServiceRecord.CostBearer2SupportConceptOid.Value, out var existingSignatures); + + if (hasMonatsunterschrift && (isNew || false == areTimesEqual)) + { + var customerName = newServiceRecord.SupportConcept.CustomerName; + + var customerText = customerName is object ? $"Klient {customerName}" : "diesem Hilfeplan"; + + result.Add(new ServiceRecordValidationResultDC + { + ResultType = ServiceRecordValidationResult.SignatureExists, + Allow = true, + CustomerName = newServiceRecord.SupportConcept.CustomerName, + EmployeeName = newServiceRecord.Employee.ToString(), + Message = $"Für den ausgewählten Zeitraum existiert bereits eine Monatsunterschrift bei {customerText}, die mit dem Anlegen dieses Eintrags verfällt und neu geleistet werden muss." + }); + } + } + } + + if (newServiceRecord.ServiceRecordOid.HasValue && newServiceRecord.ServiceDescription.Category.IsBillable) + { + 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, + CustomerName = newServiceRecord.SupportConcept?.CustomerName, + EmployeeName = newServiceRecord.Employee?.ToString() + }); + } + } + + 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); + + } + } + } + } + } +#endregion + try { - // Alle Einträge des Tages holen, für den dokumentiert wird - var span = new DateTimeSpan() { StartDate = new DateTime(newServiceRecord.Start.Value.Year, newServiceRecord.Start.Value.Month, newServiceRecord.Start.Value.Day), EndDate = newServiceRecord.Start.Value.Date.AddDays(1) }; - var eintraegeAmTag = DAOFactory.SearchDAO.FindServiceRecordsInSpan(newServiceRecord.CostBearer2SupportConceptOid.Value, span); + // 5. Frist (ANPASSUNG: immer auch die Arbeittszeit prüfen + + if (_AppSettings.AnzTageZeiterfassErfolgt > 0) + { + var dt = new DateTime(start.Year, start.Month, start.Day); + dt = dt.AddDays(_AppSettings.AnzTageZeiterfassErfolgt + 1); + if (DateTime.Now >= dt) + result.Add(new ServiceRecordValidationResultDC { ResultType = ServiceRecordValidationResult.FristAbgelaufen }); + } + + } + catch (Exception) + { + result.Add(new ServiceRecordValidationResultDC { ResultType = ServiceRecordValidationResult.Error }); + } + + #region Standard + 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 der 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 Abwesenheit Mitarbeiter + var resultMa = CheckAbwesenheitMitarbeiter(newServiceRecord, reldc, rd, isGroupRecord, result); + + if (resultMa != null) + { + result.Add(resultMa); + } + + // 11. 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); + } + + #endregion + + // Warnung bei Fehlkontakt, wenn bereits eine abrechenbare Leistung an dem Tag gebucht wurde + if (newServiceRecord.Start.HasValue && newServiceRecord.SupportConcept != null && newServiceRecord.SupportConcept.CostBearer.Contains("LVR") && newServiceRecord.ServiceDescription.Name.ToLower().Contains("fehlkontakt")) + { + // Alle Einträge des Tages holen, für den dokumentiert wird + var span = new DateTimeSpan() { StartDate = new DateTime(newServiceRecord.Start.Value.Year, newServiceRecord.Start.Value.Month, newServiceRecord.Start.Value.Day), EndDate = newServiceRecord.Start.Value.Date.AddDays(1) }; + var eintraegeAmTag = DAOFactory.SearchDAO.FindServiceRecordsInSpan(newServiceRecord.CostBearer2SupportConceptOid.Value, span); if (eintraegeAmTag != null && eintraegeAmTag.Count() > 0) - { - list.Add(new ServiceRecordValidationResultDC + { + result.Add(new ServiceRecordValidationResultDC { ResultType = ServiceRecordValidationResult.Custom, Allow = true, Message = "Achtung, bitte beachten, dass bereits eine andere aberechenbare Leistung an diesem Tag im Hilfeplan erbracht wurde und ein Fehlkontakt daher nicht zulässig ist." + "\nWollen Sie dennoch speichern?" }); - } - } + } + } - else if (newServiceRecord.Start.HasValue && newServiceRecord.SupportConcept != null && newServiceRecord.SupportConcept.CostBearer.Contains("LVR") + else if (newServiceRecord.Start.HasValue && newServiceRecord.SupportConcept != null && newServiceRecord.SupportConcept.CostBearer.Contains("LVR") && newServiceRecord.ServiceDescription.Category.IsBillable && !newServiceRecord.ServiceDescription.Name.ToLower().Contains("fehlkontakt")) { // Alle Einträge des Tages holen, für den dokumentiert wird @@ -59,7 +449,7 @@ namespace BeWoAuxilioRitter.Validation if (eintraegeAmTag != null && eintraegeAmTag.Any(s => s.ServiceDescription.ServiceCategory.IsBillable && s.ServiceDescription.Name.ToLower().Contains("fehlkontakt"))) { - list.Add(new ServiceRecordValidationResultDC + result.Add(new ServiceRecordValidationResultDC { ResultType = ServiceRecordValidationResult.Custom, Allow = true, @@ -70,13 +460,23 @@ namespace BeWoAuxilioRitter.Validation } if (newServiceRecord.Start.HasValue && newServiceRecord.Start.Value.Date > DateTime.Now) { - list.Add(new ServiceRecordValidationResultDC + result.Add(new ServiceRecordValidationResultDC { ResultType = ServiceRecordValidationResult.Custom, Message = "Das gewählte Datum darf nicht in der Zukunft liegen." }); } - return list; + + return result; + } + // Muss hier mit rein, da es sonst eine NPE gibt + public override String[] GetIgnoreServiceDescriptionsForOverlappingCheck() + { + if (_AppSettings.ShowWorktime && _AppSettings.EnableArbeitszeiterfassung) + { + return new[] { "Arbeitszeit", "Arbeitszeiterfassung" }; + } + return null; } } } \ No newline at end of file