- Start Mitarbeiterstundenkonto - Anzahl Tage in der die Arbeitszeiterfassung erfolgen muss
1419 lines
60 KiB
C#
1419 lines
60 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;
|
|
using BeWo.Data.Security;
|
|
using BeWo.Service.Security;
|
|
|
|
using SecurityUtils = BeWo.Service.Security.SecurityUtils;
|
|
|
|
namespace BeWo.Service.Plugins
|
|
{
|
|
public class ServiceRecordValidator : AbstractIDSpecificDefaultClass<ServiceRecordValidator>
|
|
{
|
|
private IList<ServiceRecord> _EmployeeRecords = null;
|
|
private AppSettings _AppSettings = null;
|
|
|
|
|
|
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;
|
|
_AppSettings = AppSettings.CreateSettings();
|
|
|
|
foreach (ServiceRecordDC groupRecord in groupServiceRecords)
|
|
{
|
|
DateTime start = groupRecord.Start.Value;
|
|
bool noTime = start.Hour == 0 && start.Minute == 0 && start.Second == 1; //Keine Zeit angegeben
|
|
|
|
// 0. Monatsunterschrift ist vorhanden und die Leistungskategorie ist abrechenbar
|
|
if(groupRecord.ServiceDescription.Category.IsBillable)
|
|
{
|
|
var startDate = groupRecord.Start.Value;
|
|
var endDate = groupRecord.End.Value;
|
|
|
|
var klientenoid = groupRecord.SupportConcept?.Customer?.CustomerOid;
|
|
var mitarbeiteroid = groupRecord.Employee?.EmployeeOid;
|
|
|
|
if(klientenoid is object && mitarbeiteroid is object)
|
|
{
|
|
var hasMonatsunterschrift = DAOFactory.SearchDAO.HasConfirmationReceiptSignatureByDateAndEmployeeAndCustomer(klientenoid.Value, mitarbeiteroid.Value, startDate, endDate, groupRecord.CostBearer2SupportConceptOid.Value, out var existingSignatures);
|
|
|
|
if(hasMonatsunterschrift)
|
|
{
|
|
result.AddOrUpdateValueInDictionary(groupRecord, ServiceRecordValidationResult.SignatureExists);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
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
|
|
|
|
if (_AppSettings.AnzTageZeiterfassErfolgt > 0)
|
|
{
|
|
var dt = new DateTime(date.Value.Year, date.Value.Month, 1);
|
|
dt = dt.AddDays(_AppSettings.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;
|
|
}
|
|
|
|
_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);
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
try
|
|
{
|
|
// 5. Frist
|
|
|
|
|
|
if (!cb2ScOid.HasValue && _AppSettings.ShowWorktime && _AppSettings.EnableArbeitszeiterfassung && IstArbeitszeit(newServiceRecord.ServiceDescription.Category.Name, newServiceRecord.ServiceDescription.Name))
|
|
{
|
|
CheckArbeitszeitBedingungen(newServiceRecord, start, duration, result);
|
|
}
|
|
else 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 });
|
|
}
|
|
|
|
|
|
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);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
public virtual ServiceRecordValidationResultDC CheckAbwesenheitMitarbeiter(ServiceRecordDC newServiceRecord, SupportConceptCostBearerRelDC reldc, decimal rd, bool isGroupRecord, List<ServiceRecordValidationResultDC> result)
|
|
{
|
|
var e = DAOFactory.GenericDAO.LoadByID<Employee>(newServiceRecord.Employee.EmployeeOid);
|
|
long? userOid = SecurityUtils.GetLoggedInUser().Oid;
|
|
|
|
if (e.AbsenceTimes != null && e.AbsenceTimes.Count > 0)
|
|
{
|
|
if (e.AbsenceTimes.Any(a => a.AbsenceSpan.StartDate <= newServiceRecord.End.Value && (!a.End.HasValue || a.End.HasValue && (a.End >= newServiceRecord.End.Value)
|
|
|| a.End.Value.Date == newServiceRecord.End.Value.Date)))
|
|
{
|
|
foreach (var at in e.AbsenceTimes)
|
|
{
|
|
if (
|
|
at.Start <= newServiceRecord.Start.Value && (!at.End.HasValue || at.End.Value >= newServiceRecord.Start.Value) // normalerweise
|
|
|| (at.Start.Value.Date == newServiceRecord.Start.Value.Date && (!at.End.HasValue || at.End.Value >= newServiceRecord.Start.Value) && newServiceRecord.Start >= at.Start) // bei weniger als ganztägigen Abwesenheiten, kann es Überschneidung geben
|
|
|| (at.End.HasValue && at.End.Value.Date == newServiceRecord.End.Value.Date && at.End.Value >= newServiceRecord.End.Value && newServiceRecord.Start <= at.Start) // bei weniger als ganztägigen Abwesenheiten, kann es Überschneidung geben
|
|
)
|
|
{
|
|
if (SecurityUtils.GetLoggedInUser().Oid.HasValue && newServiceRecord.Employee.EmployeeOid == SecurityUtils.GetLoggedInUser().Oid.Value)
|
|
{
|
|
String message = String.Format("Achtung bei Ihnen ist zu diesem Termin eine Abwesenheit eingetragen.\nMöchten Sie trotzdem speichern?");
|
|
return new ServiceRecordValidationResultDC
|
|
{
|
|
ResultType = ServiceRecordValidationResult.Custom,
|
|
Message = message,
|
|
Allow = true
|
|
};
|
|
}
|
|
else
|
|
{
|
|
String message = String.Format("{0} ist zu diesem Termin abwesend.\nMöchten Sie trotzdem speichern?", e.Person.FirstNameLastName);
|
|
return new ServiceRecordValidationResultDC
|
|
{
|
|
ResultType = ServiceRecordValidationResult.Custom,
|
|
Message = message,
|
|
Allow = true
|
|
};
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private void CheckArbeitszeitBedingungen(ServiceRecordDC newServiceRecord, DateTime start, decimal duration, List<ServiceRecordValidationResultDC> result)
|
|
{
|
|
if (!IstArbeitszeit(newServiceRecord.ServiceDescription.Category.Name, newServiceRecord.ServiceDescription.Name))
|
|
{
|
|
return;
|
|
}
|
|
|
|
//Prüfe Frist
|
|
if (_AppSettings.AnzTageArbeitszeiterfassungErfolgt > 0)
|
|
{
|
|
var dt = new DateTime(start.Year, start.Month, start.Day);
|
|
dt = dt.AddDays(_AppSettings.AnzTageArbeitszeiterfassungErfolgt + 1);
|
|
if (DateTime.Now >= dt)
|
|
{
|
|
String msg = $"Die Arbeitszeiterfassung muss innerhalb von {_AppSettings.AnzTageArbeitszeiterfassungErfolgt} Tagen erfolgen.";
|
|
if (_AppSettings.AnzTageZeiterfassErfolgt == 1)
|
|
{
|
|
msg = $"Die Arbeitszeiterfassung muss innerhalb von einem Tag erfolgen.";
|
|
}
|
|
|
|
result.Add(new ServiceRecordValidationResultDC
|
|
{
|
|
ResultType = ServiceRecordValidationResult.Custom,
|
|
Message = msg,
|
|
Allow = false
|
|
|
|
});
|
|
}
|
|
|
|
}
|
|
|
|
|
|
var empRecords = GetEmployeeServiceRecordsWithTimeForLastDay(newServiceRecord, start, newServiceRecord.Employee.EmployeeOid);
|
|
if (_AppSettings.EnableRestTimeWarning)
|
|
{
|
|
DateTime? letztesEnde = null;
|
|
|
|
foreach (var item in empRecords)
|
|
{
|
|
if (IstArbeitszeit(item.ServiceDescription.ServiceCategory.Name, item.ServiceDescription.Name) && item.End < newServiceRecord.Start)
|
|
{
|
|
if (item.End.HasValue)
|
|
{
|
|
if (letztesEnde == null || item.End > letztesEnde)
|
|
{
|
|
letztesEnde = item.End;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (letztesEnde.HasValue)
|
|
{
|
|
//das letzte Ende muss mindestens 11 Stunden zurück liegen (gesetzliche Ruhezeit)
|
|
//Ist etwas schwierig zu prüfen, ob zwischen dem letzten Ende und dem neuen Start eine Ruhezeit oder nur eine Pause eingehalten wurde.
|
|
//Daher prüfe ich noch auf autom. Pausenabzug.
|
|
var diff = newServiceRecord.Start.Value.Subtract(letztesEnde.Value);
|
|
var stundenPause = diff.TotalMinutes / 60;
|
|
if (stundenPause < 11 && (_AppSettings.EnableAutomaticPauses || (stundenPause > 1 && newServiceRecord.Start.Value.Date == letztesEnde.Value.Date))) // Entweder auto. Pausenabzug und dann muss die Ruhezeit eingehalten werden oder max. 1 Stunde Pause am gleichen Tag)
|
|
{
|
|
String message = String.Format("Die eingegebene Startzeit muss mindestens 11 Stunden nach der letzten Beendigung liegen.\nMöchten Sie trotzdem speichern?");
|
|
result.Add(new ServiceRecordValidationResultDC
|
|
{
|
|
ResultType = ServiceRecordValidationResult.Custom,
|
|
Message = message,
|
|
Allow = true
|
|
|
|
});
|
|
}
|
|
}
|
|
}
|
|
if (_AppSettings.MaxWorkTimeHoursBeforeWarning > 0)
|
|
{
|
|
if (duration > _AppSettings.MaxWorkTimeHoursBeforeWarning * 60 && IstArbeitszeit(newServiceRecord.ServiceDescription.Category.Name, newServiceRecord.ServiceDescription.Name))
|
|
{
|
|
String message = String.Format("Die eingegebene Dauer der Arbeitszeit überschreitet die maximal zulässige Zeit bevor eine Pause eingehalten werden muss.\nMöchten Sie trotzdem speichern?");
|
|
result.Add(new ServiceRecordValidationResultDC
|
|
{
|
|
ResultType = ServiceRecordValidationResult.Custom,
|
|
Message = message,
|
|
Allow = true
|
|
|
|
});
|
|
}
|
|
}
|
|
|
|
// Prüfe Abwesenheit Mitarbeiter
|
|
var resultMa = CheckAbwesenheitMitarbeiter(newServiceRecord, null, 0, false, result);
|
|
if (resultMa != null)
|
|
{
|
|
result.Add(resultMa);
|
|
}
|
|
}
|
|
|
|
public virtual bool IstArbeitszeit(String serviceCategory, String serviceDecription)
|
|
{
|
|
if (serviceCategory.StartsWith("Arbeitszeit") || serviceDecription.StartsWith("Arbeitszeit")) //Arbeitszeit und Arbeitszeiterfassung etc.
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
public virtual ServiceRecordValidationResultDC CheckAbwesenheiten(ServiceRecordDC newServiceRecord, IList<ServiceRecord> 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<Calculations>(reldc.CostBearer.CostBearerID) ??
|
|
Calculations.GetInstance(reldc.CostBearer.CostBearerID);
|
|
|
|
Dictionary<DateTimeSpan, decimal> 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<CostBearer2SupportConcept>(newServiceRecord.CostBearer2SupportConceptOid.Value);
|
|
//var customer = c2s.SupportConcept.Customer;
|
|
//var absenceTimes = MapperFactory.AbsenceTimeDC_AbsenceTime.MapToNewDCs(customer.AbsenceTimes);
|
|
|
|
Dictionary<DateTimeSpan, decimal> span2Geleistet = new Dictionary<DateTimeSpan, decimal>();
|
|
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<ServiceRecord>(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<ServiceRecordStatisticFactory>();
|
|
SupportConceptStatisticsDC stats = null;
|
|
|
|
if (factory == null)
|
|
{
|
|
factory = new ServiceRecordStatisticFactory();
|
|
}
|
|
|
|
Calculations calc = PluginLoader.FindClass<Calculations>(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<ServiceRecord> 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 empRecords = GetEmployeeServiceRecordsWithTimeForLastDay(newServiceRecord, start, employeeOid);
|
|
|
|
|
|
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 IList<ServiceRecord> GetEmployeeServiceRecordsWithTimeForLastDay(ServiceRecordDC newServiceRecord, DateTime start, long employeeOid)
|
|
{
|
|
if (_EmployeeRecords == null)
|
|
{
|
|
//Liefere alle ServiceRecords (außer dem zu ändernden) die eine Uhrzeit haben)
|
|
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();
|
|
}
|
|
if (newServiceRecord.GroupOid.HasValue)
|
|
{
|
|
empRecords = empRecords.Where(r => !r.GroupOid.HasValue || r.GroupOid != newServiceRecord.GroupOid).ToList();
|
|
}
|
|
_EmployeeRecords = empRecords.Where(r => r.Start.HasValue && r.Start.Value.Second == 0).ToList();
|
|
}
|
|
return _EmployeeRecords;
|
|
}
|
|
|
|
public virtual String[] GetIgnoreCategoriesForOverlappingCheck()
|
|
{
|
|
if (_AppSettings.ShowWorktime && _AppSettings.EnableArbeitszeiterfassung)
|
|
{
|
|
return new[] { "Arbeitszeit", "Arbeitszeiterfassung" };
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public virtual String[] GetIgnoreServiceDescriptionsForOverlappingCheck()
|
|
{
|
|
if (_AppSettings.ShowWorktime && _AppSettings.EnableArbeitszeiterfassung)
|
|
{
|
|
return new[] { "Arbeitszeit", "Arbeitszeiterfassung" };
|
|
}
|
|
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<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.\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<ServiceRecordDC> {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<ServiceRecord>(serviceRecords.Where(sr => sr.ServiceRecordOid.HasValue).Select(sr => sr.ServiceRecordOid.Value));
|
|
|
|
var oldVsNew = new Dictionary<ServiceRecord, ServiceRecordDC>();
|
|
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;
|
|
}
|
|
}
|
|
} |