From 1f853494adae091d74a1e4f0e6bc92e64f9549b0 Mon Sep 17 00:00:00 2001 From: Lyndon Date: Thu, 16 Jul 2026 16:01:13 +0200 Subject: [PATCH] Kalender: Tooltip erweitert und Bugs behoben. --- .../DevExpressSchedulerController.cs | 40 +++++- .../Models/DevExpressSchedulerModel.cs | 2 + .../OwnSoftSchedulerLocalizer.cs | 10 ++ .../CustomAppointmentToolTipPartial.cshtml | 99 +++++++++++++-- .../DevExpressScheduler.cshtml | 119 ++++++++++++++---- .../SchedulerPagePartial.cshtml | 67 ++++++---- Data/Access/SearchDAO.cs | 1 - Host/Web.config | 1 + Service/ServiceContracts/IResourceService.cs | 11 +- .../ResourceServiceImp.cs | 14 +++ 10 files changed, 294 insertions(+), 70 deletions(-) diff --git a/BeWoPlanerMobil/Controllers/DevExpressSchedulerController.cs b/BeWoPlanerMobil/Controllers/DevExpressSchedulerController.cs index 10b7aca38..30117d119 100644 --- a/BeWoPlanerMobil/Controllers/DevExpressSchedulerController.cs +++ b/BeWoPlanerMobil/Controllers/DevExpressSchedulerController.cs @@ -193,6 +193,15 @@ namespace BeWoPlanerMobil.Controllers var appointments = allAppointments; + var changedOrDeletedOccurrences = KalenderService.FindAppointmentsByRecurrenceId(appointments.Select(apt => apt.RecurrenceId).ToList(), true); + + Model.RecurrenceIdsWithOccurrences = new List(); + + foreach(var occ in changedOrDeletedOccurrences) + { + Model.RecurrenceIdsWithOccurrences.AddIfNotIn(occ.RecurrenceIdReference); + } + foreach(var appointment in appointments) { if(false == appointment.IsPrivate || appointment.Originator.EmployeeOid.Equals(employeeOid) || appointment.EmployeeList.Any(e => e.Employee.EmployeeOid.Equals(employeeOid))) @@ -489,9 +498,8 @@ namespace BeWoPlanerMobil.Controllers foreach(var appointment in appointmentsToInsert) { - if(appointment.Type != (int) AppointmentType.DeletedOccurrence) + if(appointment.Type != (int) AppointmentType.DeletedOccurrence && appointment.Type != (int) AppointmentType.ChangedOccurrence) { - // Beim Löschen eines Serientermins wird hier ein Insert für die gelöschte Ausnahme erstellt appointment.CustomerList = AppointmentModel.SelectedCustomers; appointment.ResourceList = AppointmentModel.SelectedResources; appointment.EmployeeList = new List(); @@ -580,14 +588,38 @@ namespace BeWoPlanerMobil.Controllers case "Reload": ReloadAppointments(); break; - case "debug_DummyAppointments": - CreateDummyAppointments(); + case "DeleteSeries": + DeleteAppointment(((List) Model.Appointments).FirstOrDefault(appointment => appointment.SchedulerAppointmentOid == long.Parse(apptID))); + break; + case "DeleteApt": + DeleteAppointment(((List) Model.Appointments).FirstOrDefault(appointment => appointment.SchedulerAppointmentOid == long.Parse(apptID))); break; + case "ResetSeries": + var oid2Versions = KalenderService.LoadOccurrencesByRecurrenceId(apptID).Where(appointment => appointment.RecurrenceIndexReference != null).ToDictionary(x => x.SchedulerAppointmentOid.Value, y => y.NewSchedulerAppointmentVersion.Value); + + KalenderService.DeleteSchedulerAppointments(oid2Versions); + + ReloadAppointments(); + break; } return PartialView("SchedulerPagePartial", Model); } + private void DeleteAppointment(SchedulerAppointmentDC appointment2Delete) + { + if(appointment2Delete is null || appointment2Delete.SchedulerAppointmentOid is null || appointment2Delete.NewSchedulerAppointmentVersion is null) + { + return; + } + + var oid2Version = new Dictionary { { appointment2Delete.SchedulerAppointmentOid.Value, appointment2Delete.NewSchedulerAppointmentVersion.Value } }; + + KalenderService.DeleteSchedulerAppointments(oid2Version); + + ReloadAppointments(); + } + private void CreateDummyAppointments() { var today = DateTime.Today; diff --git a/BeWoPlanerMobil/Models/DevExpressSchedulerModel.cs b/BeWoPlanerMobil/Models/DevExpressSchedulerModel.cs index 5719fc229..71048fff1 100644 --- a/BeWoPlanerMobil/Models/DevExpressSchedulerModel.cs +++ b/BeWoPlanerMobil/Models/DevExpressSchedulerModel.cs @@ -25,6 +25,8 @@ namespace BeWoPlanerMobil.Models set => DevExpressSchedulerSessionModel.CurrentSchedulerViewType = value; } + public List RecurrenceIdsWithOccurrences { get; set; } + public DevExpressSchedulerSessionModel DevExpressSchedulerSessionModel => SessionModel as DevExpressSchedulerSessionModel; public List PossibleCustomers { get; set; } diff --git a/BeWoPlanerMobil/Util/SchedulerUtils/OwnSoftSchedulerLocalizer.cs b/BeWoPlanerMobil/Util/SchedulerUtils/OwnSoftSchedulerLocalizer.cs index 0eb37bd43..ee2d75ab5 100644 --- a/BeWoPlanerMobil/Util/SchedulerUtils/OwnSoftSchedulerLocalizer.cs +++ b/BeWoPlanerMobil/Util/SchedulerUtils/OwnSoftSchedulerLocalizer.cs @@ -28,6 +28,16 @@ namespace BeWoPlanerMobil.Util.SchedulerUtils return "Monat"; case SchedulerStringId.ViewShortDisplayName_Month: return "Monat"; + case SchedulerStringId.Caption_WeekOfMonthFirst: + return "Ersten"; + case SchedulerStringId.Caption_WeekOfMonthSecond: + return "Zweiten"; + case SchedulerStringId.Caption_WeekOfMonthThird: + return "Dritten"; + case SchedulerStringId.Caption_WeekOfMonthFourth: + return "Vierten"; + case SchedulerStringId.Caption_WeekOfMonthLast: + return "Letzten"; } return base.GetLocalizedString(id); diff --git a/BeWoPlanerMobil/Views/DevExpressScheduler/CustomAppointmentToolTipPartial.cshtml b/BeWoPlanerMobil/Views/DevExpressScheduler/CustomAppointmentToolTipPartial.cshtml index 0bf663494..2b02f9264 100644 --- a/BeWoPlanerMobil/Views/DevExpressScheduler/CustomAppointmentToolTipPartial.cshtml +++ b/BeWoPlanerMobil/Views/DevExpressScheduler/CustomAppointmentToolTipPartial.cshtml @@ -1,7 +1,7 @@  @@ -115,11 +171,32 @@ \ No newline at end of file diff --git a/BeWoPlanerMobil/Views/DevExpressScheduler/DevExpressScheduler.cshtml b/BeWoPlanerMobil/Views/DevExpressScheduler/DevExpressScheduler.cshtml index 02f1e3359..6f4e5a1b7 100644 --- a/BeWoPlanerMobil/Views/DevExpressScheduler/DevExpressScheduler.cshtml +++ b/BeWoPlanerMobil/Views/DevExpressScheduler/DevExpressScheduler.cshtml @@ -13,30 +13,22 @@ newAptBtn.empty(); newAptBtn.append("Neuer Termin"); } - - const observer = new MutationObserver((mutations, obs) => { - const el0 = document.getElementById("appointmentRecurrenceForm_AptRecCtl_Monthly_WmeWeekOfMnth_DDD_L_LBI0T0"); - const el1 = document.getElementById("appointmentRecurrenceForm_AptRecCtl_Monthly_WmeWeekOfMnth_DDD_L_LBI1T0"); - const el2 = document.getElementById("appointmentRecurrenceForm_AptRecCtl_Monthly_WmeWeekOfMnth_DDD_L_LBI2T0"); - const el3 = document.getElementById("appointmentRecurrenceForm_AptRecCtl_Monthly_WmeWeekOfMnth_DDD_L_LBI3T0"); - const el4 = document.getElementById("appointmentRecurrenceForm_AptRecCtl_Monthly_WmeWeekOfMnth_DDD_L_LBI4T0"); - - const dropdown = document.getElementById("appointmentRecurrenceForm_AptRecCtl_Monthly_WmeWeekOfMnth_I"); - - if(el0 && el1 && el2 && el3 && el4 && dropdown) { - $(el0).text("Ersten"); - $(el1).text("Zweiten"); - $(el2).text("Dritten"); - $(el3).text("Vierten"); - $(el4).text("Letzten"); - - obs.disconnect(); - } - }); - - observer.observe(document.body, {childList: true, subtree: true}); }); + function translateRecurrencePopup() { + const popupTitle = document.getElementById("scheduler_formBlock_RecurAptDeleteFrmContainer_PWH-1T"); + const msgSpan = document.getElementById("scheduler_formBlock_RecurAptDeleteFrmContainer_RecurAptDeleteFrmTemplateContainer_RecurrentAppointmentDeleteForm_lblConfirm"); + const checkboxTable = document.getElementById("scheduler_formBlock_RecurAptDeleteFrmContainer_RecurAptDeleteFrmTemplateContainer_RecurrentAppointmentDeleteForm_rbAction_RB0"); + const checkbox2Table = document.getElementById("scheduler_formBlock_RecurAptDeleteFrmContainer_RecurAptDeleteFrmTemplateContainer_RecurrentAppointmentDeleteForm_rbAction_RB1"); + const cancelBtnDiv = document.getElementById("scheduler_formBlock_RecurAptDeleteFrmContainer_RecurAptDeleteFrmTemplateContainer_RecurrentAppointmentDeleteForm_btnCancel_CD"); + + $(msgSpan).text("Möchten Sie diesen Termin der Serie oder die ganze Serie löschen?"); + popupTitle.textContent = "Löschen bestätigen"; + checkboxTable.querySelector("td label").textContent = "Die Serie"; + checkbox2Table.querySelector("td label").textContent = "Diesen Termin"; + cancelBtnDiv.querySelector("span").textContent = "Abbrechen"; + } + function UpdateSchedulerHeight() { scheduler.SetHeight(1); @@ -204,6 +196,52 @@ function onMenuItemClicked(s, e) { try { + if(e.itemName === "ResetRecurringApt") { + e.handled = true; + + const oid = scheduler.GetSelectedAppointmentIds()[0]; + const appointment = scheduler.GetAppointmentById(oid); + + const recurrenceId = appointment.RecurrenceId; + + if(recurrenceId !== undefined && recurrenceId !== null && recurrenceId.length > 0) { + showWarningMessageBox("Zurücksetzen der Serie bestätigen", "Sind Sie sicher, dass Sie die ausgewählte Terminserie zurücksetzen möchten?", () => { + scheduler.PerformCallback({ apptID: recurrenceId, actionId: "ResetSeries", args: null }, () => { hideSpinner(); }); + }, false); + } + } + + if(e.itemName === "DeleteApt") { + e.handled = true; + + const oid = scheduler.GetSelectedAppointmentIds()[0]; + const appointment = scheduler.GetAppointmentById(oid); + + if(appointment.appointmentType === "Occurrence") { + $("#apt-del-ok-btn").on("click", + function() { + const radioOptionId = $("input[name='apt-del-radio-group']:checked").attr("id"); + + if(radioOptionId === "apt-del-radio-1") { + const patternId = appointment.appointmentId.split("_")[0]; + + scheduler.PerformCallback({apptID: patternId, actionId: "DeleteSeries", args: null}, () => { hideSpinner(); }); + } else if(radioOptionId === "apt-del-radio-2") { + scheduler.DeleteAppointment(appointment); + } + + $("#apt-del-ok-btn").off("click"); + $("#recurrence-delete-popup").modal("hide"); + }); + + $("#recurrence-delete-popup").modal("show"); + } else { + showWarningMessageBox("Löschen bestätigen", "Sind Sie sicher, dass Sie den Termin löschen möchten?", () => { + scheduler.PerformCallback({apptID: oid, actionID: e.itemName}); + }, false); + } + } + if(e.itemName === "ToServiceRecord") { e.handled = true; scheduler.PerformCallback({ apptID: scheduler.GetSelectedAppointmentIds()[0], actionId: e.itemName }); @@ -231,11 +269,16 @@ function OnAppointmentMenuPopup(s, e) { const id = scheduler.GetSelectedAppointmentIds()[0]; const selectedAppointment = scheduler.GetAppointmentById(id); - + for(let i = 0; i < e.item.items.length; i++) { const currentItem = e.item.items[i]; switch(currentItem.name) { + case "ResetRecurringApt": + if(selectedAppointment.IsAbsenceTime || selectedAppointment.IsTask || selectedAppointment.HasOccurrencies === false) { + currentItem.SetVisible(false); + } + break; case "ToServiceRecord": if(id === undefined || selectedAppointment.IsAbsenceTime || selectedAppointment.IsTask) { currentItem.SetVisible(false); @@ -798,4 +841,32 @@ @Html.Partial("AptFltCustomersPartial", Model) -@Html.Partial("AptFltResourcesPartial", Model) \ No newline at end of file +@Html.Partial("AptFltResourcesPartial", Model) + + \ No newline at end of file diff --git a/BeWoPlanerMobil/Views/DevExpressScheduler/SchedulerPagePartial.cshtml b/BeWoPlanerMobil/Views/DevExpressScheduler/SchedulerPagePartial.cshtml index dbe6fbd62..dd5a65fd5 100644 --- a/BeWoPlanerMobil/Views/DevExpressScheduler/SchedulerPagePartial.cshtml +++ b/BeWoPlanerMobil/Views/DevExpressScheduler/SchedulerPagePartial.cshtml @@ -1,4 +1,5 @@ -@using BeWoPlanerMobil.Models +@using System.Diagnostics +@using BeWoPlanerMobil.Models @using BeWoPlanerMobil.Util.SchedulerUtils @using BeWoPlanerMobil.Views.DevExpressScheduler @using BS.Shared.DataContracts @@ -10,7 +11,7 @@ @functions { - //ToDo: So das EditForm verändern! + //ToDo: So das EditForm verändern: //https://docs.devexpress.com/AspNetMvc/119776/components/scheduler/get-started/how-to-customize-the-appointment-dialog-using-view-model-api-working-with-custom-fields?utm_source=SupportCenter&utm_medium=website&utm_campaign=docs-feedback&utm_content=T743814 static AppointmentRecurrenceFormSettings CreateAppointmentRecurrenceFormSettings(CustomAppointmentTemplateContainer container) @@ -45,13 +46,15 @@ "StatusSubMenu", "LabelSubMenu", "NewAllDayEvent", - "NewRecurringEvent" + "NewRecurringEvent", + "DeleteAppointment" }; if(menuId == SchedulerMenuItemId.AppointmentMenu) { + e.Menu.Items.Add(new MenuItem("Löschen", "DeleteApt")); e.Menu.Items.Add(new MenuItem("In die Zeiterfassung übertragen", "ToServiceRecord")); - + e.Menu.Items.Add(new MenuItem("Serie zurücksetzen", "ResetRecurringApt")); e.Menu.Items.Add(new MenuItem("Zusagen", "ConfirmApt")); e.Menu.Items.Add(new MenuItem("Mit Vorbehalt zusagen", "MaybeApt")); e.Menu.Items.Add(new MenuItem("Absagen", "CancelApt")); @@ -196,7 +199,7 @@ settings.OptionsToolTips.SetAppointmentToolTipTemplateContent(() => { Html.RenderPartial("CustomAppointmentToolTipPartial"); }); settings.OptionsToolTips.SetAppointmentMobileToolTipTemplateContent(() => { Html.RenderPartial("CustomAppointmentToolTipPartial"); }); settings.OptionsToolTips.SetAppointmentDragToolTipTemplateContent(() => { Html.RenderPartial("CustomAppointmentDragToolTip"); }); - + settings.OptionsCustomization.AllowInplaceEditor = UsedAppointmentType.None; settings.OptionsCustomization.AllowDisplayAppointmentFlyout = false; @@ -221,6 +224,14 @@ settings.InitClientAppointment = (send, evargs) => { + // TODO: TESTEN! Es scheint noch einen Bug zu geben! + var recurrenceId = evargs.Appointment.RecurrenceInfo?.Id?.ToString(); + var index = evargs.Appointment.RecurrenceIndex; + + evargs.Properties.Add("RecurrenceId", recurrenceId); + + evargs.Properties.Add("HasOccurrencies", recurrenceId is string ? Model.RecurrenceIdsWithOccurrences.Contains(recurrenceId) : false); + var isTask = evargs.Appointment.CustomFields["IsTask"] is true; var isAbsenceTime = evargs.Appointment.CustomFields["IsAbsenceTime"] is true; var canBeEdited = evargs.Appointment.CustomFields["CanBeEdited"] is true; @@ -253,40 +264,40 @@ foreach(var employee2Appointment in employeeList) { - if(employee2Appointment.SchedulerAppointmentOid is null || employee2Appointment.Employee2SchedulerAppointmentOid is null) - { - continue; - } + if(employee2Appointment.SchedulerAppointmentOid is null || employee2Appointment.Employee2SchedulerAppointmentOid is null) + { + continue; + } - var parcitipationInfoObject = - new ParticipationInfoObject( - employee2Appointment.Employee.DetailDescription, - employee2Appointment.Employee2SchedulerAppointmentOid.Value, - (int) employee2Appointment.ParticipationAnswer, - employee2Appointment.SchedulerAppointmentOid.Value); + var parcitipationInfoObject = + new ParticipationInfoObject( + employee2Appointment.Employee.DetailDescription, + employee2Appointment.Employee2SchedulerAppointmentOid.Value, + (int) employee2Appointment.ParticipationAnswer, + employee2Appointment.SchedulerAppointmentOid.Value); - participationInfos.Add(parcitipationInfoObject); + participationInfos.Add(parcitipationInfoObject); } foreach(var resource in resourceList) { - var resourceInfoObject = - new ResourceInfoObject( - oid, - resource.Name, - resource.Color - ); + var resourceInfoObject = + new ResourceInfoObject( + oid, + resource.Name, + resource.Color + ); resourceInfos.Add(resourceInfoObject); } foreach(var customer in customerList) { - var customerInfoObject = - new CustomerInfoObject( - oid, - customer.DetailDescription - ); + var customerInfoObject = + new CustomerInfoObject( + oid, + customer.DetailDescription + ); customerInfos.Add(customerInfoObject); } @@ -295,6 +306,7 @@ evargs.Properties.Add("ResourceInfos", resourceInfos); evargs.Properties.Add("CustomerInfos", customerInfos); + // originator ist bei einer Aufgabe null var canConfirm = (false == isTask || false == isAbsenceTime) && end > DateTime.Now && false == (originator?.EmployeeOid.Equals(Model.LoggedInUserOid) ?? false) && @@ -308,6 +320,7 @@ settings.Storage.Appointments.Assign(SchedulerHelper.DefaultAppointmentStorage); settings.Storage.EnableReminders = false; settings.OptionsBehavior.ClientTimeZoneId = "W. Europe Standard Time"; + settings.Width = Unit.Percentage(100); settings.Height = Unit.Percentage(100); diff --git a/Data/Access/SearchDAO.cs b/Data/Access/SearchDAO.cs index 0296182f0..118769c95 100644 --- a/Data/Access/SearchDAO.cs +++ b/Data/Access/SearchDAO.cs @@ -6815,7 +6815,6 @@ WHERE sc.Billable = 1 and sr.StartDate >= '{0:yyyy-MM-dd}' and sr.StartDate < '{ return criteria.List().ToList(); } - } } diff --git a/Host/Web.config b/Host/Web.config index c6cf2bdc0..d09e98b6e 100644 --- a/Host/Web.config +++ b/Host/Web.config @@ -162,6 +162,7 @@ --> + diff --git a/Service/ServiceContracts/IResourceService.cs b/Service/ServiceContracts/IResourceService.cs index 4c8a895d1..1d6cbeaf9 100644 --- a/Service/ServiceContracts/IResourceService.cs +++ b/Service/ServiceContracts/IResourceService.cs @@ -1,8 +1,9 @@ -using System; -using System.Collections.Generic; -using System.ServiceModel; +using BeWo.Data.Entities; using BS.Shared.Core; using BS.Shared.DataContracts; +using System; +using System.Collections.Generic; +using System.ServiceModel; namespace BeWo.Service.ServiceContracts { @@ -276,5 +277,9 @@ namespace BeWo.Service.ServiceContracts [FaultContract(typeof(BeWoFault))] [OperationContract] List LoadFilteredAppointmentsMitAufgabenForMoK(bool pHasRightToSeeAllEmployeeAppointments, long pEmployeeOid, DateTime pIntervalStart, DateTime pIntervalEnd, List pSelectedEmployees, List pSelectedCustomer, List pSelectedResources, bool pEmployeesOnly, bool pCustomersOnly, bool pResourcesOnly, bool pPrivateAppointmentsOnly, bool pOnlyMyAppointments, bool pShowTasks); + + [FaultContract(typeof(BeWoFault))] + [OperationContract] + List FindAppointmentsByRecurrenceId(List pRecurrenceIds, bool pExcludeRootAppointments = false); } } \ No newline at end of file diff --git a/Service/ServiceImplementations/ResourceServiceImp.cs b/Service/ServiceImplementations/ResourceServiceImp.cs index 56a41e601..d3a81025e 100644 --- a/Service/ServiceImplementations/ResourceServiceImp.cs +++ b/Service/ServiceImplementations/ResourceServiceImp.cs @@ -3146,5 +3146,19 @@ namespace BeWo.Service.ServiceImplementations throw Utils.CreateBeWoFaultException(e); } } + + public List FindAppointmentsByRecurrenceId(List recurrenceIds, bool excludeRootAppointments = false) + { + try + { + var occurrences = DAOFactory.SearchDAO.FindAppointmentsByRecurrenceId(recurrenceIds, excludeRootAppointments); + + return MapperFactory.SchedulerAppointmentDCSchedulerAppointment.MapToNewDCs(occurrences); + } + catch(Exception e) + { + throw Utils.CreateBeWoFaultException(e); + } + } } } \ No newline at end of file