MH: BeWoAuxilioRitter Erweiterung ServiceRecordValidator

This commit is contained in:
2026-03-30 10:18:23 +02:00
parent bb4150a16f
commit 7d793b04f8

View File

@@ -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<ServiceRecord> _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<ServiceRecordValidationResultDC> ValidateServiceRecord(ServiceRecordDC newServiceRecord, SupportConceptStatisticsDC statistics,
int maxDaysEditServiceRecordsAllowed, IList<long> employeeOids, IList<long> cb2scOids)
// TODO auch bei Arbeitszeit auf Frist prüfen ("// 5. Frist")
public override List<ServiceRecordValidationResultDC> ValidateServiceRecord(ServiceRecordDC newServiceRecord, SupportConceptStatisticsDC statisticsXX, int maxDaysEditServiceRecordsAllowed, IList<long> employeeOids, IList<long> cb2scOids)
{
var list = base.ValidateServiceRecord(newServiceRecord, statistics, maxDaysEditServiceRecordsAllowed, employeeOids, cb2scOids);
#region Standard
var result = new List<ServiceRecordValidationResultDC>();
// 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<ServiceRecord>(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<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,
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<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);
}
}
}
}
}
#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<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 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;
}
}
}