From af445c015eff50a38524dd6fa46ce0f728121e1d Mon Sep 17 00:00:00 2001 From: Stefan Date: Fri, 10 Jul 2026 13:29:35 +0200 Subject: [PATCH 01/12] Add backend-authoritative invoice draft editing (ADR 0006/0007) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the invoice draft editor to a backend single source of truth: an in-memory InvoiceDraftSession (per-token, cached) holds the editable payload, server-computed sums/VAT and validation, plus an automatic change history. The browser posts single edits; the server recomputes and signals the editing session over a dedicated SignalR hub (DraftPreviewHub) to re-fetch. This reverses the previously-documented stateless editor (EVAL_live_invoice_editing, INVOICE_LIFECYCLE §10), by explicit product decision — captured in ADR 0006 and 0007 plus the live-draft-editing concept doc. Backend (this milestone): - InvoiceDraftSession + ChangeHistoryEntry data holders - InvoiceDraftCalculator: pure port of quantChange/invSumUpdate (§13b, VAT-by-rate) and consistency checks — fully unit-tested - IInvoiceDraftCache/InvoiceDraftCache: in-memory store with idle sliding TTL - IInvoiceDraftService/InvoiceDraftEditService: open (payload or DB reload), patch, build state, flush via existing RegisterInvoiceAsync (no new persistence), preview from cache, discard (DB reload), history - InvoiceDraftExpiryService: pre-expiry warning + eviction-with-reason - DraftPreviewHub + IDraftNotifier/DraftNotifier: targeted draftReady/draftExpiring/ draftClosed signals per draft token - inv/dopen|dstate|dpatch|dpreview|dsave|dhistory|ddiscard|dclose endpoints; save reports success/failure via the existing EventService - DI + hub mapping in Program.cs Frontend (additive foundation): $fis.draft SignalR client for /draftpreview. The editor DOM inversion (routing deltas, rendering from server state) is the next, separately-verified step; existing endpoints are unaffected. Tests: 30 new (calculator, cache, expiry, patch/history, flush); 306 total passing. Co-Authored-By: Claude Opus 4.8 --- Fuchs.Tests/InvoiceDraftCacheTests.cs | 113 ++++ Fuchs.Tests/InvoiceDraftCalculatorTests.cs | 158 ++++++ Fuchs.Tests/InvoiceDraftServiceTests.cs | 155 ++++++ .../IntranetController.InvoiceDraft.cs | 146 +++++ .../IntranetController.Invoices.cs | 10 + Fuchs/Controllers/IntranetController.cs | 8 +- Fuchs/Docs/Concepts/live-draft-editing.md | 84 +++ ...006-backend-authoritative-draft-editing.md | 88 +++ .../0007-targeted-draft-signalr-groups.md | 59 ++ Fuchs/Docs/EVAL_live_invoice_editing.md | 12 + Fuchs/Docs/INVOICE_LIFECYCLE.md | 15 +- Fuchs/Notifications/DraftNotifier.cs | 45 ++ Fuchs/Notifications/DraftPreviewHub.cs | 31 ++ Fuchs/Notifications/IDraftNotifier.cs | 20 + Fuchs/Program.cs | 9 + Fuchs/Services/IInvoiceDraftCache.cs | 26 + Fuchs/Services/IInvoiceDraftService.cs | 82 +++ Fuchs/Services/InvoiceDraftCache.cs | 60 +++ Fuchs/Services/InvoiceDraftEditService.cs | 504 ++++++++++++++++++ Fuchs/Services/InvoiceDraftExpiryService.cs | 68 +++ Fuchs/code/InvoiceDraftCalculator.cs | 193 +++++++ Fuchs/code/InvoiceDraftSession.cs | 111 ++++ Fuchs/js/intranet/fis_main.js | 62 +++ Fuchs/js/intranet/fis_main_go.js | 1 + Fuchs/wwwroot/web/fis.js | 63 +++ Fuchs/wwwroot/web/fis.min.js | 4 +- 26 files changed, 2121 insertions(+), 6 deletions(-) create mode 100644 Fuchs.Tests/InvoiceDraftCacheTests.cs create mode 100644 Fuchs.Tests/InvoiceDraftCalculatorTests.cs create mode 100644 Fuchs.Tests/InvoiceDraftServiceTests.cs create mode 100644 Fuchs/Controllers/IntranetController.InvoiceDraft.cs create mode 100644 Fuchs/Docs/Concepts/live-draft-editing.md create mode 100644 Fuchs/Docs/Decisions/0006-backend-authoritative-draft-editing.md create mode 100644 Fuchs/Docs/Decisions/0007-targeted-draft-signalr-groups.md create mode 100644 Fuchs/Notifications/DraftNotifier.cs create mode 100644 Fuchs/Notifications/DraftPreviewHub.cs create mode 100644 Fuchs/Notifications/IDraftNotifier.cs create mode 100644 Fuchs/Services/IInvoiceDraftCache.cs create mode 100644 Fuchs/Services/IInvoiceDraftService.cs create mode 100644 Fuchs/Services/InvoiceDraftCache.cs create mode 100644 Fuchs/Services/InvoiceDraftEditService.cs create mode 100644 Fuchs/Services/InvoiceDraftExpiryService.cs create mode 100644 Fuchs/code/InvoiceDraftCalculator.cs create mode 100644 Fuchs/code/InvoiceDraftSession.cs diff --git a/Fuchs.Tests/InvoiceDraftCacheTests.cs b/Fuchs.Tests/InvoiceDraftCacheTests.cs new file mode 100644 index 0000000..220ba80 --- /dev/null +++ b/Fuchs.Tests/InvoiceDraftCacheTests.cs @@ -0,0 +1,113 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Fuchs.intranet; +using Fuchs.Notifications; +using Fuchs.Services; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging.Abstractions; +using Xunit; + +namespace Fuchs.Tests; + +/// +/// Covers the in-memory draft cache (storage + idle sliding TTL) and the background +/// expiry monitor that warns before eviction and closes the editor on eviction (ADR 0006). +/// +public class InvoiceDraftCacheTests +{ + private static IConfiguration Config(int idle = 30, int warn = 5) => + new ConfigurationBuilder().AddInMemoryCollection(new Dictionary + { + ["Fuchs:DraftEditing:IdleMinutes"] = idle.ToString(), + ["Fuchs:DraftEditing:ExpiryWarnMinutes"] = warn.ToString() + }).Build(); + + private sealed class FakeNotifier : IDraftNotifier + { + public readonly List<(string token, int version)> Ready = new(); + public readonly List<(string token, int secondsLeft)> Expiring = new(); + public readonly List<(string token, string reason)> Closed = new(); + public Task SignalDraftReadyAsync(string token, int version, CancellationToken ct = default) { Ready.Add((token, version)); return Task.CompletedTask; } + public Task SignalExpiringAsync(string token, int secondsLeft, CancellationToken ct = default) { Expiring.Add((token, secondsLeft)); return Task.CompletedTask; } + public Task SignalClosedAsync(string token, string reason, CancellationToken ct = default) { Closed.Add((token, reason)); return Task.CompletedTask; } + } + + // ── Cache storage ───────────────────────────────────────────────────────── + [Fact] + public void SetGet_RoundTrips_AndUnknownTokenIsNull() + { + var cache = new InvoiceDraftCache(Config()); + var s = new InvoiceDraftSession { Token = "abc" }; + cache.Set(s); + Assert.Same(s, cache.Get("abc")); + Assert.Null(cache.Get("nope")); + } + + [Fact] + public void Remove_EvictsSession() + { + var cache = new InvoiceDraftCache(Config()); + cache.Set(new InvoiceDraftSession { Token = "x" }); + Assert.NotNull(cache.Remove("x")); + Assert.Null(cache.Get("x")); + Assert.Null(cache.Remove("x")); + } + + [Fact] + public void Get_ResetsExpiryWarningFlag_SoAFreshWarningIsDue() + { + var cache = new InvoiceDraftCache(Config()); + var s = new InvoiceDraftSession { Token = "x", ExpiryWarningSent = true }; + cache.Set(s); + cache.Get("x"); + Assert.False(s.ExpiryWarningSent); + } + + // ── Expiry monitor ──────────────────────────────────────────────────────── + [Fact] + public async Task Sweep_NearTtl_WarnsOnceThenEvictsWithReason() + { + var cfg = Config(idle: 30, warn: 5); + var cache = new InvoiceDraftCache(cfg); + var notifier = new FakeNotifier(); + var svc = new InvoiceDraftExpiryService(cache, notifier, cfg, NullLogger.Instance); + + var s = new InvoiceDraftSession { Token = "a" }; + cache.Set(s); + + // Idle 26 min → inside the 5-min warning window (30-5=25) but not yet expired. + s.LastAccessUtc = DateTime.UtcNow.AddMinutes(-26); + await svc.SweepAsync(CancellationToken.None); + Assert.Single(notifier.Expiring); + Assert.Empty(notifier.Closed); + Assert.True(s.ExpiryWarningSent); + + // Another sweep while still idle must not spam a second warning. + await svc.SweepAsync(CancellationToken.None); + Assert.Single(notifier.Expiring); + + // Past the TTL → evicted and the editor is told to close with a reason. + s.LastAccessUtc = DateTime.UtcNow.AddMinutes(-31); + await svc.SweepAsync(CancellationToken.None); + Assert.Single(notifier.Closed); + Assert.Equal(("a", "expired"), notifier.Closed[0]); + Assert.Null(cache.Get("a")); + } + + [Fact] + public async Task Sweep_FreshSession_DoesNothing() + { + var cfg = Config(idle: 30, warn: 5); + var cache = new InvoiceDraftCache(cfg); + var notifier = new FakeNotifier(); + var svc = new InvoiceDraftExpiryService(cache, notifier, cfg, NullLogger.Instance); + cache.Set(new InvoiceDraftSession { Token = "fresh" }); + + await svc.SweepAsync(CancellationToken.None); + + Assert.Empty(notifier.Expiring); + Assert.Empty(notifier.Closed); + } +} diff --git a/Fuchs.Tests/InvoiceDraftCalculatorTests.cs b/Fuchs.Tests/InvoiceDraftCalculatorTests.cs new file mode 100644 index 0000000..8b6357f --- /dev/null +++ b/Fuchs.Tests/InvoiceDraftCalculatorTests.cs @@ -0,0 +1,158 @@ +using System.Linq; +using Fuchs.intranet; +using Newtonsoft.Json.Linq; +using Xunit; + +namespace Fuchs.Tests; + +/// +/// Verifies the server-side port of the former client-side invoice math +/// (quantChange + invSumUpdate). Because the truth now lives in the +/// backend (ADR 0006), this logic is finally unit-testable directly. +/// +public class InvoiceDraftCalculatorTests +{ + private static InvoiceDraftSession SessionWith(string reqJson, bool p13b = false) + { + var s = new InvoiceDraftSession { Token = "t" }; + s.Admin = new JObject { ["p13b"] = p13b }; + s.New = new JObject { ["invoiceemail"] = "kunde@example.de", ["invoiceaddress"] = "Weg 1" }; + s.Req = JArray.Parse(reqJson); + return s; + } + + // ── RecomputeTotals ────────────────────────────────────────────────────── + [Fact] + public void RecomputeTotals_SumsNetVatServiceAndPerBlock() + { + var s = SessionWith(@"[ + { 'Id':'10','items':[ + {'net_val':100,'vat_val':19,'svcnet_val':0,'svcvat_val':0,'vat':'19%','Type':'material'}, + {'net_val':50,'vat_val':9.5,'svcnet_val':50,'svcvat_val':9.5,'vat':'19%','Type':'Service'} ] }, + { 'Id':'11','items':[ + {'net_val':200,'vat_val':14,'svcnet_val':0,'svcvat_val':0,'vat':'7%','Type':'material'} ] } + ]"); + + InvoiceDraftCalculator.RecomputeTotals(s); + + Assert.Equal(350m, s.Sums.TotalNet); + Assert.Equal(42.5m, s.Sums.TotalVat); + Assert.Equal(392.5m, s.Sums.TotalGross); + Assert.Equal(50m, s.Sums.ServiceNet); + Assert.Equal(9.5m, s.Sums.ServiceVat); + Assert.Equal(28.5m, s.Sums.VatByRate["19"]); + Assert.Equal(14m, s.Sums.VatByRate["7"]); + Assert.Equal(150m, s.Sums.NetByBlock["10"]); + Assert.Equal(200m, s.Sums.NetByBlock["11"]); + } + + [Fact] + public void RecomputeTotals_ReverseCharge_SuppressesVatAndGrossEqualsNet() + { + var s = SessionWith(@"[{ 'Id':'1','items':[ + {'net_val':100,'vat_val':19,'vat':'19%','Type':'material'} ] }]", p13b: true); + + InvoiceDraftCalculator.RecomputeTotals(s); + + Assert.Equal(100m, s.Sums.TotalNet); + Assert.Equal(100m, s.Sums.TotalGross); + Assert.Equal(0m, s.Sums.TotalVat); + Assert.Empty(s.Sums.VatByRate); + } + + [Fact] + public void RecomputeTotals_EmptyDraft_AllZero() + { + var s = SessionWith("[]"); + InvoiceDraftCalculator.RecomputeTotals(s); + Assert.Equal(0m, s.Sums.TotalNet); + Assert.Equal(0m, s.Sums.TotalGross); + Assert.Empty(s.Sums.VatByRate); + } + + // ── RecomputeItem (quantChange port) ───────────────────────────────────── + [Theory] + [InlineData("Service", true)] + [InlineData("material", false)] + public void RecomputeItem_DerivesLineValuesFromQtyPriceVat(string type, bool isService) + { + var item = new JObject + { + ["quantityhours"] = 5, ["net"] = "10", ["vat"] = "19", ["Type"] = type + }; + + InvoiceDraftCalculator.RecomputeItem(item); + + Assert.Equal(50m, item["net_val"]!.Value()); + Assert.Equal(9.5m, item["vat_val"]!.Value()); + if (isService) + { + Assert.Equal(50m, item["svcnet_val"]!.Value()); + Assert.Equal(9.5m, item["svcvat_val"]!.Value()); + } + else + { + Assert.Null(item["svcnet_val"]); + } + } + + [Fact] + public void RecomputeItem_ZeroQuantity_LeavesValuesUntouched() + { + var item = new JObject { ["quantityhours"] = 0, ["net"] = "10", ["vat"] = "19", ["Type"] = "material" }; + InvoiceDraftCalculator.RecomputeItem(item); + Assert.Null(item["net_val"]); // guard qty>0 && price>0 not met → no derivation + } + + // ── NormalizeRate ──────────────────────────────────────────────────────── + [Theory] + [InlineData("19,0%", "19")] + [InlineData("7%", "7")] + [InlineData("19", "19")] + [InlineData("", "")] + [InlineData("0", "")] + [InlineData("7,5", "7.5")] + public void NormalizeRate_CanonicalisesRateStrings(string raw, string expected) + => Assert.Equal(expected, InvoiceDraftCalculator.NormalizeRate(raw)); + + // ── Validate ───────────────────────────────────────────────────────────── + [Fact] + public void Validate_ValidDraft_NoErrors() + { + var s = SessionWith(@"[{ 'Id':'1','items':[ + {'net_val':100,'vat_val':19,'vat':'19%','Type':'material'} ] }]"); + InvoiceDraftCalculator.RecomputeTotals(s); + InvoiceDraftCalculator.Validate(s); + Assert.DoesNotContain(s.ValidationMessages, m => m.Severity == "error"); + } + + [Theory] + [InlineData("", "warning")] // missing email → advisory + [InlineData("not-an-email", "error")] + public void Validate_EmailProblems_AreFlagged(string email, string severity) + { + var s = SessionWith(@"[{ 'Id':'1','items':[{'net_val':10,'vat':'19%','Type':'material'}] }]"); + s.New["invoiceemail"] = email; + InvoiceDraftCalculator.RecomputeTotals(s); + InvoiceDraftCalculator.Validate(s); + Assert.Contains(s.ValidationMessages, m => m.Field == "email" && m.Severity == severity); + } + + [Fact] + public void Validate_NoItems_IsError() + { + var s = SessionWith("[]"); + InvoiceDraftCalculator.RecomputeTotals(s); + InvoiceDraftCalculator.Validate(s); + Assert.Contains(s.ValidationMessages, m => m.Field == "items" && m.Severity == "error"); + } + + [Fact] + public void Validate_UnknownVatRate_IsWarning() + { + var s = SessionWith(@"[{ 'Id':'1','items':[{'net_val':10,'vat_val':0.5,'vat':'5%','Type':'material'}] }]"); + InvoiceDraftCalculator.RecomputeTotals(s); + InvoiceDraftCalculator.Validate(s); + Assert.Contains(s.ValidationMessages, m => m.Field == "vat" && m.Severity == "warning"); + } +} diff --git a/Fuchs.Tests/InvoiceDraftServiceTests.cs b/Fuchs.Tests/InvoiceDraftServiceTests.cs new file mode 100644 index 0000000..616cb33 --- /dev/null +++ b/Fuchs.Tests/InvoiceDraftServiceTests.cs @@ -0,0 +1,155 @@ +using System.Linq; +using System.Threading.Tasks; +using Fuchs.intranet; +using Fuchs.Services; +using Microsoft.Data.SqlClient; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging.Abstractions; +using MigraDoc.DocumentObjectModel; +using Newtonsoft.Json.Linq; +using OCORE.security; +using Xunit; +using static OCORE.OCORE_dictionaries; + +namespace Fuchs.Tests; + +/// +/// Exercises the draft edit orchestrator's pure paths (open/patch/history/flush) +/// without any database, proving the backend-authoritative model behaves correctly +/// end-to-end at the service seam (ADR 0006). +/// +public class InvoiceDraftServiceTests +{ + /// Captures the invoice handed to registration and returns it with a fake DB id — no SQL. + private sealed class FakeInvoiceService : IInvoiceService + { + public FdsInvoiceData? Registered; + public bool? LastChange; + public Task RegisterInvoiceAsync(FdsInvoiceData invoice, bool change, string invId, string userAccountId, DatabaseSecurity dbSec) + { + Registered = invoice; + LastChange = change; + invoice.InvoiceRegistration = new GenericObjectDictionary(new System.Collections.Generic.Dictionary { ["Id"] = "INV42" }); + return Task.FromResult(invoice); + } + public Task LoadInvoiceAsync(string id, string u, DatabaseSecurity s) => throw new System.NotSupportedException(); + public Document GenerateInvoicePdf(FdsInvoiceData i, bool d) => throw new System.NotSupportedException(); + public Task RenderInvoicePdfBytesAsync(FdsInvoiceData i, bool d) => throw new System.NotSupportedException(); + public Task StoreInvoiceDocumentFileAsync(FdsInvoiceData i, bool d, string u, DatabaseSecurity s) => throw new System.NotSupportedException(); + public Task GetInvoiceFileAsync(FdsInvoiceData i, bool d, fds.IFdsMfr m) => throw new System.NotSupportedException(); + } + + private static (InvoiceDraftEditService svc, FakeInvoiceService inv, InvoiceDraftCache cache) NewService() + { + var cfg = new ConfigurationBuilder().Build(); + var cache = new InvoiceDraftCache(cfg); + var inv = new FakeInvoiceService(); + var svc = new InvoiceDraftEditService(cache, inv, intranet: null!, NullLogger.Instance); + return (svc, inv, cache); + } + + private static JObject Payload() => JObject.Parse(@"{ + 'admin':{'p13b':false,'type':'r','paymentterms':'10wd'}, + 'new':{'invoiceemail':'a@b.de','invoiceaddress':'Weg 1','invoicetitle':'Rechnung'}, + 'req':[{'Id':'1','items':[ + {'Id':'900','net_val':100,'vat_val':19,'vat':'19%','Type':'material','net':'10','quantityhours':10} ]}] + }"); + + [Fact] + public void OpenFromPayload_SeedsSessionAndComputesTotals() + { + var (svc, _, _) = NewService(); + var s = svc.OpenFromPayload(Payload(), "user1"); + Assert.False(string.IsNullOrEmpty(s.Token)); + Assert.Equal(0, s.Version); + Assert.Equal(100m, s.Sums.TotalNet); + Assert.Equal(119m, s.Sums.TotalGross); + } + + [Fact] + public void ApplyPatch_Email_MutatesBumpsVersionAndRecordsHistory() + { + var (svc, _, _) = NewService(); + var s = svc.OpenFromPayload(Payload(), "user1"); + + var s2 = svc.ApplyPatch(s.Token, new InvoiceDraftDelta { Target = "email", Value = JToken.FromObject("neu@x.de") }); + + Assert.NotNull(s2); + Assert.Equal(1, s2!.Version); + Assert.Equal("neu@x.de", s2.New["invoiceemail"]!.Value()); + var h = Assert.Single(s2.History); + Assert.Equal("email", h.Target); + Assert.Equal("a@b.de", h.OldValue); + Assert.Equal("neu@x.de", h.NewValue); + Assert.Equal(1, h.Version); + } + + [Fact] + public void ApplyPatch_ItemQty_RecomputesLineAndTotals() + { + var (svc, _, _) = NewService(); + var s = svc.OpenFromPayload(Payload(), "user1"); + + var s2 = svc.ApplyPatch(s.Token, new InvoiceDraftDelta { Target = "item.qty", Ref = "900", Value = JToken.FromObject(5) }); + + // qty 5 × price 10 = 50 net, 19% → 9.5 VAT. + Assert.Equal(50m, s2!.Sums.TotalNet); + Assert.Equal(9.5m, s2.Sums.VatByRate["19"]); + } + + [Fact] + public void ApplyPatch_P13bToggle_FlipsAndSuppressesVat() + { + var (svc, _, _) = NewService(); + var s = svc.OpenFromPayload(Payload(), "user1"); + + var s2 = svc.ApplyPatch(s.Token, new InvoiceDraftDelta { Target = "p13b" }); // no value → toggle + + Assert.Equal(100m, s2!.Sums.TotalGross); // reverse-charge → gross == net + Assert.Empty(s2.Sums.VatByRate); + } + + [Fact] + public void ApplyPatch_UnknownToken_ReturnsNull() + { + var (svc, _, _) = NewService(); + Assert.Null(svc.ApplyPatch("ghost", new InvoiceDraftDelta { Target = "email", Value = JToken.FromObject("x@y.de") })); + } + + [Fact] + public async Task FlushToDbAsync_RegistersWithMappedTotals_AndSetsInvId() + { + var (svc, inv, _) = NewService(); + var s = svc.OpenFromPayload(Payload(), "user1"); + + var result = await svc.FlushToDbAsync(s.Token, "user1", null!); + + Assert.NotNull(result); + Assert.Equal("INV42", result!.Id); + Assert.False(inv.LastChange); // new draft (no prior InvId) → create, not update + Assert.Equal("INV42", svc.Get(s.Token)!.InvId); + + // The FdsInvoiceData handed to registration carries the session's server-computed totals. + var prms = inv.Registered!.BuildInvoiceParams(change: false, invId: ""); + var balance = prms.First(p => p.ParameterName == "@InvoiceBalance"); + Assert.Equal("119", System.Convert.ToString(balance.Value, System.Globalization.CultureInfo.InvariantCulture)); + var vatRate = prms.First(p => p.ParameterName == "@InvoiceVAT_1"); + Assert.Equal("19", vatRate.Value); + } + + [Fact] + public async Task DiscardAsync_NeverSaved_ReturnsSessionUnchanged() + { + var (svc, _, _) = NewService(); + var s = svc.OpenFromPayload(Payload(), "user1"); + var back = await svc.DiscardAsync(s.Token, "user1", null!); + Assert.Same(s, back); // no InvId → nothing to reload from the DB + } + + [Fact] + public void GetHistory_UnknownToken_IsEmpty() + { + var (svc, _, _) = NewService(); + Assert.Empty(svc.GetHistory("ghost")); + } +} diff --git a/Fuchs/Controllers/IntranetController.InvoiceDraft.cs b/Fuchs/Controllers/IntranetController.InvoiceDraft.cs new file mode 100644 index 0000000..8f53344 --- /dev/null +++ b/Fuchs/Controllers/IntranetController.InvoiceDraft.cs @@ -0,0 +1,146 @@ +using Fuchs.intranet; +using Fuchs.Services; +using Microsoft.AspNetCore.Mvc; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using static OCORE.web.mvc_helper_async; + +namespace Fuchs.Controllers; + +// Partial class: live, backend-authoritative invoice draft editing (ADR 0006). +// The browser posts single edits here; the server mutates the in-memory session +// (the source of truth), recomputes/validates, and pings the editing browser over +// SignalR (draftReady) to re-fetch. Commands are ordinary POSTs — the hub carries +// only signals (ADR 0007). +public partial class IntranetController +{ + /// Standard 410 when a session token is unknown/expired — the client re-opens the draft. + private IActionResult DraftGone() => StatusCode(410, new { error = "expired" }); + + // POST inv/dopen — { id? | payload? } → { token, version } + private async Task HandleDraftOpen(string fn, string id, string code) + { + InvoiceDraftSession session; + if (HasForm("id") && !string.IsNullOrEmpty(Form("id"))) + { + _logger.LogInformation("Draft dopen: from DB draft {InvId} user={User}", Form("id"), UserAccountID); + session = await _invoiceDrafts.OpenFromDraftAsync(Form("id"), UserAccountID, DbSec); + } + else if (HasForm("payload")) + { + _logger.LogInformation("Draft dopen: from payload user={User}", UserAccountID); + JObject payload; + try { payload = JObject.Parse(Form("payload")); } + catch (JsonException ex) + { + _logger.LogWarning(ex, "Draft dopen: invalid payload JSON user={User}", UserAccountID); + return BadRequest400(); + } + session = _invoiceDrafts.OpenFromPayload(payload, UserAccountID); + } + else + { + _logger.LogWarning("Draft dopen: neither 'id' nor 'payload' supplied user={User}", UserAccountID); + return BadRequest400(); + } + // The browser holds the token from this response and fetches dstate directly; there is + // no server 'draftReady' on open (it would race the client's group-join). Signals drive + // only subsequent server-side changes. + return await JSONAsync(new { token = session.Token, version = session.Version }); + } + + // POST inv/dstate — { token } → full view state + private async Task HandleDraftState(string fn, string id, string code) + { + if (!HasForm("token")) return BadRequest400(); + var session = _invoiceDrafts.Get(Form("token")); + if (session == null) return DraftGone(); + return await JSONAsync(_invoiceDrafts.BuildState(session)); + } + + // POST inv/dpatch — { token, delta } → { ok, version }; signals draftReady + private async Task HandleDraftPatch(string fn, string id, string code) + { + if (!HasForm("token", "delta")) return BadRequest400(); + InvoiceDraftDelta? delta; + try { delta = JsonConvert.DeserializeObject(Form("delta")); } + catch (JsonException ex) + { + _logger.LogWarning(ex, "Draft dpatch: invalid delta JSON user={User}", UserAccountID); + return BadRequest400(); + } + if (delta == null || string.IsNullOrEmpty(delta.Target)) return BadRequest400(); + + var session = _invoiceDrafts.ApplyPatch(Form("token"), delta); + if (session == null) return DraftGone(); + await _draftNotifier.SignalDraftReadyAsync(session.Token, session.Version); + return await JSONAsync(new { ok = true, version = session.Version }); + } + + // POST inv/dpreview — { token } → { img[], total } (rendered straight from the cache) + private async Task HandleDraftPreview(string fn, string id, string code) + { + if (!HasForm("token")) return BadRequest400(); + var doc = _invoiceDrafts.RenderPreview(Form("token")); + if (doc == null) return DraftGone(); + var imgcol = await _pdf.DocToImageCollectionAsync(doc); + return await JSONAsync(new { img = imgcol.ImgB64Array, total = imgcol.TotalPages }); + } + + // POST inv/dsave — { token } → { ok, invid }; flush cache→DB + business event + draftReady + private async Task HandleDraftSave(string fn, string id, string code) + { + if (!HasForm("token")) return BadRequest400(); + string token = Form("token"); + var before = _invoiceDrafts.Get(token); + if (before == null) return DraftGone(); + bool existed = !string.IsNullOrEmpty(before.InvId); + + var fdInv = await _invoiceDrafts.FlushToDbAsync(token, UserAccountID, DbSec); + if (fdInv == null) return DraftGone(); + if (string.IsNullOrEmpty(fdInv.Id)) + return await InvoiceIssueResult("Der Zwischenstand konnte aufgrund eines Fehlers nicht gespeichert werden."); + + await _events.InvoiceDraftRegisteredAsync(fdInv, existed, UserAccountID); + var after = _invoiceDrafts.Get(token); + if (after != null) await _draftNotifier.SignalDraftReadyAsync(after.Token, after.Version); + return await JSONAsync(new { ok = true, invid = fdInv.Id }); + } + + // POST inv/dhistory — { token } → { history[] } + private async Task HandleDraftHistory(string fn, string id, string code) + { + if (!HasForm("token")) return BadRequest400(); + if (_invoiceDrafts.Get(Form("token")) == null) return DraftGone(); + var history = _invoiceDrafts.GetHistory(Form("token")) + .Select(h => new + { + timestamp = h.TimestampUtc, + target = h.Target, + @ref = h.Ref, + oldValue = h.OldValue, + newValue = h.NewValue, + version = h.Version + }); + return await JSONAsync(new { history }); + } + + // POST inv/ddiscard — { token } → { ok, version }; reload from DB + draftReady + private async Task HandleDraftDiscard(string fn, string id, string code) + { + if (!HasForm("token")) return BadRequest400(); + var session = await _invoiceDrafts.DiscardAsync(Form("token"), UserAccountID, DbSec); + if (session == null) return DraftGone(); + await _draftNotifier.SignalDraftReadyAsync(session.Token, session.Version); + return await JSONAsync(new { ok = true, version = session.Version }); + } + + // POST inv/dclose — { token } → { ok } + private async Task HandleDraftClose(string fn, string id, string code) + { + if (!HasForm("token")) return BadRequest400(); + bool ok = _invoiceDrafts.Close(Form("token")); + _logger.LogDebug("Draft dclose token={Token} removed={Removed} user={User}", Form("token"), ok, UserAccountID); + return await JSONAsync(new { ok }); + } +} diff --git a/Fuchs/Controllers/IntranetController.Invoices.cs b/Fuchs/Controllers/IntranetController.Invoices.cs index cbc0dbb..de8af6a 100644 --- a/Fuchs/Controllers/IntranetController.Invoices.cs +++ b/Fuchs/Controllers/IntranetController.Invoices.cs @@ -158,6 +158,16 @@ public partial class IntranetController fds.FdsMfr.UpdateNeed.Reset, new[] { relId }); return await JSONAsync(new { ok = true }); + // ── Live backend-authoritative draft editing (ADR 0006) ─────────── + case "dopen": return await HandleDraftOpen(fn, id, code); + case "dstate": return await HandleDraftState(fn, id, code); + case "dpatch": return await HandleDraftPatch(fn, id, code); + case "dpreview": return await HandleDraftPreview(fn, id, code); + case "dsave": return await HandleDraftSave(fn, id, code); + case "dhistory": return await HandleDraftHistory(fn, id, code); + case "ddiscard": return await HandleDraftDiscard(fn, id, code); + case "dclose": return await HandleDraftClose(fn, id, code); + default: _logger.LogWarning("Do_Process_Invoices: unhandled action id={Id}, user={User}", id, UserAccountID); return await JSONAsync(new { ok = true }); diff --git a/Fuchs/Controllers/IntranetController.cs b/Fuchs/Controllers/IntranetController.cs index 7a088b2..8062128 100644 --- a/Fuchs/Controllers/IntranetController.cs +++ b/Fuchs/Controllers/IntranetController.cs @@ -35,6 +35,8 @@ public partial class IntranetController : Microsoft.AspNetCore.Mvc.Controller private readonly IInvoiceService _invoices; private readonly IReminderService _reminders; private readonly IEventService _events; + private readonly IInvoiceDraftService _invoiceDrafts; + private readonly IDraftNotifier _draftNotifier; private readonly List _allowedNonAuth = new() { "spwc", "spw" }; private readonly List _allowedGet = new() { @@ -62,7 +64,9 @@ public partial class IntranetController : Microsoft.AspNetCore.Mvc.Controller IReportService reports, IInvoiceService invoices, IReminderService reminders, - IEventService events) + IEventService events, + IInvoiceDraftService invoiceDrafts, + IDraftNotifier draftNotifier) { _intranet = intranet; _mfr = mfr; @@ -76,6 +80,8 @@ public partial class IntranetController : Microsoft.AspNetCore.Mvc.Controller _invoices = invoices; _reminders = reminders; _events = events; + _invoiceDrafts = invoiceDrafts; + _draftNotifier = draftNotifier; } /// Merged query-string + form parameters (form wins) for report processing. diff --git a/Fuchs/Docs/Concepts/live-draft-editing.md b/Fuchs/Docs/Concepts/live-draft-editing.md new file mode 100644 index 0000000..0555f0d --- /dev/null +++ b/Fuchs/Docs/Concepts/live-draft-editing.md @@ -0,0 +1,84 @@ +--- +status: Active +lastUpdated: 2026-07-10 +applyTo: + - "Fuchs/Services/InvoiceDraft*" + - "Fuchs/Services/IInvoiceDraft*" + - "Fuchs/code/InvoiceDraftSession.cs" + - "Fuchs/code/InvoiceDraftCalculator.cs" + - "Fuchs/Notifications/DraftPreviewHub.cs" + - "Fuchs/Notifications/*DraftNotifier*" + - "Fuchs/Controllers/IntranetController.InvoiceDraft.cs" + - "Fuchs/js/intranet/**" +relatedDecisions: + - "0006-backend-authoritative-draft-editing.md" + - "0007-targeted-draft-signalr-groups.md" +--- + +# Live draft editing (backend-authoritative invoice previews) + +## Summary +While a back-office user edits an invoice draft, the authoritative state is held in +server memory, not in the browser. The browser posts single edits, the server mutates +the cached record, recomputes totals/VAT and re-validates, then pushes a "state changed" +signal so the browser re-fetches and re-renders. This makes the backend the single source +of truth (server-computed sums, consistency checks, in-place PDF preview, change history, +explicit discard), reversing the earlier stateless editor. Invoices are the pilot; +reminders are intended to mirror the same design. + +## How it works + +``` +Open: Browser --POST inv/dopen {id | payload}--> server builds InvoiceDraftSession, caches it + Browser --SignalR JoinDraft(token)--> joins the draft's group; spinner while loading + Browser --POST inv/dstate {token}--> renders admin/new/req + server sums + validation + +Edit: Browser --POST inv/dpatch {token, delta}--> mutate + recompute + validate + version++ + Server --SignalR draftReady{token,version}--> Browser re-fetches inv/dstate, re-renders + +Preview: Browser --POST inv/dpreview {token}--> PDF rendered straight from the cache (no upload) +Save: Browser --POST inv/dsave {token}--> flush cache->DB (RegisterInvoiceAsync) + EventService toast +History: Browser --POST inv/dhistory {token}--> change list -> "Änderungshistorie" dialog +Discard: Browser --POST inv/ddiscard {token}--> reload session from DB draft -> draftReady +Close: Browser --POST inv/dclose {token}--> session removed (+ LeaveDraft) + +Expiry: Server (timer) --SignalR draftExpiring{token,secondsLeft}--> warn "bitte zwischenspeichern" + Server (evict) --SignalR draftClosed{token,reason}--> close the editor with a reason +``` + +- **Session** (`InvoiceDraftSession`) is a pure data holder: the editable payload as the + exact editor JSON (`admin` / `new` / `req` blocks with `items`), plus server-computed + `Sums`, `ValidationMessages`, `History`, `Version`, `Token`, `InvId`, `LastAccessUtc`. +- **Calculation** (`InvoiceDraftCalculator`, static/pure) ports the former client math: + `RecomputeItem` (quantity × price × VAT, the `quantChange` port), `RecomputeTotals` + (the `invSumUpdate`/`csms` aggregation + §13b reverse-charge), and `Validate` + (email/address/items/VAT-rate/negative-total checks). Being pure, it is exhaustively + unit-tested. +- **Orchestration** (`InvoiceDraftEditService`, scoped) opens sessions (from a fresh + payload or by reloading a DB draft via `fds__getInvoice`, reshaped like + `BuildInvoiceRequestList`), applies deltas (`ApplyDelta`), builds the view-state DTO, + flushes to the DB by reusing `IInvoiceService.RegisterInvoiceAsync` (no new persistence + path), renders previews from a synthesised registration, and discards by reloading. +- **Cache** (`InvoiceDraftCache`, singleton) stores sessions by token with an idle sliding + TTL; `InvoiceDraftExpiryService` (a `BackgroundService`) warns before, and evicts after, + the TTL. TTL/warn-lead are configurable under `Fuchs:DraftEditing`. +- **Signals** (`DraftPreviewHub` at `/draftpreview` + `IDraftNotifier`) are targeted at the + editing browser via a group named after the session token: `draftReady`, `draftExpiring`, + `draftClosed`. Business success/failure still flows through `IEventService`/`NotificationHub`. +- **Frontend** (`$fis.draft` in `fis_main.js`, editor in `fis.inv_shared.js`) opens/joins, + posts one delta per change, shows a loading state whenever awaiting a signal, and offers + "Änderungen verwerfen" and "Änderungshistorie" menu actions. It no longer computes totals. + +## Key files +- `Fuchs/code/InvoiceDraftSession.cs` — session + `ChangeHistoryEntry` + `InvoiceDraftSums`. +- `Fuchs/code/InvoiceDraftCalculator.cs` — pure recompute + validate. +- `Fuchs/Services/InvoiceDraftCache.cs` / `IInvoiceDraftCache.cs` — in-memory store + TTL. +- `Fuchs/Services/InvoiceDraftEditService.cs` / `IInvoiceDraftService.cs` — orchestration + delta contract. +- `Fuchs/Services/InvoiceDraftExpiryService.cs` — idle warn/evict monitor. +- `Fuchs/Notifications/DraftPreviewHub.cs`, `DraftNotifier.cs`, `IDraftNotifier.cs` — targeted signals. +- `Fuchs/Controllers/IntranetController.InvoiceDraft.cs` — `inv/d*` endpoints. +- `Fuchs/js/intranet/fis_main.js`, `Fuchs/js/intranet/modules/fis.inv_shared.js` — client. + +## Related decisions +- [0006 — Backend-authoritative draft editing](../Decisions/0006-backend-authoritative-draft-editing.md) +- [0007 — Targeted draft SignalR groups](../Decisions/0007-targeted-draft-signalr-groups.md) diff --git a/Fuchs/Docs/Decisions/0006-backend-authoritative-draft-editing.md b/Fuchs/Docs/Decisions/0006-backend-authoritative-draft-editing.md new file mode 100644 index 0000000..000596a --- /dev/null +++ b/Fuchs/Docs/Decisions/0006-backend-authoritative-draft-editing.md @@ -0,0 +1,88 @@ +--- +status: Accepted +date: 2026-07-10 +applyTo: + - "Fuchs/Services/InvoiceDraft*" + - "Fuchs/Services/IInvoiceDraft*" + - "Fuchs/code/InvoiceDraftSession.cs" + - "Fuchs/code/InvoiceDraftCalculator.cs" + - "Fuchs/Notifications/DraftPreviewHub.cs" + - "Fuchs/Notifications/*DraftNotifier*" + - "Fuchs/Controllers/IntranetController.InvoiceDraft.cs" + - "Fuchs/js/intranet/**" +supersededBy: "" +--- + +# 0006 — Invoice draft editing is backend-authoritative over an in-memory cache + +## Context +The invoice editor was deliberately **stateless**: the browser held the working +model, computed totals/VAT client-side (`invSumUpdate` in `fis.inv_shared.js`) and +re-posted the whole `invc` JSON on every preview/save. `EVAL_live_invoice_editing.md` +(2026) recommended keeping it that way and **against** a server-cached, SignalR-driven +model, because the real-time/co-editing benefits were weak for a single back-office +editor. + +The product owner has since decided the trade-off differently and prioritised a +**single source of truth in the backend** with server-computed sums, server-side +plausibility/consistency checks, in-place PDF preview without re-upload, an automatic +change history, and an explicit discard. This decision records that reversal and the +architecture chosen to implement it. + +## Decision +While a user edits an invoice draft, the authoritative state lives **server-side** in +an in-memory `InvoiceDraftSession` (`Fuchs/code/InvoiceDraftSession.cs`), held by the +singleton `IInvoiceDraftCache` and orchestrated by the scoped `IInvoiceDraftService` +(`InvoiceDraftEditService`). The browser is a pure view/input layer. + +- **Truth & calculation on the server.** `InvoiceDraftCalculator` is the pure, + unit-tested port of the former client-side math (`quantChange` + `invSumUpdate`), + including the §13b reverse-charge rule and VAT-per-rate grouping. The browser never + computes totals; it renders the server's `sums`. +- **Commands are ordinary POSTs; signals are SignalR.** The editor posts single edits + to `inv/dpatch` (and `dopen`/`dstate`/`dpreview`/`dsave`/`dhistory`/`ddiscard`/`dclose`). + The server mutates the session, recomputes, validates, bumps a version, and pings the + editing browser (`draftReady`) to re-fetch `inv/dstate`. See + [0007](0007-targeted-draft-signalr-groups.md) for the targeted-signal transport. +- **Cache-only until Zwischenspeichern/Finalise.** Opening builds the session (from a + brand-new payload or by reloading a DB draft); edits touch only the cache. `dsave` + flushes the session to the DB by reusing the existing + `IInvoiceService.RegisterInvoiceAsync` — **no new persistence path** — and reports + success/failure through the existing `IEventService` (ADR 0001). Finalise continues + through `req/sconf`. +- **Preview from cache.** `inv/dpreview` renders the draft PDF straight from the session + (synthesised registration), with no client upload. +- **Automatic change history.** Every applied patch appends a `ChangeHistoryEntry` + (cache-only, never persisted); `inv/dhistory` exposes it for the "Änderungshistorie" + dialog. +- **Idle lifecycle with user warning.** `InvoiceDraftExpiryService` warns the editing + browser before a session's idle TTL lapses (`draftExpiring`) and, on eviction, tells + it to close the editor with a reason (`draftClosed`). TTL and warning lead are under + `Fuchs:DraftEditing`. + +## Consequences +- The server is now **stateful for in-progress drafts**. This is acceptable for a + single-instance deployment; **scale-out requires sticky sessions or a distributed + cache/SignalR backplane** — none exist today, so this is a documented limitation, not + a silent assumption. +- New editor interactions must be modelled as a **delta** applied server-side (add a + case in `InvoiceDraftEditService.ApplyDelta` + calculator handling), never as a new + client-side calculation. Do not reintroduce client-side totals. +- `FdsInvoiceData` stays a pure data holder; `InvoiceDraftSession` is likewise a data + holder, with all logic in the service/calculator (mirrors the existing service split). +- Reminders (Mahnungen) are intended to follow the identical pattern as a second phase; + this decision covers invoices first (the pilot) and applies to the reminder mirror + when built. +- `EVAL_live_invoice_editing.md` and `INVOICE_LIFECYCLE.md` §4/§10 (the "stateless + editor" invariant) are superseded by this decision for the draft-editing flow and have + been annotated accordingly. + +## Alternatives considered +- **Keep the stateless editor** (the prior recommendation): rejected by the product + owner in favour of a backend single source of truth. +- **Full bidirectional SignalR hub for commands too**: rejected — edits as POSTs reuse + the existing controller/auth pattern and avoid a command reconnect/replay protocol; the + hub carries only coordination signals. +- **Write-through to the DB on every edit**: rejected — conflicts with the + "Zwischenspeichern = persist the cache" semantics and adds DB load; the cache is the + truth until an explicit save/finalise. diff --git a/Fuchs/Docs/Decisions/0007-targeted-draft-signalr-groups.md b/Fuchs/Docs/Decisions/0007-targeted-draft-signalr-groups.md new file mode 100644 index 0000000..f39ccec --- /dev/null +++ b/Fuchs/Docs/Decisions/0007-targeted-draft-signalr-groups.md @@ -0,0 +1,59 @@ +--- +status: Accepted +date: 2026-07-10 +applyTo: + - "Fuchs/Notifications/DraftPreviewHub.cs" + - "Fuchs/Notifications/IDraftNotifier.cs" + - "Fuchs/Notifications/DraftNotifier.cs" + - "Fuchs/Program.cs" + - "Fuchs/js/intranet/**" +supersededBy: "" +--- + +# 0007 — Draft-editing signals are targeted via a dedicated hub with per-draft groups + +## Context +Backend-authoritative draft editing (ADR 0006) needs to notify **exactly the one +browser** editing a given draft that its cached state changed, is about to expire, or +was closed. The existing `NotificationHub` (ADR 0002) deliberately **broadcasts** every +business toast to all logged-in sessions and explicitly deferred per-user/targeted +delivery as "a new decision". Draft coordination pings are high-frequency, per-editor, +and must not spray to every session. + +## Decision +Draft signals use a **dedicated** SignalR hub, `DraftPreviewHub`, mapped at +`/draftpreview` (separate from `NotificationHub` at `/notifications`). Targeting is by +**SignalR group named after the draft's session token**: + +- The client calls the hub methods `JoinDraft(token)` / `LeaveDraft(token)` to + subscribe/unsubscribe its connection to a draft's group. The hub carries **no + commands** — only group membership (edits are POSTs; see ADR 0006). +- The server sends via `IDraftNotifier` (`DraftNotifier`) to `Clients.Group(token)`: + `draftReady{token,version}` (re-fetch), `draftExpiring{token,secondsLeft}` (idle + warning), `draftClosed{token,reason}` (session evicted/discarded → close the editor). +- Like `EventService`, delivery failures are logged and swallowed — a missed + coordination ping must never fail the underlying operation; the client also re-syncs on + reconnect and on its next POST. + +Business success/failure messages for draft operations (e.g. "Zwischenstand +gespeichert") continue to flow through `IEventService`/`NotificationHub`, **not** this +hub — the two channels stay separate. + +## Consequences +- The session **token doubles as the group name**; it is an opaque GUID and must not + encode sensitive data. Any browser that knows a token can join its group, so tokens + must be treated as capabilities and only handed to the authenticated editor that opened + the draft. +- Adding a new draft signal means adding a method to `IDraftNotifier` + `DraftNotifier` + and a client handler in `$fis.draft` — not overloading the business notification path. +- ADR 0002 is unchanged: `NotificationHub` stays broadcast-only for toasts. This hub is + the answer to its "if per-user targeting becomes necessary, that is a new decision". +- Multi-instance scale-out needs a SignalR backplane for group delivery — same limitation + as ADR 0006. + +## Alternatives considered +- **Reuse `NotificationHub` with groups**: rejected — it would entangle broadcast toasts + with targeted, high-frequency editing pings and force ADR 0002's broadcast contract to + change. A separate hub keeps the concerns and their decisions independent. +- **Per-user groups (by account id)**: rejected — a user may open two drafts/tabs; + per-draft-token groups target the precise editor and naturally support that. diff --git a/Fuchs/Docs/EVAL_live_invoice_editing.md b/Fuchs/Docs/EVAL_live_invoice_editing.md index 7038470..bdf22ac 100644 --- a/Fuchs/Docs/EVAL_live_invoice_editing.md +++ b/Fuchs/Docs/EVAL_live_invoice_editing.md @@ -1,5 +1,17 @@ # Evaluation — Backend-cached invoice editing over SignalR +> **⚠️ Superseded (2026-07-10).** This note's recommendation (keep the editor +> stateless; do **not** build the SignalR/server-cached model) was reversed by the +> product owner. Invoice draft editing is now backend-authoritative over an in-memory +> cache — see **ADR +> [`Decisions/0006-backend-authoritative-draft-editing.md`](Decisions/0006-backend-authoritative-draft-editing.md)**, +> [`Decisions/0007-targeted-draft-signalr-groups.md`](Decisions/0007-targeted-draft-signalr-groups.md) +> and the concept doc [`Concepts/live-draft-editing.md`](Concepts/live-draft-editing.md). +> The analysis below is retained for the historical rationale and the risks it flagged +> (server-held state, scaling/backplane, reconnect) — which the new design addresses or +> accepts explicitly as documented limitations. + + **Idea (as proposed):** hold invoices that users are editing in a **server-side cache**, keep a **SignalR / WebSocket** connection open, apply each front-end change **in the backend**, and **push the recomputed state back** to the browser. diff --git a/Fuchs/Docs/INVOICE_LIFECYCLE.md b/Fuchs/Docs/INVOICE_LIFECYCLE.md index 90b3cb6..90ded0f 100644 --- a/Fuchs/Docs/INVOICE_LIFECYCLE.md +++ b/Fuchs/Docs/INVOICE_LIFECYCLE.md @@ -337,9 +337,18 @@ flowchart TD ## 10. Key invariants worth remembering -- **Stateless editor**: every preview/save/finalise call re-posts the full - `invc` JSON; the server never holds a partial invoice in memory or session - between requests (see `EVAL_live_invoice_editing.md`). +> **⚠️ Updated (2026-07-10):** the "stateless editor" invariant below describes the +> **legacy** draft-editing flow. Invoice draft editing is being moved to a +> **backend-authoritative** model where the server holds the draft in an in-memory +> cache (the single source of truth), the browser posts single edits and re-fetches on +> a SignalR signal, and totals are computed server-side. See ADR +> [`Decisions/0006-backend-authoritative-draft-editing.md`](Decisions/0006-backend-authoritative-draft-editing.md) +> and [`Concepts/live-draft-editing.md`](Concepts/live-draft-editing.md). Finalise/email +> (§5–§6) are unchanged. The remaining invariants below still hold. + +- **Stateless editor** *(legacy — see the note above; superseded by ADR 0006)*: every + preview/save/finalise call re-posts the full `invc` JSON; the server never holds a + partial invoice in memory or session between requests (see `EVAL_live_invoice_editing.md`). - **Totals come from the registration, not the rendered lines**: `sms.ttn` /`sms.ttb` (posted) become `InvoiceBalance`/`InvoiceBalance_net`; display mode (set pricing) never changes what the customer owes. diff --git a/Fuchs/Notifications/DraftNotifier.cs b/Fuchs/Notifications/DraftNotifier.cs new file mode 100644 index 0000000..7b661d5 --- /dev/null +++ b/Fuchs/Notifications/DraftNotifier.cs @@ -0,0 +1,45 @@ +using Microsoft.AspNetCore.SignalR; +using Microsoft.Extensions.Logging; + +namespace Fuchs.Notifications; + +/// +/// over the . Sends to the +/// SignalR group named after the draft token so only the editing browser is notified. +/// Like , delivery failures are logged and +/// swallowed — a missed coordination ping must never fail the underlying operation +/// (the client also re-syncs on reconnect and on its next POST). +/// +public sealed class DraftNotifier : IDraftNotifier +{ + private readonly IHubContext _hub; + private readonly ILogger _logger; + + public DraftNotifier(IHubContext hub, ILogger logger) + { + _hub = hub; + _logger = logger; + } + + public Task SignalDraftReadyAsync(string token, int version, CancellationToken cancellationToken = default) => + SendAsync(token, "draftReady", new { token, version }, cancellationToken); + + public Task SignalExpiringAsync(string token, int secondsLeft, CancellationToken cancellationToken = default) => + SendAsync(token, "draftExpiring", new { token, secondsLeft }, cancellationToken); + + public Task SignalClosedAsync(string token, string reason, CancellationToken cancellationToken = default) => + SendAsync(token, "draftClosed", new { token, reason }, cancellationToken); + + private async Task SendAsync(string token, string method, object payload, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(token)) return; + try + { + await _hub.Clients.Group(token).SendAsync(method, payload, cancellationToken); + } + catch (Exception ex) + { + _logger.LogWarning(ex, "Draft signal {Method} failed for token {Token}", method, token); + } + } +} diff --git a/Fuchs/Notifications/DraftPreviewHub.cs b/Fuchs/Notifications/DraftPreviewHub.cs new file mode 100644 index 0000000..c361f26 --- /dev/null +++ b/Fuchs/Notifications/DraftPreviewHub.cs @@ -0,0 +1,31 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.SignalR; + +namespace Fuchs.Notifications; + +/// +/// SignalR hub for live invoice/reminder draft editing (see ADR 0006 / 0007). +/// +/// Deliberately separate from : that hub broadcasts +/// business toasts to all logged-in sessions (ADR 0002), whereas draft +/// signals must be targeted at the one browser editing a given draft. +/// Targeting is done with a SignalR group named after the draft's session token — +/// each editor calls after opening a draft. +/// +/// The hub carries no commands: edits, saves and discards travel as ordinary POSTs +/// (see ADR 0006). The hub only manages group membership and delivers the server's +/// draftReady / draftExpiring / draftClosed signals. +/// +[Authorize] +public sealed class DraftPreviewHub : Hub +{ + /// Subscribes this connection to a draft's signal group. + public Task JoinDraft(string token) => + string.IsNullOrEmpty(token) ? Task.CompletedTask + : Groups.AddToGroupAsync(Context.ConnectionId, token); + + /// Unsubscribes this connection from a draft's signal group. + public Task LeaveDraft(string token) => + string.IsNullOrEmpty(token) ? Task.CompletedTask + : Groups.RemoveFromGroupAsync(Context.ConnectionId, token); +} diff --git a/Fuchs/Notifications/IDraftNotifier.cs b/Fuchs/Notifications/IDraftNotifier.cs new file mode 100644 index 0000000..7993ff1 --- /dev/null +++ b/Fuchs/Notifications/IDraftNotifier.cs @@ -0,0 +1,20 @@ +namespace Fuchs.Notifications; + +/// +/// Sends system-internal draft-editing signals to the one browser editing a +/// given draft, over the group keyed by session token +/// (see ADR 0006 / 0007). These are coordination pings, not business notifications: +/// user-facing success/failure messages (e.g. "Zwischenstand gespeichert") still go +/// through / . +/// +public interface IDraftNotifier +{ + /// The cached draft reached a new — the client should re-fetch its state. + Task SignalDraftReadyAsync(string token, int version, CancellationToken cancellationToken = default); + + /// The draft is about to expire in s unless saved — warn the user. + Task SignalExpiringAsync(string token, int secondsLeft, CancellationToken cancellationToken = default); + + /// The draft session was removed (evicted/expired/discarded) — the client must close the editor and show why. + Task SignalClosedAsync(string token, string reason, CancellationToken cancellationToken = default); +} diff --git a/Fuchs/Program.cs b/Fuchs/Program.cs index b2ad009..7a36eb7 100644 --- a/Fuchs/Program.cs +++ b/Fuchs/Program.cs @@ -110,6 +110,14 @@ public class Program builder.Services.AddScoped(); builder.Services.AddScoped(); + // Live, backend-authoritative invoice draft editing (ADR 0006): an in-memory + // draft cache (singleton), the scoped edit orchestrator, a targeted SignalR + // notifier over the dedicated DraftPreviewHub, and the idle-expiry monitor. + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + builder.Services.AddScoped(); + builder.Services.AddHostedService(); + // Secondary archive: invoice/reminder PDFs additionally stored in Azure Blob Storage. // Disabled by default (Fuchs:AzureStorage:Enabled) — see AzureBlobStorageService. builder.Services.Configure(builder.Configuration.GetSection("Fuchs:AzureStorage")); @@ -184,6 +192,7 @@ public class Program app.UseAuthentication(); app.UseAuthorization(); app.MapHub("/notifications"); + app.MapHub("/draftpreview"); // Intranet routes (root-level — this IS the website) app.MapControllerRoute( diff --git a/Fuchs/Services/IInvoiceDraftCache.cs b/Fuchs/Services/IInvoiceDraftCache.cs new file mode 100644 index 0000000..e7eda33 --- /dev/null +++ b/Fuchs/Services/IInvoiceDraftCache.cs @@ -0,0 +1,26 @@ +using Fuchs.intranet; + +namespace Fuchs.Services; + +/// +/// In-memory store of live invoice draft editing sessions (see ADR 0006). +/// Singleton, single-instance only — scale-out would need a distributed cache / +/// sticky sessions (documented limitation). Keyed by the session token. +/// +public interface IInvoiceDraftCache +{ + /// Stores (or replaces) a session under its token. + void Set(InvoiceDraftSession session); + + /// Returns the session for the token, or null if absent/evicted. Touches LastAccessUtc on hit. + InvoiceDraftSession? Get(string token); + + /// Removes the session (explicit close/discard/finalise). Returns the removed session, if any. + InvoiceDraftSession? Remove(string token); + + /// Snapshot of all live sessions — used by the expiry monitor. Does not touch access time. + IReadOnlyList Snapshot(); + + /// The configured idle time-to-live before a session is eligible for eviction. + TimeSpan IdleTtl { get; } +} diff --git a/Fuchs/Services/IInvoiceDraftService.cs b/Fuchs/Services/IInvoiceDraftService.cs new file mode 100644 index 0000000..9f3c082 --- /dev/null +++ b/Fuchs/Services/IInvoiceDraftService.cs @@ -0,0 +1,82 @@ +using Fuchs.intranet; +using MigraDoc.DocumentObjectModel; +using Newtonsoft.Json.Linq; +using OCORE.security; + +namespace Fuchs.Services; + +/// +/// Orchestrates a live, backend-authoritative invoice draft editing session +/// (ADR 0006). Owns the lifecycle around an : +/// open (seed the cache), apply single edits, build the view state, render a PDF +/// preview from the cache, flush to the DB ("Zwischenspeichern"), discard (reload +/// from the DB) and expose the change history. All totals/VAT are computed by +/// — the browser never calculates. +/// +public interface IInvoiceDraftService +{ + /// + /// Seeds a new cache session for a brand-new draft from the editor's initially + /// assembled payload (admin / new / req blocks). Computes + /// totals + validation and returns the session (with its fresh token/version). + /// + InvoiceDraftSession OpenFromPayload(JObject payload, string userAccountId); + + /// + /// Seeds a cache session by loading an existing DB draft (fds__getInvoice) + /// and reshaping it into the editor's block/item structure. Computes + caches. + /// + Task OpenFromDraftAsync(string invId, string userAccountId, DatabaseSecurity dbSec); + + /// Returns the cached session for the token (touching its TTL), or null if absent/expired. + InvoiceDraftSession? Get(string token); + + /// + /// Applies one editor change to the cached session: mutates the payload, + /// re-derives affected item math + totals, re-validates, appends a history entry + /// and bumps the version. Returns the mutated session, or null if the token is unknown. + /// + InvoiceDraftSession? ApplyPatch(string token, InvoiceDraftDelta delta); + + /// Builds the JSON view-state DTO the frontend renders (payload + server sums + validation + version). + object BuildState(InvoiceDraftSession session); + + /// The draft's change history for the "Änderungshistorie" dialog (empty if the token is unknown). + IReadOnlyList GetHistory(string token); + + /// + /// Persists the cached session to the DB via the existing invoice registration + /// path ("Zwischenspeichern"). Sets on success. + /// Returns the registered invoice data (for the success event), or null if the token is unknown. + /// + Task FlushToDbAsync(string token, string userAccountId, DatabaseSecurity dbSec); + + /// Renders a draft PDF straight from the cached session (no client upload). Null if token unknown. + Document? RenderPreview(string token); + + /// + /// Discards the session's in-memory changes by reloading it from the DB draft + /// (requires a prior flush / an existing InvId). Bumps the version so the + /// client refetches. Returns the reloaded session, or null if the token is unknown. + /// + Task DiscardAsync(string token, string userAccountId, DatabaseSecurity dbSec); + + /// Removes the session from the cache (explicit close/finalise). Returns true if one was present. + bool Close(string token); +} + +/// +/// A single editor change posted to inv/dpatch. names the +/// field/operation (e.g. "email", "p13b", "item.qty"); is the item or +/// block id it applies to (when relevant); is the new value. +/// +public sealed class InvoiceDraftDelta +{ + public string Target { get; set; } = ""; + public string Ref { get; set; } = ""; + public JToken? Value { get; set; } + + /// The new value as a string (empty when null), for history and simple field assignments. + public string ValueString => + Value == null || Value.Type == JTokenType.Null ? "" : Value.Type == JTokenType.String ? Value.Value() ?? "" : Value.ToString(); +} diff --git a/Fuchs/Services/InvoiceDraftCache.cs b/Fuchs/Services/InvoiceDraftCache.cs new file mode 100644 index 0000000..ecdff69 --- /dev/null +++ b/Fuchs/Services/InvoiceDraftCache.cs @@ -0,0 +1,60 @@ +using System.Collections.Concurrent; +using Fuchs.intranet; +using Microsoft.Extensions.Configuration; + +namespace Fuchs.Services; + +/// +/// Single-instance, in-memory implementation of +/// backed by a keyed by session +/// token. A plain dictionary (rather than IMemoryCache) is used on purpose: +/// the needs to enumerate sessions and warn +/// the user before eviction, which opaque cache-entry expiry does not allow. +/// +/// Idle TTL and the pre-expiry warning lead time are configurable under +/// Fuchs:DraftEditing (IdleMinutes / ExpiryWarnMinutes). +/// +public sealed class InvoiceDraftCache : IInvoiceDraftCache +{ + private readonly ConcurrentDictionary _sessions = new(StringComparer.Ordinal); + + public TimeSpan IdleTtl { get; } + /// How long before the idle TTL a warning is emitted to the user. + public TimeSpan ExpiryWarnLead { get; } + + public InvoiceDraftCache(IConfiguration configuration) + { + int idleMinutes = configuration.GetValue("Fuchs:DraftEditing:IdleMinutes", 30); + int warnMinutes = configuration.GetValue("Fuchs:DraftEditing:ExpiryWarnMinutes", 5); + IdleTtl = TimeSpan.FromMinutes(Math.Max(1, idleMinutes)); + ExpiryWarnLead = TimeSpan.FromMinutes(Math.Clamp(warnMinutes, 1, Math.Max(1, idleMinutes - 1))); + } + + public void Set(InvoiceDraftSession session) + { + if (string.IsNullOrEmpty(session.Token)) throw new ArgumentException("Session has no token.", nameof(session)); + session.Touch(); + _sessions[session.Token] = session; + } + + public InvoiceDraftSession? Get(string token) + { + if (string.IsNullOrEmpty(token)) return null; + if (_sessions.TryGetValue(token, out var s)) + { + s.Touch(); + // A touch resets the idle window, so a fresh warning is due next time it lapses. + s.ExpiryWarningSent = false; + return s; + } + return null; + } + + public InvoiceDraftSession? Remove(string token) + { + if (string.IsNullOrEmpty(token)) return null; + return _sessions.TryRemove(token, out var s) ? s : null; + } + + public IReadOnlyList Snapshot() => _sessions.Values.ToList(); +} diff --git a/Fuchs/Services/InvoiceDraftEditService.cs b/Fuchs/Services/InvoiceDraftEditService.cs new file mode 100644 index 0000000..0178167 --- /dev/null +++ b/Fuchs/Services/InvoiceDraftEditService.cs @@ -0,0 +1,504 @@ +using System.Globalization; +using System.Web; +using Fuchs.intranet; +using Microsoft.Data.SqlClient; +using Microsoft.Extensions.Logging; +using MigraDoc.DocumentObjectModel; +using Newtonsoft.Json.Linq; +using OCORE.security; +using OCORE.SQL; +using static OCORE.commons; +using static OCORE.OCORE_dictionaries; +using static OCORE.SQL.sql; + +namespace Fuchs.Services; + +/// +/// Backend-authoritative invoice draft editing (ADR 0006). Holds the truth in an +/// (via ), applies +/// single edits, computes totals with , renders +/// previews and flushes to the DB by reusing the existing +/// registration path — no new persistence. Deliberately I/O-thin so the calculation +/// remains unit-testable. +/// +public sealed class InvoiceDraftEditService : IInvoiceDraftService +{ + private readonly IInvoiceDraftCache _cache; + private readonly IInvoiceService _invoices; + private readonly Fuchs_intranet _intranet; + private readonly ILogger _logger; + + public InvoiceDraftEditService(IInvoiceDraftCache cache, IInvoiceService invoices, + Fuchs_intranet intranet, ILogger logger) + { + _cache = cache; + _invoices = invoices; + _intranet = intranet; + _logger = logger; + } + + private string Conn => _intranet.Intranet__SQLConnectionString; + + // ── Open ───────────────────────────────────────────────────────────────── + public InvoiceDraftSession OpenFromPayload(JObject payload, string userAccountId) + { + var session = new InvoiceDraftSession + { + Token = NewToken(), + UserAccountId = userAccountId, + InvId = payload["invid"]?.Value() ?? payload["id"]?.Value() ?? "" + }; + session.Admin = payload["admin"] as JObject ?? new JObject(); + session.New = payload["new"] as JObject ?? new JObject(); + session.Req = payload["req"] as JArray ?? new JArray(); + Refresh(session); + _cache.Set(session); + _logger.LogInformation("Draft session {Token} opened from payload (invId={InvId}, user={User})", + session.Token, session.InvId, userAccountId); + return session; + } + + public async Task OpenFromDraftAsync(string invId, string userAccountId, DatabaseSecurity dbSec) + { + var session = new InvoiceDraftSession { Token = NewToken(), UserAccountId = userAccountId, InvId = invId }; + await LoadDraftIntoAsync(session, invId, userAccountId, dbSec); + Refresh(session); + _cache.Set(session); + _logger.LogInformation("Draft session {Token} opened from DB draft {InvId} (user={User})", + session.Token, invId, userAccountId); + return session; + } + + public InvoiceDraftSession? Get(string token) => _cache.Get(token); + + // ── Patch ────────────────────────────────────────────────────────────────── + public InvoiceDraftSession? ApplyPatch(string token, InvoiceDraftDelta delta) + { + var session = _cache.Get(token); + if (session == null) return null; + + string oldValue = ""; + bool mutated = ApplyDelta(session, delta, ref oldValue); + if (!mutated) + { + _logger.LogDebug("Draft {Token}: no-op patch target={Target} ref={Ref}", token, delta.Target, delta.Ref); + return session; + } + + Refresh(session); + session.Version++; + session.History.Add(new ChangeHistoryEntry + { + UserAccountId = session.UserAccountId, + Target = delta.Target, + Ref = delta.Ref, + OldValue = oldValue, + NewValue = delta.ValueString, + Version = session.Version + }); + _cache.Set(session); + return session; + } + + /// Applies one delta to the payload; returns whether anything changed and captures the prior value. + private static bool ApplyDelta(InvoiceDraftSession s, InvoiceDraftDelta d, ref string oldValue) + { + switch (d.Target) + { + case "email": return SetNew(s, "invoiceemail", d, ref oldValue); + case "address": return SetNew(s, "invoiceaddress", d, ref oldValue); + case "title": return SetNew(s, "invoicetitle", d, ref oldValue); + case "provisionperiod": return SetNew(s, "provisionperiod", d, ref oldValue); + case "provisionlocation": + oldValue = Str(s.New["provisionlocation"]); + s.New["provisionlocation"] = d.ValueString; + s.New["loc"] = d.ValueString; // editor mirrors both + return true; + case "contact": return SetContact(s, d, ref oldValue); + case "setmode": return SetAdmin(s, "setmode", d, ref oldValue); + case "p13b": + oldValue = Str(s.Admin["p13b"]); + bool next = d.Value != null && d.Value.Type != JTokenType.Null + ? AsBool(d.Value) + : !AsBool(s.Admin["p13b"]); // toggle when no explicit value + s.Admin["p13b"] = next; + return true; + case "item.qty": return SetItem(s, d, "quantityhours", recompute: true, ref oldValue); + case "item.price": return SetItem(s, d, "net", recompute: true, ref oldValue); + case "item.note": return SetItem(s, d, "Note", recompute: false, ref oldValue); + case "item.remove": return RemoveItem(s, d, ref oldValue); + case "block.combine": + case "item.combine": return SetBlockFlag(s, d, "onesum", ref oldValue); + case "block.remove": return RemoveBlock(s, d, ref oldValue); + default: return false; + } + } + + private static bool SetNew(InvoiceDraftSession s, string key, InvoiceDraftDelta d, ref string oldValue) + { + oldValue = Str(s.New[key]); + s.New[key] = d.ValueString; + return true; + } + + private static bool SetAdmin(InvoiceDraftSession s, string key, InvoiceDraftDelta d, ref string oldValue) + { + oldValue = Str(s.Admin[key]); + s.Admin[key] = d.ValueString; + return true; + } + + private static bool SetContact(InvoiceDraftSession s, InvoiceDraftDelta d, ref string oldValue) + { + oldValue = Str(s.New["CustomValues"]); + JObject cvo = TryParseObject(oldValue); + if (d.Value is JObject vo) + { + cvo["contactName"] = vo["name"] ?? vo["contactName"] ?? ""; + cvo["contactEmail"] = vo["email"] ?? vo["contactEmail"] ?? ""; + } + s.New["CustomValues"] = cvo.ToString(Newtonsoft.Json.Formatting.None); + return true; + } + + private static bool SetItem(InvoiceDraftSession s, InvoiceDraftDelta d, string key, bool recompute, ref string oldValue) + { + var item = FindItem(s, d.Ref); + if (item == null) return false; + oldValue = Str(item[key]); + item[key] = d.Value ?? JValue.CreateString(d.ValueString); + if (recompute) InvoiceDraftCalculator.RecomputeItem(item); + return true; + } + + private static bool RemoveItem(InvoiceDraftSession s, InvoiceDraftDelta d, ref string oldValue) + { + var item = FindItem(s, d.Ref); + if (item == null) return false; + oldValue = Str(item["NameOrNumber"]).ne(Str(item["htmltext"])); + item.Remove(); + return true; + } + + private static bool SetBlockFlag(InvoiceDraftSession s, InvoiceDraftDelta d, string key, ref string oldValue) + { + var block = FindBlock(s, d.Ref); + if (block == null) return false; + oldValue = Str(block[key]); + block[key] = d.Value != null && d.Value.Type != JTokenType.Null ? AsBool(d.Value) : !AsBool(block[key]); + return true; + } + + private static bool RemoveBlock(InvoiceDraftSession s, InvoiceDraftDelta d, ref string oldValue) + { + var block = FindBlock(s, d.Ref); + if (block == null) return false; + oldValue = Str(block["text"]); + block.Remove(); + return true; + } + + // ── View state / history ──────────────────────────────────────────────── + public object BuildState(InvoiceDraftSession session) + { + session.Touch(); + return new + { + token = session.Token, + version = session.Version, + invid = session.InvId, + isDraft = session.IsDraft, + admin = session.Admin, + @new = session.New, + req = session.Req, + sums = new + { + total_net = session.Sums.TotalNet, + total_gross = session.Sums.TotalGross, + total_vat = session.Sums.TotalVat, + service_net = session.Sums.ServiceNet, + service_vat = session.Sums.ServiceVat, + vat = session.Sums.VatByRate, + block_net = session.Sums.NetByBlock + }, + validation = session.ValidationMessages.Select(v => new { field = v.Field, severity = v.Severity, message = v.Message }), + historyCount = session.History.Count + }; + } + + public IReadOnlyList GetHistory(string token) => + _cache.Get(token)?.History ?? (IReadOnlyList)Array.Empty(); + + // ── Flush / preview / discard ───────────────────────────────────────────── + public async Task FlushToDbAsync(string token, string userAccountId, DatabaseSecurity dbSec) + { + var session = _cache.Get(token); + if (session == null) return null; + + var fds = BuildFdsData(session); + bool change = !string.IsNullOrEmpty(session.InvId); + var reg = await _invoices.RegisterInvoiceAsync(fds, change, session.InvId, userAccountId, dbSec); + if (!string.IsNullOrEmpty(reg.Id)) + { + session.InvId = reg.Id; + _cache.Set(session); + _logger.LogInformation("Draft {Token} flushed to DB invoice {InvId} (change={Change}, user={User})", + token, reg.Id, change, userAccountId); + } + return reg; + } + + public Document? RenderPreview(string token) + { + var session = _cache.Get(token); + if (session == null) return null; + var fds = BuildFdsData(session); + fds.InvoiceRegistration = SynthesizeRegistration(session); + fds.IsDraft = true; + return _invoices.GenerateInvoicePdf(fds, draft: true); + } + + public async Task DiscardAsync(string token, string userAccountId, DatabaseSecurity dbSec) + { + var session = _cache.Get(token); + if (session == null) return null; + if (string.IsNullOrEmpty(session.InvId)) + { + _logger.LogInformation("Draft {Token} discard requested but never saved — nothing to reload (user={User})", token, userAccountId); + return session; + } + await LoadDraftIntoAsync(session, session.InvId, userAccountId, dbSec); + Refresh(session); + session.Version++; + session.History.Add(new ChangeHistoryEntry + { + UserAccountId = userAccountId, + Target = "discard", + NewValue = "Änderungen verworfen", + Version = session.Version + }); + _cache.Set(session); + _logger.LogInformation("Draft {Token} discarded, reloaded from DB invoice {InvId} (user={User})", token, session.InvId, userAccountId); + return session; + } + + public bool Close(string token) => _cache.Remove(token) != null; + + // ── Internals ────────────────────────────────────────────────────────────── + private static void Refresh(InvoiceDraftSession session) + { + InvoiceDraftCalculator.RecomputeTotals(session); + InvoiceDraftCalculator.Validate(session); + } + + private static string NewToken() => Guid.NewGuid().ToString("N"); + + private static JObject? FindBlock(InvoiceDraftSession s, string blockId) + { + foreach (var b in s.Req) + if (b is JObject bo && Str(bo["Id"]) == blockId) return bo; + return null; + } + + private static JObject? FindItem(InvoiceDraftSession s, string itemId) + { + foreach (var b in s.Req) + if (b is JObject bo && bo["items"] is JArray items) + foreach (var it in items) + if (it is JObject io && Str(io["Id"]) == itemId) return io; + return null; + } + + /// Builds the from the session — the server-side port of invcPayload. + private FdsInvoiceData BuildFdsData(InvoiceDraftSession session) + { + var adm = (JObject)session.Admin.DeepClone(); + var nw = (JObject)session.New.DeepClone(); + nw["total_net"] = session.Sums.TotalNet; + nw["total_gross"] = session.Sums.TotalGross; + nw["title"] = nw["invoicetitle"] ?? nw["title"] ?? ""; + nw["provisionlocation"] = nw["loc"] ?? nw["provisionlocation"] ?? ""; + nw["paymentterm"] = adm["paymentterms"] ?? nw["paymentterm"] ?? ""; + adm["customerid"] = adm["customerid"] ?? adm["CustomerId"]; + + var vat = new JObject(); + foreach (var kv in session.Sums.VatByRate) vat[kv.Key] = kv.Value; + var sms = new JObject + { + ["ttn"] = session.Sums.TotalNet, + ["ttb"] = session.Sums.TotalGross, + ["ttvat"] = session.Sums.TotalVat, + ["tscn"] = session.Sums.ServiceNet, + ["tscvat"] = session.Sums.ServiceVat, + ["vat"] = vat + }; + + var jobj = new JObject + { + ["admin"] = adm, + ["new"] = nw, + ["sms"] = sms, + ["req"] = session.Req.DeepClone() + }; + return new FdsInvoiceData(jobj); + } + + /// + /// Synthesises the InvoiceRegistration dictionary a draft PDF render needs, + /// straight from the cached session — so a preview requires no DB round-trip and no + /// client upload. Mirrors the columns fds__getInvoice would return for a draft. + /// + private GenericObjectDictionary SynthesizeRegistration(InvoiceDraftSession session) + { + string title = Str(session.New["invoicetitle"]).ne(Str(session.New["title"])); + string loc = Str(session.New["provisionlocation"]).ne(Str(session.New["loc"])); + var d = new Dictionary + { + ["Id"] = session.InvId, + ["InvoiceType"] = Str(session.Admin["type"]).ne("R"), + ["InvoiceId"] = "", + ["InvoiceTitle"] = title, + ["SendToAddress"] = Str(session.New["invoiceaddress"]), + ["SendToEmail"] = Str(session.New["invoiceemail"]), + ["ProvisionLocation"] = loc, + ["ProvisionPeriod"] = Str(session.New["provisionperiod"]), + ["PaymentTerm"] = Str(session.Admin["paymentterms"]).ne(Str(session.New["paymentterm"])), + ["InvoiceBalance"] = session.Sums.TotalGross, + ["InvoiceBalance_net"] = session.Sums.TotalNet, + ["CustomValues"] = Str(session.New["CustomValues"]), + ["InvoiceOptions"] = BuildInvoiceOptions(session), + ["DateCreated"] = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture) + }; + + int idx = 0; + foreach (var kv in session.Sums.VatByRate) + { + idx++; + if (idx > 2) break; + d[$"InvoiceVAT_{idx}"] = kv.Key; + d[$"InvoiceVAT_net{idx}"] = kv.Value; + } + return new GenericObjectDictionary(d); + } + + /// Builds the InvoiceOptions CSV (§13b + setmode) from the session admin flags — matches . + private static string BuildInvoiceOptions(InvoiceDraftSession session) + { + var tokens = new List(); + if (AsBool(session.Admin["p13b"])) tokens.Add("§13b"); + string setmode = Str(session.Admin["setmode"]).Trim().ToLowerInvariant(); + if (setmode is "itemprices" or "setonly") tokens.Add("setmode:" + setmode); + return string.Join(",", tokens); + } + + /// Loads the DB draft (fds__getInvoice) into the session's payload — the port of HandleInvoiceGet + BuildInvoiceRequestList. + private async Task LoadDraftIntoAsync(InvoiceDraftSession session, string invId, string userAccountId, DatabaseSecurity dbSec) + { + var pl = new List { SQL_VarChar("@authuser", userAccountId), SQL_VarChar("@Id", invId) }; + var dset = await getSQLDataSet_async( + "EXECUTE [dbo].[fds__getInvoice] @Id, @authuser;", + Conn, pl, tablenames: new[] { "admin", "inv", "req", "itm" }, + Security: dbSec, options: new FIS_SQLOptions()); + if (!string.IsNullOrEmpty(dset.Exception)) + _logger.LogError("LoadDraftIntoAsync sql exception for {InvId}: {Ex}", invId, dset.Exception); + + var adminDic = dset.Table("admin").FirstRow.toObjectDictionary(); + var invDic = dset.Table("inv").FirstRow.toObjectDictionary(); + string invoiceOptions = invDic.nz("InvoiceOptions", ""); + bool p13b = invoiceOptions.Split(',').Contains("§13b"); + string setmode = invoiceOptions.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) + .FirstOrDefault(t => t.StartsWith("setmode:", StringComparison.OrdinalIgnoreCase))?["setmode:".Length..] ?? ""; + + var admin = JObject.FromObject(adminDic); + admin["type"] = admin["type"] ?? JValue.CreateString(invDic.nz("InvoiceType").Substr(0, 1)); + admin["p13b"] = p13b; + if (!string.IsNullOrEmpty(setmode)) admin["setmode"] = setmode; + + var nw = new JObject + { + ["invoicetitle"] = invDic.nz("InvoiceTitle"), + ["title"] = invDic.nz("InvoiceTitle"), + ["invoiceaddress"] = invDic.nz("SendToAddress"), + ["invoiceemail"] = invDic.nz("SendToEmail"), + ["provisionlocation"] = invDic.nz("ProvisionLocation"), + ["loc"] = invDic.nz("ProvisionLocation"), + ["provisionperiod"] = invDic.nz("ProvisionPeriod"), + ["CustomValues"] = invDic.nz("CustomValues"), + ["paymentterm"] = invDic.nz("PaymentTerm") + }; + + session.Admin = admin; + session.New = nw; + session.Req = BuildDraftBlocks(dset); + session.IsDraft = invDic.getItem("IsFinal", false) is not true; + } + + /// Reshapes the fds__getInvoice req/itm tables into the editor's block/item JSON (port of BuildInvoiceRequestList). + private static JArray BuildDraftBlocks(SQLDataSet dset) + { + var blocks = new JArray(); + foreach (System.Data.DataRow rq in dset.Tables("req").Select("", + dset.Tables("req").Columns.Contains("order") ? "order" : "")) + { + var rdic = rq.toObjectDictionary(); + var block = new JObject + { + ["Id"] = rdic["mfr__servicerequest"]?.ToString() ?? "", + ["InvRqId"] = rdic["Id"]?.ToString() ?? "", + ["text"] = HttpUtility.HtmlDecode(rdic["title"]?.ToString() ?? "") + }; + var items = new JArray(); + if (dset.Contains("itm")) + { + foreach (System.Data.DataRow sitm in dset.Tables("itm").Select( + $"[InvRqId] = '{rdic["Id"]}'", + dset.Tables("itm").Columns.Contains("order") ? "order" : "")) + { + var di = sitm.toObjectDictionary(); + double net = Convert.ToDouble(di.no("value_total", 0)); + double vat = Convert.ToDouble(di.no("vat", 0)); + items.Add(new JObject + { + ["Id"] = di["Id"]?.ToString() ?? "", + ["net_val"] = net, + ["vat_val"] = net * vat * 0.01, + ["vat"] = vat == 0 ? "" : vat.ToString("0.00", FuchsPdf.DeCulture) + "%", + ["svcnet_val"] = Convert.ToDouble(di.no("value_service", 0)), + ["svcvat_val"] = 0, + ["net"] = Convert.ToDouble(di.no("value", 0)), + ["quantity"] = di.nz("Quantity"), + ["Type"] = di.nz("Type"), + ["Note"] = di.nz("Text"), + ["htmltext"] = di.nz("Text"), + ["position"] = di.nz("Position"), + ["SortOrder"] = di.nz("SortOrder") + }); + } + } + block["items"] = items; + blocks.Add(block); + } + return blocks; + } + + // ── token helpers ───────────────────────────────────────────────────────── + private static string Str(JToken? t) => + t == null || t.Type == JTokenType.Null ? "" : t.Type == JTokenType.String ? t.Value() ?? "" : t.ToString(); + + private static bool AsBool(JToken? t) + { + if (t == null || t.Type == JTokenType.Null) return false; + if (t.Type == JTokenType.Boolean) return t.Value(); + string s = Str(t).Trim().ToLowerInvariant(); + return s is "1" or "true" or "yes" or "ja" or "on"; + } + + private static JObject TryParseObject(string json) + { + if (!string.IsNullOrWhiteSpace(json) && json.TrimStart().StartsWith('{')) + { + try { return JObject.Parse(json); } catch { /* fall through */ } + } + return new JObject(); + } +} diff --git a/Fuchs/Services/InvoiceDraftExpiryService.cs b/Fuchs/Services/InvoiceDraftExpiryService.cs new file mode 100644 index 0000000..3ef2e8e --- /dev/null +++ b/Fuchs/Services/InvoiceDraftExpiryService.cs @@ -0,0 +1,68 @@ +using Fuchs.Notifications; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace Fuchs.Services; + +/// +/// Background monitor for the invoice draft cache (ADR 0006). Because a draft's +/// truth lives only in server memory until the user saves, idle sessions must not +/// vanish silently: this service warns the editing browser before a session's +/// idle TTL lapses ("bitte zwischenspeichern"), and when the TTL is finally reached +/// it evicts the session and tells the browser to close the editor with a reason. +/// All hints travel over the via . +/// +public sealed class InvoiceDraftExpiryService : BackgroundService +{ + private readonly IInvoiceDraftCache _cache; + private readonly IDraftNotifier _notifier; + private readonly ILogger _logger; + private readonly TimeSpan _warnLead; + private readonly TimeSpan _interval; + + public InvoiceDraftExpiryService(IInvoiceDraftCache cache, IDraftNotifier notifier, + IConfiguration configuration, ILogger logger) + { + _cache = cache; + _notifier = notifier; + _logger = logger; + int warnMinutes = configuration.GetValue("Fuchs:DraftEditing:ExpiryWarnMinutes", 5); + _warnLead = TimeSpan.FromMinutes(Math.Clamp(warnMinutes, 1, Math.Max(1, (int)cache.IdleTtl.TotalMinutes - 1))); + _interval = TimeSpan.FromSeconds(30); + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + using var timer = new PeriodicTimer(_interval); + try + { + while (await timer.WaitForNextTickAsync(stoppingToken)) + await SweepAsync(stoppingToken); + } + catch (OperationCanceledException) { /* shutting down */ } + } + + /// One pass over all live sessions. Internal so it can be driven directly from unit tests. + internal async Task SweepAsync(CancellationToken cancellationToken) + { + DateTime now = DateTime.UtcNow; + foreach (var session in _cache.Snapshot()) + { + TimeSpan idle = now - session.LastAccessUtc; + if (idle >= _cache.IdleTtl) + { + _cache.Remove(session.Token); + _logger.LogInformation("Draft {Token} evicted after {Idle} idle (user={User})", + session.Token, idle, session.UserAccountId); + await _notifier.SignalClosedAsync(session.Token, "expired", cancellationToken); + } + else if (idle >= _cache.IdleTtl - _warnLead && !session.ExpiryWarningSent) + { + session.ExpiryWarningSent = true; + int secondsLeft = (int)Math.Max(0, (_cache.IdleTtl - idle).TotalSeconds); + await _notifier.SignalExpiringAsync(session.Token, secondsLeft, cancellationToken); + } + } + } +} diff --git a/Fuchs/code/InvoiceDraftCalculator.cs b/Fuchs/code/InvoiceDraftCalculator.cs new file mode 100644 index 0000000..444e57a --- /dev/null +++ b/Fuchs/code/InvoiceDraftCalculator.cs @@ -0,0 +1,193 @@ +using System.Globalization; +using Newtonsoft.Json.Linq; + +namespace Fuchs.intranet; + +/// +/// Server-side, pure port of the invoice totals/VAT math that used to live in the +/// browser (quantChange + invSumUpdate in fis.inv_shared.js). +/// This is the authoritative calculation for a live draft (ADR 0006): given the +/// editable payload of an , it (re)computes each +/// item's line values, aggregates block/rate totals into +/// , and runs the plausibility/consistency +/// checks into . +/// +/// Kept static and free of I/O so it is exhaustively unit-testable — the payoff the +/// old EVAL_live_invoice_editing.md predicted once the truth moved server-side. +/// +public static class InvoiceDraftCalculator +{ + /// + /// Re-derives a single item's line values from quantity × net price × VAT rate — + /// the port of the editor's quantChange. Only applied when an item's + /// quantity/price actually changes (osum/set/text lines keep their stored values, + /// exactly as the client only ran quantChange on edited quantity rows). + /// Mirrors the guard qty > 0 && price > 0. + /// + public static void RecomputeItem(JObject item) + { + int qty = (int)Dec(item["quantityhours"]); + decimal net = Dec(item["net"]); + decimal vat = RatePercent(Str(item["vat"])) * 0.01m; // "19%"/"19,0%" → 0.19 + if (qty > 0 && net > 0) + { + decimal netVal = decimal.Round(qty * net, 2, MidpointRounding.AwayFromZero); + decimal vatVal = decimal.Round(qty * net * vat, 2, MidpointRounding.AwayFromZero); + item["net_val"] = netVal; + item["vat_val"] = vatVal; + if (string.Equals(Str(item["Type"]), "service", StringComparison.OrdinalIgnoreCase)) + { + item["svcnet_val"] = netVal; + item["svcvat_val"] = vatVal; + } + } + } + + /// + /// Aggregates all line items into the draft's totals — the port of invSumUpdate's + /// csms accumulation plus the §13b reverse-charge rule (VAT suppressed → gross = net). + /// VAT is grouped by the item's rate string (matching the editor's sms.vat map). + /// + public static void RecomputeTotals(InvoiceDraftSession session) + { + var sums = new InvoiceDraftSums(); + bool p13b = Flag(session.Admin, "p13b"); + + foreach (var blockTok in session.Req) + { + if (blockTok is not JObject block) continue; + decimal blockNet = 0; + string blockId = Str(block["Id"]); + if (block["items"] is JArray items) + { + foreach (var itemTok in items) + { + if (itemTok is not JObject item) continue; + decimal netVal = Dec(item["net_val"]); + decimal vatVal = Dec(item["vat_val"]); + decimal svcNet = Dec(item["svcnet_val"]); + decimal svcVat = Dec(item["svcvat_val"]); + + sums.ServiceNet += svcNet; + sums.ServiceVat += svcVat; + sums.TotalNet += netVal; + sums.TotalVat += vatVal; + sums.TotalGross += netVal + vatVal; + blockNet += netVal; + + string rate = NormalizeRate(Str(item["vat"])); + if (rate.Length > 0) + sums.VatByRate[rate] = sums.VatByRate.GetValueOrDefault(rate) + vatVal; + } + } + if (!string.IsNullOrEmpty(blockId)) + sums.NetByBlock[blockId] = sums.NetByBlock.GetValueOrDefault(blockId) + blockNet; + } + + if (p13b) + { + // Reverse-charge: no VAT lines, gross equals net (mirrors invSumUpdate's else-branch). + sums.TotalGross = sums.TotalNet; + sums.TotalVat = 0; + sums.VatByRate.Clear(); + } + + session.Sums = sums; + } + + /// + /// Refreshes the draft's plausibility / consistency findings. "error" severity marks + /// issues that should block a clean finalise; "warning" is advisory. Kept in German, + /// user-readable, so the frontend can render them directly. + /// + public static void Validate(InvoiceDraftSession session) + { + session.ValidationMessages.Clear(); + void Add(string field, string sev, string msg) => + session.ValidationMessages.Add(new InvoiceDraftValidationMessage(field, sev, msg)); + + // Recipient email + string email = Str(session.New["invoiceemail"]).Trim(); + if (email.Length == 0) + Add("email", "warning", "Es ist keine E-Mail-Adresse hinterlegt — die Rechnung kann nicht per E-Mail versandt werden."); + else if (!IsValidEmail(email)) + Add("email", "error", "Die E-Mail-Adresse ist ungültig."); + + // Recipient address + if (Str(session.New["invoiceaddress"]).Trim().Length == 0) + Add("address", "warning", "Es ist keine Rechnungsanschrift hinterlegt."); + + // At least one priced line + if (!HasAnyItem(session)) + Add("items", "error", "Die Rechnung enthält keine Positionen."); + + // VAT rate sanity (only when not reverse-charge) + if (!Flag(session.Admin, "p13b")) + { + foreach (var rate in session.Sums.VatByRate.Keys) + if (!IsKnownVatRate(rate)) + Add("vat", "warning", $"Ungewöhnlicher Umsatzsteuersatz: {rate}%."); + } + + // Negative total + if (session.Sums.TotalGross < 0) + Add("total", "warning", "Der Rechnungsbetrag ist negativ."); + } + + // ── helpers ────────────────────────────────────────────────────────────── + private static bool HasAnyItem(InvoiceDraftSession session) + { + foreach (var blockTok in session.Req) + if (blockTok is JObject block && block["items"] is JArray items && items.Count > 0) + return true; + return false; + } + + /// Parses a JToken to a decimal, tolerating German ("12,50") and invariant ("12.50") strings and "%". + internal static decimal Dec(JToken? token) + { + if (token == null || token.Type == JTokenType.Null) return 0; + if (token.Type is JTokenType.Float or JTokenType.Integer) return token.Value(); + return FuchsPdf.ParseDec(Str(token), out decimal d) ? d : 0; + } + + private static string Str(JToken? token) => + token == null || token.Type == JTokenType.Null ? "" : token.Value() ?? ""; + + private static bool Flag(JObject obj, string key) + { + var t = obj[key]; + if (t == null || t.Type == JTokenType.Null) return false; + if (t.Type == JTokenType.Boolean) return t.Value(); + string s = Str(t).Trim().ToLowerInvariant(); + return s is "1" or "true" or "yes" or "ja" or "on"; + } + + /// Normalises a VAT rate string ("19,0%", "7%", "19") to a canonical numeric string ("19", "7"). + internal static string NormalizeRate(string? raw) + { + string s = (raw ?? "").Replace("%", "").Trim().Replace(',', '.'); + if (s.Length == 0) return ""; + if (!double.TryParse(s, NumberStyles.Any, CultureInfo.InvariantCulture, out double d) || d == 0) return ""; + return d == Math.Floor(d) + ? ((long)d).ToString(CultureInfo.InvariantCulture) + : d.ToString(CultureInfo.InvariantCulture); + } + + private static bool IsKnownVatRate(string rate) => rate is "0" or "7" or "19"; + + /// Parses a VAT rate string ("19%", "19,0%", "7") to its numeric percent (German/invariant tolerant). + internal static decimal RatePercent(string? raw) + { + string s = (raw ?? "").Replace("%", "").Trim().Replace(',', '.'); + return decimal.TryParse(s, NumberStyles.Any, CultureInfo.InvariantCulture, out decimal d) ? d : 0; + } + + private static bool IsValidEmail(string email) + { + int at = email.IndexOf('@'); + if (at <= 0 || at != email.LastIndexOf('@')) return false; + int dot = email.IndexOf('.', at); + return dot > at + 1 && dot < email.Length - 1; + } +} diff --git a/Fuchs/code/InvoiceDraftSession.cs b/Fuchs/code/InvoiceDraftSession.cs new file mode 100644 index 0000000..727da86 --- /dev/null +++ b/Fuchs/code/InvoiceDraftSession.cs @@ -0,0 +1,111 @@ +using Newtonsoft.Json.Linq; + +namespace Fuchs.intranet; + +/// +/// Server-side, in-memory editing state for a single invoice draft — the +/// authoritative source of truth while a back-office user is editing a draft in +/// the browser (see ADR 0006). The browser is a pure view/input layer: it posts +/// single changes (), the server +/// mutates this session, recomputes totals/VAT (replacing the former client-side +/// invSumUpdate) and validates, then signals the browser to re-fetch. +/// +/// This is a data holder only — all calculation, validation, persistence +/// and rendering live in +/// (mirroring the / +/// split). The editable payload is kept as the exact JSON shape the editor already +/// speaks (admin / new / req), so flushing to the DB can reuse +/// unchanged. +/// +public sealed class InvoiceDraftSession +{ + /// Opaque per-editor token; also the SignalR group name for targeted signals. + public string Token { get; init; } = ""; + + /// Owning user account id (drafts are single-user; used for auth + events). + public string UserAccountId { get; init; } = ""; + + /// DB invoice id once the session has been flushed (Zwischenspeichern); empty while cache-only. + public string InvId { get; set; } = ""; + + /// Always true here — sessions only ever hold unfinalised drafts. + public bool IsDraft { get; set; } = true; + + /// Bumped on every applied mutation; the browser refetches when the signalled version changes. + public int Version { get; set; } + + /// UTC of the last read/write; drives the idle sliding-TTL and expiry warnings. + public DateTime LastAccessUtc { get; set; } = DateTime.UtcNow; + + /// Guards against sending more than one expiry warning per idle window. + public bool ExpiryWarningSent { get; set; } + + // ── Editable payload (exact editor JSON shape) ─────────────────────────── + /// Header/admin flags: type, customerid, p13b, setmode, paymentterms… + public JObject Admin { get; set; } = new(); + + /// Recipient/new fields: title/invoicetitle, invoiceaddress, invoiceemail, provisionlocation/-period, CustomValues… + public JObject New { get; set; } = new(); + + /// Service-request blocks; each block is a JObject with an items JArray (the line items). + public JArray Req { get; set; } = new(); + + // ── Computed (by the draft service; never trusted from the client) ─────── + /// Server-computed totals/VAT — the values the client used to compute in invSumUpdate. + public InvoiceDraftSums Sums { get; set; } = new(); + + /// Plausibility / consistency results, refreshed on every recompute. + public List ValidationMessages { get; } = new(); + + /// Automatic change history, appended on every applied patch. Cache-only (never persisted). + public List History { get; } = new(); + + public void Touch() => LastAccessUtc = DateTime.UtcNow; +} + +/// Server-computed invoice totals — the authoritative replacement for the browser's sms object. +public sealed class InvoiceDraftSums +{ + /// Total net (ttn). + public decimal TotalNet { get; set; } + /// Total gross (ttb); equals net when §13b reverse-charge is active. + public decimal TotalGross { get; set; } + /// Total VAT (ttvat). + public decimal TotalVat { get; set; } + /// Service net (tscn) — the service-refund base. + public decimal ServiceNet { get; set; } + /// Service VAT (tscvat). + public decimal ServiceVat { get; set; } + /// VAT amount per rate string (e.g. "19" → 123.45), matching the editor's sms.vat map. + public Dictionary VatByRate { get; } = new(); + /// Net per block, keyed by block id — feeds the per-block sub-sum row. + public Dictionary NetByBlock { get; } = new(); +} + +/// A single plausibility/consistency finding for the draft. +/// Logical field the message relates to (e.g. "email", "address", "items"). +/// "error" blocks a clean finalise; "warning"/"info" are advisory. +/// German, user-readable text. +public readonly record struct InvoiceDraftValidationMessage(string Field, string Severity, string Message); + +/// +/// One automatically-recorded change in the draft's history (shown in the +/// "Änderungshistorie" dialog). Captured on every applied patch; lives only for +/// the cache lifetime of the session and is never persisted to the database. +/// +public sealed class ChangeHistoryEntry +{ + public DateTime TimestampUtc { get; init; } = DateTime.UtcNow; + /// User account id that made the change. + public string UserAccountId { get; init; } = ""; + /// The change target/op as sent by the editor (e.g. "item.qty", "email", "p13b"). + public string Target { get; init; } = ""; + /// Optional item/block id the change applied to. + public string Ref { get; init; } = ""; + /// Previous value, stringified for display (may be empty). + public string OldValue { get; init; } = ""; + /// New value, stringified for display (may be empty). + public string NewValue { get; init; } = ""; + /// Version the session reached after applying this change. + public int Version { get; init; } +} diff --git a/Fuchs/js/intranet/fis_main.js b/Fuchs/js/intranet/fis_main.js index c1d065a..02f9b77 100644 --- a/Fuchs/js/intranet/fis_main.js +++ b/Fuchs/js/intranet/fis_main.js @@ -284,3 +284,65 @@ $fis.notifications = { }, 9000); } }; + +/* Live draft-editing client (ADR 0006/0007). Separate SignalR connection to the + dedicated /draftpreview hub; the server signals the *one* browser editing a draft + (group = session token) to re-fetch (draftReady), warns before idle expiry + (draftExpiring), and tells it to close on eviction (draftClosed). The editor + (fis.inv_shared.js) registers the open draft via $fis.draft.bind(token, {...}). */ +$fis.draft = { + connection: null, + active: null, /* { token, onReady(version), onExpiring(secondsLeft), onClosed(reason) } */ + init: function () { + if (typeof signalR === 'undefined' || this.connection !== null || !$ocms.auth.useraccount_id) { + return; + } + this.connection = new signalR.HubConnectionBuilder() + .withUrl('/draftpreview') + .withAutomaticReconnect() + .build(); + this.connection.on('draftReady', (p) => this._dispatch('onReady', p, (p) => p.version)); + this.connection.on('draftExpiring', (p) => this._dispatch('onExpiring', p, (p) => p.secondsLeft)); + this.connection.on('draftClosed', (p) => this._dispatch('onClosed', p, (p) => p.reason)); + /* Re-join the active draft's group after a (re)connect — group membership is + per-connection and is lost when the socket drops. */ + this.connection.onreconnected(() => { if (this.active) { this._invoke('JoinDraft', this.active.token); } }); + this.connection.onclose(() => { + console.warn('Draft connection closed; retrying in 5s.'); + this.connection = null; + setTimeout(() => { this.init(); if (this.active) { this.bind(this.active.token, this.active); } }, 5000); + }); + this.start(); + }, + start: function () { + this.connection.start() + .then(() => { if (this.active) { this._invoke('JoinDraft', this.active.token); } }) + .catch((err) => { + console.warn('Draft connection failed to start; retrying in 5s.', err); + this.connection = null; + setTimeout(() => this.init(), 5000); + }); + }, + /* Registers the currently open draft and joins its signal group. handlers: + { onReady, onExpiring, onClosed }. */ + bind: function (token, handlers) { + if (!token) { return; } + this.active = $.extend({ token: token }, handlers || {}); + if (this.connection === null) { this.init(); } + this._invoke('JoinDraft', token); + }, + /* Unregisters + leaves the group (editor closed). */ + release: function (token) { + if (this.active && (!token || this.active.token === token)) { this.active = null; } + this._invoke('LeaveDraft', token); + }, + _invoke: function (method, token) { + if (!token || !this.connection || this.connection.state !== 'Connected') { return; } + this.connection.invoke(method, token).catch((err) => console.warn('Draft ' + method + ' failed', err)); + }, + _dispatch: function (handler, payload, argOf) { + payload = payload || {}; + if (!this.active || this.active.token !== payload.token) { return; } + if (typeof this.active[handler] === 'function') { this.active[handler](argOf(payload)); } + } +}; diff --git a/Fuchs/js/intranet/fis_main_go.js b/Fuchs/js/intranet/fis_main_go.js index 8dec451..a23cbc2 100644 --- a/Fuchs/js/intranet/fis_main_go.js +++ b/Fuchs/js/intranet/fis_main_go.js @@ -1,4 +1,5 @@ $(document).ready(function () { $fis.notifications.init(); + $fis.draft.init(); $fis.ov(); }); diff --git a/Fuchs/wwwroot/web/fis.js b/Fuchs/wwwroot/web/fis.js index c78be9d..f7955e7 100644 --- a/Fuchs/wwwroot/web/fis.js +++ b/Fuchs/wwwroot/web/fis.js @@ -3129,6 +3129,68 @@ $fis.notifications = { } }; +/* Live draft-editing client (ADR 0006/0007). Separate SignalR connection to the + dedicated /draftpreview hub; the server signals the *one* browser editing a draft + (group = session token) to re-fetch (draftReady), warns before idle expiry + (draftExpiring), and tells it to close on eviction (draftClosed). The editor + (fis.inv_shared.js) registers the open draft via $fis.draft.bind(token, {...}). */ +$fis.draft = { + connection: null, + active: null, /* { token, onReady(version), onExpiring(secondsLeft), onClosed(reason) } */ + init: function () { + if (typeof signalR === 'undefined' || this.connection !== null || !$ocms.auth.useraccount_id) { + return; + } + this.connection = new signalR.HubConnectionBuilder() + .withUrl('/draftpreview') + .withAutomaticReconnect() + .build(); + this.connection.on('draftReady', (p) => this._dispatch('onReady', p, (p) => p.version)); + this.connection.on('draftExpiring', (p) => this._dispatch('onExpiring', p, (p) => p.secondsLeft)); + this.connection.on('draftClosed', (p) => this._dispatch('onClosed', p, (p) => p.reason)); + /* Re-join the active draft's group after a (re)connect — group membership is + per-connection and is lost when the socket drops. */ + this.connection.onreconnected(() => { if (this.active) { this._invoke('JoinDraft', this.active.token); } }); + this.connection.onclose(() => { + console.warn('Draft connection closed; retrying in 5s.'); + this.connection = null; + setTimeout(() => { this.init(); if (this.active) { this.bind(this.active.token, this.active); } }, 5000); + }); + this.start(); + }, + start: function () { + this.connection.start() + .then(() => { if (this.active) { this._invoke('JoinDraft', this.active.token); } }) + .catch((err) => { + console.warn('Draft connection failed to start; retrying in 5s.', err); + this.connection = null; + setTimeout(() => this.init(), 5000); + }); + }, + /* Registers the currently open draft and joins its signal group. handlers: + { onReady, onExpiring, onClosed }. */ + bind: function (token, handlers) { + if (!token) { return; } + this.active = $.extend({ token: token }, handlers || {}); + if (this.connection === null) { this.init(); } + this._invoke('JoinDraft', token); + }, + /* Unregisters + leaves the group (editor closed). */ + release: function (token) { + if (this.active && (!token || this.active.token === token)) { this.active = null; } + this._invoke('LeaveDraft', token); + }, + _invoke: function (method, token) { + if (!token || !this.connection || this.connection.state !== 'Connected') { return; } + this.connection.invoke(method, token).catch((err) => console.warn('Draft ' + method + ' failed', err)); + }, + _dispatch: function (handler, payload, argOf) { + payload = payload || {}; + if (!this.active || this.active.token !== payload.token) { return; } + if (typeof this.active[handler] === 'function') { this.active[handler](argOf(payload)); } + } +}; + (function () { Array.prototype.push.apply($ocms.ocmsmenu,[ { lbl: $t.m_inv, id: 'm_inv', fnc: 'init:inv', ico: 'glyphicon glyphicon-list-alt'} @@ -3143,5 +3205,6 @@ $fis.notifications = { })(); $(document).ready(function () { $fis.notifications.init(); + $fis.draft.init(); $fis.ov(); }); diff --git a/Fuchs/wwwroot/web/fis.min.js b/Fuchs/wwwroot/web/fis.min.js index 7999308..b3cb91c 100644 --- a/Fuchs/wwwroot/web/fis.min.js +++ b/Fuchs/wwwroot/web/fis.min.js @@ -1,4 +1,4 @@ -var t,e,$t={lng:"de-DE",dn:["So","Mo","Di","Mi","Do","Fr","Sa"],mn:["Januar","Februar","März","April","Mai","Juni","Juli","August","September","Oktober","November","Dezember"],ma:["Jan","Feb","Mrz","Apr","Mai","Jun","Jul","Aug","Sep","Okt","Nov","Dez"],datepattern:"(0[1-9]|1[0-9]|2[0-9]|3[01]).(0[1-9]|1[012]).[0-9]{4}",datetimepattern:"(0[1-9]|1[0-9]|2[0-9]|3[01]).(0[1-9]|1[012]).[0-9]{4}\\s([0-5][0-9]):([0-5][0-9])",dateplaceholder:"dd.MM.yyyy",datetimeplaceholder:"dd.MM.yyyy HH:mm",dateformat:"dd.MM.yyyy",datetimeformat:"dd.MM.yyyy HH:mm",f1:"Der Server hat einen Fehler gemeldet: \n",f2:"Bitte versuchen Sie es erneut.",m0:"Diese Internet-Seite benötigt einen html5-kompatiblen Browser.",m0b:"Unterstützt werden bspw: Internet Explorer ab Version 10, Firefox ab Version 31, Chrome ab Version 31, Safari ab Version 7, Opera ab Version 27",m1:"Dieser Datensatz ist momentan von jemand anderem zur Bearbeitung gesperrt.",m2:"Diese Funktion ist zur Zeit nicht verfügbar",t1:"Eingabe erforderlich.",t2:"Eingabe ist nicht erforderlich.",true:"Ja",false:"Nein",alert:"Hinweis",confirm:"Bestätigen",open:"Öffnen","not implemented":"Diese Funktion in zur Zeit noch nicht verfügbar.",l0:"Anmeldung",l1:"Email / Anmeldename",l2:"Email-Adresse / Anmeldename",l3:"Passwort",l4:"Benutzer",l5:"Wird vom System ermittelt...",l6:"Anmelden",l7:"Passwort vergessen?",l7a:'Die "Passwort vergessen"-Funktion läuft in zwei Schritten ab:\n \nIm ersten Schritt wird eine SMS mit einem Code an die hinterlegte Mobilfunk-Nummer versandt.\nIm zweiten Schritt geben Sie bitte diesen Code in das Formular ein und übermitteln es erneut.\n \nIn beiden Schritten wird aus Sicherheitsgründen kein Fehler angezeigt und auch dann ein erfolgreicher Versand bestätigt, wenn die Kombination aus Email-Adresse und Nachname nicht gefunden wurde und/oder der code falsch ist.',l8:"Keinen Account?",l9:"Anmeldenamen der Email-Adresse wurde nicht erkannt.",l10:"Nachname",l11:"Email-Adresse",l12:"Passwort zusenden",l13:"Das Passwort wurde erfolgreich verschickt",l14:"Das Passwort konnte nicht verschickt werden",l15:"Sie sind nicht berechtigt, diese Funktion auszuführen.",l16:"Sie müssen zunächst einen Account angeben.",l17:"Die Kombination aus Anmeldenamen und Passwort konnte nicht bestätigt werden.",l18:"Es gibt ein Problem mit dem Formular.\nEs kann momentan nicht verarbeitet und versendet werden.",name:"Name",submit:"Senden",cancel:"Abbrechen",noop:"Diese Funktion is noch nicht verfügar."};t=self,e=()=>(()=>{var t={d:(e,n)=>{for(var o in n)t.o(n,o)&&!t.o(e,o)&&Object.defineProperty(e,o,{enumerable:!0,get:n[o]})}};t.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(t){if("object"==typeof window)return window}}(),t.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e),t.r=t=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"t",{value:!0})};var e,n={};t.r(n),t.d(n,{AbortError:()=>r,DefaultHttpClient:()=>D,HttpClient:()=>h,HttpError:()=>o,HttpResponse:()=>d,HttpTransportType:()=>U,HubConnection:()=>O,HubConnectionBuilder:()=>tt,HubConnectionState:()=>j,JsonHubProtocol:()=>K,LogLevel:()=>e,MessageType:()=>N,NullLogger:()=>p,Subject:()=>q,TimeoutError:()=>i,TransferFormat:()=>B,VERSION:()=>f});class o extends Error{constructor(t,e){const n=new.target.prototype;super(`${t}: Status code '${e}'`),this.statusCode=e,this.__proto__=n}}class i extends Error{constructor(t="A timeout occurred."){const e=new.target.prototype;super(t),this.__proto__=e}}class r extends Error{constructor(t="An abort occurred."){const e=new.target.prototype;super(t),this.__proto__=e}}class s extends Error{constructor(t,e){const n=new.target.prototype;super(t),this.transport=e,this.errorType="UnsupportedTransportError",this.__proto__=n}}class a extends Error{constructor(t,e){const n=new.target.prototype;super(t),this.transport=e,this.errorType="DisabledTransportError",this.__proto__=n}}class c extends Error{constructor(t,e){const n=new.target.prototype;super(t),this.transport=e,this.errorType="FailedToStartTransportError",this.__proto__=n}}class l extends Error{constructor(t){const e=new.target.prototype;super(t),this.errorType="FailedToNegotiateWithServerError",this.__proto__=e}}class u extends Error{constructor(t,e){const n=new.target.prototype;super(t),this.innerErrors=e,this.__proto__=n}}class d{constructor(t,e,n){this.statusCode=t,this.statusText=e,this.content=n}}class h{get(t,e){return this.send({...e,method:"GET",url:t})}post(t,e){return this.send({...e,method:"POST",url:t})}delete(t,e){return this.send({...e,method:"DELETE",url:t})}getCookieString(t){return""}}!function(t){t[t.Trace=0]="Trace",t[t.Debug=1]="Debug",t[t.Information=2]="Information",t[t.Warning=3]="Warning",t[t.Error=4]="Error",t[t.Critical=5]="Critical",t[t.None=6]="None"}(e||(e={}));class p{constructor(){}log(t,e){}}p.instance=new p;const f="10.0.0";class m{static isRequired(t,e){if(null==t)throw new Error(`The '${e}' argument is required.`)}static isNotEmpty(t,e){if(!t||t.match(/^\s*$/))throw new Error(`The '${e}' argument should not be empty.`)}static isIn(t,e,n){if(!(t in e))throw new Error(`Unknown ${n} value: ${t}.`)}}class g{static get isBrowser(){return!g.isNode&&"object"==typeof window&&"object"==typeof window.document}static get isWebWorker(){return!g.isNode&&"object"==typeof self&&"importScripts"in self}static get isReactNative(){return!g.isNode&&"object"==typeof window&&void 0===window.document}static get isNode(){return"undefined"!=typeof process&&process.release&&"node"===process.release.name}}function y(t,e){let n="";return b(t)?(n=`Binary data of length ${t.byteLength}`,e&&(n+=`. Content: '${function(t){const e=new Uint8Array(t);let n="";return e.forEach((t=>{n+=`0x${t<16?"0":""}${t.toString(16)} `})),n.substring(0,n.length-1)}(t)}'`)):"string"==typeof t&&(n=`String data of length ${t.length}`,e&&(n+=`. Content: '${t}'`)),n}function b(t){return t&&"undefined"!=typeof ArrayBuffer&&(t instanceof ArrayBuffer||t.constructor&&"ArrayBuffer"===t.constructor.name)}async function v(t,n,o,i,r,s){const a={},[c,l]=C();a[c]=l,t.log(e.Trace,`(${n} transport) sending data. ${y(r,s.logMessageContent)}.`);const u=b(r)?"arraybuffer":"text",d=await o.post(i,{content:r,headers:{...a,...s.headers},responseType:u,timeout:s.timeout,withCredentials:s.withCredentials});t.log(e.Trace,`(${n} transport) request complete. Response status: ${d.statusCode}.`)}class ${constructor(t,e){this.i=t,this.h=e}dispose(){const t=this.i.observers.indexOf(this.h);t>-1&&this.i.observers.splice(t,1),0===this.i.observers.length&&this.i.cancelCallback&&this.i.cancelCallback().catch((t=>{}))}}class w{constructor(t){this.l=t,this.out=console}log(t,n){if(t>=this.l){const o=`[${(new Date).toISOString()}] ${e[t]}: ${n}`;switch(t){case e.Critical:case e.Error:this.out.error(o);break;case e.Warning:this.out.warn(o);break;case e.Information:this.out.info(o);break;default:this.out.log(o)}}}}function C(){let t="X-SignalR-User-Agent";return g.isNode&&(t="User-Agent"),[t,S(f,x(),g.isNode?"NodeJS":"Browser",T())]}function S(t,e,n,o){let i="Microsoft SignalR/";const r=t.split(".");return i+=`${r[0]}.${r[1]}`,i+=` (${t}; `,i+=e&&""!==e?`${e}; `:"Unknown OS; ",i+=`${n}`,i+=o?`; ${o}`:"; Unknown Runtime Version",i+=")",i}function x(){if(!g.isNode)return"";switch(process.platform){case"win32":return"Windows NT";case"darwin":return"macOS";case"linux":return"Linux";default:return process.platform}}function T(){if(g.isNode)return process.versions.node}function _(t){return t.stack?t.stack:t.message?t.message:`${t}`}class E extends h{constructor(e){if(super(),this.u=e,"undefined"==typeof fetch||g.isNode){const t=require;this.p=new(t("tough-cookie").CookieJar),"undefined"==typeof fetch?this.m=t("node-fetch"):this.m=fetch,this.m=t("fetch-cookie")(this.m,this.p)}else this.m=fetch.bind(function(){if("undefined"!=typeof globalThis)return globalThis;if("undefined"!=typeof self)return self;if("undefined"!=typeof window)return window;if(void 0!==t.g)return t.g;throw new Error("could not find global")}());if("undefined"==typeof AbortController){const t=require;this.v=t("abort-controller")}else this.v=AbortController}async send(t){if(t.abortSignal&&t.abortSignal.aborted)throw new r;if(!t.method)throw new Error("No method defined.");if(!t.url)throw new Error("No url defined.");const n=new this.v;let s;t.abortSignal&&(t.abortSignal.onabort=()=>{n.abort(),s=new r});let a,c=null;if(t.timeout){const o=t.timeout;c=setTimeout((()=>{n.abort(),this.u.log(e.Warning,"Timeout from HTTP request."),s=new i}),o)}""===t.content&&(t.content=void 0),t.content&&(t.headers=t.headers||{},b(t.content)?t.headers["Content-Type"]="application/octet-stream":t.headers["Content-Type"]="text/plain;charset=UTF-8");try{a=await this.m(t.url,{body:t.content,cache:"no-cache",credentials:!0===t.withCredentials?"include":"same-origin",headers:{"X-Requested-With":"XMLHttpRequest",...t.headers},method:t.method,mode:"cors",redirect:"follow",signal:n.signal})}catch(t){if(s)throw s;throw this.u.log(e.Warning,`Error from HTTP request. ${t}.`),t}finally{c&&clearTimeout(c),t.abortSignal&&(t.abortSignal.onabort=null)}if(!a.ok){const t=await I(a,"text");throw new o(t||a.statusText,a.status)}const l=I(a,t.responseType),u=await l;return new d(a.status,a.statusText,u)}getCookieString(t){let e="";return g.isNode&&this.p&&this.p.getCookies(t,((t,n)=>e=n.join("; "))),e}}function I(t,e){let n;switch(e){case"arraybuffer":n=t.arrayBuffer();break;case"text":default:n=t.text();break;case"blob":case"document":case"json":throw new Error(`${e} is not supported.`)}return n}class k extends h{constructor(t){super(),this.u=t}send(t){return t.abortSignal&&t.abortSignal.aborted?Promise.reject(new r):t.method?t.url?new Promise(((n,s)=>{const a=new XMLHttpRequest;a.open(t.method,t.url,!0),a.withCredentials=void 0===t.withCredentials||t.withCredentials,a.setRequestHeader("X-Requested-With","XMLHttpRequest"),""===t.content&&(t.content=void 0),t.content&&(b(t.content)?a.setRequestHeader("Content-Type","application/octet-stream"):a.setRequestHeader("Content-Type","text/plain;charset=UTF-8"));const c=t.headers;c&&Object.keys(c).forEach((t=>{a.setRequestHeader(t,c[t])})),t.responseType&&(a.responseType=t.responseType),t.abortSignal&&(t.abortSignal.onabort=()=>{a.abort(),s(new r)}),t.timeout&&(a.timeout=t.timeout),a.onload=()=>{t.abortSignal&&(t.abortSignal.onabort=null),a.status>=200&&a.status<300?n(new d(a.status,a.statusText,a.response||a.responseText)):s(new o(a.response||a.responseText||a.statusText,a.status))},a.onerror=()=>{this.u.log(e.Warning,`Error from HTTP request. ${a.status}: ${a.statusText}.`),s(new o(a.statusText,a.status))},a.ontimeout=()=>{this.u.log(e.Warning,"Timeout from HTTP request."),s(new i)},a.send(t.content)})):Promise.reject(new Error("No url defined.")):Promise.reject(new Error("No method defined."))}}class D extends h{constructor(t){if(super(),"undefined"!=typeof fetch||g.isNode)this.$=new E(t);else{if("undefined"==typeof XMLHttpRequest)throw new Error("No usable HttpClient found.");this.$=new k(t)}}send(t){return t.abortSignal&&t.abortSignal.aborted?Promise.reject(new r):t.method?t.url?this.$.send(t):Promise.reject(new Error("No url defined.")):Promise.reject(new Error("No method defined."))}getCookieString(t){return this.$.getCookieString(t)}}class A{static write(t){return`${t}${A.RecordSeparator}`}static parse(t){if(t[t.length-1]!==A.RecordSeparator)throw new Error("Message is incomplete.");const e=t.split(A.RecordSeparator);return e.pop(),e}}A.RecordSeparatorCode=30,A.RecordSeparator=String.fromCharCode(A.RecordSeparatorCode);class P{writeHandshakeRequest(t){return A.write(JSON.stringify(t))}parseHandshakeResponse(t){let e,n;if(b(t)){const o=new Uint8Array(t),i=o.indexOf(A.RecordSeparatorCode);if(-1===i)throw new Error("Message is incomplete.");const r=i+1;e=String.fromCharCode.apply(null,Array.prototype.slice.call(o.slice(0,r))),n=o.byteLength>r?o.slice(r).buffer:null}else{const o=t,i=o.indexOf(A.RecordSeparator);if(-1===i)throw new Error("Message is incomplete.");const r=i+1;e=o.substring(0,r),n=o.length>r?o.substring(r):null}const o=A.parse(e),i=JSON.parse(o[0]);if(i.type)throw new Error("Expected a handshake response from the server.");return[n,i]}}var N,j;!function(t){t[t.Invocation=1]="Invocation",t[t.StreamItem=2]="StreamItem",t[t.Completion=3]="Completion",t[t.StreamInvocation=4]="StreamInvocation",t[t.CancelInvocation=5]="CancelInvocation",t[t.Ping=6]="Ping",t[t.Close=7]="Close",t[t.Ack=8]="Ack",t[t.Sequence=9]="Sequence"}(N||(N={}));class q{constructor(){this.observers=[]}next(t){for(const e of this.observers)e.next(t)}error(t){for(const e of this.observers)e.error&&e.error(t)}complete(){for(const t of this.observers)t.complete&&t.complete()}subscribe(t){return this.observers.push(t),new $(this,t)}}class M{constructor(t,e,n){this.C=1e5,this.S=[],this.k=0,this.P=!1,this.T=1,this.I=0,this._=0,this.H=!1,this.D=t,this.R=e,this.C=n}async A(t){const e=this.D.writeMessage(t);let n=Promise.resolve();if(this.U(t)){this.k++;let t=()=>{},o=()=>{};b(e)?this._+=e.byteLength:this._+=e.length,this._>=this.C&&(n=new Promise(((e,n)=>{t=e,o=n}))),this.S.push(new R(e,this.k,t,o))}try{this.H||await this.R.send(e)}catch{this.L()}await n}N(t){let e=-1;for(let n=0;nthis.T?this.R.stop(new Error("Sequence ID greater than amount of messages we've received.")):this.T=t.sequenceId}L(){this.H=!0,this.P=!0}async B(){const t=0!==this.S.length?this.S[0].q:this.k+1;await this.R.send(this.D.writeMessage({type:N.Sequence,sequenceId:t}));const e=this.S;for(const t of e)await this.R.send(t.M);this.H=!1}X(t){null!=t||(t=new Error("Unable to reconnect to server."));for(const e of this.S)e.J(t)}U(t){switch(t.type){case N.Invocation:case N.StreamItem:case N.Completion:case N.StreamInvocation:case N.CancelInvocation:return!0;case N.Close:case N.Sequence:case N.Ping:case N.Ack:return!1}}O(){void 0===this.V&&(this.V=setTimeout((async()=>{try{this.H||await this.R.send(this.D.writeMessage({type:N.Ack,sequenceId:this.I}))}catch{}clearTimeout(this.V),this.V=void 0}),1e3))}}class R{constructor(t,e,n,o){this.M=t,this.q=e,this.j=n,this.J=o}}!function(t){t.Disconnected="Disconnected",t.Connecting="Connecting",t.Connected="Connected",t.Disconnecting="Disconnecting",t.Reconnecting="Reconnecting"}(j||(j={}));class O{static create(t,e,n,o,i,r,s){return new O(t,e,n,o,i,r,s)}constructor(t,n,o,i,r,s,a){this.K=0,this.G=()=>{this.u.log(e.Warning,"The page is being frozen, this will likely lead to the connection being closed and messages being lost. For more information see the docs at https://learn.microsoft.com/aspnet/core/signalr/javascript-client#bsleep")},m.isRequired(t,"connection"),m.isRequired(n,"logger"),m.isRequired(o,"protocol"),this.serverTimeoutInMilliseconds=null!=r?r:3e4,this.keepAliveIntervalInMilliseconds=null!=s?s:15e3,this.Y=null!=a?a:1e5,this.u=n,this.D=o,this.connection=t,this.Z=i,this.tt=new P,this.connection.onreceive=t=>this.et(t),this.connection.onclose=t=>this.st(t),this.it={},this.nt={},this.rt=[],this.ot=[],this.ht=[],this.ct=0,this.lt=!1,this.ut=j.Disconnected,this.dt=!1,this.ft=this.D.writeMessage({type:N.Ping})}get state(){return this.ut}get connectionId(){return this.connection&&this.connection.connectionId||null}get baseUrl(){return this.connection.baseUrl||""}set baseUrl(t){if(this.ut!==j.Disconnected&&this.ut!==j.Reconnecting)throw new Error("The HubConnection must be in the Disconnected or Reconnecting state to change the url.");if(!t)throw new Error("The HubConnection url must be a valid url.");this.connection.baseUrl=t}start(){return this.wt=this.gt(),this.wt}async gt(){if(this.ut!==j.Disconnected)return Promise.reject(new Error("Cannot start a HubConnection that is not in the 'Disconnected' state."));this.ut=j.Connecting,this.u.log(e.Debug,"Starting HubConnection.");try{await this.yt(),g.isBrowser&&window.document.addEventListener("freeze",this.G),this.ut=j.Connected,this.dt=!0,this.u.log(e.Debug,"HubConnection connected successfully.")}catch(t){return this.ut=j.Disconnected,this.u.log(e.Debug,`HubConnection failed to start successfully because of error '${t}'.`),Promise.reject(t)}}async yt(){this.vt=void 0,this.lt=!1;const t=new Promise(((t,e)=>{this.bt=t,this.Et=e}));await this.connection.start(this.D.transferFormat);try{let n=this.D.version;this.connection.features.reconnect||(n=1);const o={protocol:this.D.name,version:n};if(this.u.log(e.Debug,"Sending handshake request."),await this.$t(this.tt.writeHandshakeRequest(o)),this.u.log(e.Information,`Using HubProtocol '${this.D.name}'.`),this.Ct(),this.St(),this.kt(),await t,this.vt)throw this.vt;!!this.connection.features.reconnect&&(this.Pt=new M(this.D,this.connection,this.Y),this.connection.features.disconnected=this.Pt.L.bind(this.Pt),this.connection.features.resend=()=>{if(this.Pt)return this.Pt.B()}),this.connection.features.inherentKeepAlive||await this.$t(this.ft)}catch(t){throw this.u.log(e.Debug,`Hub handshake failed with error '${t}' during start(). Stopping HubConnection.`),this.Ct(),this.Tt(),await this.connection.stop(t),t}}async stop(){const t=this.wt;this.connection.features.reconnect=!1,this.It=this._t(),await this.It;try{await t}catch(t){}}_t(t){if(this.ut===j.Disconnected)return this.u.log(e.Debug,`Call to HubConnection.stop(${t}) ignored because it is already in the disconnected state.`),Promise.resolve();if(this.ut===j.Disconnecting)return this.u.log(e.Debug,`Call to HttpConnection.stop(${t}) ignored because the connection is already in the disconnecting state.`),this.It;const n=this.ut;return this.ut=j.Disconnecting,this.u.log(e.Debug,"Stopping HubConnection."),this.Ht?(this.u.log(e.Debug,"Connection stopped during reconnect delay. Done reconnecting."),clearTimeout(this.Ht),this.Ht=void 0,this.Dt(),Promise.resolve()):(n===j.Connected&&this.Rt(),this.Ct(),this.Tt(),this.vt=t||new r("The connection was stopped before the hub handshake could complete."),this.connection.stop(t))}async Rt(){try{await this.xt(this.At())}catch{}}stream(t,...e){const[n,o]=this.Ut(e),i=this.Lt(t,e,o);let r;const s=new q;return s.cancelCallback=()=>{const t=this.Nt(i.invocationId);return delete this.it[i.invocationId],r.then((()=>this.xt(t)))},this.it[i.invocationId]=(t,e)=>{e?s.error(e):t&&(t.type===N.Completion?t.error?s.error(new Error(t.error)):s.complete():s.next(t.item))},r=this.xt(i).catch((t=>{s.error(t),delete this.it[i.invocationId]})),this.qt(n,r),s}$t(t){return this.kt(),this.connection.send(t)}xt(t){return this.Pt?this.Pt.A(t):this.$t(this.D.writeMessage(t))}send(t,...e){const[n,o]=this.Ut(e),i=this.xt(this.Mt(t,e,!0,o));return this.qt(n,i),i}invoke(t,...e){const[n,o]=this.Ut(e),i=this.Mt(t,e,!1,o);return new Promise(((t,e)=>{this.it[i.invocationId]=(n,o)=>{o?e(o):n&&(n.type===N.Completion?n.error?e(new Error(n.error)):t(n.result):e(new Error(`Unexpected message type: ${n.type}`)))};const o=this.xt(i).catch((t=>{e(t),delete this.it[i.invocationId]}));this.qt(n,o)}))}on(t,e){t&&e&&(t=t.toLowerCase(),this.nt[t]||(this.nt[t]=[]),-1===this.nt[t].indexOf(e)&&this.nt[t].push(e))}off(t,e){if(!t)return;t=t.toLowerCase();const n=this.nt[t];if(n)if(e){const o=n.indexOf(e);-1!==o&&(n.splice(o,1),0===n.length&&delete this.nt[t])}else delete this.nt[t]}onclose(t){t&&this.rt.push(t)}onreconnecting(t){t&&this.ot.push(t)}onreconnected(t){t&&this.ht.push(t)}et(t){if(this.Ct(),this.lt||(t=this.jt(t),this.lt=!0),t){const n=this.D.parseMessages(t,this.u);for(const o of n)if(!this.Pt||this.Pt.W(o))switch(o.type){case N.Invocation:this.Wt(o).catch((t=>{this.u.log(e.Error,`Invoke client method threw error: ${_(t)}`)}));break;case N.StreamItem:case N.Completion:{const n=this.it[o.invocationId];if(n){o.type===N.Completion&&delete this.it[o.invocationId];try{n(o)}catch(t){this.u.log(e.Error,`Stream callback threw error: ${_(t)}`)}}break}case N.Ping:break;case N.Close:{this.u.log(e.Information,"Close message received from server.");const t=o.error?new Error("Server returned an error on close: "+o.error):void 0;!0===o.allowReconnect?this.connection.stop(t):this.It=this._t(t);break}case N.Ack:this.Pt&&this.Pt.N(o);break;case N.Sequence:this.Pt&&this.Pt.F(o);break;default:this.u.log(e.Warning,`Invalid message type: ${o.type}.`)}}this.St()}jt(t){let n,o;try{[o,n]=this.tt.parseHandshakeResponse(t)}catch(t){const n="Error parsing handshake response: "+t;this.u.log(e.Error,n);const o=new Error(n);throw this.Et(o),o}if(n.error){const t="Server returned handshake error: "+n.error;this.u.log(e.Error,t);const o=new Error(t);throw this.Et(o),o}return this.u.log(e.Debug,"Server handshake complete."),this.bt(),o}kt(){this.connection.features.inherentKeepAlive||(this.K=(new Date).getTime()+this.keepAliveIntervalInMilliseconds,this.Tt())}St(){if(!this.connection.features||!this.connection.features.inherentKeepAlive){this.Ot=setTimeout((()=>this.serverTimeout()),this.serverTimeoutInMilliseconds);let t=this.K-(new Date).getTime();if(t<0)return void(this.ut===j.Connected&&this.Ft());void 0===this.Bt&&(t<0&&(t=0),this.Bt=setTimeout((async()=>{this.ut===j.Connected&&await this.Ft()}),t))}}serverTimeout(){this.connection.stop(new Error("Server timeout elapsed without receiving a message from the server."))}async Wt(t){const n=t.target.toLowerCase(),o=this.nt[n];if(!o)return this.u.log(e.Warning,`No client method with the name '${n}' found.`),void(t.invocationId&&(this.u.log(e.Warning,`No result given for '${n}' method and invocation ID '${t.invocationId}'.`),await this.xt(this.Xt(t.invocationId,"Client didn't provide a result.",null))));const i=o.slice(),r=!!t.invocationId;let s,a,c;for(const o of i)try{const i=s;s=await o.apply(this,t.arguments),r&&s&&i&&(this.u.log(e.Error,`Multiple results provided for '${n}'. Sending error to server.`),c=this.Xt(t.invocationId,"Client provided multiple results.",null)),a=void 0}catch(t){a=t,this.u.log(e.Error,`A callback for the method '${n}' threw error '${t}'.`)}c?await this.xt(c):r?(a?c=this.Xt(t.invocationId,`${a}`,null):void 0!==s?c=this.Xt(t.invocationId,null,s):(this.u.log(e.Warning,`No result given for '${n}' method and invocation ID '${t.invocationId}'.`),c=this.Xt(t.invocationId,"Client didn't provide a result.",null)),await this.xt(c)):s&&this.u.log(e.Error,`Result given for '${n}' method but server is not expecting a result.`)}st(t){this.u.log(e.Debug,`HubConnection.connectionClosed(${t}) called while in state ${this.ut}.`),this.vt=this.vt||t||new r("The underlying connection was closed before the hub handshake could complete."),this.bt&&this.bt(),this.Jt(t||new Error("Invocation canceled due to the underlying connection being closed.")),this.Ct(),this.Tt(),this.ut===j.Disconnecting?this.Dt(t):this.ut===j.Connected&&this.Z?this.zt(t):this.ut===j.Connected&&this.Dt(t)}Dt(t){if(this.dt){this.ut=j.Disconnected,this.dt=!1,this.Pt&&(this.Pt.X(null!=t?t:new Error("Connection closed.")),this.Pt=void 0),g.isBrowser&&window.document.removeEventListener("freeze",this.G);try{this.rt.forEach((e=>e.apply(this,[t])))}catch(n){this.u.log(e.Error,`An onclose callback called with error '${t}' threw error '${n}'.`)}}}async zt(t){const n=Date.now();let o=0,i=void 0!==t?t:new Error("Attempting to reconnect due to a unknown error."),r=this.Vt(o,0,i);if(null===r)return this.u.log(e.Debug,"Connection not reconnecting because the IRetryPolicy returned null on the first reconnect attempt."),void this.Dt(t);if(this.ut=j.Reconnecting,t?this.u.log(e.Information,`Connection reconnecting because of error '${t}'.`):this.u.log(e.Information,"Connection reconnecting."),0!==this.ot.length){try{this.ot.forEach((e=>e.apply(this,[t])))}catch(n){this.u.log(e.Error,`An onreconnecting callback called with error '${t}' threw error '${n}'.`)}if(this.ut!==j.Reconnecting)return void this.u.log(e.Debug,"Connection left the reconnecting state in onreconnecting callback. Done reconnecting.")}for(;null!==r;){if(this.u.log(e.Information,`Reconnect attempt number ${o+1} will start in ${r} ms.`),await new Promise((t=>{this.Ht=setTimeout(t,r)})),this.Ht=void 0,this.ut!==j.Reconnecting)return void this.u.log(e.Debug,"Connection left the reconnecting state during reconnect delay. Done reconnecting.");try{if(await this.yt(),this.ut=j.Connected,this.u.log(e.Information,"HubConnection reconnected successfully."),0!==this.ht.length)try{this.ht.forEach((t=>t.apply(this,[this.connection.connectionId])))}catch(t){this.u.log(e.Error,`An onreconnected callback called with connectionId '${this.connection.connectionId}; threw error '${t}'.`)}return}catch(t){if(this.u.log(e.Information,`Reconnect attempt failed because of error '${t}'.`),this.ut!==j.Reconnecting)return this.u.log(e.Debug,`Connection moved to the '${this.ut}' from the reconnecting state during reconnect attempt. Done reconnecting.`),void(this.ut===j.Disconnecting&&this.Dt());o++,i=t instanceof Error?t:new Error(t.toString()),r=this.Vt(o,Date.now()-n,i)}}this.u.log(e.Information,`Reconnect retries have been exhausted after ${Date.now()-n} ms and ${o} failed attempts. Connection disconnecting.`),this.Dt()}Vt(t,n,o){try{return this.Z.nextRetryDelayInMilliseconds({elapsedMilliseconds:n,previousRetryCount:t,retryReason:o})}catch(o){return this.u.log(e.Error,`IRetryPolicy.nextRetryDelayInMilliseconds(${t}, ${n}) threw error '${o}'.`),null}}Jt(t){const n=this.it;this.it={},Object.keys(n).forEach((o=>{const i=n[o];try{i(null,t)}catch(n){this.u.log(e.Error,`Stream 'error' callback called with '${t}' threw error: ${_(n)}`)}}))}Tt(){this.Bt&&(clearTimeout(this.Bt),this.Bt=void 0)}Ct(){this.Ot&&clearTimeout(this.Ot)}Mt(t,e,n,o){if(n)return 0!==o.length?{target:t,arguments:e,streamIds:o,type:N.Invocation}:{target:t,arguments:e,type:N.Invocation};{const n=this.ct;return this.ct++,0!==o.length?{target:t,arguments:e,invocationId:n.toString(),streamIds:o,type:N.Invocation}:{target:t,arguments:e,invocationId:n.toString(),type:N.Invocation}}}qt(t,e){if(0!==t.length){e||(e=Promise.resolve());for(const n in t)t[n].subscribe({complete:()=>{e=e.then((()=>this.xt(this.Xt(n))))},error:t=>{let o;o=t instanceof Error?t.message:t&&t.toString?t.toString():"Unknown error",e=e.then((()=>this.xt(this.Xt(n,o))))},next:t=>{e=e.then((()=>this.xt(this.Kt(n,t))))}})}}Ut(t){const e=[],n=[];for(let o=0;o0)&&(e=!1,this.te=await this.Zt()),this.ee(t);const n=await this.Yt.send(t);return e&&401===n.statusCode&&this.Zt?(this.te=await this.Zt(),this.ee(t),await this.Yt.send(t)):n}ee(t){t.headers||(t.headers={}),this.te?t.headers[L.Authorization]=`Bearer ${this.te}`:this.Zt&&t.headers[L.Authorization]&&delete t.headers[L.Authorization]}getCookieString(t){return this.Yt.getCookieString(t)}}var U,B;!function(t){t[t.None=0]="None",t[t.WebSockets=1]="WebSockets",t[t.ServerSentEvents=2]="ServerSentEvents",t[t.LongPolling=4]="LongPolling"}(U||(U={})),function(t){t[t.Text=1]="Text",t[t.Binary=2]="Binary"}(B||(B={}));class W{constructor(){this.se=!1,this.onabort=null}abort(){this.se||(this.se=!0,this.onabort&&this.onabort())}get signal(){return this}get aborted(){return this.se}}class X{get pollAborted(){return this.ie.aborted}constructor(t,e,n){this.$=t,this.u=e,this.ie=new W,this.ne=n,this.re=!1,this.onreceive=null,this.onclose=null}async connect(t,n){if(m.isRequired(t,"url"),m.isRequired(n,"transferFormat"),m.isIn(n,B,"transferFormat"),this.oe=t,this.u.log(e.Trace,"(LongPolling transport) Connecting."),n===B.Binary&&"undefined"!=typeof XMLHttpRequest&&"string"!=typeof(new XMLHttpRequest).responseType)throw new Error("Binary protocols over XmlHttpRequest not implementing advanced features are not supported.");const[i,r]=C(),s={[i]:r,...this.ne.headers},a={abortSignal:this.ie.signal,headers:s,timeout:1e5,withCredentials:this.ne.withCredentials};n===B.Binary&&(a.responseType="arraybuffer");const c=`${t}&_=${Date.now()}`;this.u.log(e.Trace,`(LongPolling transport) polling: ${c}.`);const l=await this.$.get(c,a);200!==l.statusCode?(this.u.log(e.Error,`(LongPolling transport) Unexpected response code: ${l.statusCode}.`),this.he=new o(l.statusText||"",l.statusCode),this.re=!1):this.re=!0,this.ce=this.ae(this.oe,a)}async ae(t,n){try{for(;this.re;)try{const i=`${t}&_=${Date.now()}`;this.u.log(e.Trace,`(LongPolling transport) polling: ${i}.`);const r=await this.$.get(i,n);204===r.statusCode?(this.u.log(e.Information,"(LongPolling transport) Poll terminated by server."),this.re=!1):200!==r.statusCode?(this.u.log(e.Error,`(LongPolling transport) Unexpected response code: ${r.statusCode}.`),this.he=new o(r.statusText||"",r.statusCode),this.re=!1):r.content?(this.u.log(e.Trace,`(LongPolling transport) data received. ${y(r.content,this.ne.logMessageContent)}.`),this.onreceive&&this.onreceive(r.content)):this.u.log(e.Trace,"(LongPolling transport) Poll timed out, reissuing.")}catch(t){this.re?t instanceof i?this.u.log(e.Trace,"(LongPolling transport) Poll timed out, reissuing."):(this.he=t,this.re=!1):this.u.log(e.Trace,`(LongPolling transport) Poll errored after shutdown: ${t.message}`)}}finally{this.u.log(e.Trace,"(LongPolling transport) Polling complete."),this.pollAborted||this.le()}}async send(t){return this.re?v(this.u,"LongPolling",this.$,this.oe,t,this.ne):Promise.reject(new Error("Cannot send until the transport is connected"))}async stop(){this.u.log(e.Trace,"(LongPolling transport) Stopping polling."),this.re=!1,this.ie.abort();try{await this.ce,this.u.log(e.Trace,`(LongPolling transport) sending DELETE request to ${this.oe}.`);const t={},[n,i]=C();t[n]=i;const r={headers:{...t,...this.ne.headers},timeout:this.ne.timeout,withCredentials:this.ne.withCredentials};let s;try{await this.$.delete(this.oe,r)}catch(t){s=t}s?s instanceof o&&(404===s.statusCode?this.u.log(e.Trace,"(LongPolling transport) A 404 response was returned from sending a DELETE request."):this.u.log(e.Trace,`(LongPolling transport) Error sending a DELETE request: ${s}`)):this.u.log(e.Trace,"(LongPolling transport) DELETE request accepted.")}finally{this.u.log(e.Trace,"(LongPolling transport) Stop finished."),this.le()}}le(){if(this.onclose){let t="(LongPolling transport) Firing onclose event.";this.he&&(t+=" Error: "+this.he),this.u.log(e.Trace,t),this.onclose(this.he)}}}class V{constructor(t,e,n,o){this.$=t,this.te=e,this.u=n,this.ne=o,this.onreceive=null,this.onclose=null}async connect(t,n){return m.isRequired(t,"url"),m.isRequired(n,"transferFormat"),m.isIn(n,B,"transferFormat"),this.u.log(e.Trace,"(SSE transport) Connecting."),this.oe=t,this.te&&(t+=(t.indexOf("?")<0?"?":"&")+`access_token=${encodeURIComponent(this.te)}`),new Promise(((o,i)=>{let r,s=!1;if(n===B.Text){if(g.isBrowser||g.isWebWorker)r=new this.ne.EventSource(t,{withCredentials:this.ne.withCredentials});else{const e=this.$.getCookieString(t),n={};n.Cookie=e;const[o,i]=C();n[o]=i,r=new this.ne.EventSource(t,{withCredentials:this.ne.withCredentials,headers:{...n,...this.ne.headers}})}try{r.onmessage=t=>{if(this.onreceive)try{this.u.log(e.Trace,`(SSE transport) data received. ${y(t.data,this.ne.logMessageContent)}.`),this.onreceive(t.data)}catch(t){return void this.ue(t)}},r.onerror=t=>{s?this.ue():i(new Error("EventSource failed to connect. The connection could not be found on the server, either the connection ID is not present on the server, or a proxy is refusing/buffering the connection. If you have multiple servers check that sticky sessions are enabled."))},r.onopen=()=>{this.u.log(e.Information,`SSE connected to ${this.oe}`),this.de=r,s=!0,o()}}catch(t){return void i(t)}}else i(new Error("The Server-Sent Events transport only supports the 'Text' transfer format"))}))}async send(t){return this.de?v(this.u,"SSE",this.$,this.oe,t,this.ne):Promise.reject(new Error("Cannot send until the transport is connected"))}stop(){return this.ue(),Promise.resolve()}ue(t){this.de&&(this.de.close(),this.de=void 0,this.onclose&&this.onclose(t))}}class Z{constructor(t,e,n,o,i,r){this.u=n,this.Zt=e,this.fe=o,this.pe=i,this.$=t,this.onreceive=null,this.onclose=null,this.we=r}async connect(t,n){let o;return m.isRequired(t,"url"),m.isRequired(n,"transferFormat"),m.isIn(n,B,"transferFormat"),this.u.log(e.Trace,"(WebSockets transport) Connecting."),this.Zt&&(o=await this.Zt()),new Promise(((i,r)=>{let s;t=t.replace(/^http/,"ws");const a=this.$.getCookieString(t);let c=!1;if(g.isNode||g.isReactNative){const e={},[n,i]=C();e[n]=i,o&&(e[L.Authorization]=`Bearer ${o}`),a&&(e[L.Cookie]=a),s=new this.pe(t,void 0,{headers:{...e,...this.we}})}else o&&(t+=(t.indexOf("?")<0?"?":"&")+`access_token=${encodeURIComponent(o)}`);s||(s=new this.pe(t)),n===B.Binary&&(s.binaryType="arraybuffer"),s.onopen=n=>{this.u.log(e.Information,`WebSocket connected to ${t}.`),this.ge=s,c=!0,i()},s.onerror=t=>{let n=null;n="undefined"!=typeof ErrorEvent&&t instanceof ErrorEvent?t.error:"There was an error with the transport",this.u.log(e.Information,`(WebSockets transport) ${n}.`)},s.onmessage=t=>{if(this.u.log(e.Trace,`(WebSockets transport) data received. ${y(t.data,this.fe)}.`),this.onreceive)try{this.onreceive(t.data)}catch(t){return void this.ue(t)}},s.onclose=t=>{if(c)this.ue(t);else{let e=null;e="undefined"!=typeof ErrorEvent&&t instanceof ErrorEvent?t.error:"WebSocket failed to connect. The connection could not be found on the server, either the endpoint may not be a SignalR endpoint, the connection ID is not present on the server, or there is a proxy blocking WebSockets. If you have multiple servers check that sticky sessions are enabled.",r(new Error(e))}}}))}send(t){return this.ge&&this.ge.readyState===this.pe.OPEN?(this.u.log(e.Trace,`(WebSockets transport) sending data. ${y(t,this.fe)}.`),this.ge.send(t),Promise.resolve()):Promise.reject("WebSocket is not in the OPEN state")}stop(){return this.ge&&this.ue(void 0),Promise.resolve()}ue(t){this.ge&&(this.ge.onclose=()=>{},this.ge.onmessage=()=>{},this.ge.onerror=()=>{},this.ge.close(),this.ge=void 0),this.u.log(e.Trace,"(WebSockets transport) socket closed."),this.onclose&&(!this.me(t)||!1!==t.wasClean&&1e3===t.code?t instanceof Error?this.onclose(t):this.onclose():this.onclose(new Error(`WebSocket closed with status code: ${t.code} (${t.reason||"no reason given"}).`)))}me(t){return t&&"boolean"==typeof t.wasClean&&"number"==typeof t.code}}class J{constructor(t,n={}){var o;if(this.ye=()=>{},this.features={},this.ve=1,m.isRequired(t,"url"),this.u=void 0===(o=n.logger)?new w(e.Information):null===o?p.instance:void 0!==o.log?o:new w(o),this.baseUrl=this.be(t),(n=n||{}).logMessageContent=void 0!==n.logMessageContent&&n.logMessageContent,"boolean"!=typeof n.withCredentials&&void 0!==n.withCredentials)throw new Error("withCredentials option was not a 'boolean' or 'undefined' value");n.withCredentials=void 0===n.withCredentials||n.withCredentials,n.timeout=void 0===n.timeout?1e5:n.timeout;let i=null,r=null;if(g.isNode){const t=require;i=t("ws"),r=t("eventsource")}g.isNode||"undefined"==typeof WebSocket||n.WebSocket?g.isNode&&!n.WebSocket&&i&&(n.WebSocket=i):n.WebSocket=WebSocket,g.isNode||"undefined"==typeof EventSource||n.EventSource?g.isNode&&!n.EventSource&&void 0!==r&&(n.EventSource=r):n.EventSource=EventSource,this.$=new F(n.httpClient||new D(this.u),n.accessTokenFactory),this.ut="Disconnected",this.dt=!1,this.ne=n,this.onreceive=null,this.onclose=null}async start(t){if(t=t||B.Binary,m.isIn(t,B,"transferFormat"),this.u.log(e.Debug,`Starting connection with transfer format '${B[t]}'.`),"Disconnected"!==this.ut)return Promise.reject(new Error("Cannot start an HttpConnection that is not in the 'Disconnected' state."));if(this.ut="Connecting",this.Ee=this.yt(t),await this.Ee,"Disconnecting"===this.ut){const t="Failed to start the HttpConnection before stop() was called.";return this.u.log(e.Error,t),await this.It,Promise.reject(new r(t))}if("Connected"!==this.ut){const t="HttpConnection.startInternal completed gracefully but didn't enter the connection into the connected state!";return this.u.log(e.Error,t),Promise.reject(new r(t))}this.dt=!0}send(t){return"Connected"!==this.ut?Promise.reject(new Error("Cannot send data if the connection is not in the 'Connected' State.")):(this.$e||(this.$e=new Y(this.transport)),this.$e.send(t))}async stop(t){return"Disconnected"===this.ut?(this.u.log(e.Debug,`Call to HttpConnection.stop(${t}) ignored because the connection is already in the disconnected state.`),Promise.resolve()):"Disconnecting"===this.ut?(this.u.log(e.Debug,`Call to HttpConnection.stop(${t}) ignored because the connection is already in the disconnecting state.`),this.It):(this.ut="Disconnecting",this.It=new Promise((t=>{this.ye=t})),await this._t(t),void await this.It)}async _t(t){this.Ce=t;try{await this.Ee}catch(t){}if(this.transport){try{await this.transport.stop()}catch(t){this.u.log(e.Error,`HttpConnection.transport.stop() threw error '${t}'.`),this.Se()}this.transport=void 0}else this.u.log(e.Debug,"HttpConnection.transport is undefined in HttpConnection.stop() because start() failed.")}async yt(t){let n=this.baseUrl;this.Zt=this.ne.accessTokenFactory,this.$.Zt=this.Zt;try{if(this.ne.skipNegotiation){if(this.ne.transport!==U.WebSockets)throw new Error("Negotiation can only be skipped when using the WebSocket transport directly.");this.transport=this.ke(U.WebSockets),await this.Pe(n,t)}else{let e=null,o=0;do{if(e=await this.Te(n),"Disconnecting"===this.ut||"Disconnected"===this.ut)throw new r("The connection was stopped during negotiation.");if(e.error)throw new Error(e.error);if(e.ProtocolVersion)throw new Error("Detected a connection attempt to an ASP.NET SignalR Server. This client only supports connecting to an ASP.NET Core SignalR Server. See https://aka.ms/signalr-core-differences for details.");if(e.url&&(n=e.url),e.accessToken){const t=e.accessToken;this.Zt=()=>t,this.$.te=t,this.$.Zt=void 0}o++}while(e.url&&o<100);if(100===o&&e.url)throw new Error("Negotiate redirection limit exceeded.");await this.Ie(n,this.ne.transport,e,t)}this.transport instanceof X&&(this.features.inherentKeepAlive=!0),"Connecting"===this.ut&&(this.u.log(e.Debug,"The HttpConnection connected successfully."),this.ut="Connected")}catch(t){return this.u.log(e.Error,"Failed to start the connection: "+t),this.ut="Disconnected",this.transport=void 0,this.ye(),Promise.reject(t)}}async Te(t){const n={},[i,r]=C();n[i]=r;const s=this._e(t);this.u.log(e.Debug,`Sending negotiation request: ${s}.`);try{const t=await this.$.post(s,{content:"",headers:{...n,...this.ne.headers},timeout:this.ne.timeout,withCredentials:this.ne.withCredentials});if(200!==t.statusCode)return Promise.reject(new Error(`Unexpected status code returned from negotiate '${t.statusCode}'`));const e=JSON.parse(t.content);return(!e.negotiateVersion||e.negotiateVersion<1)&&(e.connectionToken=e.connectionId),e.useStatefulReconnect&&!0!==this.ne.He?Promise.reject(new l("Client didn't negotiate Stateful Reconnect but the server did.")):e}catch(t){let n="Failed to complete negotiation with the server: "+t;return t instanceof o&&404===t.statusCode&&(n+=" Either this is not a SignalR endpoint or there is a proxy blocking the connection."),this.u.log(e.Error,n),Promise.reject(new l(n))}}De(t,e){return e?t+(-1===t.indexOf("?")?"?":"&")+`id=${e}`:t}async Ie(t,n,o,i){let s=this.De(t,o.connectionToken);if(this.Re(n))return this.u.log(e.Debug,"Connection was provided an instance of ITransport, using that directly."),this.transport=n,await this.Pe(s,i),void(this.connectionId=o.connectionId);const a=[],l=o.availableTransports||[];let d=o;for(const o of l){const l=this.xe(o,n,i,!0===(null==d?void 0:d.useStatefulReconnect));if(l instanceof Error)a.push(`${o.transport} failed:`),a.push(l);else if(this.Re(l)){if(this.transport=l,!d){try{d=await this.Te(t)}catch(t){return Promise.reject(t)}s=this.De(t,d.connectionToken)}try{return await this.Pe(s,i),void(this.connectionId=d.connectionId)}catch(t){if(this.u.log(e.Error,`Failed to start the transport '${o.transport}': ${t}`),d=void 0,a.push(new c(`${o.transport} failed: ${t}`,U[o.transport])),"Connecting"!==this.ut){const t="Failed to select transport before stop() was called.";return this.u.log(e.Debug,t),Promise.reject(new r(t))}}}}return a.length>0?Promise.reject(new u(`Unable to connect to the server with any of the available transports. ${a.join(" ")}`,a)):Promise.reject(new Error("None of the transports supported by the client are supported by the server."))}ke(t){switch(t){case U.WebSockets:if(!this.ne.WebSocket)throw new Error("'WebSocket' is not supported in your environment.");return new Z(this.$,this.Zt,this.u,this.ne.logMessageContent,this.ne.WebSocket,this.ne.headers||{});case U.ServerSentEvents:if(!this.ne.EventSource)throw new Error("'EventSource' is not supported in your environment.");return new V(this.$,this.$.te,this.u,this.ne);case U.LongPolling:return new X(this.$,this.u,this.ne);default:throw new Error(`Unknown transport: ${t}.`)}}Pe(t,e){return this.transport.onreceive=this.onreceive,this.features.reconnect?this.transport.onclose=async n=>{let o=!1;if(this.features.reconnect){try{this.features.disconnected(),await this.transport.connect(t,e),await this.features.resend()}catch{o=!0}o&&this.Se(n)}else this.Se(n)}:this.transport.onclose=t=>this.Se(t),this.transport.connect(t,e)}xe(t,n,o,i){const r=U[t.transport];if(null==r)return this.u.log(e.Debug,`Skipping transport '${t.transport}' because it is not supported by this client.`),new Error(`Skipping transport '${t.transport}' because it is not supported by this client.`);if(!function(t,e){return!t||!!(e&t)}(n,r))return this.u.log(e.Debug,`Skipping transport '${U[r]}' because it was disabled by the client.`),new a(`'${U[r]}' is disabled by the client.`,r);if(!(t.transferFormats.map((t=>B[t])).indexOf(o)>=0))return this.u.log(e.Debug,`Skipping transport '${U[r]}' because it does not support the requested transfer format '${B[o]}'.`),new Error(`'${U[r]}' does not support ${B[o]}.`);if(r===U.WebSockets&&!this.ne.WebSocket||r===U.ServerSentEvents&&!this.ne.EventSource)return this.u.log(e.Debug,`Skipping transport '${U[r]}' because it is not supported in your environment.'`),new s(`'${U[r]}' is not supported in your environment.`,r);this.u.log(e.Debug,`Selecting transport '${U[r]}'.`);try{return this.features.reconnect=r===U.WebSockets?i:void 0,this.ke(r)}catch(t){return t}}Re(t){return t&&"object"==typeof t&&"connect"in t}Se(t){if(this.u.log(e.Debug,`HttpConnection.stopConnection(${t}) called while in state ${this.ut}.`),this.transport=void 0,t=this.Ce||t,this.Ce=void 0,"Disconnected"!==this.ut){if("Connecting"===this.ut)throw this.u.log(e.Warning,`Call to HttpConnection.stopConnection(${t}) was ignored because the connection is still in the connecting state.`),new Error(`HttpConnection.stopConnection(${t}) was called while the connection is still in the connecting state.`);if("Disconnecting"===this.ut&&this.ye(),t?this.u.log(e.Error,`Connection disconnected with error '${t}'.`):this.u.log(e.Information,"Connection disconnected."),this.$e&&(this.$e.stop().catch((t=>{this.u.log(e.Error,`TransportSendQueue.stop() threw error '${t}'.`)})),this.$e=void 0),this.connectionId=void 0,this.ut="Disconnected",this.dt){this.dt=!1;try{this.onclose&&this.onclose(t)}catch(n){this.u.log(e.Error,`HttpConnection.onclose(${t}) threw error '${n}'.`)}}}else this.u.log(e.Debug,`Call to HttpConnection.stopConnection(${t}) was ignored because the connection is already in the disconnected state.`)}be(t){if(0===t.lastIndexOf("https://",0)||0===t.lastIndexOf("http://",0))return t;if(!g.isBrowser)throw new Error(`Cannot resolve '${t}'.`);const n=window.document.createElement("a");return n.href=t,this.u.log(e.Information,`Normalizing '${t}' to '${n.href}'.`),n.href}_e(t){const e=new URL(t);e.pathname.endsWith("/")?e.pathname+="negotiate":e.pathname+="/negotiate";const n=new URLSearchParams(e.searchParams);return n.has("negotiateVersion")||n.append("negotiateVersion",this.ve.toString()),n.has("useStatefulReconnect")?"true"===n.get("useStatefulReconnect")&&(this.ne.He=!0):!0===this.ne.He&&n.append("useStatefulReconnect","true"),e.search=n.toString(),e.toString()}}class Y{constructor(t){this.Ae=t,this.Ue=[],this.Le=!0,this.Ne=new Q,this.qe=new Q,this.Me=this.je()}send(t){return this.We(t),this.qe||(this.qe=new Q),this.qe.promise}stop(){return this.Le=!1,this.Ne.resolve(),this.Me}We(t){if(this.Ue.length&&typeof this.Ue[0]!=typeof t)throw new Error(`Expected data to be of type ${typeof this.Ue} but was of type ${typeof t}`);this.Ue.push(t),this.Ne.resolve()}async je(){for(;;){if(await this.Ne.promise,!this.Le){this.qe&&this.qe.reject("Connection stopped.");break}this.Ne=new Q;const t=this.qe;this.qe=void 0;const e="string"==typeof this.Ue[0]?this.Ue.join(""):Y.Oe(this.Ue);this.Ue.length=0;try{await this.Ae.send(e),t.resolve()}catch(e){t.reject(e)}}}static Oe(t){const e=t.map((t=>t.byteLength)).reduce(((t,e)=>t+e)),n=new Uint8Array(e);let o=0;for(const e of t)n.set(new Uint8Array(e),o),o+=e.byteLength;return n.buffer}}class Q{constructor(){this.promise=new Promise(((t,e)=>[this.j,this.Fe]=[t,e]))}resolve(){this.j()}reject(t){this.Fe(t)}}class K{constructor(){this.name="json",this.version=2,this.transferFormat=B.Text}parseMessages(t,n){if("string"!=typeof t)throw new Error("Invalid input for JSON hub protocol. Expected a string.");if(!t)return[];null===n&&(n=p.instance);const o=A.parse(t),i=[];for(const t of o){const o=JSON.parse(t);if("number"!=typeof o.type)throw new Error("Invalid payload.");switch(o.type){case N.Invocation:this.U(o);break;case N.StreamItem:this.Be(o);break;case N.Completion:this.Xe(o);break;case N.Ping:case N.Close:break;case N.Ack:this.Je(o);break;case N.Sequence:this.ze(o);break;default:n.log(e.Information,"Unknown message type '"+o.type+"' ignored.");continue}i.push(o)}return i}writeMessage(t){return A.write(JSON.stringify(t))}U(t){this.Ve(t.target,"Invalid payload for Invocation message."),void 0!==t.invocationId&&this.Ve(t.invocationId,"Invalid payload for Invocation message.")}Be(t){if(this.Ve(t.invocationId,"Invalid payload for StreamItem message."),void 0===t.item)throw new Error("Invalid payload for StreamItem message.")}Xe(t){if(t.result&&t.error)throw new Error("Invalid payload for Completion message.");!t.result&&t.error&&this.Ve(t.error,"Invalid payload for Completion message."),this.Ve(t.invocationId,"Invalid payload for Completion message.")}Je(t){if("number"!=typeof t.sequenceId)throw new Error("Invalid SequenceId for Ack message.")}ze(t){if("number"!=typeof t.sequenceId)throw new Error("Invalid SequenceId for Sequence message.")}Ve(t,e){if("string"!=typeof t||""===t)throw new Error(e)}}const G={trace:e.Trace,debug:e.Debug,info:e.Information,information:e.Information,warn:e.Warning,warning:e.Warning,error:e.Error,critical:e.Critical,none:e.None};class tt{configureLogging(t){if(m.isRequired(t,"logging"),void 0!==t.log)this.logger=t;else if("string"==typeof t){const e=function(t){const e=G[t.toLowerCase()];if(void 0!==e)return e;throw new Error(`Unknown log level: ${t}`)}(t);this.logger=new w(e)}else this.logger=new w(t);return this}withUrl(t,e){return m.isRequired(t,"url"),m.isNotEmpty(t,"url"),this.url=t,this.httpConnectionOptions="object"==typeof e?{...this.httpConnectionOptions,...e}:{...this.httpConnectionOptions,transport:e},this}withHubProtocol(t){return m.isRequired(t,"protocol"),this.protocol=t,this}withAutomaticReconnect(t){if(this.reconnectPolicy)throw new Error("A reconnectPolicy has already been set.");return t?Array.isArray(t)?this.reconnectPolicy=new z(t):this.reconnectPolicy=t:this.reconnectPolicy=new z,this}withServerTimeout(t){return m.isRequired(t,"milliseconds"),this.Ke=t,this}withKeepAliveInterval(t){return m.isRequired(t,"milliseconds"),this.Ge=t,this}withStatefulReconnect(t){return void 0===this.httpConnectionOptions&&(this.httpConnectionOptions={}),this.httpConnectionOptions.He=!0,this.Y=null==t?void 0:t.bufferSize,this}build(){const t=this.httpConnectionOptions||{};if(void 0===t.logger&&(t.logger=this.logger),!this.url)throw new Error("The 'HubConnectionBuilder.withUrl' method must be called before building the connection.");const e=new J(this.url,t);return O.create(e,this.logger||p.instance,this.protocol||new K,this.reconnectPolicy,this.Ke,this.Ge,this.Y)}}return Uint8Array.prototype.indexOf||Object.defineProperty(Uint8Array.prototype,"indexOf",{value:Array.prototype.indexOf,writable:!0}),Uint8Array.prototype.slice||Object.defineProperty(Uint8Array.prototype,"slice",{value:function(t,e){return new Uint8Array(Array.prototype.slice.call(this,t,e))},writable:!0}),Uint8Array.prototype.forEach||Object.defineProperty(Uint8Array.prototype,"forEach",{value:Array.prototype.forEach,writable:!0}),n})(),"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.signalR=e():t.signalR=e(),$.extend($t,{t1:"Eingabe erforderlich",t2:"Bitte überprüfen Sie Ihre Eingaben im Formular.",b0:"Erstellt",b1:"Zuletzt geändert",b2:"von",t12:"Der Server hat einen Fehler zurückgegeben. Bitte versuchen Sie es erneut.",t17:"Eine Email mit einem Aktivierungs-Link wurde an deine Adresse versandt.",t18:"Ein Account mit deinem Namen existiert bereits. Dennoch erstellen?",t19:"Einträge sind entweder unngültig oder zu kurz.",t20:"Der Server hat einen Fehler gemeldet. Bitte versuch es erneut.",t21:"Der Zugang wurde nicht gefunden.",t30a:"Als erledigt markieren.",t30b:"Als unerledigt markieren.",t55:"Ein Email mit einem Aktivierungs-Link wurde an Ihre Adresse versandt.",t56:"Ein Zugang für diesen Namen besteht bereits. Trotzdem erstellen?",t57:"Ein bestehender Zugang wurde für diese Serie registriert.",t60:"Bitte geben Sie Email-Adresse an, die Sie hier hinterlegt haben.",t61:"Ihr Passwort wurde erfolgreich versandt.",t62:"Die angegebene Email-Adresse stimmt nicht mit der hier hinterlegten überein.",ov:"Persönliche Übersicht"});var $v={}; +var t,e,$t={lng:"de-DE",dn:["So","Mo","Di","Mi","Do","Fr","Sa"],mn:["Januar","Februar","März","April","Mai","Juni","Juli","August","September","Oktober","November","Dezember"],ma:["Jan","Feb","Mrz","Apr","Mai","Jun","Jul","Aug","Sep","Okt","Nov","Dez"],datepattern:"(0[1-9]|1[0-9]|2[0-9]|3[01]).(0[1-9]|1[012]).[0-9]{4}",datetimepattern:"(0[1-9]|1[0-9]|2[0-9]|3[01]).(0[1-9]|1[012]).[0-9]{4}\\s([0-5][0-9]):([0-5][0-9])",dateplaceholder:"dd.MM.yyyy",datetimeplaceholder:"dd.MM.yyyy HH:mm",dateformat:"dd.MM.yyyy",datetimeformat:"dd.MM.yyyy HH:mm",f1:"Der Server hat einen Fehler gemeldet: \n",f2:"Bitte versuchen Sie es erneut.",m0:"Diese Internet-Seite benötigt einen html5-kompatiblen Browser.",m0b:"Unterstützt werden bspw: Internet Explorer ab Version 10, Firefox ab Version 31, Chrome ab Version 31, Safari ab Version 7, Opera ab Version 27",m1:"Dieser Datensatz ist momentan von jemand anderem zur Bearbeitung gesperrt.",m2:"Diese Funktion ist zur Zeit nicht verfügbar",t1:"Eingabe erforderlich.",t2:"Eingabe ist nicht erforderlich.",true:"Ja",false:"Nein",alert:"Hinweis",confirm:"Bestätigen",open:"Öffnen","not implemented":"Diese Funktion in zur Zeit noch nicht verfügbar.",l0:"Anmeldung",l1:"Email / Anmeldename",l2:"Email-Adresse / Anmeldename",l3:"Passwort",l4:"Benutzer",l5:"Wird vom System ermittelt...",l6:"Anmelden",l7:"Passwort vergessen?",l7a:'Die "Passwort vergessen"-Funktion läuft in zwei Schritten ab:\n \nIm ersten Schritt wird eine SMS mit einem Code an die hinterlegte Mobilfunk-Nummer versandt.\nIm zweiten Schritt geben Sie bitte diesen Code in das Formular ein und übermitteln es erneut.\n \nIn beiden Schritten wird aus Sicherheitsgründen kein Fehler angezeigt und auch dann ein erfolgreicher Versand bestätigt, wenn die Kombination aus Email-Adresse und Nachname nicht gefunden wurde und/oder der code falsch ist.',l8:"Keinen Account?",l9:"Anmeldenamen der Email-Adresse wurde nicht erkannt.",l10:"Nachname",l11:"Email-Adresse",l12:"Passwort zusenden",l13:"Das Passwort wurde erfolgreich verschickt",l14:"Das Passwort konnte nicht verschickt werden",l15:"Sie sind nicht berechtigt, diese Funktion auszuführen.",l16:"Sie müssen zunächst einen Account angeben.",l17:"Die Kombination aus Anmeldenamen und Passwort konnte nicht bestätigt werden.",l18:"Es gibt ein Problem mit dem Formular.\nEs kann momentan nicht verarbeitet und versendet werden.",name:"Name",submit:"Senden",cancel:"Abbrechen",noop:"Diese Funktion is noch nicht verfügar."};t=self,e=()=>(()=>{var t={d:(e,n)=>{for(var i in n)t.o(n,i)&&!t.o(e,i)&&Object.defineProperty(e,i,{enumerable:!0,get:n[i]})}};t.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(t){if("object"==typeof window)return window}}(),t.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e),t.r=t=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"t",{value:!0})};var e,n={};t.r(n),t.d(n,{AbortError:()=>r,DefaultHttpClient:()=>D,HttpClient:()=>h,HttpError:()=>i,HttpResponse:()=>d,HttpTransportType:()=>U,HubConnection:()=>O,HubConnectionBuilder:()=>tt,HubConnectionState:()=>j,JsonHubProtocol:()=>K,LogLevel:()=>e,MessageType:()=>N,NullLogger:()=>p,Subject:()=>q,TimeoutError:()=>o,TransferFormat:()=>B,VERSION:()=>f});class i extends Error{constructor(t,e){const n=new.target.prototype;super(`${t}: Status code '${e}'`),this.statusCode=e,this.__proto__=n}}class o extends Error{constructor(t="A timeout occurred."){const e=new.target.prototype;super(t),this.__proto__=e}}class r extends Error{constructor(t="An abort occurred."){const e=new.target.prototype;super(t),this.__proto__=e}}class s extends Error{constructor(t,e){const n=new.target.prototype;super(t),this.transport=e,this.errorType="UnsupportedTransportError",this.__proto__=n}}class a extends Error{constructor(t,e){const n=new.target.prototype;super(t),this.transport=e,this.errorType="DisabledTransportError",this.__proto__=n}}class c extends Error{constructor(t,e){const n=new.target.prototype;super(t),this.transport=e,this.errorType="FailedToStartTransportError",this.__proto__=n}}class l extends Error{constructor(t){const e=new.target.prototype;super(t),this.errorType="FailedToNegotiateWithServerError",this.__proto__=e}}class u extends Error{constructor(t,e){const n=new.target.prototype;super(t),this.innerErrors=e,this.__proto__=n}}class d{constructor(t,e,n){this.statusCode=t,this.statusText=e,this.content=n}}class h{get(t,e){return this.send({...e,method:"GET",url:t})}post(t,e){return this.send({...e,method:"POST",url:t})}delete(t,e){return this.send({...e,method:"DELETE",url:t})}getCookieString(t){return""}}!function(t){t[t.Trace=0]="Trace",t[t.Debug=1]="Debug",t[t.Information=2]="Information",t[t.Warning=3]="Warning",t[t.Error=4]="Error",t[t.Critical=5]="Critical",t[t.None=6]="None"}(e||(e={}));class p{constructor(){}log(t,e){}}p.instance=new p;const f="10.0.0";class m{static isRequired(t,e){if(null==t)throw new Error(`The '${e}' argument is required.`)}static isNotEmpty(t,e){if(!t||t.match(/^\s*$/))throw new Error(`The '${e}' argument should not be empty.`)}static isIn(t,e,n){if(!(t in e))throw new Error(`Unknown ${n} value: ${t}.`)}}class g{static get isBrowser(){return!g.isNode&&"object"==typeof window&&"object"==typeof window.document}static get isWebWorker(){return!g.isNode&&"object"==typeof self&&"importScripts"in self}static get isReactNative(){return!g.isNode&&"object"==typeof window&&void 0===window.document}static get isNode(){return"undefined"!=typeof process&&process.release&&"node"===process.release.name}}function y(t,e){let n="";return v(t)?(n=`Binary data of length ${t.byteLength}`,e&&(n+=`. Content: '${function(t){const e=new Uint8Array(t);let n="";return e.forEach((t=>{n+=`0x${t<16?"0":""}${t.toString(16)} `})),n.substring(0,n.length-1)}(t)}'`)):"string"==typeof t&&(n=`String data of length ${t.length}`,e&&(n+=`. Content: '${t}'`)),n}function v(t){return t&&"undefined"!=typeof ArrayBuffer&&(t instanceof ArrayBuffer||t.constructor&&"ArrayBuffer"===t.constructor.name)}async function b(t,n,i,o,r,s){const a={},[c,l]=C();a[c]=l,t.log(e.Trace,`(${n} transport) sending data. ${y(r,s.logMessageContent)}.`);const u=v(r)?"arraybuffer":"text",d=await i.post(o,{content:r,headers:{...a,...s.headers},responseType:u,timeout:s.timeout,withCredentials:s.withCredentials});t.log(e.Trace,`(${n} transport) request complete. Response status: ${d.statusCode}.`)}class ${constructor(t,e){this.i=t,this.h=e}dispose(){const t=this.i.observers.indexOf(this.h);t>-1&&this.i.observers.splice(t,1),0===this.i.observers.length&&this.i.cancelCallback&&this.i.cancelCallback().catch((t=>{}))}}class w{constructor(t){this.l=t,this.out=console}log(t,n){if(t>=this.l){const i=`[${(new Date).toISOString()}] ${e[t]}: ${n}`;switch(t){case e.Critical:case e.Error:this.out.error(i);break;case e.Warning:this.out.warn(i);break;case e.Information:this.out.info(i);break;default:this.out.log(i)}}}}function C(){let t="X-SignalR-User-Agent";return g.isNode&&(t="User-Agent"),[t,S(f,x(),g.isNode?"NodeJS":"Browser",T())]}function S(t,e,n,i){let o="Microsoft SignalR/";const r=t.split(".");return o+=`${r[0]}.${r[1]}`,o+=` (${t}; `,o+=e&&""!==e?`${e}; `:"Unknown OS; ",o+=`${n}`,o+=i?`; ${i}`:"; Unknown Runtime Version",o+=")",o}function x(){if(!g.isNode)return"";switch(process.platform){case"win32":return"Windows NT";case"darwin":return"macOS";case"linux":return"Linux";default:return process.platform}}function T(){if(g.isNode)return process.versions.node}function _(t){return t.stack?t.stack:t.message?t.message:`${t}`}class E extends h{constructor(e){if(super(),this.u=e,"undefined"==typeof fetch||g.isNode){const t=require;this.p=new(t("tough-cookie").CookieJar),"undefined"==typeof fetch?this.m=t("node-fetch"):this.m=fetch,this.m=t("fetch-cookie")(this.m,this.p)}else this.m=fetch.bind(function(){if("undefined"!=typeof globalThis)return globalThis;if("undefined"!=typeof self)return self;if("undefined"!=typeof window)return window;if(void 0!==t.g)return t.g;throw new Error("could not find global")}());if("undefined"==typeof AbortController){const t=require;this.v=t("abort-controller")}else this.v=AbortController}async send(t){if(t.abortSignal&&t.abortSignal.aborted)throw new r;if(!t.method)throw new Error("No method defined.");if(!t.url)throw new Error("No url defined.");const n=new this.v;let s;t.abortSignal&&(t.abortSignal.onabort=()=>{n.abort(),s=new r});let a,c=null;if(t.timeout){const i=t.timeout;c=setTimeout((()=>{n.abort(),this.u.log(e.Warning,"Timeout from HTTP request."),s=new o}),i)}""===t.content&&(t.content=void 0),t.content&&(t.headers=t.headers||{},v(t.content)?t.headers["Content-Type"]="application/octet-stream":t.headers["Content-Type"]="text/plain;charset=UTF-8");try{a=await this.m(t.url,{body:t.content,cache:"no-cache",credentials:!0===t.withCredentials?"include":"same-origin",headers:{"X-Requested-With":"XMLHttpRequest",...t.headers},method:t.method,mode:"cors",redirect:"follow",signal:n.signal})}catch(t){if(s)throw s;throw this.u.log(e.Warning,`Error from HTTP request. ${t}.`),t}finally{c&&clearTimeout(c),t.abortSignal&&(t.abortSignal.onabort=null)}if(!a.ok){const t=await k(a,"text");throw new i(t||a.statusText,a.status)}const l=k(a,t.responseType),u=await l;return new d(a.status,a.statusText,u)}getCookieString(t){let e="";return g.isNode&&this.p&&this.p.getCookies(t,((t,n)=>e=n.join("; "))),e}}function k(t,e){let n;switch(e){case"arraybuffer":n=t.arrayBuffer();break;case"text":default:n=t.text();break;case"blob":case"document":case"json":throw new Error(`${e} is not supported.`)}return n}class I extends h{constructor(t){super(),this.u=t}send(t){return t.abortSignal&&t.abortSignal.aborted?Promise.reject(new r):t.method?t.url?new Promise(((n,s)=>{const a=new XMLHttpRequest;a.open(t.method,t.url,!0),a.withCredentials=void 0===t.withCredentials||t.withCredentials,a.setRequestHeader("X-Requested-With","XMLHttpRequest"),""===t.content&&(t.content=void 0),t.content&&(v(t.content)?a.setRequestHeader("Content-Type","application/octet-stream"):a.setRequestHeader("Content-Type","text/plain;charset=UTF-8"));const c=t.headers;c&&Object.keys(c).forEach((t=>{a.setRequestHeader(t,c[t])})),t.responseType&&(a.responseType=t.responseType),t.abortSignal&&(t.abortSignal.onabort=()=>{a.abort(),s(new r)}),t.timeout&&(a.timeout=t.timeout),a.onload=()=>{t.abortSignal&&(t.abortSignal.onabort=null),a.status>=200&&a.status<300?n(new d(a.status,a.statusText,a.response||a.responseText)):s(new i(a.response||a.responseText||a.statusText,a.status))},a.onerror=()=>{this.u.log(e.Warning,`Error from HTTP request. ${a.status}: ${a.statusText}.`),s(new i(a.statusText,a.status))},a.ontimeout=()=>{this.u.log(e.Warning,"Timeout from HTTP request."),s(new o)},a.send(t.content)})):Promise.reject(new Error("No url defined.")):Promise.reject(new Error("No method defined."))}}class D extends h{constructor(t){if(super(),"undefined"!=typeof fetch||g.isNode)this.$=new E(t);else{if("undefined"==typeof XMLHttpRequest)throw new Error("No usable HttpClient found.");this.$=new I(t)}}send(t){return t.abortSignal&&t.abortSignal.aborted?Promise.reject(new r):t.method?t.url?this.$.send(t):Promise.reject(new Error("No url defined.")):Promise.reject(new Error("No method defined."))}getCookieString(t){return this.$.getCookieString(t)}}class A{static write(t){return`${t}${A.RecordSeparator}`}static parse(t){if(t[t.length-1]!==A.RecordSeparator)throw new Error("Message is incomplete.");const e=t.split(A.RecordSeparator);return e.pop(),e}}A.RecordSeparatorCode=30,A.RecordSeparator=String.fromCharCode(A.RecordSeparatorCode);class P{writeHandshakeRequest(t){return A.write(JSON.stringify(t))}parseHandshakeResponse(t){let e,n;if(v(t)){const i=new Uint8Array(t),o=i.indexOf(A.RecordSeparatorCode);if(-1===o)throw new Error("Message is incomplete.");const r=o+1;e=String.fromCharCode.apply(null,Array.prototype.slice.call(i.slice(0,r))),n=i.byteLength>r?i.slice(r).buffer:null}else{const i=t,o=i.indexOf(A.RecordSeparator);if(-1===o)throw new Error("Message is incomplete.");const r=o+1;e=i.substring(0,r),n=i.length>r?i.substring(r):null}const i=A.parse(e),o=JSON.parse(i[0]);if(o.type)throw new Error("Expected a handshake response from the server.");return[n,o]}}var N,j;!function(t){t[t.Invocation=1]="Invocation",t[t.StreamItem=2]="StreamItem",t[t.Completion=3]="Completion",t[t.StreamInvocation=4]="StreamInvocation",t[t.CancelInvocation=5]="CancelInvocation",t[t.Ping=6]="Ping",t[t.Close=7]="Close",t[t.Ack=8]="Ack",t[t.Sequence=9]="Sequence"}(N||(N={}));class q{constructor(){this.observers=[]}next(t){for(const e of this.observers)e.next(t)}error(t){for(const e of this.observers)e.error&&e.error(t)}complete(){for(const t of this.observers)t.complete&&t.complete()}subscribe(t){return this.observers.push(t),new $(this,t)}}class M{constructor(t,e,n){this.C=1e5,this.S=[],this.k=0,this.P=!1,this.T=1,this.I=0,this._=0,this.H=!1,this.D=t,this.R=e,this.C=n}async A(t){const e=this.D.writeMessage(t);let n=Promise.resolve();if(this.U(t)){this.k++;let t=()=>{},i=()=>{};v(e)?this._+=e.byteLength:this._+=e.length,this._>=this.C&&(n=new Promise(((e,n)=>{t=e,i=n}))),this.S.push(new R(e,this.k,t,i))}try{this.H||await this.R.send(e)}catch{this.L()}await n}N(t){let e=-1;for(let n=0;nthis.T?this.R.stop(new Error("Sequence ID greater than amount of messages we've received.")):this.T=t.sequenceId}L(){this.H=!0,this.P=!0}async B(){const t=0!==this.S.length?this.S[0].q:this.k+1;await this.R.send(this.D.writeMessage({type:N.Sequence,sequenceId:t}));const e=this.S;for(const t of e)await this.R.send(t.M);this.H=!1}X(t){null!=t||(t=new Error("Unable to reconnect to server."));for(const e of this.S)e.J(t)}U(t){switch(t.type){case N.Invocation:case N.StreamItem:case N.Completion:case N.StreamInvocation:case N.CancelInvocation:return!0;case N.Close:case N.Sequence:case N.Ping:case N.Ack:return!1}}O(){void 0===this.V&&(this.V=setTimeout((async()=>{try{this.H||await this.R.send(this.D.writeMessage({type:N.Ack,sequenceId:this.I}))}catch{}clearTimeout(this.V),this.V=void 0}),1e3))}}class R{constructor(t,e,n,i){this.M=t,this.q=e,this.j=n,this.J=i}}!function(t){t.Disconnected="Disconnected",t.Connecting="Connecting",t.Connected="Connected",t.Disconnecting="Disconnecting",t.Reconnecting="Reconnecting"}(j||(j={}));class O{static create(t,e,n,i,o,r,s){return new O(t,e,n,i,o,r,s)}constructor(t,n,i,o,r,s,a){this.K=0,this.G=()=>{this.u.log(e.Warning,"The page is being frozen, this will likely lead to the connection being closed and messages being lost. For more information see the docs at https://learn.microsoft.com/aspnet/core/signalr/javascript-client#bsleep")},m.isRequired(t,"connection"),m.isRequired(n,"logger"),m.isRequired(i,"protocol"),this.serverTimeoutInMilliseconds=null!=r?r:3e4,this.keepAliveIntervalInMilliseconds=null!=s?s:15e3,this.Y=null!=a?a:1e5,this.u=n,this.D=i,this.connection=t,this.Z=o,this.tt=new P,this.connection.onreceive=t=>this.et(t),this.connection.onclose=t=>this.st(t),this.it={},this.nt={},this.rt=[],this.ot=[],this.ht=[],this.ct=0,this.lt=!1,this.ut=j.Disconnected,this.dt=!1,this.ft=this.D.writeMessage({type:N.Ping})}get state(){return this.ut}get connectionId(){return this.connection&&this.connection.connectionId||null}get baseUrl(){return this.connection.baseUrl||""}set baseUrl(t){if(this.ut!==j.Disconnected&&this.ut!==j.Reconnecting)throw new Error("The HubConnection must be in the Disconnected or Reconnecting state to change the url.");if(!t)throw new Error("The HubConnection url must be a valid url.");this.connection.baseUrl=t}start(){return this.wt=this.gt(),this.wt}async gt(){if(this.ut!==j.Disconnected)return Promise.reject(new Error("Cannot start a HubConnection that is not in the 'Disconnected' state."));this.ut=j.Connecting,this.u.log(e.Debug,"Starting HubConnection.");try{await this.yt(),g.isBrowser&&window.document.addEventListener("freeze",this.G),this.ut=j.Connected,this.dt=!0,this.u.log(e.Debug,"HubConnection connected successfully.")}catch(t){return this.ut=j.Disconnected,this.u.log(e.Debug,`HubConnection failed to start successfully because of error '${t}'.`),Promise.reject(t)}}async yt(){this.vt=void 0,this.lt=!1;const t=new Promise(((t,e)=>{this.bt=t,this.Et=e}));await this.connection.start(this.D.transferFormat);try{let n=this.D.version;this.connection.features.reconnect||(n=1);const i={protocol:this.D.name,version:n};if(this.u.log(e.Debug,"Sending handshake request."),await this.$t(this.tt.writeHandshakeRequest(i)),this.u.log(e.Information,`Using HubProtocol '${this.D.name}'.`),this.Ct(),this.St(),this.kt(),await t,this.vt)throw this.vt;!!this.connection.features.reconnect&&(this.Pt=new M(this.D,this.connection,this.Y),this.connection.features.disconnected=this.Pt.L.bind(this.Pt),this.connection.features.resend=()=>{if(this.Pt)return this.Pt.B()}),this.connection.features.inherentKeepAlive||await this.$t(this.ft)}catch(t){throw this.u.log(e.Debug,`Hub handshake failed with error '${t}' during start(). Stopping HubConnection.`),this.Ct(),this.Tt(),await this.connection.stop(t),t}}async stop(){const t=this.wt;this.connection.features.reconnect=!1,this.It=this._t(),await this.It;try{await t}catch(t){}}_t(t){if(this.ut===j.Disconnected)return this.u.log(e.Debug,`Call to HubConnection.stop(${t}) ignored because it is already in the disconnected state.`),Promise.resolve();if(this.ut===j.Disconnecting)return this.u.log(e.Debug,`Call to HttpConnection.stop(${t}) ignored because the connection is already in the disconnecting state.`),this.It;const n=this.ut;return this.ut=j.Disconnecting,this.u.log(e.Debug,"Stopping HubConnection."),this.Ht?(this.u.log(e.Debug,"Connection stopped during reconnect delay. Done reconnecting."),clearTimeout(this.Ht),this.Ht=void 0,this.Dt(),Promise.resolve()):(n===j.Connected&&this.Rt(),this.Ct(),this.Tt(),this.vt=t||new r("The connection was stopped before the hub handshake could complete."),this.connection.stop(t))}async Rt(){try{await this.xt(this.At())}catch{}}stream(t,...e){const[n,i]=this.Ut(e),o=this.Lt(t,e,i);let r;const s=new q;return s.cancelCallback=()=>{const t=this.Nt(o.invocationId);return delete this.it[o.invocationId],r.then((()=>this.xt(t)))},this.it[o.invocationId]=(t,e)=>{e?s.error(e):t&&(t.type===N.Completion?t.error?s.error(new Error(t.error)):s.complete():s.next(t.item))},r=this.xt(o).catch((t=>{s.error(t),delete this.it[o.invocationId]})),this.qt(n,r),s}$t(t){return this.kt(),this.connection.send(t)}xt(t){return this.Pt?this.Pt.A(t):this.$t(this.D.writeMessage(t))}send(t,...e){const[n,i]=this.Ut(e),o=this.xt(this.Mt(t,e,!0,i));return this.qt(n,o),o}invoke(t,...e){const[n,i]=this.Ut(e),o=this.Mt(t,e,!1,i);return new Promise(((t,e)=>{this.it[o.invocationId]=(n,i)=>{i?e(i):n&&(n.type===N.Completion?n.error?e(new Error(n.error)):t(n.result):e(new Error(`Unexpected message type: ${n.type}`)))};const i=this.xt(o).catch((t=>{e(t),delete this.it[o.invocationId]}));this.qt(n,i)}))}on(t,e){t&&e&&(t=t.toLowerCase(),this.nt[t]||(this.nt[t]=[]),-1===this.nt[t].indexOf(e)&&this.nt[t].push(e))}off(t,e){if(!t)return;t=t.toLowerCase();const n=this.nt[t];if(n)if(e){const i=n.indexOf(e);-1!==i&&(n.splice(i,1),0===n.length&&delete this.nt[t])}else delete this.nt[t]}onclose(t){t&&this.rt.push(t)}onreconnecting(t){t&&this.ot.push(t)}onreconnected(t){t&&this.ht.push(t)}et(t){if(this.Ct(),this.lt||(t=this.jt(t),this.lt=!0),t){const n=this.D.parseMessages(t,this.u);for(const i of n)if(!this.Pt||this.Pt.W(i))switch(i.type){case N.Invocation:this.Wt(i).catch((t=>{this.u.log(e.Error,`Invoke client method threw error: ${_(t)}`)}));break;case N.StreamItem:case N.Completion:{const n=this.it[i.invocationId];if(n){i.type===N.Completion&&delete this.it[i.invocationId];try{n(i)}catch(t){this.u.log(e.Error,`Stream callback threw error: ${_(t)}`)}}break}case N.Ping:break;case N.Close:{this.u.log(e.Information,"Close message received from server.");const t=i.error?new Error("Server returned an error on close: "+i.error):void 0;!0===i.allowReconnect?this.connection.stop(t):this.It=this._t(t);break}case N.Ack:this.Pt&&this.Pt.N(i);break;case N.Sequence:this.Pt&&this.Pt.F(i);break;default:this.u.log(e.Warning,`Invalid message type: ${i.type}.`)}}this.St()}jt(t){let n,i;try{[i,n]=this.tt.parseHandshakeResponse(t)}catch(t){const n="Error parsing handshake response: "+t;this.u.log(e.Error,n);const i=new Error(n);throw this.Et(i),i}if(n.error){const t="Server returned handshake error: "+n.error;this.u.log(e.Error,t);const i=new Error(t);throw this.Et(i),i}return this.u.log(e.Debug,"Server handshake complete."),this.bt(),i}kt(){this.connection.features.inherentKeepAlive||(this.K=(new Date).getTime()+this.keepAliveIntervalInMilliseconds,this.Tt())}St(){if(!this.connection.features||!this.connection.features.inherentKeepAlive){this.Ot=setTimeout((()=>this.serverTimeout()),this.serverTimeoutInMilliseconds);let t=this.K-(new Date).getTime();if(t<0)return void(this.ut===j.Connected&&this.Ft());void 0===this.Bt&&(t<0&&(t=0),this.Bt=setTimeout((async()=>{this.ut===j.Connected&&await this.Ft()}),t))}}serverTimeout(){this.connection.stop(new Error("Server timeout elapsed without receiving a message from the server."))}async Wt(t){const n=t.target.toLowerCase(),i=this.nt[n];if(!i)return this.u.log(e.Warning,`No client method with the name '${n}' found.`),void(t.invocationId&&(this.u.log(e.Warning,`No result given for '${n}' method and invocation ID '${t.invocationId}'.`),await this.xt(this.Xt(t.invocationId,"Client didn't provide a result.",null))));const o=i.slice(),r=!!t.invocationId;let s,a,c;for(const i of o)try{const o=s;s=await i.apply(this,t.arguments),r&&s&&o&&(this.u.log(e.Error,`Multiple results provided for '${n}'. Sending error to server.`),c=this.Xt(t.invocationId,"Client provided multiple results.",null)),a=void 0}catch(t){a=t,this.u.log(e.Error,`A callback for the method '${n}' threw error '${t}'.`)}c?await this.xt(c):r?(a?c=this.Xt(t.invocationId,`${a}`,null):void 0!==s?c=this.Xt(t.invocationId,null,s):(this.u.log(e.Warning,`No result given for '${n}' method and invocation ID '${t.invocationId}'.`),c=this.Xt(t.invocationId,"Client didn't provide a result.",null)),await this.xt(c)):s&&this.u.log(e.Error,`Result given for '${n}' method but server is not expecting a result.`)}st(t){this.u.log(e.Debug,`HubConnection.connectionClosed(${t}) called while in state ${this.ut}.`),this.vt=this.vt||t||new r("The underlying connection was closed before the hub handshake could complete."),this.bt&&this.bt(),this.Jt(t||new Error("Invocation canceled due to the underlying connection being closed.")),this.Ct(),this.Tt(),this.ut===j.Disconnecting?this.Dt(t):this.ut===j.Connected&&this.Z?this.zt(t):this.ut===j.Connected&&this.Dt(t)}Dt(t){if(this.dt){this.ut=j.Disconnected,this.dt=!1,this.Pt&&(this.Pt.X(null!=t?t:new Error("Connection closed.")),this.Pt=void 0),g.isBrowser&&window.document.removeEventListener("freeze",this.G);try{this.rt.forEach((e=>e.apply(this,[t])))}catch(n){this.u.log(e.Error,`An onclose callback called with error '${t}' threw error '${n}'.`)}}}async zt(t){const n=Date.now();let i=0,o=void 0!==t?t:new Error("Attempting to reconnect due to a unknown error."),r=this.Vt(i,0,o);if(null===r)return this.u.log(e.Debug,"Connection not reconnecting because the IRetryPolicy returned null on the first reconnect attempt."),void this.Dt(t);if(this.ut=j.Reconnecting,t?this.u.log(e.Information,`Connection reconnecting because of error '${t}'.`):this.u.log(e.Information,"Connection reconnecting."),0!==this.ot.length){try{this.ot.forEach((e=>e.apply(this,[t])))}catch(n){this.u.log(e.Error,`An onreconnecting callback called with error '${t}' threw error '${n}'.`)}if(this.ut!==j.Reconnecting)return void this.u.log(e.Debug,"Connection left the reconnecting state in onreconnecting callback. Done reconnecting.")}for(;null!==r;){if(this.u.log(e.Information,`Reconnect attempt number ${i+1} will start in ${r} ms.`),await new Promise((t=>{this.Ht=setTimeout(t,r)})),this.Ht=void 0,this.ut!==j.Reconnecting)return void this.u.log(e.Debug,"Connection left the reconnecting state during reconnect delay. Done reconnecting.");try{if(await this.yt(),this.ut=j.Connected,this.u.log(e.Information,"HubConnection reconnected successfully."),0!==this.ht.length)try{this.ht.forEach((t=>t.apply(this,[this.connection.connectionId])))}catch(t){this.u.log(e.Error,`An onreconnected callback called with connectionId '${this.connection.connectionId}; threw error '${t}'.`)}return}catch(t){if(this.u.log(e.Information,`Reconnect attempt failed because of error '${t}'.`),this.ut!==j.Reconnecting)return this.u.log(e.Debug,`Connection moved to the '${this.ut}' from the reconnecting state during reconnect attempt. Done reconnecting.`),void(this.ut===j.Disconnecting&&this.Dt());i++,o=t instanceof Error?t:new Error(t.toString()),r=this.Vt(i,Date.now()-n,o)}}this.u.log(e.Information,`Reconnect retries have been exhausted after ${Date.now()-n} ms and ${i} failed attempts. Connection disconnecting.`),this.Dt()}Vt(t,n,i){try{return this.Z.nextRetryDelayInMilliseconds({elapsedMilliseconds:n,previousRetryCount:t,retryReason:i})}catch(i){return this.u.log(e.Error,`IRetryPolicy.nextRetryDelayInMilliseconds(${t}, ${n}) threw error '${i}'.`),null}}Jt(t){const n=this.it;this.it={},Object.keys(n).forEach((i=>{const o=n[i];try{o(null,t)}catch(n){this.u.log(e.Error,`Stream 'error' callback called with '${t}' threw error: ${_(n)}`)}}))}Tt(){this.Bt&&(clearTimeout(this.Bt),this.Bt=void 0)}Ct(){this.Ot&&clearTimeout(this.Ot)}Mt(t,e,n,i){if(n)return 0!==i.length?{target:t,arguments:e,streamIds:i,type:N.Invocation}:{target:t,arguments:e,type:N.Invocation};{const n=this.ct;return this.ct++,0!==i.length?{target:t,arguments:e,invocationId:n.toString(),streamIds:i,type:N.Invocation}:{target:t,arguments:e,invocationId:n.toString(),type:N.Invocation}}}qt(t,e){if(0!==t.length){e||(e=Promise.resolve());for(const n in t)t[n].subscribe({complete:()=>{e=e.then((()=>this.xt(this.Xt(n))))},error:t=>{let i;i=t instanceof Error?t.message:t&&t.toString?t.toString():"Unknown error",e=e.then((()=>this.xt(this.Xt(n,i))))},next:t=>{e=e.then((()=>this.xt(this.Kt(n,t))))}})}}Ut(t){const e=[],n=[];for(let i=0;i0)&&(e=!1,this.te=await this.Zt()),this.ee(t);const n=await this.Yt.send(t);return e&&401===n.statusCode&&this.Zt?(this.te=await this.Zt(),this.ee(t),await this.Yt.send(t)):n}ee(t){t.headers||(t.headers={}),this.te?t.headers[z.Authorization]=`Bearer ${this.te}`:this.Zt&&t.headers[z.Authorization]&&delete t.headers[z.Authorization]}getCookieString(t){return this.Yt.getCookieString(t)}}var U,B;!function(t){t[t.None=0]="None",t[t.WebSockets=1]="WebSockets",t[t.ServerSentEvents=2]="ServerSentEvents",t[t.LongPolling=4]="LongPolling"}(U||(U={})),function(t){t[t.Text=1]="Text",t[t.Binary=2]="Binary"}(B||(B={}));class W{constructor(){this.se=!1,this.onabort=null}abort(){this.se||(this.se=!0,this.onabort&&this.onabort())}get signal(){return this}get aborted(){return this.se}}class X{get pollAborted(){return this.ie.aborted}constructor(t,e,n){this.$=t,this.u=e,this.ie=new W,this.ne=n,this.re=!1,this.onreceive=null,this.onclose=null}async connect(t,n){if(m.isRequired(t,"url"),m.isRequired(n,"transferFormat"),m.isIn(n,B,"transferFormat"),this.oe=t,this.u.log(e.Trace,"(LongPolling transport) Connecting."),n===B.Binary&&"undefined"!=typeof XMLHttpRequest&&"string"!=typeof(new XMLHttpRequest).responseType)throw new Error("Binary protocols over XmlHttpRequest not implementing advanced features are not supported.");const[o,r]=C(),s={[o]:r,...this.ne.headers},a={abortSignal:this.ie.signal,headers:s,timeout:1e5,withCredentials:this.ne.withCredentials};n===B.Binary&&(a.responseType="arraybuffer");const c=`${t}&_=${Date.now()}`;this.u.log(e.Trace,`(LongPolling transport) polling: ${c}.`);const l=await this.$.get(c,a);200!==l.statusCode?(this.u.log(e.Error,`(LongPolling transport) Unexpected response code: ${l.statusCode}.`),this.he=new i(l.statusText||"",l.statusCode),this.re=!1):this.re=!0,this.ce=this.ae(this.oe,a)}async ae(t,n){try{for(;this.re;)try{const o=`${t}&_=${Date.now()}`;this.u.log(e.Trace,`(LongPolling transport) polling: ${o}.`);const r=await this.$.get(o,n);204===r.statusCode?(this.u.log(e.Information,"(LongPolling transport) Poll terminated by server."),this.re=!1):200!==r.statusCode?(this.u.log(e.Error,`(LongPolling transport) Unexpected response code: ${r.statusCode}.`),this.he=new i(r.statusText||"",r.statusCode),this.re=!1):r.content?(this.u.log(e.Trace,`(LongPolling transport) data received. ${y(r.content,this.ne.logMessageContent)}.`),this.onreceive&&this.onreceive(r.content)):this.u.log(e.Trace,"(LongPolling transport) Poll timed out, reissuing.")}catch(t){this.re?t instanceof o?this.u.log(e.Trace,"(LongPolling transport) Poll timed out, reissuing."):(this.he=t,this.re=!1):this.u.log(e.Trace,`(LongPolling transport) Poll errored after shutdown: ${t.message}`)}}finally{this.u.log(e.Trace,"(LongPolling transport) Polling complete."),this.pollAborted||this.le()}}async send(t){return this.re?b(this.u,"LongPolling",this.$,this.oe,t,this.ne):Promise.reject(new Error("Cannot send until the transport is connected"))}async stop(){this.u.log(e.Trace,"(LongPolling transport) Stopping polling."),this.re=!1,this.ie.abort();try{await this.ce,this.u.log(e.Trace,`(LongPolling transport) sending DELETE request to ${this.oe}.`);const t={},[n,o]=C();t[n]=o;const r={headers:{...t,...this.ne.headers},timeout:this.ne.timeout,withCredentials:this.ne.withCredentials};let s;try{await this.$.delete(this.oe,r)}catch(t){s=t}s?s instanceof i&&(404===s.statusCode?this.u.log(e.Trace,"(LongPolling transport) A 404 response was returned from sending a DELETE request."):this.u.log(e.Trace,`(LongPolling transport) Error sending a DELETE request: ${s}`)):this.u.log(e.Trace,"(LongPolling transport) DELETE request accepted.")}finally{this.u.log(e.Trace,"(LongPolling transport) Stop finished."),this.le()}}le(){if(this.onclose){let t="(LongPolling transport) Firing onclose event.";this.he&&(t+=" Error: "+this.he),this.u.log(e.Trace,t),this.onclose(this.he)}}}class V{constructor(t,e,n,i){this.$=t,this.te=e,this.u=n,this.ne=i,this.onreceive=null,this.onclose=null}async connect(t,n){return m.isRequired(t,"url"),m.isRequired(n,"transferFormat"),m.isIn(n,B,"transferFormat"),this.u.log(e.Trace,"(SSE transport) Connecting."),this.oe=t,this.te&&(t+=(t.indexOf("?")<0?"?":"&")+`access_token=${encodeURIComponent(this.te)}`),new Promise(((i,o)=>{let r,s=!1;if(n===B.Text){if(g.isBrowser||g.isWebWorker)r=new this.ne.EventSource(t,{withCredentials:this.ne.withCredentials});else{const e=this.$.getCookieString(t),n={};n.Cookie=e;const[i,o]=C();n[i]=o,r=new this.ne.EventSource(t,{withCredentials:this.ne.withCredentials,headers:{...n,...this.ne.headers}})}try{r.onmessage=t=>{if(this.onreceive)try{this.u.log(e.Trace,`(SSE transport) data received. ${y(t.data,this.ne.logMessageContent)}.`),this.onreceive(t.data)}catch(t){return void this.ue(t)}},r.onerror=t=>{s?this.ue():o(new Error("EventSource failed to connect. The connection could not be found on the server, either the connection ID is not present on the server, or a proxy is refusing/buffering the connection. If you have multiple servers check that sticky sessions are enabled."))},r.onopen=()=>{this.u.log(e.Information,`SSE connected to ${this.oe}`),this.de=r,s=!0,i()}}catch(t){return void o(t)}}else o(new Error("The Server-Sent Events transport only supports the 'Text' transfer format"))}))}async send(t){return this.de?b(this.u,"SSE",this.$,this.oe,t,this.ne):Promise.reject(new Error("Cannot send until the transport is connected"))}stop(){return this.ue(),Promise.resolve()}ue(t){this.de&&(this.de.close(),this.de=void 0,this.onclose&&this.onclose(t))}}class J{constructor(t,e,n,i,o,r){this.u=n,this.Zt=e,this.fe=i,this.pe=o,this.$=t,this.onreceive=null,this.onclose=null,this.we=r}async connect(t,n){let i;return m.isRequired(t,"url"),m.isRequired(n,"transferFormat"),m.isIn(n,B,"transferFormat"),this.u.log(e.Trace,"(WebSockets transport) Connecting."),this.Zt&&(i=await this.Zt()),new Promise(((o,r)=>{let s;t=t.replace(/^http/,"ws");const a=this.$.getCookieString(t);let c=!1;if(g.isNode||g.isReactNative){const e={},[n,o]=C();e[n]=o,i&&(e[z.Authorization]=`Bearer ${i}`),a&&(e[z.Cookie]=a),s=new this.pe(t,void 0,{headers:{...e,...this.we}})}else i&&(t+=(t.indexOf("?")<0?"?":"&")+`access_token=${encodeURIComponent(i)}`);s||(s=new this.pe(t)),n===B.Binary&&(s.binaryType="arraybuffer"),s.onopen=n=>{this.u.log(e.Information,`WebSocket connected to ${t}.`),this.ge=s,c=!0,o()},s.onerror=t=>{let n=null;n="undefined"!=typeof ErrorEvent&&t instanceof ErrorEvent?t.error:"There was an error with the transport",this.u.log(e.Information,`(WebSockets transport) ${n}.`)},s.onmessage=t=>{if(this.u.log(e.Trace,`(WebSockets transport) data received. ${y(t.data,this.fe)}.`),this.onreceive)try{this.onreceive(t.data)}catch(t){return void this.ue(t)}},s.onclose=t=>{if(c)this.ue(t);else{let e=null;e="undefined"!=typeof ErrorEvent&&t instanceof ErrorEvent?t.error:"WebSocket failed to connect. The connection could not be found on the server, either the endpoint may not be a SignalR endpoint, the connection ID is not present on the server, or there is a proxy blocking WebSockets. If you have multiple servers check that sticky sessions are enabled.",r(new Error(e))}}}))}send(t){return this.ge&&this.ge.readyState===this.pe.OPEN?(this.u.log(e.Trace,`(WebSockets transport) sending data. ${y(t,this.fe)}.`),this.ge.send(t),Promise.resolve()):Promise.reject("WebSocket is not in the OPEN state")}stop(){return this.ge&&this.ue(void 0),Promise.resolve()}ue(t){this.ge&&(this.ge.onclose=()=>{},this.ge.onmessage=()=>{},this.ge.onerror=()=>{},this.ge.close(),this.ge=void 0),this.u.log(e.Trace,"(WebSockets transport) socket closed."),this.onclose&&(!this.me(t)||!1!==t.wasClean&&1e3===t.code?t instanceof Error?this.onclose(t):this.onclose():this.onclose(new Error(`WebSocket closed with status code: ${t.code} (${t.reason||"no reason given"}).`)))}me(t){return t&&"boolean"==typeof t.wasClean&&"number"==typeof t.code}}class Z{constructor(t,n={}){var i;if(this.ye=()=>{},this.features={},this.ve=1,m.isRequired(t,"url"),this.u=void 0===(i=n.logger)?new w(e.Information):null===i?p.instance:void 0!==i.log?i:new w(i),this.baseUrl=this.be(t),(n=n||{}).logMessageContent=void 0!==n.logMessageContent&&n.logMessageContent,"boolean"!=typeof n.withCredentials&&void 0!==n.withCredentials)throw new Error("withCredentials option was not a 'boolean' or 'undefined' value");n.withCredentials=void 0===n.withCredentials||n.withCredentials,n.timeout=void 0===n.timeout?1e5:n.timeout;let o=null,r=null;if(g.isNode){const t=require;o=t("ws"),r=t("eventsource")}g.isNode||"undefined"==typeof WebSocket||n.WebSocket?g.isNode&&!n.WebSocket&&o&&(n.WebSocket=o):n.WebSocket=WebSocket,g.isNode||"undefined"==typeof EventSource||n.EventSource?g.isNode&&!n.EventSource&&void 0!==r&&(n.EventSource=r):n.EventSource=EventSource,this.$=new F(n.httpClient||new D(this.u),n.accessTokenFactory),this.ut="Disconnected",this.dt=!1,this.ne=n,this.onreceive=null,this.onclose=null}async start(t){if(t=t||B.Binary,m.isIn(t,B,"transferFormat"),this.u.log(e.Debug,`Starting connection with transfer format '${B[t]}'.`),"Disconnected"!==this.ut)return Promise.reject(new Error("Cannot start an HttpConnection that is not in the 'Disconnected' state."));if(this.ut="Connecting",this.Ee=this.yt(t),await this.Ee,"Disconnecting"===this.ut){const t="Failed to start the HttpConnection before stop() was called.";return this.u.log(e.Error,t),await this.It,Promise.reject(new r(t))}if("Connected"!==this.ut){const t="HttpConnection.startInternal completed gracefully but didn't enter the connection into the connected state!";return this.u.log(e.Error,t),Promise.reject(new r(t))}this.dt=!0}send(t){return"Connected"!==this.ut?Promise.reject(new Error("Cannot send data if the connection is not in the 'Connected' State.")):(this.$e||(this.$e=new Y(this.transport)),this.$e.send(t))}async stop(t){return"Disconnected"===this.ut?(this.u.log(e.Debug,`Call to HttpConnection.stop(${t}) ignored because the connection is already in the disconnected state.`),Promise.resolve()):"Disconnecting"===this.ut?(this.u.log(e.Debug,`Call to HttpConnection.stop(${t}) ignored because the connection is already in the disconnecting state.`),this.It):(this.ut="Disconnecting",this.It=new Promise((t=>{this.ye=t})),await this._t(t),void await this.It)}async _t(t){this.Ce=t;try{await this.Ee}catch(t){}if(this.transport){try{await this.transport.stop()}catch(t){this.u.log(e.Error,`HttpConnection.transport.stop() threw error '${t}'.`),this.Se()}this.transport=void 0}else this.u.log(e.Debug,"HttpConnection.transport is undefined in HttpConnection.stop() because start() failed.")}async yt(t){let n=this.baseUrl;this.Zt=this.ne.accessTokenFactory,this.$.Zt=this.Zt;try{if(this.ne.skipNegotiation){if(this.ne.transport!==U.WebSockets)throw new Error("Negotiation can only be skipped when using the WebSocket transport directly.");this.transport=this.ke(U.WebSockets),await this.Pe(n,t)}else{let e=null,i=0;do{if(e=await this.Te(n),"Disconnecting"===this.ut||"Disconnected"===this.ut)throw new r("The connection was stopped during negotiation.");if(e.error)throw new Error(e.error);if(e.ProtocolVersion)throw new Error("Detected a connection attempt to an ASP.NET SignalR Server. This client only supports connecting to an ASP.NET Core SignalR Server. See https://aka.ms/signalr-core-differences for details.");if(e.url&&(n=e.url),e.accessToken){const t=e.accessToken;this.Zt=()=>t,this.$.te=t,this.$.Zt=void 0}i++}while(e.url&&i<100);if(100===i&&e.url)throw new Error("Negotiate redirection limit exceeded.");await this.Ie(n,this.ne.transport,e,t)}this.transport instanceof X&&(this.features.inherentKeepAlive=!0),"Connecting"===this.ut&&(this.u.log(e.Debug,"The HttpConnection connected successfully."),this.ut="Connected")}catch(t){return this.u.log(e.Error,"Failed to start the connection: "+t),this.ut="Disconnected",this.transport=void 0,this.ye(),Promise.reject(t)}}async Te(t){const n={},[o,r]=C();n[o]=r;const s=this._e(t);this.u.log(e.Debug,`Sending negotiation request: ${s}.`);try{const t=await this.$.post(s,{content:"",headers:{...n,...this.ne.headers},timeout:this.ne.timeout,withCredentials:this.ne.withCredentials});if(200!==t.statusCode)return Promise.reject(new Error(`Unexpected status code returned from negotiate '${t.statusCode}'`));const e=JSON.parse(t.content);return(!e.negotiateVersion||e.negotiateVersion<1)&&(e.connectionToken=e.connectionId),e.useStatefulReconnect&&!0!==this.ne.He?Promise.reject(new l("Client didn't negotiate Stateful Reconnect but the server did.")):e}catch(t){let n="Failed to complete negotiation with the server: "+t;return t instanceof i&&404===t.statusCode&&(n+=" Either this is not a SignalR endpoint or there is a proxy blocking the connection."),this.u.log(e.Error,n),Promise.reject(new l(n))}}De(t,e){return e?t+(-1===t.indexOf("?")?"?":"&")+`id=${e}`:t}async Ie(t,n,i,o){let s=this.De(t,i.connectionToken);if(this.Re(n))return this.u.log(e.Debug,"Connection was provided an instance of ITransport, using that directly."),this.transport=n,await this.Pe(s,o),void(this.connectionId=i.connectionId);const a=[],l=i.availableTransports||[];let d=i;for(const i of l){const l=this.xe(i,n,o,!0===(null==d?void 0:d.useStatefulReconnect));if(l instanceof Error)a.push(`${i.transport} failed:`),a.push(l);else if(this.Re(l)){if(this.transport=l,!d){try{d=await this.Te(t)}catch(t){return Promise.reject(t)}s=this.De(t,d.connectionToken)}try{return await this.Pe(s,o),void(this.connectionId=d.connectionId)}catch(t){if(this.u.log(e.Error,`Failed to start the transport '${i.transport}': ${t}`),d=void 0,a.push(new c(`${i.transport} failed: ${t}`,U[i.transport])),"Connecting"!==this.ut){const t="Failed to select transport before stop() was called.";return this.u.log(e.Debug,t),Promise.reject(new r(t))}}}}return a.length>0?Promise.reject(new u(`Unable to connect to the server with any of the available transports. ${a.join(" ")}`,a)):Promise.reject(new Error("None of the transports supported by the client are supported by the server."))}ke(t){switch(t){case U.WebSockets:if(!this.ne.WebSocket)throw new Error("'WebSocket' is not supported in your environment.");return new J(this.$,this.Zt,this.u,this.ne.logMessageContent,this.ne.WebSocket,this.ne.headers||{});case U.ServerSentEvents:if(!this.ne.EventSource)throw new Error("'EventSource' is not supported in your environment.");return new V(this.$,this.$.te,this.u,this.ne);case U.LongPolling:return new X(this.$,this.u,this.ne);default:throw new Error(`Unknown transport: ${t}.`)}}Pe(t,e){return this.transport.onreceive=this.onreceive,this.features.reconnect?this.transport.onclose=async n=>{let i=!1;if(this.features.reconnect){try{this.features.disconnected(),await this.transport.connect(t,e),await this.features.resend()}catch{i=!0}i&&this.Se(n)}else this.Se(n)}:this.transport.onclose=t=>this.Se(t),this.transport.connect(t,e)}xe(t,n,i,o){const r=U[t.transport];if(null==r)return this.u.log(e.Debug,`Skipping transport '${t.transport}' because it is not supported by this client.`),new Error(`Skipping transport '${t.transport}' because it is not supported by this client.`);if(!function(t,e){return!t||!!(e&t)}(n,r))return this.u.log(e.Debug,`Skipping transport '${U[r]}' because it was disabled by the client.`),new a(`'${U[r]}' is disabled by the client.`,r);if(!(t.transferFormats.map((t=>B[t])).indexOf(i)>=0))return this.u.log(e.Debug,`Skipping transport '${U[r]}' because it does not support the requested transfer format '${B[i]}'.`),new Error(`'${U[r]}' does not support ${B[i]}.`);if(r===U.WebSockets&&!this.ne.WebSocket||r===U.ServerSentEvents&&!this.ne.EventSource)return this.u.log(e.Debug,`Skipping transport '${U[r]}' because it is not supported in your environment.'`),new s(`'${U[r]}' is not supported in your environment.`,r);this.u.log(e.Debug,`Selecting transport '${U[r]}'.`);try{return this.features.reconnect=r===U.WebSockets?o:void 0,this.ke(r)}catch(t){return t}}Re(t){return t&&"object"==typeof t&&"connect"in t}Se(t){if(this.u.log(e.Debug,`HttpConnection.stopConnection(${t}) called while in state ${this.ut}.`),this.transport=void 0,t=this.Ce||t,this.Ce=void 0,"Disconnected"!==this.ut){if("Connecting"===this.ut)throw this.u.log(e.Warning,`Call to HttpConnection.stopConnection(${t}) was ignored because the connection is still in the connecting state.`),new Error(`HttpConnection.stopConnection(${t}) was called while the connection is still in the connecting state.`);if("Disconnecting"===this.ut&&this.ye(),t?this.u.log(e.Error,`Connection disconnected with error '${t}'.`):this.u.log(e.Information,"Connection disconnected."),this.$e&&(this.$e.stop().catch((t=>{this.u.log(e.Error,`TransportSendQueue.stop() threw error '${t}'.`)})),this.$e=void 0),this.connectionId=void 0,this.ut="Disconnected",this.dt){this.dt=!1;try{this.onclose&&this.onclose(t)}catch(n){this.u.log(e.Error,`HttpConnection.onclose(${t}) threw error '${n}'.`)}}}else this.u.log(e.Debug,`Call to HttpConnection.stopConnection(${t}) was ignored because the connection is already in the disconnected state.`)}be(t){if(0===t.lastIndexOf("https://",0)||0===t.lastIndexOf("http://",0))return t;if(!g.isBrowser)throw new Error(`Cannot resolve '${t}'.`);const n=window.document.createElement("a");return n.href=t,this.u.log(e.Information,`Normalizing '${t}' to '${n.href}'.`),n.href}_e(t){const e=new URL(t);e.pathname.endsWith("/")?e.pathname+="negotiate":e.pathname+="/negotiate";const n=new URLSearchParams(e.searchParams);return n.has("negotiateVersion")||n.append("negotiateVersion",this.ve.toString()),n.has("useStatefulReconnect")?"true"===n.get("useStatefulReconnect")&&(this.ne.He=!0):!0===this.ne.He&&n.append("useStatefulReconnect","true"),e.search=n.toString(),e.toString()}}class Y{constructor(t){this.Ae=t,this.Ue=[],this.Le=!0,this.Ne=new Q,this.qe=new Q,this.Me=this.je()}send(t){return this.We(t),this.qe||(this.qe=new Q),this.qe.promise}stop(){return this.Le=!1,this.Ne.resolve(),this.Me}We(t){if(this.Ue.length&&typeof this.Ue[0]!=typeof t)throw new Error(`Expected data to be of type ${typeof this.Ue} but was of type ${typeof t}`);this.Ue.push(t),this.Ne.resolve()}async je(){for(;;){if(await this.Ne.promise,!this.Le){this.qe&&this.qe.reject("Connection stopped.");break}this.Ne=new Q;const t=this.qe;this.qe=void 0;const e="string"==typeof this.Ue[0]?this.Ue.join(""):Y.Oe(this.Ue);this.Ue.length=0;try{await this.Ae.send(e),t.resolve()}catch(e){t.reject(e)}}}static Oe(t){const e=t.map((t=>t.byteLength)).reduce(((t,e)=>t+e)),n=new Uint8Array(e);let i=0;for(const e of t)n.set(new Uint8Array(e),i),i+=e.byteLength;return n.buffer}}class Q{constructor(){this.promise=new Promise(((t,e)=>[this.j,this.Fe]=[t,e]))}resolve(){this.j()}reject(t){this.Fe(t)}}class K{constructor(){this.name="json",this.version=2,this.transferFormat=B.Text}parseMessages(t,n){if("string"!=typeof t)throw new Error("Invalid input for JSON hub protocol. Expected a string.");if(!t)return[];null===n&&(n=p.instance);const i=A.parse(t),o=[];for(const t of i){const i=JSON.parse(t);if("number"!=typeof i.type)throw new Error("Invalid payload.");switch(i.type){case N.Invocation:this.U(i);break;case N.StreamItem:this.Be(i);break;case N.Completion:this.Xe(i);break;case N.Ping:case N.Close:break;case N.Ack:this.Je(i);break;case N.Sequence:this.ze(i);break;default:n.log(e.Information,"Unknown message type '"+i.type+"' ignored.");continue}o.push(i)}return o}writeMessage(t){return A.write(JSON.stringify(t))}U(t){this.Ve(t.target,"Invalid payload for Invocation message."),void 0!==t.invocationId&&this.Ve(t.invocationId,"Invalid payload for Invocation message.")}Be(t){if(this.Ve(t.invocationId,"Invalid payload for StreamItem message."),void 0===t.item)throw new Error("Invalid payload for StreamItem message.")}Xe(t){if(t.result&&t.error)throw new Error("Invalid payload for Completion message.");!t.result&&t.error&&this.Ve(t.error,"Invalid payload for Completion message."),this.Ve(t.invocationId,"Invalid payload for Completion message.")}Je(t){if("number"!=typeof t.sequenceId)throw new Error("Invalid SequenceId for Ack message.")}ze(t){if("number"!=typeof t.sequenceId)throw new Error("Invalid SequenceId for Sequence message.")}Ve(t,e){if("string"!=typeof t||""===t)throw new Error(e)}}const G={trace:e.Trace,debug:e.Debug,info:e.Information,information:e.Information,warn:e.Warning,warning:e.Warning,error:e.Error,critical:e.Critical,none:e.None};class tt{configureLogging(t){if(m.isRequired(t,"logging"),void 0!==t.log)this.logger=t;else if("string"==typeof t){const e=function(t){const e=G[t.toLowerCase()];if(void 0!==e)return e;throw new Error(`Unknown log level: ${t}`)}(t);this.logger=new w(e)}else this.logger=new w(t);return this}withUrl(t,e){return m.isRequired(t,"url"),m.isNotEmpty(t,"url"),this.url=t,this.httpConnectionOptions="object"==typeof e?{...this.httpConnectionOptions,...e}:{...this.httpConnectionOptions,transport:e},this}withHubProtocol(t){return m.isRequired(t,"protocol"),this.protocol=t,this}withAutomaticReconnect(t){if(this.reconnectPolicy)throw new Error("A reconnectPolicy has already been set.");return t?Array.isArray(t)?this.reconnectPolicy=new L(t):this.reconnectPolicy=t:this.reconnectPolicy=new L,this}withServerTimeout(t){return m.isRequired(t,"milliseconds"),this.Ke=t,this}withKeepAliveInterval(t){return m.isRequired(t,"milliseconds"),this.Ge=t,this}withStatefulReconnect(t){return void 0===this.httpConnectionOptions&&(this.httpConnectionOptions={}),this.httpConnectionOptions.He=!0,this.Y=null==t?void 0:t.bufferSize,this}build(){const t=this.httpConnectionOptions||{};if(void 0===t.logger&&(t.logger=this.logger),!this.url)throw new Error("The 'HubConnectionBuilder.withUrl' method must be called before building the connection.");const e=new Z(this.url,t);return O.create(e,this.logger||p.instance,this.protocol||new K,this.reconnectPolicy,this.Ke,this.Ge,this.Y)}}return Uint8Array.prototype.indexOf||Object.defineProperty(Uint8Array.prototype,"indexOf",{value:Array.prototype.indexOf,writable:!0}),Uint8Array.prototype.slice||Object.defineProperty(Uint8Array.prototype,"slice",{value:function(t,e){return new Uint8Array(Array.prototype.slice.call(this,t,e))},writable:!0}),Uint8Array.prototype.forEach||Object.defineProperty(Uint8Array.prototype,"forEach",{value:Array.prototype.forEach,writable:!0}),n})(),"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.signalR=e():t.signalR=e(),$.extend($t,{t1:"Eingabe erforderlich",t2:"Bitte überprüfen Sie Ihre Eingaben im Formular.",b0:"Erstellt",b1:"Zuletzt geändert",b2:"von",t12:"Der Server hat einen Fehler zurückgegeben. Bitte versuchen Sie es erneut.",t17:"Eine Email mit einem Aktivierungs-Link wurde an deine Adresse versandt.",t18:"Ein Account mit deinem Namen existiert bereits. Dennoch erstellen?",t19:"Einträge sind entweder unngültig oder zu kurz.",t20:"Der Server hat einen Fehler gemeldet. Bitte versuch es erneut.",t21:"Der Zugang wurde nicht gefunden.",t30a:"Als erledigt markieren.",t30b:"Als unerledigt markieren.",t55:"Ein Email mit einem Aktivierungs-Link wurde an Ihre Adresse versandt.",t56:"Ein Zugang für diesen Namen besteht bereits. Trotzdem erstellen?",t57:"Ein bestehender Zugang wurde für diese Serie registriert.",t60:"Bitte geben Sie Email-Adresse an, die Sie hier hinterlegt haben.",t61:"Ihr Passwort wurde erfolgreich versandt.",t62:"Die angegebene Email-Adresse stimmt nicht mit der hier hinterlegten überein.",ov:"Persönliche Übersicht"});var $v={}; /*! loadCSS. [c]2020 Filament Group, Inc. MIT License */ /*! onloadCSS. (onload callback for loadCSS) [c]2017 Filament Group, Inc. MIT License */ -function onloadCSS(t,e){e=e||{};let n=function(e){return new Promise(((n,o)=>{t.addEventListener?e.addEventListener("load",newcb):t.attachEvent&&e.attachEvent("onload",newcb),"isApplicationInstalled"in navigator&&"onloadcssdefined"in t&&e.onloadcssdefined(newcb)}))};if(Array.isArray(t)){let o=t.length;Promise.all(t.map(n)).then((function(t){var n=t.reduce(((t,e)=>t+(!0===e?1:0)));!async function(t){!0===t&&"function"==typeof e.success?e.success():!0===t&&"object"==typeof e.success&&e.success instanceof Promise&&await e.success(),e.complete()}(o===n)}))}else n(t)}!function(t){"use strict";var e=function(e,n,o,i){var r,s=t.document,a=s.createElement("link");if(n)r=n;else{var c=(s.body||s.getElementsByTagName("head")[0]).childNodes;r=c[c.length-1]}var l=s.styleSheets;if(i)for(var u in i)i.hasOwnProperty(u)&&a.setAttribute(u,i[u]);a.rel="stylesheet",a.href=e,a.media="only x",function t(e){if(s.body)return e();setTimeout((function(){t(e)}))}((function(){r.parentNode.insertBefore(a,n?r:r.nextSibling)}));var d=function(t){for(var e=a.href,n=l.length;n--;)if(l[n].href===e)return t();setTimeout((function(){d(t)}))};function h(){a.addEventListener&&a.removeEventListener("load",h),a.media=o||"all"}return a.addEventListener&&a.addEventListener("load",h),a.onloadcssdefined=d,d(h),a};"undefined"!=typeof exports?exports.loadCSS=e:t.loadCSS=e}("undefined"!=typeof global?global:this);const isIE=/MSIE\/|Trident/gi.test(window.navigator.userAgent)||void 0!==window.document.documentMode,isfileapi=!!(window.File&&window.FileReader&&window.FileList&&window.Blob);var $ocms={auth:{},no:function(t){t.stopPropagation()},vmin:function(t){var e=$(window).width*(t||1),n=$(window).height*(t||1);return e($ocms.baseurl+"/"+(t||"")).replace(/\/\//,"/"),cexi:null};function deepCopy(t){var e,n,o;if("object"!=typeof t||null===t)return t;for(o in e=Array.isArray(t)?[]:{},t)n=t[o],e[o]=deepCopy(n);return e}function fields_definition(t,e,n){this.label_sng=!0===Array.isArray(t)?"":t||"",this.label_pl=!0===Array.isArray(t)?"":e||"",this.fields=!0===Array.isArray(t)?t:n||[],this.itm=function(t){for(var e=0;e0)for(var n=0;nt||"")).filter(((t,e)=>""!==t)).join(e)}function parseDt(t,e,n){t=(t||"").substr(0,e.length);var o=e,i=t.length>0&&e.split(";").some((function(e){for(var n,i=/[^yMdhms0-9]/gi,r=!0;null!==(n=i.exec(e));)r=r&&e.substr(n.index,1)===t.substr(n.index,1);var s=t.length===e.length&&r;return!0===s&&(o=e),s}));if(!0===i){for(var r,s=[0,0,0,0,0,0,0],a=/(mm{1,2}|dd{1,2}|MM{1,2}|(yy){2,4}|ss{1,2}|hh{1,2})(?!\w)/g;null!==(r=a.exec(o));)s["yMdhms".indexOf(r[0].substr(0,1))]=parseInt(("yy"===r[0]?"20":"")+t.substr(r.index,r[0].length))-("M"===r[0].substr(0,1)?1:0);var c=new(Function.prototype.bind.apply(Date,[null].concat(s)));return"string"==typeof n?fdt(c,n):c}return!1}function bool(t,e){return"boolean"==typeof t?t:"boolean"==typeof e&&e}function booln(t,e){return"boolean"==typeof t?t:"number"==typeof t?1===t:"boolean"==typeof e&&e}Date.prototype.isValid=function(){return!isNaN(this)},Date.prototype.format=function(t){return fdt(this,t)},Date.prototype.addDays=function(t){return this.setDate(this.getDate()+t),this},Date.prototype.isBetween=function(t,e){return this>t&&this section");$(window).scroll((function(e){let n=$(window).scrollTop(),o=$("body");o.toggleClass("unfocus",n>vh()-1.2*t),o.toggleClass("btb",n>.5*vh()-t)}))},$ocms.cf_reset=function(){return $("#contentframe").empty()},function(t){t.fn.scrollTo=function(e){if(t(this).length>0){var n=t(this).offset().top||0;n>0&&t("html, body").animate({scrollTop:n-hh()},2e3)}},t.fn.ldng=function(e){var n=!0;return"boolean"==typeof e?n=e:"number"==typeof e&&(n=e>0),t(this).toggleClass("loading",n)},"function"!=typeof t.noop&&(t.noop=function(){}),t.fn.hasAttr=function(e){var n=t(this).attr(e);return void 0!==n&&!1!==n},t.fn.parseCssPx=function(e){try{return parseFloat(t(this).css(e).replace("px","")||0)}catch(t){return 0}},t.max=function(t,e){return isNaN(t)&&isNaN(e)?null:isNaN(t)&&!isNaN(e)?e:!isNaN(e)&&isNaN(e)||t>=e?t:e},t.min=function(t,e){return isNaN(t)&&isNaN(e)?null:isNaN(t)&&!isNaN(e)?e:!isNaN(e)&&isNaN(e)||t<=e?t:e},t.lim=function(t,e){return isNaN(t)?null:isNaN(e)?t:e<=t?e:t},t.fn.enterKey=function(e){return this.each((function(){t(this).keypress((function(t){"13"===(t.keyCode?t.keyCode:t.which).toString()&&e.call(this,t)}))}))}}(jQuery),$ocms.defaultTimeout=3e4,$ocms.AjaxEX=function(t){var e=this;e.responseText=e.responseText||"";var n=e.getResponseHeader("x-ocms-code")||"";e.internalCode=""!==n&&!1===isNaN(n)?parseInt(n):-1,e.isInternal=e.internalCode>-1,e.internalText=decodeURIComponent((e.getResponseHeader("x-ocms-desc")||"").replace(/\+/g,"%20")||"");var o=e.internalText||t,i=e.internalCode||e.status;e.logtext=o+" ("+i+")"},$ocms.postXTS=function(t){$ocms.postXT.call(this,$.extend(t,{sync:!0}))},$ocms.postXT=function(t){if((t=t||{}).trycount=t.trycount||0,""!==(t.url||"")){t.url=-1!==t.url.indexOf("&yy=")?t.url:t.url.indexOf("?")>-1?t.url+"&yy="+(new Date).getTime():t.url+"?yy="+(new Date).getTime();var e=t.context||this;switch(t.context=e,t.retryLimit=t.retryLimit||0,t.timeout=t.timeout||$ocms.defaultTimeout,t.timeout<100&&(t.timeout=1e3*t.timeout),t.data=t.data||{},t.contentType=t.contentType||"multipart/form-data; charset=UTF-8",t.islogin="boolean"==typeof t.islogin&&t.islogin,t.contentType){case"":case"json":t.contentType="application/json; charset=utf-8";break;case"form":t.contentType="application/x-www-form-urlencoded; charset=UTF-8";break;case"multi":t.contentType="multipart/form-data";break;case"text":t.contentType="text/plain; charset=UTF-8"}if(t.form instanceof jQuery?(t.data=t.form.serializeObject(),t.contentType="form-data"):t.lzw instanceof jQuery&&(t.data.lzw=$.ccLZW(t.lzw.serializeAnything(!0)).join(",")),"multipart/form-data"!==t.contentType.substr(0,19)&&"form-data"!==t.contentType.substr(0,9)||t.data instanceof FormData!=!1)t.data instanceof FormData&&(t.contentType=!1,t.processData=!1);else{t.contentType=!1;var n=new FormData;$.each(t.files||[],(function(t,e){n.append("upload_file",e)})),$.each(t.data||{},(function(t,e){n.append(t,e)})),t.data=n,t.processData=!1}var o={type:t.method||"post",url:t.url,data:t.data,processData:"boolean"!=typeof t.processData||t.processData,contentType:t.contentType,cache:t.cache||!1,timeout:t.timeout,beforeSend:function(n){$(t.loading).ldng(),$("body").addClass("ldng"),"function"==typeof t.beforesend&&t.beforesend.apply(e,[n])},success:function(n,o,i){"false"===n||"not authorized"===n?("function"==typeof t.error&&t.error.apply(e,[i,o,n]),"function"==typeof $.status&&$.status(o+" - "+n)):"function"==typeof t.success&&t.success.apply(e,[n,o,i])},error:function(n,o,i){if($ocms.AjaxEX.call(n,o),-1===t.url.indexOf("doc.ashx")||-1!==t.url.indexOf("ftest")){if(401===n.status&&111===n.internalCode&&!1===t.islogin&&"function"==typeof $ocms.login.dlg)$ocms.login.dlg({ajo:t});else if("timeout"===o||302===n.status)return t.tryCount++,t.tryCount<=t.retryLimit?void $ocms.postXT(t):void 0;"function"==typeof t.error?t.error.apply(e,[n,o,i]):"function"==typeof $ocms.failure?$ocms.failure.apply(e,[n]):"function"==typeof $.status&&$.status("Server error: "+o+" - "+i)}},dataType:t.datatype||"json",complete:function(n,o){"function"==typeof t.complete&&t.complete.apply(e,[n,o]),$(t.loading).ldng(0),$("body").removeClass("ldng");let i=$("body > .timer");if(i.length>0){let t=new Date(n.getResponseHeader("ocms_cec")||""),e=new Date(n.getResponseHeader("ocms_cex")||"");if(t.isValid()&&e.isValid()){let n=new Date,o=Math.abs(e-t);n.setMilliseconds(n.getMilliseconds()+o),i.data({cex:n,ctt:o}),$ocms.cex_timer()}}},context:e,async:!0};"boolean"==typeof t.sync&&(o.async=!1===t.sync),!0==("boolean"==typeof t.contentType&&!1===t.contentType)&&(o.contentType=!1),$.ajax(o)}},$ocms.cex_timer=function(){$ocms.cexi||($ocms.cexi=setInterval($ocms.cex_timer,15e3));let t=$("body > .timer"),e=t.data("cex"),n=t.data("ctt"),o=new Date;if(e instanceof Date&&e.isValid()&&"number"==typeof n&&n>0&&e>o){let i=Math.abs(o-e)/n*100;t.css("width",i.toString()+"%"),i<98&&(!$ocms.cex_lp||Math.abs(o-$ocms.cex_lp)>6e5)&&$ocms.postXT({url:$ocms.url("ping"),success:()=>{$ocms.cex_lp=o},timeout:5e3,error:()=>{}})}},$ocms.vbl_send=function(t){var e=t.data||{};if(""!==(e.url||"")){var n=$("#contentframe form:first"),o={url:e.url,data:new FormData,success:function(t){"function"==typeof e.success?e.success(t):"string"==typeof e.success&&alert(e.success)},error:function(t,n,o){"function"==typeof e.error?e.error(o):"string"==typeof e.error&&alert(e.error)},complete:function(){n.ldng(0)}},i=!0;n.find("input").each((function(){var t=$(this),e=t.nza("name"),n=t.val(),r=$(this).prop("required")||!1;if(""!==e){var s=""!==n||!1===r;i=i&&s,!0===s?(o.data.append(e,n),t[0].setCustomValidity("")):""!==$(this).nza("ocms-nvnote")&&t[0].setCustomValidity($(this).nza("ocms-nvnote"))}})),!0===i&&(n.ldng(1),$ocms.postXT.call(this,o))}},function(t){t.fn.nza=function(e,n){var o=t(this).attr(e);return void 0!==o&&!1!==o?o:n||""},t.fn.serializeObject=function(e,n){var o=/\r?\n/g,i=/^(?:submit|button|image|reset|file)$/i,r=/^(?:input|select|textarea|keygen)/i,s=/^(?:checkbox|radio)$/i,a=bool((n=n||{}).typedvalues,!1),c={},l=t(this),u=l.find(':input:not([nosend],[type="file"])').addBack(":input"),d=!0;return t.each(u.not(".tinymce").get(),(function(n,l){var u=t(this),h=this,p=(this.type||"").toLowerCase(),f=u.prop("required")||!1;if(!0===(h.name&&!u.is(":disabled")&&r.test(h.nodeName)&&!i.test(p))){var m=u.val(),g=h.name,y=u.nza("data-format").split(":"),b=u.nza("pattern")||".*";if(!0===s.test(p)&&(m=h.checked?""!==m?m:"true":""),"date"===y[0].substr(0,4)&&y.length>1)"boolean"==typeof(m=parseDt(m,y.slice(1).join(":")))&&(m=null),null===m&&"date"===u.prop("type").substr(0,4)&&!1===isNaN(new Date(u.val()))&&(m=new Date(u.val())),m instanceof Date==!0&&"function"==typeof m.getMonth?!1===a&&(m=fdt(m,"date"===y[0]?"dts":"iso")):m=null;else if("number"===p&&!0===a){let t;t="integer"===y[0]?parseInt(m):parseFloat(m),m=isNaN(t)?m:t}if(!0!==f||""!==(m||"")&&null!==m.match(b)?!0===bool(e,!1)&&h.setCustomValidity(""):(!0===bool(e,!1)&&h.setCustomValidity(u.nza("ocms-nvnote",$ocms.t.inv||"Invalid field")),m=null),null!=m&&"string"==typeof m){let t=c[g];null!=t?Array.isArray(t)?t.push(m.replace(o,"\r\n")):c[g]=[t,m.replace(o,"\r\n")]:c[g]=m.replace(o,"\r\n")}else if(null!=m){let t=c[g];null!=t?Array.isArray(t)?t.push(m):c[g]=[t,m]:c[g]=m}else d=!1}})),u.filter(".tinymce").each((function(e,n){var o=t(this),i=((this.type||"").toLowerCase(),o.prop("required")||!1);try{var r=tinymce.get(t(n).attr("id"));if(r){var s=t(n).attr("name"),a=r.getContent();!1===i||""!==(a||"")?c[s]=a:d=!1}}catch(e){t.noop()}})),l.toggleClass("invalid",!d),d?c:null},t.fn.sendForm=function(e,n,o){var i=t(this);o=o||{};var r={url:e,success:function(t){if(o.response=t,"function"==typeof n)n(t);i.closest("div.modal").remove()},error:function(t,e,n){"function"==typeof o.error?o.error.call(this,t):$ocms.failure.call(this,t)},complete:function(){i.ldng(0),"function"==typeof o.complete&&o.complete.call(this,jqXHR)}},s=i.find('input[type="file"]');r.data=new FormData,s.length>0&&t.each(s[0].files,(function(t,e){r.data.append(t,e),r.data.append("file_lastmodified",$ocms.isodt(e.lastModifiedDate))}));var a=i.serializeObject();t.each(a||{},(function(t,e){r.data.append(t,e)})),i.ldng(),$ocms.postXT.call(this,r)},t.fn.checkValidity=function(){var e=t(this),n=!0;return e.each((function(t,e){n=n&&e.checkValidity()})),n},t.fn.wrap=function(e,n){var o=t(this),i=$$.dc(e).attr(n||{}).insertAfter(o);return o.append(i),i}}(jQuery),$ocms.logout=function(){$ocms.postXT({url:$ocms.url("logout"),complete:function(){window.location.reload()}})},$ocms.login={send:function(t){t.preventDefault();var e=$(this);if(!0===e.find("#dbtn-confirm").hasClass("disabled"))return!1;var n=e.serializeObject();return n.loginaccount=ne(n.loginaccount,$ocms.auth.account||$ocms.auth.requestedaccount||""),n.loginaccount=ne(n.loginaccount,$ocms.auth.account||$ocms.auth.requestedaccount||""),""===ne(n.loginaccount)&&!0===bool($ocms.auth.accountrequired,!0)?(alert($t.l16),!1):($ocms.postXT({url:$ocms.url("login"),data:n,success:function(){window.location.reload()}}),!1)},uichange:function(){let t=$(this),e=t.closest("form"),n=bool($ocms.auth.accountrequired,!0),o=ne(e.find('[name="loginaccount"]').val(),$ocms.auth.account||$ocms.auth.requestedaccount||"");if(""!==o||!1===n){var i=e.find('[name="userlogin"]').empty().val(""),r=e.find('[name="username"]').empty().val(""),s=$("#dlg_userlogin_sel").empty().val(""),a=t.val()||"";if(!1===t.checkValidity()&&""===a)return;var c=t.closest("table").ldng();$ocms.postXT.call(this,{url:$ocms.url("auth"),data:{userinfo:a,account:o||""},success:function(t,e,n){if(1===t.length){var o=t[0];i.val(o.login).change().attr("required","").removeAttr("nosend"),r.val(o.name).change().attr("required","").show(),s.removeAttr("required").attr("nosend","").hide()}else t.length>0?(r.hide().removeAttr("required"),i.removeAttr("required").attr("nosend",""),0===s.length&&(s=$("").attr({name:"userlogin",size:t.length,id:"dlg_userlogin_sel",class:"form-control",required:""}).css({width:"100%","max-width":"100%",padding:"2px"}).insertAfter(r)),$.each(t,(function(t,e){var n=$("").attr({value:e.login,style:"padding-top: 2px; padding-bottom: 5px;","border-bottom":"1px solid #EEE;"}).text(e.name).appendTo(s);t%2==0&&n.css({"background-color":"#F9F9F9"})})),s.attr("required","").removeAttr("nosend")):(s.hide().attr("nosend",""),r.attr("required","").show(),i.attr("required","").removeAttr("nosend"),alert($t.l9))},error:function(t){$ocms.failure.call(this,t)},complete:function(){c.ldng(0)}})}else alert($t.l18)},sendpassword:function(t){var e=$(''),n=e.find(".form-body"),o=null;e.find("form").submit((function(t){t.preventDefault();var i=$(this).serializeObject(!0),r=null===o,s=r?"spwc":"spw";return $ocms.postXT.call(this,{url:$ocms.url(s),data:i,complete:function(){r?(n.append('
Ihnen wurde ein Code per SMS zugesandt.
Bitte tragen Sie den hier ein:
'),o=$('
').appendTo(n)):(alert($t.l13),e.remove())},error:()=>{}}),!1})),e.find(".modal-close").click((function(){e.remove()}));var i=[];$.each($t.l7a.split("\n"),((t,e)=>{Array.prototype.push.apply(i,[$("
"),$("").text(e)])})),e.find(".modal-note").append($('').text($t.alert)).append(i),e.appendTo("body"),setTimeout((function(){$(".modal").find('input[name="lastname"]').focus()}),600)}};var $$={s:function(t){return $("").text(t)},br:function(){return $("
")},sc:function(t,e){return $("").addClass(t).text(e)},td:function(t,e){var n=$("");return t instanceof jQuery==!0?n.appendTo(t):"object"==typeof t?n.attr(t):"string"==typeof t&&n.text(t),"object"==typeof e?n.attr(e):"string"==typeof e&&n.text(e),n},th:function(t,e){var n=$("");return t instanceof jQuery==!0?n.appendTo(t):"object"==typeof t?n.attr(t):"string"==typeof t&&n.text(t),"object"==typeof e?n.attr(e):"string"==typeof e&&n.text(e),n},tdc:function(t,e,n){return $$.td(e,n).addClass(t)},td2:function(t){var e=$('');return"string"===$.type(t)?e.text(t):t instanceof jQuery?e.append(t):"function"==typeof t?t.call(e):e.html(" "),e},td3:function(t){var e=$('');return"string"===$.type(t)?e.text(t):t instanceof jQuery?e.append(t):"function"==typeof t?t.call(e):e.html(" "),e},tdtr:function(t,e){var n=$$.tr().appendTo(e);return t instanceof jQuery==!0||"string"==typeof t?t.appendTo($$.td().appendTo(n)):!0===Array.isArray(t)&&$.each(t,(function(t,e){$(e).appendTo($$.td().appendTo(n))})),n},tr:function(t,e){var n=$("");return t instanceof jQuery==!0?n.appendTo(t):"object"==typeof t&&n.attr(t),"object"==typeof e&&n.attr(e),n},trc:function(t,e){var n=$("").addClass(t);return e instanceof jQuery==!0?n.appendTo(e):"object"==typeof e&&n.attr(e),n},d:function(t){return $("
").attr(t||{})},dc:function(t,e,n,o){var i=$("
").addClass(t);return e instanceof jQuery==!0?i.appendTo(e):"object"==typeof e?i.attr(e):"function"==typeof e?i.click(e):"string"==typeof e&&i.text(e),"string"==typeof n?i.text(n):"object"==typeof n?i.attr(n):"function"==typeof n&&i.click(n),"string"==typeof o?i.text(o):"object"==typeof o?i.attr(o):"function"==typeof o&&i.click(o),i},df:function(t){return $("
 
").attr(t||{})},opt:function(t,e,n){var o=$("");return"string"==typeof t?o.attr("value",t):"object"==typeof t&&o.attr(t),"string"==typeof e?o.text(e):"object"==typeof e&&o.attr(e),"object"==typeof n&&o.attr(n),o},eOpt:function(t){var e=$('');return t&&e.attr("selected","selected"),e},tbl:function(t){return $("
").attr(t||{})},tblc:function(t){return $("
").addClass(t)},thead:function(t){let e=$("");return t instanceof jQuery&&e.prependTo(t),e},tbody:function(t){let e=$("");return t instanceof jQuery&&e.appendTo(t),e},tblset:function(t,e){let n=$$.tbl(t||{});return e instanceof jQuery&&e.append(n),{tbl:n,hd:$$.thead().appendTo(n),bdy:$$.tbody().appendTo(n)}},i:function(t){return $("").attr(t||{})},img:function(t,e){return $("").attr("src",t).attr(e||{})},sel:function(t){return $("").attr(t||{})},btn:function(t){return $("").attr(t||{})},a:function(t){return $("").attr(t||{})},li:function(t){return $("
  • ").attr(t||{})},ul:function(t){return $("
      ").attr(t||{})},nav:function(t){return $("").attr(t||{})},lbl:function(t,e){var n=$("");return"string"==typeof t&&n.text(t),"object"==typeof t?n.attr(t):"object"==typeof e&&n.attr(e),n},txt:function(t){return $("").attr(t||{})},0:function(t,e){return $("<"+t+">").attr(e||{})},bbtn:function(t,e){return $$.btn({type:"button",class:"btn"}).addClass(e).text(t)},svg:t=>$(document.createElementNS("http://www.w3.org/2000/svg",t))};function getMonday(t){var e=(t=new Date(t)).getDay(),n=t.getDate()-e+(0==e?-6:1);return new Date(t.setDate(n))}function $lf(t){var e=void 0===t?null:"number"==typeof t&&1!==t||"boolean"==typeof cl&&!1===t;return $("#listframe").tC("hd",e).is(".hd")}function $nuf(t){if(t&&t.stopPropagation(),!$(this).is(".disabled")){var e=function(t){t.removeClass("vis").find("li.dropdown").removeClass("open").removeClass("vis").attr("aria-expanded","false")},n=$(this).parent("li.dropdown");if(n.length>0){n.tC("open"),navs=!0===n.is(".open")?"true":"false",n.attr("aria-expanded",navs);var o=n.closest("nav");o.find("li.dropdown").not(n.parentsUntil("nav")).not(n).removeClass("open").attr("aria-expanded","false"),!1===n.is(".open")&&n.find("li.dropdown").removeClass("open").attr("aria-expanded","false"),e($("nav").not(o))}else e($("nav"))}}function $tbr(){return $lf(0),$("#topbar").ocmsmenu([])}function $lfr(){return $("#sidebar").empty(),$("#listframe").removeClass("fix").addClass("hd").empty()}function $cfr(){return $tbr(),$("#contentframe").empty()}function jObj(t,e){let n={};if("{"===(t||"").substr(0,1))try{n=JSON.parse(t)}catch(t){n={}}return n[e]||""}function string(t,e){var n,o=t||"";return $.each(e||[],(function(t,e){n=new RegExp("\\{"+t.toString()+"\\}","ig"),o=o.replace(n,e)})),o}function init_tooltip(t){var e=!0===("boolean"==typeof t&&t)&&"mouse";$("[title]").qtip({position:{target:e,adjust:{x:2,y:2},viewport:!0},events:{hidden:$ocms.tooltip_hidden},effect:!1}),$("div.tooltiptext").each((function(){$(this).filter(":not(:empty)").parent().qtip({suppress:!1,content:{text:$(this).clone()},position:{target:e,adjust:{x:2,y:2},viewport:!0},events:{hidden:$ocms.tooltip_hidden}})}))}String.prototype.trim=function(){return this.replace(/^\s+|\s+$/g,"")},String.prototype.left=function(t){if("string"===$.type(t)){var e=this.indexOf(t);return e>0?this.slice(0,e):""}return this.substring(0,t)},String.prototype.right=function(t){if("string"===$.type(t)){var e=this.indexOf(t);return e>0?this.substring(this.length-e):""}return this.substring(this.length-t)},Array.prototype.move=function(t,e){if(e>=this.length)for(var n=e-this.length;1+n--;)this.push(void 0);return this.splice(e,0,this.splice(t,1)[0]),this},function(t){t.fn.appendToIf=function(e,n){var o=t(this),i="function"==typeof n?n(o):n;return!0===("boolean"!=typeof i||i)&&o.appendTo(e),o},t.fn.appendIf=function(e,n){var o=t(this),i="function"==typeof n?n(o):n;return!0===("boolean"!=typeof i||i)&&o.append(e),o},t.fn.rwText=function(e,n,o){var i=t(this).empty();o=t.extend({wrap:!0},o);var r=!0===Array.isArray(e)?e:(null==e?"":String(e)).split("\n");return t.each(r,(function(t,e){""!==(e||"")&&(t>0&&i.append($$.br()),i.append(!0===o.wrap?$$.s(e):e))})),n&&i.attr("title",n),i},t.fn.loadSel=function(e,n,o){if("SELECT"===t(this).prop("tagName").toUpperCase()){var i=t(this);$ocms.postXT.call(this,{url:e,data:n||{},success:function(e){t.each(e,(function(){i.append($$.opt(e.value,e.text))}))},complete:function(){i.ldng(0),"function"==typeof o&&o.call(i)}})}},t.fn.emptyWithEditors=function(e){var n=t(this);return n.find(":input.tinymce").each((function(e,n){try{var o=tinymce.get(t(n).attr("id"));o&&o.remove()}catch(e){t.noop()}})),n.empty()},t.fn.cssValue=function(t){if(this.length>0){var e=this.css(t)||"";if(""===e)return 0;var n=/(^[\d\.]*)(\D{1,3}$)/gi.exec(e);return null!==n?"rem"===n[2]?$ocms.rpx(parseFloat(n[1])):parseFloat(n[1]):!1===isNaN(e)?parseFloat(e):0}return 0},t.fn.veryInnerHeight=function(){let e=e=>t(this).cssValue(e);return t(this).innerHeight()-e("padding-top")-e("padding-bottom")},t.fn.veryInnerWidth=function(){let e=e=>t(this).cssValue(e);return t(this).innerWidth()-e("padding-left")-e("padding-right")},t.fn.marginWidth=function(){let e=e=>t(this).cssValue(e);return e("margin-left")+e("margin-right")},t.fn.marginHeight=function(){let e=e=>t(this).cssValue(e);return e("margin-top")+e("margin-bottom")},t.inArrayRegEx=function(e,n,o){var i="regexp"===t.type(e)?e:new RegExp(e);if(!n)return-1;for(var r=o=o||0;r7){o=e.split(","),i=(n||(t<0?"rgb(0,0,0)":"rgb(255,255,255)")).split(",");var c=a(o[0].slice(4)),l=a(o[1]),u=a(o[2]);return"rgb("+(s((a(i[0].slice(4))-c)*r)+c)+","+(s((a(i[1])-l)*r)+l)+","+(s((a(i[2])-u)*r)+u)+")"}var d=(o=a(e.slice(1),16))>>16,h=o>>8&255,p=255&o;return"#"+(16777216+65536*(s((((i=a((n||(t<0?"#000000":"#FFFFFF")).slice(1),16))>>16)-d)*r)+d)+256*(s(((i>>8&255)-h)*r)+h)+(s(((255&i)-p)*r)+p)).toString(16).slice(1)},t.fn.IN=function(e){return t(this).fadeIn(400,e),t(this)},t.fn.OUT=function(e){return t(this).fadeOut(400,e),t(this)},t.fn.tooltip=function(e,n){var o=!0===("boolean"==typeof e&&e)&&"mouse",i="boolean"==typeof n&&n,r=t(this);return r.each((function(){var e=i?t(this).find(".tooltiptext"):t(this).children(".tooltiptext");t(e).length>0?e.each((function(){var e=t(this);t(this).filter(":not(:empty)").parent().qtip({suppress:!1,content:{text:e.clone()},position:{target:o,adjust:{x:2,y:2},viewport:!0},events:{hidden:$ocms.tooltip_hidden},show:{effect:!1},hide:{effect:!1}}),e.remove()})):t(this).qtip({position:{target:o,adjust:{x:2,y:2},viewport:!0},events:{hidden:$ocms.tooltip_hidden},effect:!1})})),r},t.fn.rC=function(e){return t(this).removeClass(e)},t.fn.aC=function(e){return t(this).addClass(e)},t.fn.tC=function(e,n){return t(this).toggleClass(e,n)}}(jQuery),function(t){t.fn.ocmsmenu=function(e,n){var o=t(this);return $ocms.menu.call(o,e,n),o},t.fn.activatemenu=function(){var e=t(this).filter("nav");return e.find("a").not(".on").addClass("on").click($nuf),e.find(".nav-btn").not(".on").addClass("on").click((function(e){e.stopPropagation();var n=t(this);t(n.attr("data-target")).tC(n.attr("data-toggle"))})),e}}(jQuery);class ObjectArray extends Array{isEmpty(){return 0===this[0].length}static get[Symbol.species](){return Array}filter(t){return"function"==typeof t?new ObjectArray(this[0].filter(t)):this}remove(t){if("function"!=typeof t)return this;{let e=this[0].findIndex(t);for(;e>-1;)this[0].splice(e),e=this[0].findIndex(t)}}sortBy(t){return"function"==typeof t&&this[0].sort(t),this}sortString(t){return this[0].sort(((e,n)=>{let o=(e[t]||"").toString().toUpperCase(),i=(n[t]||"").toString().toUpperCase();return console.debug(o.localeCompare(i)),o.localeCompare(i)})),this}sortNum(t){return this[0].sort(((e,n)=>{let o=e[t],i=n[t];return!0===isNaN(i)&&!1===isNaN(o)||oi?1:0})),this}sum(t){return this[0].reduce(((e,n)=>e+(!0===isNaN(n[t])?0:n[t])),0)}groupBy(t){return this[0].reduce((function(e,n){let o=n[t];return e[o]||(e[o]=[]),e[o].push(n),e}),{})}each(t){if("function"==typeof t){let e=!1;this[0].forEach(((n,o,i)=>{if(!1===e){let r=t(n,o,i);"boolean"==typeof r&&!1===r&&(e=!0)}}))}}get toArray(){return this[0]}}class NumArray extends Array{sum(){return this.reduce(((t,e)=>t+e))}first(){return this[0]}last(){return this[this.length-1]}average(){return this.sum()/this.length}range(){let t=this.map((t=>t)).sort();return{min:t[0],max:t[this.length-1]}}static get[Symbol.species](){return Array}}$ocms.ocmsmenu=[{lbl:"",id:"m_home",ico:"glyphicon glyphicon-home",fnc:"init:home"},{fnc:"separator"}],function(t){t.multline=function(t){let e=t.split("\n"),n=$$.d();return $.each(e,((t,e)=>{n.append($$.s(e))})),n.html()},t.tooltip_hidden=function(t,e){$(this).remove(),e.rendered=!1},t.isJSONDateString=function(t){return"string"==typeof t&&/^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})?)?$/.test(t)},t.failure=function(e){11110===(e.internalCode||-1)?t.login.dlg():alert($t.f1+"\n"+(e.internalText||""))},t.getScript=function(e,n){var o=[],i=[],r=function(t){return"string"==typeof t&&""!==(t||"")},s=function(t,e){!0===bool(e.condition,!0)&&(""!==(e.script||"")&&i.push({url:e.script,module:e.module||""}),!0===r(e.css||"")?o.push(e.css):!0===Array.isArray(e.css)&&Array.prototype.push.apply(o,e.css.filter(r)))};!0===r(e||"")?i.push(e):!0===Array.isArray(e)?$.each(e,s):"object"==typeof e&&""!==(e.script||"")&&s(0,e);let a=[];$.each(o,(function(t,e){""!==(e||"")&&a.push(loadCSS(e))}));let c=i.map((function(e,n){let o=e.url,r=e.module||"";if(""===r){let t=new Promise((function(t,e){try{!async function(){$.ajax({url:o,dataType:"script",success:function(){t(i)},error:function(){e(i)},timeout:3e4})}()}catch(t){console.debug(t.message+"%o",t)}}));return t}return t.loadmodule(r,o,e.alias)}));Promise.all(c).then(n)},t.loadmodule=function(e,n,o){let i=new Promise((function(i,r){!async function(){try{let s=(n.startsWith("/")||n.startsWith(".")?"":"/")+n;import(s).then((n=>{t[e]=n[o||"default"],i(e)})).catch((t=>{console.debug(t.message+"%o",t),r(e)}))}catch(t){console.debug(t.message+"%o",t)}}()}));return i},t.ocms_auth=function(e,n,o,i){!1===$.isPlainObject(t.auth.modules)&&(t.auth.modules={});var r=0;t.auth.modules[e+(o||"")]?((r=t.auth.modules[e+(o||"")])<2&&(o||"")===auth.guid&&(r=2),r>=(n||0)&&i(false)):t.postXT({url:t.url("auth"),data:{module:e,person_guid:o||""},success:function(s){r=s[e],t.auth.modules[e+(o||"")]=r,r<2&&(o||"")===t.auth.person_guid&&(r=2),r>=(n||0)&&i(false)},error:function(e){t.failure.call(this,e)}})},t.auth.locale="de",t.ocms_prepauth=function(e,n,o){t.postXT({url:t.url("auth"),data:{fn:"csv",modules:e,person_guid:n||""},success:function(e){t.ocms_regauth(e)},error:function(e){t.failure.call(this,e)},complete:function(){o()}})},t.ocms_regauth=function(t){$.each(t||{},(function(t,e){auth.modules[t]=parseInt(e)}))},t.init=function(e){var n="string"==typeof e?e:(e.data||{}).fn||"";""!==n&&("home"===n?($cfr(),$lfr(),$("#topbar").ocmsmenu([],!0),$("#activemodule").text($t.ov),t.ov.call($("#contentframe"))):($cfr(),$lfr(),$("#topbar").ocmsmenu([]),t.postXT({url:t.url(n+"/auth"),success:function(e){void 0===t[n]&&(t[n]={}),t[n].auth=e,e.manage>0&&t.getScript({module:n,script:["web/imdl",n,t.auth.locale||"de","js"].join("."),css:["web/imdl",n,"css"].join("."),condition:"function"!=typeof t[n].init2},(function(){t[n].init2()}))},error:function(){$("#contentframe").empty()}})))},t.menuarray=function(t){this.array=[],this.sep=function(){this.length>0&&"separator"!==this.array[array.length-1].fnc&&this.push({fnc:"separator"})},this.push=function(t){return void 0===t?null:(!0===Array.isArray(t)?Array.prototype.push.apply(this.array,t):"object"==typeof t&&this.array.push(t),t)},this.unshift=function(t){return void 0===t?null:(!0===Array.isArray(t)?Array.prototype.unshift.apply(this.array,t):"object"==typeof t&&this.array.unshift(t),t)},this.push(t)},t.menu=function(e,n){e=e||[];var o=$(this).removeClass("vis");if(!0===bool(n,!0)&&!1===o.is("#mainmenu")&&o.empty(),!1===bool(n,!1)&&o.is("#sidebar,#topbar")&&(e.unshift({id:"sbctrl",glyph:"glyphicon-th-list",aclass:"fbtn",fnc:function(){$lf()}}),$lf(0)),0===(e||[]).length)o.empty().addClass("hd");else{o.removeClass("hd");var i=!0===o.is("nav")?o:o.children("nav");1!==i.length&&(i=$("").tC("nv",o.is("#sidebar")).tC("ctxt",o.is("#topbar")).appendTo(o));var r,s=$$.ul().appendTo(i),a=function(t,e){var n=$(this).addClass("dropdown submenu");t.append($$.sc("caret dd")).addClass("dds dropdown-toggle").attr({"aria-expanded":"false"}),""!==(e.ico||"")&&t.prepend($$.sc("ico "+e.ico));var o=$$.ul({class:"dropdown-menu",role:"menu"}).appendTo(n);$.each(e.itm||[],(function(t,e){r.call(o,e)}))},c=function(t){$(this).tC("disabled","boolean"==typeof t.disabled?t.disabled:"string"==typeof t.disabled&&"subs"===t.disabled&&0===(t.itm||[]).length)};r=function(e){var n,o=$$.li({id:e.id}).attr(e.attr||{}).addClass(e.lclass).appendTo($(this)),i="string"==typeof e.fnc&&""!==e.fnc?e.fnc.split(":")[0]:"";""!==i&&"init"!==i?o.attr("role",i).appendIf($$.s(e.lbl),""!==ne(e.lbl)):(n=$$.a({class:"on",role:"button"}).addClass(e.aclass).appendTo(o).append($$.s(e.lbl)),c.call(n,e),(e.itm||[]).length>0&&a.call(o,n,e),n.click($nuf),"function"==typeof e.fnc?n.click(e.data||{},e.fnc):"init"===i&&n.click($.extend({},e.data||{},{fn:e.fnc.split(":")[1]}),t.init))},$.each(e,(function(e,n){var o,i=$$.li({id:n.id}).attr(n.attr||{}).addClass(n.lclass),a="string"==typeof n.fnc&&""!==n.fnc?n.fnc.split(":")[0]:"";if(""!==a&&"init"!==a)i.attr("role",a).appendIf($$.s(n.lbl),""!==ne(n.lbl));else{if(o=$$.a({class:"on",role:"button"}).addClass(n.aclass).appendTo(i),c.call(o,n),""!==(n.lbl||"")&&o.append($$.s(n.lbl)),""!==(n.ico||"")&&o.prepend($$.sc("ico "+n.ico)),""!==(n.glyph||"")&&o.prepend($$.sc("glyphicon "+n.glyph)),(n.itm||[]).length>0){i.addClass("dropdown"),o.append($$.sc("caret dd")).addClass("dds dropdown-toggle").attr({"aria-expanded":"false"});var l=$$.ul({class:"dropdown-menu",role:"menu"}).appendTo(i);$.each(n.itm||[],(function(t,e){r.call(l,e)}))}(n.sel||[]).length>0||(o.click($nuf),"function"==typeof n.fnc?o.click(n.data||{},n.fnc):"init"===a&&o.click($.extend({},n.data||{},{fn:n.fnc.split(":")[1]}),t.init))}i.appendTo(s)})),i.activatemenu()}},t.easytbl=(t,e)=>{e=e||{};let n=$$.tbl().addClass(e.class).css("border-collapse","collapse"),o=($$.tbody(n),!0===bool(e.frame,!1)?{padding:"5px",border:"1px solid #727272"}:{});if(!0===Array.isArray(e.header)){let t=$$.thead(n);$.each(e.header,((n,i)=>$$.th(t).css(e.cellcss||o).rwText(i)))}else if(!0===bool(e.header,!1)&&(t||[]).length>0){let i=$$.thead(n);$.each(Object.keys(t[0]),((t,n)=>$$.th(i).css(e.cellcss||o).rwText(n)))}return $.each(t||[],((t,i)=>{let r=$$.tr();$.each(i,((t,n)=>{n=n||"";let i=$$.td(r).css(e.cellcss||o);n instanceof jQuery?i.append(n):"string"==typeof n&&("<"===n.substring(0,1)?i.append(n):i.text(n))})),n.append(r)})),n},t.dlgtbl=(e,n,o)=>{o=o||{};let i=t.easytbl(e,o);t.dlg(i,$.extend({title:n},o))},t.dlg=function(t,n){n=n||{};let o=$("body > .modal").length>0,i=t=>typeof n[t],r=t=>"function"===i(t);if(!0===bool(n.exclusive,!0)&&!0===o)return void alert($t.dbldlg||"Es ist bereits ein Dialog geöffnet");let s=$$.dc("modal",$("body")),a=$$.dc("modal-dialog",s);!1===isNaN(n.zindex)?s.css("zIndex",n.zindex):!0===o&&s.css("zIndex",parseInt($("body > .modal:last").cssValue("zIndex"))+200),!1===isNaN(n.zindex_min)&&s.cssValue("zIndex")').appendTo(d)),""!==ne(n.title)&&(c=$$.dc("modal-header",d),$("

      ").text(n.title).appendTo(c));let p=$$.dc("modal-body",d),f=$$.dc("modal-footer",d);t instanceof jQuery==!0&&p.append(t);let m=function(t){t&&"function"==typeof t.stopPropagation&&t.stopPropagation(),a.removeClass("in"),!0===r("closing")&&n.closing.call(d),p.hide().emptyWithEditors(),s.remove(),!0===r("close")&&n.close.call(d)};if(d.find(":input[required]").length>0&&($$.dc("note_required",f).append($$.sc("ind_required","*")).append($$.s($t.t1||"Eingabe erforderlich")),$$.dc("note_invalid",f).append($$.s($t.t2||"Bitte überprüfen Sie Ihre Eingaben im Formular."))),!0===r("cancel")){$$.bbtn(n.cancelbutton||"Abbrechen","cancel").attr({type:"button",role:"cancel"}).appendTo(f).click((function(t){n.cancel.call(d,t);t.stopPropagation(),m()}))}if(!0===r("confirm")){let t=$$.bbtn(n.button||"OK","confirm").attr({type:!0===bool(n.form,!1)?"submit":"button",role:"confirm"}).appendTo(f);!0===h?(d.submit((function(t){try{n.confirm.call(d,t)}finally{t.preventDefault()}return!1})),d.on("modal_submit",(function(){n.confirm.call(d,e)}))):(t.click((function(t){n.confirm.call(d,t);t.stopPropagation()})),d.on("modal_submit",(function(){t.click()})))}else!0===h&&d.submit((function(t){return t.preventDefault(),!1}));return d.on("modal_close",(function(){m()})),l.click(m),!0===r("opening")&&n.opening.call(d),a.addClass("in"),ne(n.mode).indexOf("maxbody")>-1&&p.css("min-height",(u.height()-c.outerHeight()-f.outerHeight()).toString()+"px"),!0===r("open")&&n.open.call(d),{hd:c,bdy:p,ft:f,ct:u,dlg:a,c:d}},t.mform=function(e){let n=$$.dc("form-body"),o=Array.isArray(e)?e:e instanceof fields_definition?e.fields:[];return $.each(o||[],(function(e,o){let i=o.type||"";if("ignore"===i)return!0;let r=$$.dc("form-group",n),s=o.id||"dlg_"+(o.name||"")+("html"===o.type?"_"+(65536*(1+Math.random())||0).toString(16).substr(9):""),a=$$.lbl(o.label||o.name,{for:s}).appendTo($$.dc("form-itm",r)),c=$$.dc("form-itm",r),l=$$.i({id:s,name:o.name,placeholder:o.placeholder,type:o.type});switch(i){case"email":o.pattern=ne(o.pattern,"[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,}$");break;case"url":o.pattern=ne(o.pattern,"https?://.+");break;case"number":o.pattern=ne(o.pattern,"[-+]?[0-9]*[.,]?[0-9]*"),l.attr("step",o.precision||"any"),l.attr("data-format","float");break;case"integer":case"int":o.pattern=ne(o.pattern,"[-+]?[0-9]*"),l.attr("type","number"),l.attr("data-format","integer");break;case"date":if(""!==ne(o.pattern,$t.datepattern)&&(o.pattern=ne(o.pattern,"("+$t.datepattern+")|([0-9]{4}.(0[1-9]|1[012]).(0[1-9]|1[0-9]|2[0-9]|3[01]))")),""!==ne(o.placeholder,$t.dateplaceholder)&&l.attr("placeholder",ne(o.placeholder,$t.dateplaceholder)),"string"==typeof o.value){var u=o.value.substr(0,10);o.value="date"!==l.prop("type")?fdt(u+"T00:00:00",ne(o.dateformat,$t.dateformat)):u}l.attr("data-format","date:"+ne(o.dateformat,$t.dateformat)+";yyyy-MM-dd");break;case"datetime":l.attr("type","datetime-local"),""!==ne(o.pattern,$t.datetimepattern)&&(o.pattern=ne(o.pattern,"("+$t.datetimepattern+")|([0-9]{4}-(0[1-9]|1[012])-(0[1-9]|1[0-9]|2[0-9]|3[01])\\s([0-5][0-9]):([0-5][0-9]))")),""!==ne(o.placeholder,$t.datetimeplaceholder)&&l.attr("placeholder",ne(o.placeholder,$t.datetimeplaceholder)),"string"==typeof o.value&&"T"===o.value.substr(10,1)&&(o.value="datetime"!==l.prop("type").substr(0,8)?fdt(o.value,ne(o.datetimeformat,$t.datetimeformat)):o.value),l.attr("data-format","datetime:"+ne(o.datetimeformat,$t.datetimeformat)+";yyyy-MM-dd HH:mm:ss");break;case"hidden":r.addClass("hd");break;case"html":case"text":l=$$.txt({id:s,name:o.name,placeholder:o.placeholder,type:o.type}),l.tC("tinymce","html"===o.type);break;case"bool":case"boolean":o.url=[{value:"true",label:($t||{}).true||"Yes"},{value:"false",label:($t||{}).false||"No"}],"boolean"==typeof o.value&&(o.value=o.value?"true":"false");case"select":l=$$.sel({id:s,name:o.name,type:o.type}),!1===bool(o.required,!1)&&$$.eOpt().appendTo(l);try{var d=function(t){!0===Array.isArray(t)&&$.each(t,(function(t,e){"string"==typeof e?$$.opt(e,e).appendTo(l):!0===Array.isArray(e)?$$.opt(e[0],e[1]).appendTo(l):"object"==typeof e&&$$.opt(e.value,e.label||e.text).appendTo(l)}))};!0===Array.isArray(o.url)?d(o.url):"function"==typeof o.url?o.url.call(l):"string"==typeof o.url&&t.postXT({url:o.url,success:d})}catch(t){$.noop()}break;default:""!==ne(o["max-length"])&&l.attr("max-length",o["max-length"])}""!==ne(o.pattern)&&l.attr("pattern",o.pattern),l.val(o.value).change(),l.change((function(){$(this)[0].setCustomValidity("")})),l.addClass("form-control").prop("required",bool(o.required,!1)).prop("readonly",bool(o.readonly,!1)).appendTo(c),!0===bool(o.required,!1)&&a.append($$.sc("ind_required","*")),"object"==typeof o.attr&&l.attr(o.attr),"object"==typeof o.prop&&l.prop(o.prop),"string"==typeof o.class&&l.addClass(o.class),"function"==typeof o.change&&(l.change(o.change),!0===bool(o.applychange,!1)&&void 0!==o.value&&l.change()),""!==(o.note||"")&&$$.dc("form-note",c).rwText(o.note),"function"==typeof o.complete&&o.complete.call(l)})),n},t.initMCE=function(t,e){t=$(t),e=e||{};try{let n={target:t[0],inline:!1,width:e.width||"100%",statusbar:!1,document_base_url:window.location.origin+"/",content_style:"ph:before {content: '«'; color: #BBB; font-style:italic; } ph:after {content: '»'; color: #BBB; font-style:italic; } ph { color: #AAA; font-style:italic; }",relative_urls:!1,remove_script_host:!1};!0===bool(e.hidemenu,!1)&&(n.menubar=!1,n.menu={}),!0===bool(e.hidetoolbar,!1)&&(n.toolbar=!1),$.extend(n,e||{}),tinymce.init(n)}catch(t){alert(t.message)}},t.dlgform=function(e,n){n=n||{};let o,i=$$.dc("frm").append(t.mform(e||[]).addClass("stacked"));n.addcontent instanceof jQuery&&i.append(n.addcontent),"function"==typeof n.submit?o=n.submit:"function"==typeof n.success&&(o=function(e){var o=$(this).ldng(1),i=$.extend({loginaccount:t.auth.account||""},o.serializeObject(bool(n.checkvalidity,!0),{typedvalues:bool(n.typedvalues,!1)}));""!==(n.url||"")?t.postXT({url:n.url,data:i,success:function(t){n.success.call(this,t),o.trigger("modal_close")},error:function(){alert($t.l17)},complete:function(){o.ldng(0)},timeout:6e4}):(n.success.call(this,i),o.trigger("modal_close"))});let r={form:!0,title:n.title||"",button:n.button||$t.submit,confirm:o,size:n.size||[500,600],open:function(){let e=$(this).find(".tinymce");e.length>0&&t.initMCE(e,n.tinymce||{})}};return t.dlg.call(this,i,r)},t.login.dlg=function(e){e=e||{};let n=[{name:"userinfo",label:$t.l1,type:"string",value:t.auth.login,change:t.login.uichange,required:!0},{name:"userlogin",type:"hidden",required:!0,value:t.auth.login},{name:"username",type:"string",label:$t.l4,required:!0,readonly:!0,placeholder:$t.l5,value:t.auth.fullname_rev},{name:"userpass",type:"password",label:$t.l3,required:!0,placeholder:$t.l3}];""===(t.auth.account||"")&&n.unshift({id:"dlg_loginaccount",name:"loginaccount",type:"string",required:!0,value:t.auth.account});let o=$$.dc("frm").append(t.mform(n).addClass("stacked")),i=t.dlg.call(this,o,{form:!0,title:$t.l0,button:$t.submit,confirm:function(n){var o=$(this).ldng(1),i=$.extend({loginaccount:t.auth.account||""},o.serializeObject());t.postXT({url:"/vt/login",data:i,success:function(n){""!==((n||{}).login||"")&&(o.trigger("modal_close"),t.auth=n,"object"==typeof e.ajo&&(e.ajo.islogin,$.ajax(e.ajo)))},error:function(){alert($t.l17)},complete:function(){o.ldng(0)},timeout:6e4})},size:[500,600]}),r=$$.dc("modal-content").css("height","auto").attr("novalidate","true").append($$.dc("modal-header").appendIf($("

      ").text(t.auth.accountname),""!==(t.auth.accountname||"")).append($("

      Vereinsmanager

      ")));i.dlg.prepend(r)},t.addNoEntryInfo=function(t){$(this).append($$.dc("noentryinfo").text(t||$t.t11))}}($ocms),Element.prototype.matches||(Element.prototype.matches=Element.prototype.msMatchesSelector||Element.prototype.webkitMatchesSelector),Element.prototype.closest||(Element.prototype.closest=function(t){var e=this;do{if(Element.prototype.matches.call(e,t))return e;e=e.parentElement||e.parentNode}while(null!==e&&1===e.nodeType);return null}),function(t,e){var n,o;"object"==typeof window&&(window[t]=(n=function(t){var e=window,n=document.body,o=document.documentElement,i=Math.max(0,e.pageXOffset||o.scrollLeft||n.scrollLeft||0)-(o.clientLeft||0),r=Math.max(0,e.pageYOffset||o.scrollTop||n.scrollTop||0)-(o.clientTop||0);return{x:t?Math.max(0,t.pageX||t.clientX||0)-i:0,y:t?Math.max(0,t.pageY||t.clientY||0)-r:0}},(o=function(t,e){t&&t instanceof Element&&(this._container=t,this._options=e||{},this._clickItem=null,this._dragItem=null,this._showDragItem="boolean"!=typeof this._options.dragItem||!1!==this._options.dragItem,this._hovItem=null,this._sortLists=[],this._click={},this._dragging=!1,this._dragHandleClass=this._options.dragHandleClass||"",this._parentident=this._options.parentident||"",this._swapdone="function"==typeof this._options.swapdone?this._options._swapdone:null,this._container.setAttribute("data-is-sortable",1),this._container.classList.add("sortable"),this._container.style.position="static",window.addEventListener("mousedown",this._onPress.bind(this),!0),window.addEventListener("touchstart",this._onPress.bind(this),!0),window.addEventListener("mouseup",this._onRelease.bind(this),!0),window.addEventListener("touchend",this._onRelease.bind(this),!0),window.addEventListener("mousemove",this._onMove.bind(this),!0),window.addEventListener("touchmove",this._onMove.bind(this),!0))}).prototype={constructor:o,toArray:function(t){t=t||"id";for(var e=[],n="",o=0;oo.left&&eo.top&&n-1)&&e.className.indexOf("nosort")<0)&&(t.preventDefault(),this._dragging=!0,this._click=n(t),this._makeDragItem(e),this._onMove(t),!0)}t&&!1===e.call(this,t.target)&&""!==this._parentident&&t.target.closest(this._parentident)&&e.call(this,t.target.closest(this._parentident))},_onRelease:function(t){this._dragging=!1,this._trashDragItem()},_onMove:function(t){if(this._dragItem&&this._dragging){t.preventDefault();var e=n(t),o=this._container;!0===this._showDragItem&&this._moveItem(this._dragItem,e.x-this._click.x,e.y-this._click.y);for(var i=0;i0?a.mousedown(c).addClass("dctrl"):s.mousedown(c).addClass("dctrl"),t(this)}}(jQuery),$(document).ready((function(){$("html").click((function(t){$nuf()})),$("#listframe").click((function(t){t.stopPropagation(),$nuf()})),$("#mainmenu").ocmsmenu($ocms.ocmsmenu),$("#mainmenu").activatemenu()})),$.extend($t,{m_inv:"Rechnungen",m_req:"Aufträge",m_rep:"Berichte",m_todo:"ToDos",m_bcd:"BankBuchungen",rsp:"Passwort ändern",pnm:"Die Passwörter stimmen nicht überein",cps:"Das neue Passwort wurde gespeichert.",pwr:"Bitte wählen Sie ein starkes Passwort (min 8 Zeichen, davon jeweils min 2 Zahlen, kleine und große Buchstaben, Sonderzeichen sind optional).",smsc:"Sie beötigen für diese Funktion einen SMS-Code.\nSoll dieser nun versandt werden?",wdc:"Doppelt klicken, um die Box zu aktualisieren.",wdg:{}}),$t.rspf={sms:"Der SMS-Code konnte nicht bestätigt werden",valid:"Das alte Passwort ist nicht korrekt",requirements:"Das Passwort entspricht nicht den Anforderungen.\n"+$t.pwr},$fd={rsp:new fields_definition("","",[{name:"opw",label:"aktuelles Passwort",type:"password",required:!0,attr:{"auto-complete":"current-password"}},{name:"npw",label:"neues Passwort",type:"password",required:!0,pattern:"(.{6,})",attr:{"auto-complete":"new-password"}},{name:"npwc",label:"neues Passwort (Bestätigung)",type:"password",required:!0,attr:{"auto-complete":"new-password"},note:$t.pwr},{name:"code",label:"SMS-Code",type:"string",required:!0,attr:{"auto-complete":"one-time-code"}}])},$ocms.init=function(t){var e="string"==typeof t?t:(t.data||{}).fn||"";""!==e&&("home"===e?($cfr(),$lfr(),$("#topbar").ocmsmenu([],!0),$("#activemodule").text($t.ov),$fis.ov()):($cfr(),$lfr(),$("#topbar").ocmsmenu([]),$ocms.postXT({url:$ocms.url(e+"/auth"),success:function(t){void 0===$ocms[e]&&($ocms[e]={}),$ocms[e].auth=t,t.manage>0&&$ocms.getScript({module:e,script:["/web/fis",e,$ocms.auth.locale||"de","js"].join("."),css:["/web/fis",e,"css"].join("."),condition:"function"!=typeof $ocms[e].init2},(function(){$ocms[e].init2()}))},error:function(){$("#contentframe").empty()}})))};var $fis={auth:{},db:function(){$("#mainmenu_activemodule").text($t.ov);let t=$(this).empty(),e=$$.d({id:"dashboard_frame"}).appendTo(t);$ocms.postXT({url:$ocms.url("wdg/my"),success:function(t){$.each(t,(function(t,n){var o=$$.dc("wdg_frame",e,{"data-wdg":n}).ldng(1);$ocms.wdg.call(o,{wdg:n})}))},loading:e})},ValidateEmail:function(t){return!!/^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/.test(t)},cf:t=>{let e=$("#contentframe");return!0===bool(t,!1)&&e.empty().rC("hd"),e},lf:t=>{let e=$("#listframe");return!0===bool(t,!1)&&e.empty().aC("hd").rC("fix"),e},frm_edit:function(t){let e=$fis.cf(!1),n=e.children(".cfrm"),o=e.children(".edit_frm");return n.length<1?n=$$.dc("cfrm hd").prependTo(e):!0===bool(t,!1)&&n.empty(),o.length<1&&(o=$$.dc("edit_frm").insertAfter(n)),o.empty()},frm_list:function(t,e){let n=$fis.cf(!1),o=n.children(".cfrm"),i=n.children(".list_frm");return o.length<1?o=$$.dc("cfrm hd").prependTo(n):!0===bool(t,!1)&&o.empty(),!0===bool(e,!1)&&n.children(".edit_frm").remove(),i.length<1&&(i=$$.dc("list_frm").appendTo(n)),i.empty()},lfm:()=>{let t=$fis.lf(!1),e=t.children(".lfrm");return e.length<1&&(e=$$.dc("lfrm").prependTo(t)),e},getAuth:(t,e)=>new Promise(((n,o)=>{$fis.auth[t]&&!1===bool(e,!1)?n($fis.auth[t]||-1):$ocms.postXT({url:$ocms.url("auth"),data:{module:t},success:e=>{$fis.auth[t]=e.auth||-1,n($fis.auth[t]||-1)},error:()=>{o()}})})),prepAuth:t=>new Promise(((e,n)=>{$ocms.postXT({url:$ocms.url("auth"),data:{module:t,array:1},success:t=>{$.extend($fis.auth,t||{})},complete:()=>{e()}})})),isAuth:(t,e)=>($fis.auth[t]||-1)>=(e||1),resetPass:function(t,e){confirm($t.smsc)&&($ocms.postXT({url:$ocms.url("account/sms"),data:{fn:"pwc"}}),$ocms.dlgform($fd.rsp.clone(),{title:$t.rsp||"",submit:function(t){var e=$(this).ldng(1),n=$.extend({loginaccount:$ocms.auth.account||""},e.serializeObject(!0,{typedvalues:!0}));(n.npw||"")!==(n.npwc||"")?e.find('input[name="npw"]:first')[0].setCustomValidity($t.pnm):$ocms.postXT({url:$ocms.url("account/changepassword"),data:n,success:function(t){alert($t.cps),e.trigger("modal_close")},error:function(t){alert($t.rspf[t.getResponseHeader("x-ocms-std")])},complete:function(){e.ldng(0)},timeout:6e4})}}))},wdg:function(t){let e=$(this).empty();$ocms.postXT({url:$ocms.url("wdg/one"),data:{short_name:t.wdg},timeout:9e4,success:function(n,o,i){let r=t.wdg,s=n[r];if(!s)return void e.ldng(0);let a=$.inArrayRegEx("dblwidth",s.rendering_options)>-1,c=$.inArrayRegEx("tiny",s.rendering_options)>-1;e.toggleClass("dbl",a&&!c).toggleClass("tny",c);$$.dc("wdg_hd",e,{title:ne(s.description,$t.wdc)}).toggleClass("dbl",a).text(ne(s.name,t.wdg)).dblclick((function(t){t.stopPropagation(),$fis.wdg.call(e,{wdg:r})}));let l=$$.dc("wdg_cnt",e).toggleClass("dbl",a).hide(),u=$.inArrayRegEx("bgcolor",s.rendering_options);switch(u>-1&&l.css("backgroundColor",s.rendering_options[u].toString().right(":")),s.type){case"table":var d=$$.tblset({},l),h=$$.tr().appendTo(d.hd),p=$t.wdg[r.indexOf("wdg_ev_")>=0?"wdg_ev_":r]||{};$.each(s.columns,(function(t,e){var n=p[e]?p[e].label:e;$$.th().text(n).appendTo(h)})),$.each(s.data,(function(t,e){var n=$$.tr().appendTo(d.bdy);$.each(s.columns,(function(t,o){var i=$$.td().appendTo(n);e[o]instanceof Date||!0===$ocms.isJSONDateString(e[o])?i.text(fdt(e[o],$t.dateformat)):i.rwText(e[o])}))})),$.inArray("firstrow_bold",s.rendering_options)>-1&&h.nextAll("tr:first").css("font-weight","bold");break;case"ind":$$.dc("ind",l).addClass("sts_"+(s.data.status||"")).append([$$.dc("ind").text(s.data.value),$$.lbl(s.data.label)]);break;case"image_url":l.css("background","url('"+s.url+"') no-repeat center center transparent");break;case"image_base64":l.css("background","url('data:image/png;base64,"+s.image+"') no-repeat center center transparent");break;case"html":if(l.html(s.html),$.inArray("reload_10min",s.rendering_options)>-1){var f=l.find("iframe");setTimeout((function(){f.attr("src",(function(t,e){return e}))}),6e5)}}$.inArray("reload_30min",s.rendering_options)>-1&&"html"!==s.type&&setTimeout((function(){$fis.wdg.call(e,{wdg:r})}),18e5),l.slideDown(150)},error:function(t){e.slideUp(150),$fis.failure.call(this,t)},complete:function(){e.ldng(0)}})},ov:function(){$fis.lf(!0);let t=$("#contentframe").empty(),e=$$.d({id:"dashboard_frame"}).appendTo(t);$ocms.postXT({url:$ocms.url("wdg/my"),success:function(t){$.each(t,(function(t,n){var o=$$.dc("wdg_frame",e,{"data-wdg":n}).ldng(1);$fis.wdg.call(o,{wdg:n})}))},loading:e})}};$fis.notifications={connection:null,init:function(){"undefined"!=typeof signalR&&null===this.connection&&$ocms.auth.useraccount_id&&(this.ensureFrame(),this.connection=(new signalR.HubConnectionBuilder).withUrl("/notifications").withAutomaticReconnect().build(),this.connection.on("notification",(t=>{this.push(t)})),this.connection.onclose((()=>{console.warn("Notification connection closed; retrying in 5s."),this.connection=null,setTimeout((()=>this.init()),5e3)})),this.start())},start:function(){this.connection.start().catch((t=>{console.warn("Notification connection failed to start; retrying in 5s.",t),this.connection=null,setTimeout((()=>this.init()),5e3)}))},ensureFrame:function(){$("#notification_frame").length<1&&$("
      ",{id:"notification_frame"}).appendTo($("footer:first").length?"footer:first":"body")},push:function(t){this.ensureFrame(),t=t||{};let e=$("
      ",{class:"notification_item"}).addClass((t.severity||"info").toLowerCase()).append($("
      '),n=e.find(".form-body"),i=null;e.find("form").submit((function(t){t.preventDefault();var o=$(this).serializeObject(!0),r=null===i,s=r?"spwc":"spw";return $ocms.postXT.call(this,{url:$ocms.url(s),data:o,complete:function(){r?(n.append('
      Ihnen wurde ein Code per SMS zugesandt.
      Bitte tragen Sie den hier ein:
      '),i=$('
      ').appendTo(n)):(alert($t.l13),e.remove())},error:()=>{}}),!1})),e.find(".modal-close").click((function(){e.remove()}));var o=[];$.each($t.l7a.split("\n"),((t,e)=>{Array.prototype.push.apply(o,[$("
      "),$("").text(e)])})),e.find(".modal-note").append($('').text($t.alert)).append(o),e.appendTo("body"),setTimeout((function(){$(".modal").find('input[name="lastname"]').focus()}),600)}};var $$={s:function(t){return $("").text(t)},br:function(){return $("
      ")},sc:function(t,e){return $("").addClass(t).text(e)},td:function(t,e){var n=$("");return t instanceof jQuery==!0?n.appendTo(t):"object"==typeof t?n.attr(t):"string"==typeof t&&n.text(t),"object"==typeof e?n.attr(e):"string"==typeof e&&n.text(e),n},th:function(t,e){var n=$("");return t instanceof jQuery==!0?n.appendTo(t):"object"==typeof t?n.attr(t):"string"==typeof t&&n.text(t),"object"==typeof e?n.attr(e):"string"==typeof e&&n.text(e),n},tdc:function(t,e,n){return $$.td(e,n).addClass(t)},td2:function(t){var e=$('');return"string"===$.type(t)?e.text(t):t instanceof jQuery?e.append(t):"function"==typeof t?t.call(e):e.html(" "),e},td3:function(t){var e=$('');return"string"===$.type(t)?e.text(t):t instanceof jQuery?e.append(t):"function"==typeof t?t.call(e):e.html(" "),e},tdtr:function(t,e){var n=$$.tr().appendTo(e);return t instanceof jQuery==!0||"string"==typeof t?t.appendTo($$.td().appendTo(n)):!0===Array.isArray(t)&&$.each(t,(function(t,e){$(e).appendTo($$.td().appendTo(n))})),n},tr:function(t,e){var n=$("");return t instanceof jQuery==!0?n.appendTo(t):"object"==typeof t&&n.attr(t),"object"==typeof e&&n.attr(e),n},trc:function(t,e){var n=$("").addClass(t);return e instanceof jQuery==!0?n.appendTo(e):"object"==typeof e&&n.attr(e),n},d:function(t){return $("
      ").attr(t||{})},dc:function(t,e,n,i){var o=$("
      ").addClass(t);return e instanceof jQuery==!0?o.appendTo(e):"object"==typeof e?o.attr(e):"function"==typeof e?o.click(e):"string"==typeof e&&o.text(e),"string"==typeof n?o.text(n):"object"==typeof n?o.attr(n):"function"==typeof n&&o.click(n),"string"==typeof i?o.text(i):"object"==typeof i?o.attr(i):"function"==typeof i&&o.click(i),o},df:function(t){return $("
       
      ").attr(t||{})},opt:function(t,e,n){var i=$("");return"string"==typeof t?i.attr("value",t):"object"==typeof t&&i.attr(t),"string"==typeof e?i.text(e):"object"==typeof e&&i.attr(e),"object"==typeof n&&i.attr(n),i},eOpt:function(t){var e=$('');return t&&e.attr("selected","selected"),e},tbl:function(t){return $("
      ").attr(t||{})},tblc:function(t){return $("
      ").addClass(t)},thead:function(t){let e=$("");return t instanceof jQuery&&e.prependTo(t),e},tbody:function(t){let e=$("");return t instanceof jQuery&&e.appendTo(t),e},tblset:function(t,e){let n=$$.tbl(t||{});return e instanceof jQuery&&e.append(n),{tbl:n,hd:$$.thead().appendTo(n),bdy:$$.tbody().appendTo(n)}},i:function(t){return $("").attr(t||{})},img:function(t,e){return $("").attr("src",t).attr(e||{})},sel:function(t){return $("").attr(t||{})},btn:function(t){return $("").attr(t||{})},a:function(t){return $("").attr(t||{})},li:function(t){return $("
    • ").attr(t||{})},ul:function(t){return $("
        ").attr(t||{})},nav:function(t){return $("").attr(t||{})},lbl:function(t,e){var n=$("");return"string"==typeof t&&n.text(t),"object"==typeof t?n.attr(t):"object"==typeof e&&n.attr(e),n},txt:function(t){return $("").attr(t||{})},0:function(t,e){return $("<"+t+">").attr(e||{})},bbtn:function(t,e){return $$.btn({type:"button",class:"btn"}).addClass(e).text(t)},svg:t=>$(document.createElementNS("http://www.w3.org/2000/svg",t))};function getMonday(t){var e=(t=new Date(t)).getDay(),n=t.getDate()-e+(0==e?-6:1);return new Date(t.setDate(n))}function $lf(t){var e=void 0===t?null:"number"==typeof t&&1!==t||"boolean"==typeof cl&&!1===t;return $("#listframe").tC("hd",e).is(".hd")}function $nuf(t){if(t&&t.stopPropagation(),!$(this).is(".disabled")){var e=function(t){t.removeClass("vis").find("li.dropdown").removeClass("open").removeClass("vis").attr("aria-expanded","false")},n=$(this).parent("li.dropdown");if(n.length>0){n.tC("open"),navs=!0===n.is(".open")?"true":"false",n.attr("aria-expanded",navs);var i=n.closest("nav");i.find("li.dropdown").not(n.parentsUntil("nav")).not(n).removeClass("open").attr("aria-expanded","false"),!1===n.is(".open")&&n.find("li.dropdown").removeClass("open").attr("aria-expanded","false"),e($("nav").not(i))}else e($("nav"))}}function $tbr(){return $lf(0),$("#topbar").ocmsmenu([])}function $lfr(){return $("#sidebar").empty(),$("#listframe").removeClass("fix").addClass("hd").empty()}function $cfr(){return $tbr(),$("#contentframe").empty()}function jObj(t,e){let n={};if("{"===(t||"").substr(0,1))try{n=JSON.parse(t)}catch(t){n={}}return n[e]||""}function string(t,e){var n,i=t||"";return $.each(e||[],(function(t,e){n=new RegExp("\\{"+t.toString()+"\\}","ig"),i=i.replace(n,e)})),i}function init_tooltip(t){var e=!0===("boolean"==typeof t&&t)&&"mouse";$("[title]").qtip({position:{target:e,adjust:{x:2,y:2},viewport:!0},events:{hidden:$ocms.tooltip_hidden},effect:!1}),$("div.tooltiptext").each((function(){$(this).filter(":not(:empty)").parent().qtip({suppress:!1,content:{text:$(this).clone()},position:{target:e,adjust:{x:2,y:2},viewport:!0},events:{hidden:$ocms.tooltip_hidden}})}))}String.prototype.trim=function(){return this.replace(/^\s+|\s+$/g,"")},String.prototype.left=function(t){if("string"===$.type(t)){var e=this.indexOf(t);return e>0?this.slice(0,e):""}return this.substring(0,t)},String.prototype.right=function(t){if("string"===$.type(t)){var e=this.indexOf(t);return e>0?this.substring(this.length-e):""}return this.substring(this.length-t)},Array.prototype.move=function(t,e){if(e>=this.length)for(var n=e-this.length;1+n--;)this.push(void 0);return this.splice(e,0,this.splice(t,1)[0]),this},function(t){t.fn.appendToIf=function(e,n){var i=t(this),o="function"==typeof n?n(i):n;return!0===("boolean"!=typeof o||o)&&i.appendTo(e),i},t.fn.appendIf=function(e,n){var i=t(this),o="function"==typeof n?n(i):n;return!0===("boolean"!=typeof o||o)&&i.append(e),i},t.fn.rwText=function(e,n,i){var o=t(this).empty();i=t.extend({wrap:!0},i);var r=!0===Array.isArray(e)?e:(null==e?"":String(e)).split("\n");return t.each(r,(function(t,e){""!==(e||"")&&(t>0&&o.append($$.br()),o.append(!0===i.wrap?$$.s(e):e))})),n&&o.attr("title",n),o},t.fn.loadSel=function(e,n,i){if("SELECT"===t(this).prop("tagName").toUpperCase()){var o=t(this);$ocms.postXT.call(this,{url:e,data:n||{},success:function(e){t.each(e,(function(){o.append($$.opt(e.value,e.text))}))},complete:function(){o.ldng(0),"function"==typeof i&&i.call(o)}})}},t.fn.emptyWithEditors=function(e){var n=t(this);return n.find(":input.tinymce").each((function(e,n){try{var i=tinymce.get(t(n).attr("id"));i&&i.remove()}catch(e){t.noop()}})),n.empty()},t.fn.cssValue=function(t){if(this.length>0){var e=this.css(t)||"";if(""===e)return 0;var n=/(^[\d\.]*)(\D{1,3}$)/gi.exec(e);return null!==n?"rem"===n[2]?$ocms.rpx(parseFloat(n[1])):parseFloat(n[1]):!1===isNaN(e)?parseFloat(e):0}return 0},t.fn.veryInnerHeight=function(){let e=e=>t(this).cssValue(e);return t(this).innerHeight()-e("padding-top")-e("padding-bottom")},t.fn.veryInnerWidth=function(){let e=e=>t(this).cssValue(e);return t(this).innerWidth()-e("padding-left")-e("padding-right")},t.fn.marginWidth=function(){let e=e=>t(this).cssValue(e);return e("margin-left")+e("margin-right")},t.fn.marginHeight=function(){let e=e=>t(this).cssValue(e);return e("margin-top")+e("margin-bottom")},t.inArrayRegEx=function(e,n,i){var o="regexp"===t.type(e)?e:new RegExp(e);if(!n)return-1;for(var r=i=i||0;r7){i=e.split(","),o=(n||(t<0?"rgb(0,0,0)":"rgb(255,255,255)")).split(",");var c=a(i[0].slice(4)),l=a(i[1]),u=a(i[2]);return"rgb("+(s((a(o[0].slice(4))-c)*r)+c)+","+(s((a(o[1])-l)*r)+l)+","+(s((a(o[2])-u)*r)+u)+")"}var d=(i=a(e.slice(1),16))>>16,h=i>>8&255,p=255&i;return"#"+(16777216+65536*(s((((o=a((n||(t<0?"#000000":"#FFFFFF")).slice(1),16))>>16)-d)*r)+d)+256*(s(((o>>8&255)-h)*r)+h)+(s(((255&o)-p)*r)+p)).toString(16).slice(1)},t.fn.IN=function(e){return t(this).fadeIn(400,e),t(this)},t.fn.OUT=function(e){return t(this).fadeOut(400,e),t(this)},t.fn.tooltip=function(e,n){var i=!0===("boolean"==typeof e&&e)&&"mouse",o="boolean"==typeof n&&n,r=t(this);return r.each((function(){var e=o?t(this).find(".tooltiptext"):t(this).children(".tooltiptext");t(e).length>0?e.each((function(){var e=t(this);t(this).filter(":not(:empty)").parent().qtip({suppress:!1,content:{text:e.clone()},position:{target:i,adjust:{x:2,y:2},viewport:!0},events:{hidden:$ocms.tooltip_hidden},show:{effect:!1},hide:{effect:!1}}),e.remove()})):t(this).qtip({position:{target:i,adjust:{x:2,y:2},viewport:!0},events:{hidden:$ocms.tooltip_hidden},effect:!1})})),r},t.fn.rC=function(e){return t(this).removeClass(e)},t.fn.aC=function(e){return t(this).addClass(e)},t.fn.tC=function(e,n){return t(this).toggleClass(e,n)}}(jQuery),function(t){t.fn.ocmsmenu=function(e,n){var i=t(this);return $ocms.menu.call(i,e,n),i},t.fn.activatemenu=function(){var e=t(this).filter("nav");return e.find("a").not(".on").addClass("on").click($nuf),e.find(".nav-btn").not(".on").addClass("on").click((function(e){e.stopPropagation();var n=t(this);t(n.attr("data-target")).tC(n.attr("data-toggle"))})),e}}(jQuery);class ObjectArray extends Array{isEmpty(){return 0===this[0].length}static get[Symbol.species](){return Array}filter(t){return"function"==typeof t?new ObjectArray(this[0].filter(t)):this}remove(t){if("function"!=typeof t)return this;{let e=this[0].findIndex(t);for(;e>-1;)this[0].splice(e),e=this[0].findIndex(t)}}sortBy(t){return"function"==typeof t&&this[0].sort(t),this}sortString(t){return this[0].sort(((e,n)=>{let i=(e[t]||"").toString().toUpperCase(),o=(n[t]||"").toString().toUpperCase();return console.debug(i.localeCompare(o)),i.localeCompare(o)})),this}sortNum(t){return this[0].sort(((e,n)=>{let i=e[t],o=n[t];return!0===isNaN(o)&&!1===isNaN(i)||io?1:0})),this}sum(t){return this[0].reduce(((e,n)=>e+(!0===isNaN(n[t])?0:n[t])),0)}groupBy(t){return this[0].reduce((function(e,n){let i=n[t];return e[i]||(e[i]=[]),e[i].push(n),e}),{})}each(t){if("function"==typeof t){let e=!1;this[0].forEach(((n,i,o)=>{if(!1===e){let r=t(n,i,o);"boolean"==typeof r&&!1===r&&(e=!0)}}))}}get toArray(){return this[0]}}class NumArray extends Array{sum(){return this.reduce(((t,e)=>t+e))}first(){return this[0]}last(){return this[this.length-1]}average(){return this.sum()/this.length}range(){let t=this.map((t=>t)).sort();return{min:t[0],max:t[this.length-1]}}static get[Symbol.species](){return Array}}$ocms.ocmsmenu=[{lbl:"",id:"m_home",ico:"glyphicon glyphicon-home",fnc:"init:home"},{fnc:"separator"}],function(t){t.multline=function(t){let e=t.split("\n"),n=$$.d();return $.each(e,((t,e)=>{n.append($$.s(e))})),n.html()},t.tooltip_hidden=function(t,e){$(this).remove(),e.rendered=!1},t.isJSONDateString=function(t){return"string"==typeof t&&/^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})?)?$/.test(t)},t.failure=function(e){11110===(e.internalCode||-1)?t.login.dlg():alert($t.f1+"\n"+(e.internalText||""))},t.getScript=function(e,n){var i=[],o=[],r=function(t){return"string"==typeof t&&""!==(t||"")},s=function(t,e){!0===bool(e.condition,!0)&&(""!==(e.script||"")&&o.push({url:e.script,module:e.module||""}),!0===r(e.css||"")?i.push(e.css):!0===Array.isArray(e.css)&&Array.prototype.push.apply(i,e.css.filter(r)))};!0===r(e||"")?o.push(e):!0===Array.isArray(e)?$.each(e,s):"object"==typeof e&&""!==(e.script||"")&&s(0,e);let a=[];$.each(i,(function(t,e){""!==(e||"")&&a.push(loadCSS(e))}));let c=o.map((function(e,n){let i=e.url,r=e.module||"";if(""===r){let t=new Promise((function(t,e){try{!async function(){$.ajax({url:i,dataType:"script",success:function(){t(o)},error:function(){e(o)},timeout:3e4})}()}catch(t){console.debug(t.message+"%o",t)}}));return t}return t.loadmodule(r,i,e.alias)}));Promise.all(c).then(n)},t.loadmodule=function(e,n,i){let o=new Promise((function(o,r){!async function(){try{let s=(n.startsWith("/")||n.startsWith(".")?"":"/")+n;import(s).then((n=>{t[e]=n[i||"default"],o(e)})).catch((t=>{console.debug(t.message+"%o",t),r(e)}))}catch(t){console.debug(t.message+"%o",t)}}()}));return o},t.ocms_auth=function(e,n,i,o){!1===$.isPlainObject(t.auth.modules)&&(t.auth.modules={});var r=0;t.auth.modules[e+(i||"")]?((r=t.auth.modules[e+(i||"")])<2&&(i||"")===auth.guid&&(r=2),r>=(n||0)&&o(false)):t.postXT({url:t.url("auth"),data:{module:e,person_guid:i||""},success:function(s){r=s[e],t.auth.modules[e+(i||"")]=r,r<2&&(i||"")===t.auth.person_guid&&(r=2),r>=(n||0)&&o(false)},error:function(e){t.failure.call(this,e)}})},t.auth.locale="de",t.ocms_prepauth=function(e,n,i){t.postXT({url:t.url("auth"),data:{fn:"csv",modules:e,person_guid:n||""},success:function(e){t.ocms_regauth(e)},error:function(e){t.failure.call(this,e)},complete:function(){i()}})},t.ocms_regauth=function(t){$.each(t||{},(function(t,e){auth.modules[t]=parseInt(e)}))},t.init=function(e){var n="string"==typeof e?e:(e.data||{}).fn||"";""!==n&&("home"===n?($cfr(),$lfr(),$("#topbar").ocmsmenu([],!0),$("#activemodule").text($t.ov),t.ov.call($("#contentframe"))):($cfr(),$lfr(),$("#topbar").ocmsmenu([]),t.postXT({url:t.url(n+"/auth"),success:function(e){void 0===t[n]&&(t[n]={}),t[n].auth=e,e.manage>0&&t.getScript({module:n,script:["web/imdl",n,t.auth.locale||"de","js"].join("."),css:["web/imdl",n,"css"].join("."),condition:"function"!=typeof t[n].init2},(function(){t[n].init2()}))},error:function(){$("#contentframe").empty()}})))},t.menuarray=function(t){this.array=[],this.sep=function(){this.length>0&&"separator"!==this.array[array.length-1].fnc&&this.push({fnc:"separator"})},this.push=function(t){return void 0===t?null:(!0===Array.isArray(t)?Array.prototype.push.apply(this.array,t):"object"==typeof t&&this.array.push(t),t)},this.unshift=function(t){return void 0===t?null:(!0===Array.isArray(t)?Array.prototype.unshift.apply(this.array,t):"object"==typeof t&&this.array.unshift(t),t)},this.push(t)},t.menu=function(e,n){e=e||[];var i=$(this).removeClass("vis");if(!0===bool(n,!0)&&!1===i.is("#mainmenu")&&i.empty(),!1===bool(n,!1)&&i.is("#sidebar,#topbar")&&(e.unshift({id:"sbctrl",glyph:"glyphicon-th-list",aclass:"fbtn",fnc:function(){$lf()}}),$lf(0)),0===(e||[]).length)i.empty().addClass("hd");else{i.removeClass("hd");var o=!0===i.is("nav")?i:i.children("nav");1!==o.length&&(o=$("").tC("nv",i.is("#sidebar")).tC("ctxt",i.is("#topbar")).appendTo(i));var r,s=$$.ul().appendTo(o),a=function(t,e){var n=$(this).addClass("dropdown submenu");t.append($$.sc("caret dd")).addClass("dds dropdown-toggle").attr({"aria-expanded":"false"}),""!==(e.ico||"")&&t.prepend($$.sc("ico "+e.ico));var i=$$.ul({class:"dropdown-menu",role:"menu"}).appendTo(n);$.each(e.itm||[],(function(t,e){r.call(i,e)}))},c=function(t){$(this).tC("disabled","boolean"==typeof t.disabled?t.disabled:"string"==typeof t.disabled&&"subs"===t.disabled&&0===(t.itm||[]).length)};r=function(e){var n,i=$$.li({id:e.id}).attr(e.attr||{}).addClass(e.lclass).appendTo($(this)),o="string"==typeof e.fnc&&""!==e.fnc?e.fnc.split(":")[0]:"";""!==o&&"init"!==o?i.attr("role",o).appendIf($$.s(e.lbl),""!==ne(e.lbl)):(n=$$.a({class:"on",role:"button"}).addClass(e.aclass).appendTo(i).append($$.s(e.lbl)),c.call(n,e),(e.itm||[]).length>0&&a.call(i,n,e),n.click($nuf),"function"==typeof e.fnc?n.click(e.data||{},e.fnc):"init"===o&&n.click($.extend({},e.data||{},{fn:e.fnc.split(":")[1]}),t.init))},$.each(e,(function(e,n){var i,o=$$.li({id:n.id}).attr(n.attr||{}).addClass(n.lclass),a="string"==typeof n.fnc&&""!==n.fnc?n.fnc.split(":")[0]:"";if(""!==a&&"init"!==a)o.attr("role",a).appendIf($$.s(n.lbl),""!==ne(n.lbl));else{if(i=$$.a({class:"on",role:"button"}).addClass(n.aclass).appendTo(o),c.call(i,n),""!==(n.lbl||"")&&i.append($$.s(n.lbl)),""!==(n.ico||"")&&i.prepend($$.sc("ico "+n.ico)),""!==(n.glyph||"")&&i.prepend($$.sc("glyphicon "+n.glyph)),(n.itm||[]).length>0){o.addClass("dropdown"),i.append($$.sc("caret dd")).addClass("dds dropdown-toggle").attr({"aria-expanded":"false"});var l=$$.ul({class:"dropdown-menu",role:"menu"}).appendTo(o);$.each(n.itm||[],(function(t,e){r.call(l,e)}))}(n.sel||[]).length>0||(i.click($nuf),"function"==typeof n.fnc?i.click(n.data||{},n.fnc):"init"===a&&i.click($.extend({},n.data||{},{fn:n.fnc.split(":")[1]}),t.init))}o.appendTo(s)})),o.activatemenu()}},t.easytbl=(t,e)=>{e=e||{};let n=$$.tbl().addClass(e.class).css("border-collapse","collapse"),i=($$.tbody(n),!0===bool(e.frame,!1)?{padding:"5px",border:"1px solid #727272"}:{});if(!0===Array.isArray(e.header)){let t=$$.thead(n);$.each(e.header,((n,o)=>$$.th(t).css(e.cellcss||i).rwText(o)))}else if(!0===bool(e.header,!1)&&(t||[]).length>0){let o=$$.thead(n);$.each(Object.keys(t[0]),((t,n)=>$$.th(o).css(e.cellcss||i).rwText(n)))}return $.each(t||[],((t,o)=>{let r=$$.tr();$.each(o,((t,n)=>{n=n||"";let o=$$.td(r).css(e.cellcss||i);n instanceof jQuery?o.append(n):"string"==typeof n&&("<"===n.substring(0,1)?o.append(n):o.text(n))})),n.append(r)})),n},t.dlgtbl=(e,n,i)=>{i=i||{};let o=t.easytbl(e,i);t.dlg(o,$.extend({title:n},i))},t.dlg=function(t,n){n=n||{};let i=$("body > .modal").length>0,o=t=>typeof n[t],r=t=>"function"===o(t);if(!0===bool(n.exclusive,!0)&&!0===i)return void alert($t.dbldlg||"Es ist bereits ein Dialog geöffnet");let s=$$.dc("modal",$("body")),a=$$.dc("modal-dialog",s);!1===isNaN(n.zindex)?s.css("zIndex",n.zindex):!0===i&&s.css("zIndex",parseInt($("body > .modal:last").cssValue("zIndex"))+200),!1===isNaN(n.zindex_min)&&s.cssValue("zIndex")').appendTo(d)),""!==ne(n.title)&&(c=$$.dc("modal-header",d),$("

        ").text(n.title).appendTo(c));let p=$$.dc("modal-body",d),f=$$.dc("modal-footer",d);t instanceof jQuery==!0&&p.append(t);let m=function(t){t&&"function"==typeof t.stopPropagation&&t.stopPropagation(),a.removeClass("in"),!0===r("closing")&&n.closing.call(d),p.hide().emptyWithEditors(),s.remove(),!0===r("close")&&n.close.call(d)};if(d.find(":input[required]").length>0&&($$.dc("note_required",f).append($$.sc("ind_required","*")).append($$.s($t.t1||"Eingabe erforderlich")),$$.dc("note_invalid",f).append($$.s($t.t2||"Bitte überprüfen Sie Ihre Eingaben im Formular."))),!0===r("cancel")){$$.bbtn(n.cancelbutton||"Abbrechen","cancel").attr({type:"button",role:"cancel"}).appendTo(f).click((function(t){n.cancel.call(d,t);t.stopPropagation(),m()}))}if(!0===r("confirm")){let t=$$.bbtn(n.button||"OK","confirm").attr({type:!0===bool(n.form,!1)?"submit":"button",role:"confirm"}).appendTo(f);!0===h?(d.submit((function(t){try{n.confirm.call(d,t)}finally{t.preventDefault()}return!1})),d.on("modal_submit",(function(){n.confirm.call(d,e)}))):(t.click((function(t){n.confirm.call(d,t);t.stopPropagation()})),d.on("modal_submit",(function(){t.click()})))}else!0===h&&d.submit((function(t){return t.preventDefault(),!1}));return d.on("modal_close",(function(){m()})),l.click(m),!0===r("opening")&&n.opening.call(d),a.addClass("in"),ne(n.mode).indexOf("maxbody")>-1&&p.css("min-height",(u.height()-c.outerHeight()-f.outerHeight()).toString()+"px"),!0===r("open")&&n.open.call(d),{hd:c,bdy:p,ft:f,ct:u,dlg:a,c:d}},t.mform=function(e){let n=$$.dc("form-body"),i=Array.isArray(e)?e:e instanceof fields_definition?e.fields:[];return $.each(i||[],(function(e,i){let o=i.type||"";if("ignore"===o)return!0;let r=$$.dc("form-group",n),s=i.id||"dlg_"+(i.name||"")+("html"===i.type?"_"+(65536*(1+Math.random())||0).toString(16).substr(9):""),a=$$.lbl(i.label||i.name,{for:s}).appendTo($$.dc("form-itm",r)),c=$$.dc("form-itm",r),l=$$.i({id:s,name:i.name,placeholder:i.placeholder,type:i.type});switch(o){case"email":i.pattern=ne(i.pattern,"[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,}$");break;case"url":i.pattern=ne(i.pattern,"https?://.+");break;case"number":i.pattern=ne(i.pattern,"[-+]?[0-9]*[.,]?[0-9]*"),l.attr("step",i.precision||"any"),l.attr("data-format","float");break;case"integer":case"int":i.pattern=ne(i.pattern,"[-+]?[0-9]*"),l.attr("type","number"),l.attr("data-format","integer");break;case"date":if(""!==ne(i.pattern,$t.datepattern)&&(i.pattern=ne(i.pattern,"("+$t.datepattern+")|([0-9]{4}.(0[1-9]|1[012]).(0[1-9]|1[0-9]|2[0-9]|3[01]))")),""!==ne(i.placeholder,$t.dateplaceholder)&&l.attr("placeholder",ne(i.placeholder,$t.dateplaceholder)),"string"==typeof i.value){var u=i.value.substr(0,10);i.value="date"!==l.prop("type")?fdt(u+"T00:00:00",ne(i.dateformat,$t.dateformat)):u}l.attr("data-format","date:"+ne(i.dateformat,$t.dateformat)+";yyyy-MM-dd");break;case"datetime":l.attr("type","datetime-local"),""!==ne(i.pattern,$t.datetimepattern)&&(i.pattern=ne(i.pattern,"("+$t.datetimepattern+")|([0-9]{4}-(0[1-9]|1[012])-(0[1-9]|1[0-9]|2[0-9]|3[01])\\s([0-5][0-9]):([0-5][0-9]))")),""!==ne(i.placeholder,$t.datetimeplaceholder)&&l.attr("placeholder",ne(i.placeholder,$t.datetimeplaceholder)),"string"==typeof i.value&&"T"===i.value.substr(10,1)&&(i.value="datetime"!==l.prop("type").substr(0,8)?fdt(i.value,ne(i.datetimeformat,$t.datetimeformat)):i.value),l.attr("data-format","datetime:"+ne(i.datetimeformat,$t.datetimeformat)+";yyyy-MM-dd HH:mm:ss");break;case"hidden":r.addClass("hd");break;case"html":case"text":l=$$.txt({id:s,name:i.name,placeholder:i.placeholder,type:i.type}),l.tC("tinymce","html"===i.type);break;case"bool":case"boolean":i.url=[{value:"true",label:($t||{}).true||"Yes"},{value:"false",label:($t||{}).false||"No"}],"boolean"==typeof i.value&&(i.value=i.value?"true":"false");case"select":l=$$.sel({id:s,name:i.name,type:i.type}),!1===bool(i.required,!1)&&$$.eOpt().appendTo(l);try{var d=function(t){!0===Array.isArray(t)&&$.each(t,(function(t,e){"string"==typeof e?$$.opt(e,e).appendTo(l):!0===Array.isArray(e)?$$.opt(e[0],e[1]).appendTo(l):"object"==typeof e&&$$.opt(e.value,e.label||e.text).appendTo(l)}))};!0===Array.isArray(i.url)?d(i.url):"function"==typeof i.url?i.url.call(l):"string"==typeof i.url&&t.postXT({url:i.url,success:d})}catch(t){$.noop()}break;default:""!==ne(i["max-length"])&&l.attr("max-length",i["max-length"])}""!==ne(i.pattern)&&l.attr("pattern",i.pattern),l.val(i.value).change(),l.change((function(){$(this)[0].setCustomValidity("")})),l.addClass("form-control").prop("required",bool(i.required,!1)).prop("readonly",bool(i.readonly,!1)).appendTo(c),!0===bool(i.required,!1)&&a.append($$.sc("ind_required","*")),"object"==typeof i.attr&&l.attr(i.attr),"object"==typeof i.prop&&l.prop(i.prop),"string"==typeof i.class&&l.addClass(i.class),"function"==typeof i.change&&(l.change(i.change),!0===bool(i.applychange,!1)&&void 0!==i.value&&l.change()),""!==(i.note||"")&&$$.dc("form-note",c).rwText(i.note),"function"==typeof i.complete&&i.complete.call(l)})),n},t.initMCE=function(t,e){t=$(t),e=e||{};try{let n={target:t[0],inline:!1,width:e.width||"100%",statusbar:!1,document_base_url:window.location.origin+"/",content_style:"ph:before {content: '«'; color: #BBB; font-style:italic; } ph:after {content: '»'; color: #BBB; font-style:italic; } ph { color: #AAA; font-style:italic; }",relative_urls:!1,remove_script_host:!1};!0===bool(e.hidemenu,!1)&&(n.menubar=!1,n.menu={}),!0===bool(e.hidetoolbar,!1)&&(n.toolbar=!1),$.extend(n,e||{}),tinymce.init(n)}catch(t){alert(t.message)}},t.dlgform=function(e,n){n=n||{};let i,o=$$.dc("frm").append(t.mform(e||[]).addClass("stacked"));n.addcontent instanceof jQuery&&o.append(n.addcontent),"function"==typeof n.submit?i=n.submit:"function"==typeof n.success&&(i=function(e){var i=$(this).ldng(1),o=$.extend({loginaccount:t.auth.account||""},i.serializeObject(bool(n.checkvalidity,!0),{typedvalues:bool(n.typedvalues,!1)}));""!==(n.url||"")?t.postXT({url:n.url,data:o,success:function(t){n.success.call(this,t),i.trigger("modal_close")},error:function(){alert($t.l17)},complete:function(){i.ldng(0)},timeout:6e4}):(n.success.call(this,o),i.trigger("modal_close"))});let r={form:!0,title:n.title||"",button:n.button||$t.submit,confirm:i,size:n.size||[500,600],open:function(){let e=$(this).find(".tinymce");e.length>0&&t.initMCE(e,n.tinymce||{})}};return t.dlg.call(this,o,r)},t.login.dlg=function(e){e=e||{};let n=[{name:"userinfo",label:$t.l1,type:"string",value:t.auth.login,change:t.login.uichange,required:!0},{name:"userlogin",type:"hidden",required:!0,value:t.auth.login},{name:"username",type:"string",label:$t.l4,required:!0,readonly:!0,placeholder:$t.l5,value:t.auth.fullname_rev},{name:"userpass",type:"password",label:$t.l3,required:!0,placeholder:$t.l3}];""===(t.auth.account||"")&&n.unshift({id:"dlg_loginaccount",name:"loginaccount",type:"string",required:!0,value:t.auth.account});let i=$$.dc("frm").append(t.mform(n).addClass("stacked")),o=t.dlg.call(this,i,{form:!0,title:$t.l0,button:$t.submit,confirm:function(n){var i=$(this).ldng(1),o=$.extend({loginaccount:t.auth.account||""},i.serializeObject());t.postXT({url:"/vt/login",data:o,success:function(n){""!==((n||{}).login||"")&&(i.trigger("modal_close"),t.auth=n,"object"==typeof e.ajo&&(e.ajo.islogin,$.ajax(e.ajo)))},error:function(){alert($t.l17)},complete:function(){i.ldng(0)},timeout:6e4})},size:[500,600]}),r=$$.dc("modal-content").css("height","auto").attr("novalidate","true").append($$.dc("modal-header").appendIf($("

        ").text(t.auth.accountname),""!==(t.auth.accountname||"")).append($("

        Vereinsmanager

        ")));o.dlg.prepend(r)},t.addNoEntryInfo=function(t){$(this).append($$.dc("noentryinfo").text(t||$t.t11))}}($ocms),Element.prototype.matches||(Element.prototype.matches=Element.prototype.msMatchesSelector||Element.prototype.webkitMatchesSelector),Element.prototype.closest||(Element.prototype.closest=function(t){var e=this;do{if(Element.prototype.matches.call(e,t))return e;e=e.parentElement||e.parentNode}while(null!==e&&1===e.nodeType);return null}),function(t,e){var n,i;"object"==typeof window&&(window[t]=(n=function(t){var e=window,n=document.body,i=document.documentElement,o=Math.max(0,e.pageXOffset||i.scrollLeft||n.scrollLeft||0)-(i.clientLeft||0),r=Math.max(0,e.pageYOffset||i.scrollTop||n.scrollTop||0)-(i.clientTop||0);return{x:t?Math.max(0,t.pageX||t.clientX||0)-o:0,y:t?Math.max(0,t.pageY||t.clientY||0)-r:0}},(i=function(t,e){t&&t instanceof Element&&(this._container=t,this._options=e||{},this._clickItem=null,this._dragItem=null,this._showDragItem="boolean"!=typeof this._options.dragItem||!1!==this._options.dragItem,this._hovItem=null,this._sortLists=[],this._click={},this._dragging=!1,this._dragHandleClass=this._options.dragHandleClass||"",this._parentident=this._options.parentident||"",this._swapdone="function"==typeof this._options.swapdone?this._options._swapdone:null,this._container.setAttribute("data-is-sortable",1),this._container.classList.add("sortable"),this._container.style.position="static",window.addEventListener("mousedown",this._onPress.bind(this),!0),window.addEventListener("touchstart",this._onPress.bind(this),!0),window.addEventListener("mouseup",this._onRelease.bind(this),!0),window.addEventListener("touchend",this._onRelease.bind(this),!0),window.addEventListener("mousemove",this._onMove.bind(this),!0),window.addEventListener("touchmove",this._onMove.bind(this),!0))}).prototype={constructor:i,toArray:function(t){t=t||"id";for(var e=[],n="",i=0;ii.left&&ei.top&&n-1)&&e.className.indexOf("nosort")<0)&&(t.preventDefault(),this._dragging=!0,this._click=n(t),this._makeDragItem(e),this._onMove(t),!0)}t&&!1===e.call(this,t.target)&&""!==this._parentident&&t.target.closest(this._parentident)&&e.call(this,t.target.closest(this._parentident))},_onRelease:function(t){this._dragging=!1,this._trashDragItem()},_onMove:function(t){if(this._dragItem&&this._dragging){t.preventDefault();var e=n(t),i=this._container;!0===this._showDragItem&&this._moveItem(this._dragItem,e.x-this._click.x,e.y-this._click.y);for(var o=0;o0?a.mousedown(c).addClass("dctrl"):s.mousedown(c).addClass("dctrl"),t(this)}}(jQuery),$(document).ready((function(){$("html").click((function(t){$nuf()})),$("#listframe").click((function(t){t.stopPropagation(),$nuf()})),$("#mainmenu").ocmsmenu($ocms.ocmsmenu),$("#mainmenu").activatemenu()})),$.extend($t,{m_inv:"Rechnungen",m_req:"Aufträge",m_rep:"Berichte",m_todo:"ToDos",m_bcd:"BankBuchungen",rsp:"Passwort ändern",pnm:"Die Passwörter stimmen nicht überein",cps:"Das neue Passwort wurde gespeichert.",pwr:"Bitte wählen Sie ein starkes Passwort (min 8 Zeichen, davon jeweils min 2 Zahlen, kleine und große Buchstaben, Sonderzeichen sind optional).",smsc:"Sie beötigen für diese Funktion einen SMS-Code.\nSoll dieser nun versandt werden?",wdc:"Doppelt klicken, um die Box zu aktualisieren.",wdg:{}}),$t.rspf={sms:"Der SMS-Code konnte nicht bestätigt werden",valid:"Das alte Passwort ist nicht korrekt",requirements:"Das Passwort entspricht nicht den Anforderungen.\n"+$t.pwr},$fd={rsp:new fields_definition("","",[{name:"opw",label:"aktuelles Passwort",type:"password",required:!0,attr:{"auto-complete":"current-password"}},{name:"npw",label:"neues Passwort",type:"password",required:!0,pattern:"(.{6,})",attr:{"auto-complete":"new-password"}},{name:"npwc",label:"neues Passwort (Bestätigung)",type:"password",required:!0,attr:{"auto-complete":"new-password"},note:$t.pwr},{name:"code",label:"SMS-Code",type:"string",required:!0,attr:{"auto-complete":"one-time-code"}}])},$ocms.init=function(t){var e="string"==typeof t?t:(t.data||{}).fn||"";""!==e&&("home"===e?($cfr(),$lfr(),$("#topbar").ocmsmenu([],!0),$("#activemodule").text($t.ov),$fis.ov()):($cfr(),$lfr(),$("#topbar").ocmsmenu([]),$ocms.postXT({url:$ocms.url(e+"/auth"),success:function(t){void 0===$ocms[e]&&($ocms[e]={}),$ocms[e].auth=t,t.manage>0&&$ocms.getScript({module:e,script:["/web/fis",e,$ocms.auth.locale||"de","js"].join("."),css:["/web/fis",e,"css"].join("."),condition:"function"!=typeof $ocms[e].init2},(function(){$ocms[e].init2()}))},error:function(){$("#contentframe").empty()}})))};var $fis={auth:{},db:function(){$("#mainmenu_activemodule").text($t.ov);let t=$(this).empty(),e=$$.d({id:"dashboard_frame"}).appendTo(t);$ocms.postXT({url:$ocms.url("wdg/my"),success:function(t){$.each(t,(function(t,n){var i=$$.dc("wdg_frame",e,{"data-wdg":n}).ldng(1);$ocms.wdg.call(i,{wdg:n})}))},loading:e})},ValidateEmail:function(t){return!!/^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/.test(t)},cf:t=>{let e=$("#contentframe");return!0===bool(t,!1)&&e.empty().rC("hd"),e},lf:t=>{let e=$("#listframe");return!0===bool(t,!1)&&e.empty().aC("hd").rC("fix"),e},frm_edit:function(t){let e=$fis.cf(!1),n=e.children(".cfrm"),i=e.children(".edit_frm");return n.length<1?n=$$.dc("cfrm hd").prependTo(e):!0===bool(t,!1)&&n.empty(),i.length<1&&(i=$$.dc("edit_frm").insertAfter(n)),i.empty()},frm_list:function(t,e){let n=$fis.cf(!1),i=n.children(".cfrm"),o=n.children(".list_frm");return i.length<1?i=$$.dc("cfrm hd").prependTo(n):!0===bool(t,!1)&&i.empty(),!0===bool(e,!1)&&n.children(".edit_frm").remove(),o.length<1&&(o=$$.dc("list_frm").appendTo(n)),o.empty()},lfm:()=>{let t=$fis.lf(!1),e=t.children(".lfrm");return e.length<1&&(e=$$.dc("lfrm").prependTo(t)),e},getAuth:(t,e)=>new Promise(((n,i)=>{$fis.auth[t]&&!1===bool(e,!1)?n($fis.auth[t]||-1):$ocms.postXT({url:$ocms.url("auth"),data:{module:t},success:e=>{$fis.auth[t]=e.auth||-1,n($fis.auth[t]||-1)},error:()=>{i()}})})),prepAuth:t=>new Promise(((e,n)=>{$ocms.postXT({url:$ocms.url("auth"),data:{module:t,array:1},success:t=>{$.extend($fis.auth,t||{})},complete:()=>{e()}})})),isAuth:(t,e)=>($fis.auth[t]||-1)>=(e||1),resetPass:function(t,e){confirm($t.smsc)&&($ocms.postXT({url:$ocms.url("account/sms"),data:{fn:"pwc"}}),$ocms.dlgform($fd.rsp.clone(),{title:$t.rsp||"",submit:function(t){var e=$(this).ldng(1),n=$.extend({loginaccount:$ocms.auth.account||""},e.serializeObject(!0,{typedvalues:!0}));(n.npw||"")!==(n.npwc||"")?e.find('input[name="npw"]:first')[0].setCustomValidity($t.pnm):$ocms.postXT({url:$ocms.url("account/changepassword"),data:n,success:function(t){alert($t.cps),e.trigger("modal_close")},error:function(t){alert($t.rspf[t.getResponseHeader("x-ocms-std")])},complete:function(){e.ldng(0)},timeout:6e4})}}))},wdg:function(t){let e=$(this).empty();$ocms.postXT({url:$ocms.url("wdg/one"),data:{short_name:t.wdg},timeout:9e4,success:function(n,i,o){let r=t.wdg,s=n[r];if(!s)return void e.ldng(0);let a=$.inArrayRegEx("dblwidth",s.rendering_options)>-1,c=$.inArrayRegEx("tiny",s.rendering_options)>-1;e.toggleClass("dbl",a&&!c).toggleClass("tny",c);$$.dc("wdg_hd",e,{title:ne(s.description,$t.wdc)}).toggleClass("dbl",a).text(ne(s.name,t.wdg)).dblclick((function(t){t.stopPropagation(),$fis.wdg.call(e,{wdg:r})}));let l=$$.dc("wdg_cnt",e).toggleClass("dbl",a).hide(),u=$.inArrayRegEx("bgcolor",s.rendering_options);switch(u>-1&&l.css("backgroundColor",s.rendering_options[u].toString().right(":")),s.type){case"table":var d=$$.tblset({},l),h=$$.tr().appendTo(d.hd),p=$t.wdg[r.indexOf("wdg_ev_")>=0?"wdg_ev_":r]||{};$.each(s.columns,(function(t,e){var n=p[e]?p[e].label:e;$$.th().text(n).appendTo(h)})),$.each(s.data,(function(t,e){var n=$$.tr().appendTo(d.bdy);$.each(s.columns,(function(t,i){var o=$$.td().appendTo(n);e[i]instanceof Date||!0===$ocms.isJSONDateString(e[i])?o.text(fdt(e[i],$t.dateformat)):o.rwText(e[i])}))})),$.inArray("firstrow_bold",s.rendering_options)>-1&&h.nextAll("tr:first").css("font-weight","bold");break;case"ind":$$.dc("ind",l).addClass("sts_"+(s.data.status||"")).append([$$.dc("ind").text(s.data.value),$$.lbl(s.data.label)]);break;case"image_url":l.css("background","url('"+s.url+"') no-repeat center center transparent");break;case"image_base64":l.css("background","url('data:image/png;base64,"+s.image+"') no-repeat center center transparent");break;case"html":if(l.html(s.html),$.inArray("reload_10min",s.rendering_options)>-1){var f=l.find("iframe");setTimeout((function(){f.attr("src",(function(t,e){return e}))}),6e5)}}$.inArray("reload_30min",s.rendering_options)>-1&&"html"!==s.type&&setTimeout((function(){$fis.wdg.call(e,{wdg:r})}),18e5),l.slideDown(150)},error:function(t){e.slideUp(150),$fis.failure.call(this,t)},complete:function(){e.ldng(0)}})},ov:function(){$fis.lf(!0);let t=$("#contentframe").empty(),e=$$.d({id:"dashboard_frame"}).appendTo(t);$ocms.postXT({url:$ocms.url("wdg/my"),success:function(t){$.each(t,(function(t,n){var i=$$.dc("wdg_frame",e,{"data-wdg":n}).ldng(1);$fis.wdg.call(i,{wdg:n})}))},loading:e})}};$fis.notifications={connection:null,init:function(){"undefined"!=typeof signalR&&null===this.connection&&$ocms.auth.useraccount_id&&(this.ensureFrame(),this.connection=(new signalR.HubConnectionBuilder).withUrl("/notifications").withAutomaticReconnect().build(),this.connection.on("notification",(t=>{this.push(t)})),this.connection.onclose((()=>{console.warn("Notification connection closed; retrying in 5s."),this.connection=null,setTimeout((()=>this.init()),5e3)})),this.start())},start:function(){this.connection.start().catch((t=>{console.warn("Notification connection failed to start; retrying in 5s.",t),this.connection=null,setTimeout((()=>this.init()),5e3)}))},ensureFrame:function(){$("#notification_frame").length<1&&$("
        ",{id:"notification_frame"}).appendTo($("footer:first").length?"footer:first":"body")},push:function(t){this.ensureFrame(),t=t||{};let e=$("
        ",{class:"notification_item"}).addClass((t.severity||"info").toLowerCase()).append($("
        '),n=e.find(".form-body"),i=null;e.find("form").submit((function(t){t.preventDefault();var o=$(this).serializeObject(!0),r=null===i,s=r?"spwc":"spw";return $ocms.postXT.call(this,{url:$ocms.url(s),data:o,complete:function(){r?(n.append('
        Ihnen wurde ein Code per SMS zugesandt.
        Bitte tragen Sie den hier ein:
        '),i=$('
        ').appendTo(n)):(alert($t.l13),e.remove())},error:()=>{}}),!1})),e.find(".modal-close").click((function(){e.remove()}));var o=[];$.each($t.l7a.split("\n"),((t,e)=>{Array.prototype.push.apply(o,[$("
        "),$("").text(e)])})),e.find(".modal-note").append($('').text($t.alert)).append(o),e.appendTo("body"),setTimeout((function(){$(".modal").find('input[name="lastname"]').focus()}),600)}};var $$={s:function(t){return $("").text(t)},br:function(){return $("
        ")},sc:function(t,e){return $("").addClass(t).text(e)},td:function(t,e){var n=$("");return t instanceof jQuery==!0?n.appendTo(t):"object"==typeof t?n.attr(t):"string"==typeof t&&n.text(t),"object"==typeof e?n.attr(e):"string"==typeof e&&n.text(e),n},th:function(t,e){var n=$("");return t instanceof jQuery==!0?n.appendTo(t):"object"==typeof t?n.attr(t):"string"==typeof t&&n.text(t),"object"==typeof e?n.attr(e):"string"==typeof e&&n.text(e),n},tdc:function(t,e,n){return $$.td(e,n).addClass(t)},td2:function(t){var e=$('');return"string"===$.type(t)?e.text(t):t instanceof jQuery?e.append(t):"function"==typeof t?t.call(e):e.html(" "),e},td3:function(t){var e=$('');return"string"===$.type(t)?e.text(t):t instanceof jQuery?e.append(t):"function"==typeof t?t.call(e):e.html(" "),e},tdtr:function(t,e){var n=$$.tr().appendTo(e);return t instanceof jQuery==!0||"string"==typeof t?t.appendTo($$.td().appendTo(n)):!0===Array.isArray(t)&&$.each(t,(function(t,e){$(e).appendTo($$.td().appendTo(n))})),n},tr:function(t,e){var n=$("");return t instanceof jQuery==!0?n.appendTo(t):"object"==typeof t&&n.attr(t),"object"==typeof e&&n.attr(e),n},trc:function(t,e){var n=$("").addClass(t);return e instanceof jQuery==!0?n.appendTo(e):"object"==typeof e&&n.attr(e),n},d:function(t){return $("
        ").attr(t||{})},dc:function(t,e,n,i){var o=$("
        ").addClass(t);return e instanceof jQuery==!0?o.appendTo(e):"object"==typeof e?o.attr(e):"function"==typeof e?o.click(e):"string"==typeof e&&o.text(e),"string"==typeof n?o.text(n):"object"==typeof n?o.attr(n):"function"==typeof n&&o.click(n),"string"==typeof i?o.text(i):"object"==typeof i?o.attr(i):"function"==typeof i&&o.click(i),o},df:function(t){return $("
         
        ").attr(t||{})},opt:function(t,e,n){var i=$("");return"string"==typeof t?i.attr("value",t):"object"==typeof t&&i.attr(t),"string"==typeof e?i.text(e):"object"==typeof e&&i.attr(e),"object"==typeof n&&i.attr(n),i},eOpt:function(t){var e=$('');return t&&e.attr("selected","selected"),e},tbl:function(t){return $("
        ").attr(t||{})},tblc:function(t){return $("
        ").addClass(t)},thead:function(t){let e=$("");return t instanceof jQuery&&e.prependTo(t),e},tbody:function(t){let e=$("");return t instanceof jQuery&&e.appendTo(t),e},tblset:function(t,e){let n=$$.tbl(t||{});return e instanceof jQuery&&e.append(n),{tbl:n,hd:$$.thead().appendTo(n),bdy:$$.tbody().appendTo(n)}},i:function(t){return $("").attr(t||{})},img:function(t,e){return $("").attr("src",t).attr(e||{})},sel:function(t){return $("").attr(t||{})},btn:function(t){return $("").attr(t||{})},a:function(t){return $("").attr(t||{})},li:function(t){return $("
      • ").attr(t||{})},ul:function(t){return $("
          ").attr(t||{})},nav:function(t){return $("").attr(t||{})},lbl:function(t,e){var n=$("");return"string"==typeof t&&n.text(t),"object"==typeof t?n.attr(t):"object"==typeof e&&n.attr(e),n},txt:function(t){return $("").attr(t||{})},0:function(t,e){return $("<"+t+">").attr(e||{})},bbtn:function(t,e){return $$.btn({type:"button",class:"btn"}).addClass(e).text(t)},svg:t=>$(document.createElementNS("http://www.w3.org/2000/svg",t))};function getMonday(t){var e=(t=new Date(t)).getDay(),n=t.getDate()-e+(0==e?-6:1);return new Date(t.setDate(n))}function $lf(t){var e=void 0===t?null:"number"==typeof t&&1!==t||"boolean"==typeof cl&&!1===t;return $("#listframe").tC("hd",e).is(".hd")}function $nuf(t){if(t&&t.stopPropagation(),!$(this).is(".disabled")){var e=function(t){t.removeClass("vis").find("li.dropdown").removeClass("open").removeClass("vis").attr("aria-expanded","false")},n=$(this).parent("li.dropdown");if(n.length>0){n.tC("open"),navs=!0===n.is(".open")?"true":"false",n.attr("aria-expanded",navs);var i=n.closest("nav");i.find("li.dropdown").not(n.parentsUntil("nav")).not(n).removeClass("open").attr("aria-expanded","false"),!1===n.is(".open")&&n.find("li.dropdown").removeClass("open").attr("aria-expanded","false"),e($("nav").not(i))}else e($("nav"))}}function $tbr(){return $lf(0),$("#topbar").ocmsmenu([])}function $lfr(){return $("#sidebar").empty(),$("#listframe").removeClass("fix").addClass("hd").empty()}function $cfr(){return $tbr(),$("#contentframe").empty()}function jObj(t,e){let n={};if("{"===(t||"").substr(0,1))try{n=JSON.parse(t)}catch(t){n={}}return n[e]||""}function string(t,e){var n,i=t||"";return $.each(e||[],(function(t,e){n=new RegExp("\\{"+t.toString()+"\\}","ig"),i=i.replace(n,e)})),i}function init_tooltip(t){var e=!0===("boolean"==typeof t&&t)&&"mouse";$("[title]").qtip({position:{target:e,adjust:{x:2,y:2},viewport:!0},events:{hidden:$ocms.tooltip_hidden},effect:!1}),$("div.tooltiptext").each((function(){$(this).filter(":not(:empty)").parent().qtip({suppress:!1,content:{text:$(this).clone()},position:{target:e,adjust:{x:2,y:2},viewport:!0},events:{hidden:$ocms.tooltip_hidden}})}))}String.prototype.trim=function(){return this.replace(/^\s+|\s+$/g,"")},String.prototype.left=function(t){if("string"===$.type(t)){var e=this.indexOf(t);return e>0?this.slice(0,e):""}return this.substring(0,t)},String.prototype.right=function(t){if("string"===$.type(t)){var e=this.indexOf(t);return e>0?this.substring(this.length-e):""}return this.substring(this.length-t)},Array.prototype.move=function(t,e){if(e>=this.length)for(var n=e-this.length;1+n--;)this.push(void 0);return this.splice(e,0,this.splice(t,1)[0]),this},function(t){t.fn.appendToIf=function(e,n){var i=t(this),o="function"==typeof n?n(i):n;return!0===("boolean"!=typeof o||o)&&i.appendTo(e),i},t.fn.appendIf=function(e,n){var i=t(this),o="function"==typeof n?n(i):n;return!0===("boolean"!=typeof o||o)&&i.append(e),i},t.fn.rwText=function(e,n,i){var o=t(this).empty();i=t.extend({wrap:!0},i);var r=!0===Array.isArray(e)?e:(null==e?"":String(e)).split("\n");return t.each(r,(function(t,e){""!==(e||"")&&(t>0&&o.append($$.br()),o.append(!0===i.wrap?$$.s(e):e))})),n&&o.attr("title",n),o},t.fn.loadSel=function(e,n,i){if("SELECT"===t(this).prop("tagName").toUpperCase()){var o=t(this);$ocms.postXT.call(this,{url:e,data:n||{},success:function(e){t.each(e,(function(){o.append($$.opt(e.value,e.text))}))},complete:function(){o.ldng(0),"function"==typeof i&&i.call(o)}})}},t.fn.emptyWithEditors=function(e){var n=t(this);return n.find(":input.tinymce").each((function(e,n){try{var i=tinymce.get(t(n).attr("id"));i&&i.remove()}catch(e){t.noop()}})),n.empty()},t.fn.cssValue=function(t){if(this.length>0){var e=this.css(t)||"";if(""===e)return 0;var n=/(^[\d\.]*)(\D{1,3}$)/gi.exec(e);return null!==n?"rem"===n[2]?$ocms.rpx(parseFloat(n[1])):parseFloat(n[1]):!1===isNaN(e)?parseFloat(e):0}return 0},t.fn.veryInnerHeight=function(){let e=e=>t(this).cssValue(e);return t(this).innerHeight()-e("padding-top")-e("padding-bottom")},t.fn.veryInnerWidth=function(){let e=e=>t(this).cssValue(e);return t(this).innerWidth()-e("padding-left")-e("padding-right")},t.fn.marginWidth=function(){let e=e=>t(this).cssValue(e);return e("margin-left")+e("margin-right")},t.fn.marginHeight=function(){let e=e=>t(this).cssValue(e);return e("margin-top")+e("margin-bottom")},t.inArrayRegEx=function(e,n,i){var o="regexp"===t.type(e)?e:new RegExp(e);if(!n)return-1;for(var r=i=i||0;r7){i=e.split(","),o=(n||(t<0?"rgb(0,0,0)":"rgb(255,255,255)")).split(",");var c=a(i[0].slice(4)),l=a(i[1]),u=a(i[2]);return"rgb("+(s((a(o[0].slice(4))-c)*r)+c)+","+(s((a(o[1])-l)*r)+l)+","+(s((a(o[2])-u)*r)+u)+")"}var d=(i=a(e.slice(1),16))>>16,h=i>>8&255,p=255&i;return"#"+(16777216+65536*(s((((o=a((n||(t<0?"#000000":"#FFFFFF")).slice(1),16))>>16)-d)*r)+d)+256*(s(((o>>8&255)-h)*r)+h)+(s(((255&o)-p)*r)+p)).toString(16).slice(1)},t.fn.IN=function(e){return t(this).fadeIn(400,e),t(this)},t.fn.OUT=function(e){return t(this).fadeOut(400,e),t(this)},t.fn.tooltip=function(e,n){var i=!0===("boolean"==typeof e&&e)&&"mouse",o="boolean"==typeof n&&n,r=t(this);return r.each((function(){var e=o?t(this).find(".tooltiptext"):t(this).children(".tooltiptext");t(e).length>0?e.each((function(){var e=t(this);t(this).filter(":not(:empty)").parent().qtip({suppress:!1,content:{text:e.clone()},position:{target:i,adjust:{x:2,y:2},viewport:!0},events:{hidden:$ocms.tooltip_hidden},show:{effect:!1},hide:{effect:!1}}),e.remove()})):t(this).qtip({position:{target:i,adjust:{x:2,y:2},viewport:!0},events:{hidden:$ocms.tooltip_hidden},effect:!1})})),r},t.fn.rC=function(e){return t(this).removeClass(e)},t.fn.aC=function(e){return t(this).addClass(e)},t.fn.tC=function(e,n){return t(this).toggleClass(e,n)}}(jQuery),function(t){t.fn.ocmsmenu=function(e,n){var i=t(this);return $ocms.menu.call(i,e,n),i},t.fn.activatemenu=function(){var e=t(this).filter("nav");return e.find("a").not(".on").addClass("on").click($nuf),e.find(".nav-btn").not(".on").addClass("on").click((function(e){e.stopPropagation();var n=t(this);t(n.attr("data-target")).tC(n.attr("data-toggle"))})),e}}(jQuery);class ObjectArray extends Array{isEmpty(){return 0===this[0].length}static get[Symbol.species](){return Array}filter(t){return"function"==typeof t?new ObjectArray(this[0].filter(t)):this}remove(t){if("function"!=typeof t)return this;{let e=this[0].findIndex(t);for(;e>-1;)this[0].splice(e),e=this[0].findIndex(t)}}sortBy(t){return"function"==typeof t&&this[0].sort(t),this}sortString(t){return this[0].sort(((e,n)=>{let i=(e[t]||"").toString().toUpperCase(),o=(n[t]||"").toString().toUpperCase();return console.debug(i.localeCompare(o)),i.localeCompare(o)})),this}sortNum(t){return this[0].sort(((e,n)=>{let i=e[t],o=n[t];return!0===isNaN(o)&&!1===isNaN(i)||io?1:0})),this}sum(t){return this[0].reduce(((e,n)=>e+(!0===isNaN(n[t])?0:n[t])),0)}groupBy(t){return this[0].reduce((function(e,n){let i=n[t];return e[i]||(e[i]=[]),e[i].push(n),e}),{})}each(t){if("function"==typeof t){let e=!1;this[0].forEach(((n,i,o)=>{if(!1===e){let r=t(n,i,o);"boolean"==typeof r&&!1===r&&(e=!0)}}))}}get toArray(){return this[0]}}class NumArray extends Array{sum(){return this.reduce(((t,e)=>t+e))}first(){return this[0]}last(){return this[this.length-1]}average(){return this.sum()/this.length}range(){let t=this.map((t=>t)).sort();return{min:t[0],max:t[this.length-1]}}static get[Symbol.species](){return Array}}$ocms.ocmsmenu=[{lbl:"",id:"m_home",ico:"glyphicon glyphicon-home",fnc:"init:home"},{fnc:"separator"}],function(t){t.multline=function(t){let e=t.split("\n"),n=$$.d();return $.each(e,((t,e)=>{n.append($$.s(e))})),n.html()},t.tooltip_hidden=function(t,e){$(this).remove(),e.rendered=!1},t.isJSONDateString=function(t){return"string"==typeof t&&/^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})?)?$/.test(t)},t.failure=function(e){11110===(e.internalCode||-1)?t.login.dlg():alert($t.f1+"\n"+(e.internalText||""))},t.getScript=function(e,n){var i=[],o=[],r=function(t){return"string"==typeof t&&""!==(t||"")},s=function(t,e){!0===bool(e.condition,!0)&&(""!==(e.script||"")&&o.push({url:e.script,module:e.module||""}),!0===r(e.css||"")?i.push(e.css):!0===Array.isArray(e.css)&&Array.prototype.push.apply(i,e.css.filter(r)))};!0===r(e||"")?o.push(e):!0===Array.isArray(e)?$.each(e,s):"object"==typeof e&&""!==(e.script||"")&&s(0,e);let a=[];$.each(i,(function(t,e){""!==(e||"")&&a.push(loadCSS(e))}));let c=o.map((function(e,n){let i=e.url,r=e.module||"";if(""===r){let t=new Promise((function(t,e){try{!async function(){$.ajax({url:i,dataType:"script",success:function(){t(o)},error:function(){e(o)},timeout:3e4})}()}catch(t){console.debug(t.message+"%o",t)}}));return t}return t.loadmodule(r,i,e.alias)}));Promise.all(c).then(n)},t.loadmodule=function(e,n,i){let o=new Promise((function(o,r){!async function(){try{let s=(n.startsWith("/")||n.startsWith(".")?"":"/")+n;import(s).then((n=>{t[e]=n[i||"default"],o(e)})).catch((t=>{console.debug(t.message+"%o",t),r(e)}))}catch(t){console.debug(t.message+"%o",t)}}()}));return o},t.ocms_auth=function(e,n,i,o){!1===$.isPlainObject(t.auth.modules)&&(t.auth.modules={});var r=0;t.auth.modules[e+(i||"")]?((r=t.auth.modules[e+(i||"")])<2&&(i||"")===auth.guid&&(r=2),r>=(n||0)&&o(false)):t.postXT({url:t.url("auth"),data:{module:e,person_guid:i||""},success:function(s){r=s[e],t.auth.modules[e+(i||"")]=r,r<2&&(i||"")===t.auth.person_guid&&(r=2),r>=(n||0)&&o(false)},error:function(e){t.failure.call(this,e)}})},t.auth.locale="de",t.ocms_prepauth=function(e,n,i){t.postXT({url:t.url("auth"),data:{fn:"csv",modules:e,person_guid:n||""},success:function(e){t.ocms_regauth(e)},error:function(e){t.failure.call(this,e)},complete:function(){i()}})},t.ocms_regauth=function(t){$.each(t||{},(function(t,e){auth.modules[t]=parseInt(e)}))},t.init=function(e){var n="string"==typeof e?e:(e.data||{}).fn||"";""!==n&&("home"===n?($cfr(),$lfr(),$("#topbar").ocmsmenu([],!0),$("#activemodule").text($t.ov),t.ov.call($("#contentframe"))):($cfr(),$lfr(),$("#topbar").ocmsmenu([]),t.postXT({url:t.url(n+"/auth"),success:function(e){void 0===t[n]&&(t[n]={}),t[n].auth=e,e.manage>0&&t.getScript({module:n,script:["web/imdl",n,t.auth.locale||"de","js"].join("."),css:["web/imdl",n,"css"].join("."),condition:"function"!=typeof t[n].init2},(function(){t[n].init2()}))},error:function(){$("#contentframe").empty()}})))},t.menuarray=function(t){this.array=[],this.sep=function(){this.length>0&&"separator"!==this.array[array.length-1].fnc&&this.push({fnc:"separator"})},this.push=function(t){return void 0===t?null:(!0===Array.isArray(t)?Array.prototype.push.apply(this.array,t):"object"==typeof t&&this.array.push(t),t)},this.unshift=function(t){return void 0===t?null:(!0===Array.isArray(t)?Array.prototype.unshift.apply(this.array,t):"object"==typeof t&&this.array.unshift(t),t)},this.push(t)},t.menu=function(e,n){e=e||[];var i=$(this).removeClass("vis");if(!0===bool(n,!0)&&!1===i.is("#mainmenu")&&i.empty(),!1===bool(n,!1)&&i.is("#sidebar,#topbar")&&(e.unshift({id:"sbctrl",glyph:"glyphicon-th-list",aclass:"fbtn",fnc:function(){$lf()}}),$lf(0)),0===(e||[]).length)i.empty().addClass("hd");else{i.removeClass("hd");var o=!0===i.is("nav")?i:i.children("nav");1!==o.length&&(o=$("").tC("nv",i.is("#sidebar")).tC("ctxt",i.is("#topbar")).appendTo(i));var r,s=$$.ul().appendTo(o),a=function(t,e){var n=$(this).addClass("dropdown submenu");t.append($$.sc("caret dd")).addClass("dds dropdown-toggle").attr({"aria-expanded":"false"}),""!==(e.ico||"")&&t.prepend($$.sc("ico "+e.ico));var i=$$.ul({class:"dropdown-menu",role:"menu"}).appendTo(n);$.each(e.itm||[],(function(t,e){r.call(i,e)}))},c=function(t){$(this).tC("disabled","boolean"==typeof t.disabled?t.disabled:"string"==typeof t.disabled&&"subs"===t.disabled&&0===(t.itm||[]).length)};r=function(e){var n,i=$$.li({id:e.id}).attr(e.attr||{}).addClass(e.lclass).appendTo($(this)),o="string"==typeof e.fnc&&""!==e.fnc?e.fnc.split(":")[0]:"";""!==o&&"init"!==o?i.attr("role",o).appendIf($$.s(e.lbl),""!==ne(e.lbl)):(n=$$.a({class:"on",role:"button"}).addClass(e.aclass).appendTo(i).append($$.s(e.lbl)),c.call(n,e),(e.itm||[]).length>0&&a.call(i,n,e),n.click($nuf),"function"==typeof e.fnc?n.click(e.data||{},e.fnc):"init"===o&&n.click($.extend({},e.data||{},{fn:e.fnc.split(":")[1]}),t.init))},$.each(e,(function(e,n){var i,o=$$.li({id:n.id}).attr(n.attr||{}).addClass(n.lclass),a="string"==typeof n.fnc&&""!==n.fnc?n.fnc.split(":")[0]:"";if(""!==a&&"init"!==a)o.attr("role",a).appendIf($$.s(n.lbl),""!==ne(n.lbl));else{if(i=$$.a({class:"on",role:"button"}).addClass(n.aclass).appendTo(o),c.call(i,n),""!==(n.lbl||"")&&i.append($$.s(n.lbl)),""!==(n.ico||"")&&i.prepend($$.sc("ico "+n.ico)),""!==(n.glyph||"")&&i.prepend($$.sc("glyphicon "+n.glyph)),(n.itm||[]).length>0){o.addClass("dropdown"),i.append($$.sc("caret dd")).addClass("dds dropdown-toggle").attr({"aria-expanded":"false"});var l=$$.ul({class:"dropdown-menu",role:"menu"}).appendTo(o);$.each(n.itm||[],(function(t,e){r.call(l,e)}))}(n.sel||[]).length>0||(i.click($nuf),"function"==typeof n.fnc?i.click(n.data||{},n.fnc):"init"===a&&i.click($.extend({},n.data||{},{fn:n.fnc.split(":")[1]}),t.init))}o.appendTo(s)})),o.activatemenu()}},t.easytbl=(t,e)=>{e=e||{};let n=$$.tbl().addClass(e.class).css("border-collapse","collapse"),i=($$.tbody(n),!0===bool(e.frame,!1)?{padding:"5px",border:"1px solid #727272"}:{});if(!0===Array.isArray(e.header)){let t=$$.thead(n);$.each(e.header,((n,o)=>$$.th(t).css(e.cellcss||i).rwText(o)))}else if(!0===bool(e.header,!1)&&(t||[]).length>0){let o=$$.thead(n);$.each(Object.keys(t[0]),((t,n)=>$$.th(o).css(e.cellcss||i).rwText(n)))}return $.each(t||[],((t,o)=>{let r=$$.tr();$.each(o,((t,n)=>{n=n||"";let o=$$.td(r).css(e.cellcss||i);n instanceof jQuery?o.append(n):"string"==typeof n&&("<"===n.substring(0,1)?o.append(n):o.text(n))})),n.append(r)})),n},t.dlgtbl=(e,n,i)=>{i=i||{};let o=t.easytbl(e,i);t.dlg(o,$.extend({title:n},i))},t.dlg=function(t,n){n=n||{};let i=$("body > .modal").length>0,o=t=>typeof n[t],r=t=>"function"===o(t);if(!0===bool(n.exclusive,!0)&&!0===i)return void alert($t.dbldlg||"Es ist bereits ein Dialog geöffnet");let s=$$.dc("modal",$("body")),a=$$.dc("modal-dialog",s);!1===isNaN(n.zindex)?s.css("zIndex",n.zindex):!0===i&&s.css("zIndex",parseInt($("body > .modal:last").cssValue("zIndex"))+200),!1===isNaN(n.zindex_min)&&s.cssValue("zIndex")').appendTo(d)),""!==ne(n.title)&&(c=$$.dc("modal-header",d),$("

          ").text(n.title).appendTo(c));let p=$$.dc("modal-body",d),f=$$.dc("modal-footer",d);t instanceof jQuery==!0&&p.append(t);let m=function(t){t&&"function"==typeof t.stopPropagation&&t.stopPropagation(),a.removeClass("in"),!0===r("closing")&&n.closing.call(d),p.hide().emptyWithEditors(),s.remove(),!0===r("close")&&n.close.call(d)};if(d.find(":input[required]").length>0&&($$.dc("note_required",f).append($$.sc("ind_required","*")).append($$.s($t.t1||"Eingabe erforderlich")),$$.dc("note_invalid",f).append($$.s($t.t2||"Bitte überprüfen Sie Ihre Eingaben im Formular."))),!0===r("cancel")){$$.bbtn(n.cancelbutton||"Abbrechen","cancel").attr({type:"button",role:"cancel"}).appendTo(f).click((function(t){n.cancel.call(d,t);t.stopPropagation(),m()}))}if(!0===r("confirm")){let t=$$.bbtn(n.button||"OK","confirm").attr({type:!0===bool(n.form,!1)?"submit":"button",role:"confirm"}).appendTo(f);!0===h?(d.submit((function(t){try{n.confirm.call(d,t)}finally{t.preventDefault()}return!1})),d.on("modal_submit",(function(){n.confirm.call(d,e)}))):(t.click((function(t){n.confirm.call(d,t);t.stopPropagation()})),d.on("modal_submit",(function(){t.click()})))}else!0===h&&d.submit((function(t){return t.preventDefault(),!1}));return d.on("modal_close",(function(){m()})),l.click(m),!0===r("opening")&&n.opening.call(d),a.addClass("in"),ne(n.mode).indexOf("maxbody")>-1&&p.css("min-height",(u.height()-c.outerHeight()-f.outerHeight()).toString()+"px"),!0===r("open")&&n.open.call(d),{hd:c,bdy:p,ft:f,ct:u,dlg:a,c:d}},t.mform=function(e){let n=$$.dc("form-body"),i=Array.isArray(e)?e:e instanceof fields_definition?e.fields:[];return $.each(i||[],(function(e,i){let o=i.type||"";if("ignore"===o)return!0;let r=$$.dc("form-group",n),s=i.id||"dlg_"+(i.name||"")+("html"===i.type?"_"+(65536*(1+Math.random())||0).toString(16).substr(9):""),a=$$.lbl(i.label||i.name,{for:s}).appendTo($$.dc("form-itm",r)),c=$$.dc("form-itm",r),l=$$.i({id:s,name:i.name,placeholder:i.placeholder,type:i.type});switch(o){case"email":i.pattern=ne(i.pattern,"[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,}$");break;case"url":i.pattern=ne(i.pattern,"https?://.+");break;case"number":i.pattern=ne(i.pattern,"[-+]?[0-9]*[.,]?[0-9]*"),l.attr("step",i.precision||"any"),l.attr("data-format","float");break;case"integer":case"int":i.pattern=ne(i.pattern,"[-+]?[0-9]*"),l.attr("type","number"),l.attr("data-format","integer");break;case"date":if(""!==ne(i.pattern,$t.datepattern)&&(i.pattern=ne(i.pattern,"("+$t.datepattern+")|([0-9]{4}.(0[1-9]|1[012]).(0[1-9]|1[0-9]|2[0-9]|3[01]))")),""!==ne(i.placeholder,$t.dateplaceholder)&&l.attr("placeholder",ne(i.placeholder,$t.dateplaceholder)),"string"==typeof i.value){var u=i.value.substr(0,10);i.value="date"!==l.prop("type")?fdt(u+"T00:00:00",ne(i.dateformat,$t.dateformat)):u}l.attr("data-format","date:"+ne(i.dateformat,$t.dateformat)+";yyyy-MM-dd");break;case"datetime":l.attr("type","datetime-local"),""!==ne(i.pattern,$t.datetimepattern)&&(i.pattern=ne(i.pattern,"("+$t.datetimepattern+")|([0-9]{4}-(0[1-9]|1[012])-(0[1-9]|1[0-9]|2[0-9]|3[01])\\s([0-5][0-9]):([0-5][0-9]))")),""!==ne(i.placeholder,$t.datetimeplaceholder)&&l.attr("placeholder",ne(i.placeholder,$t.datetimeplaceholder)),"string"==typeof i.value&&"T"===i.value.substr(10,1)&&(i.value="datetime"!==l.prop("type").substr(0,8)?fdt(i.value,ne(i.datetimeformat,$t.datetimeformat)):i.value),l.attr("data-format","datetime:"+ne(i.datetimeformat,$t.datetimeformat)+";yyyy-MM-dd HH:mm:ss");break;case"hidden":r.addClass("hd");break;case"html":case"text":l=$$.txt({id:s,name:i.name,placeholder:i.placeholder,type:i.type}),l.tC("tinymce","html"===i.type);break;case"bool":case"boolean":i.url=[{value:"true",label:($t||{}).true||"Yes"},{value:"false",label:($t||{}).false||"No"}],"boolean"==typeof i.value&&(i.value=i.value?"true":"false");case"select":l=$$.sel({id:s,name:i.name,type:i.type}),!1===bool(i.required,!1)&&$$.eOpt().appendTo(l);try{var d=function(t){!0===Array.isArray(t)&&$.each(t,(function(t,e){"string"==typeof e?$$.opt(e,e).appendTo(l):!0===Array.isArray(e)?$$.opt(e[0],e[1]).appendTo(l):"object"==typeof e&&$$.opt(e.value,e.label||e.text).appendTo(l)}))};!0===Array.isArray(i.url)?d(i.url):"function"==typeof i.url?i.url.call(l):"string"==typeof i.url&&t.postXT({url:i.url,success:d})}catch(t){$.noop()}break;default:""!==ne(i["max-length"])&&l.attr("max-length",i["max-length"])}""!==ne(i.pattern)&&l.attr("pattern",i.pattern),l.val(i.value).change(),l.change((function(){$(this)[0].setCustomValidity("")})),l.addClass("form-control").prop("required",bool(i.required,!1)).prop("readonly",bool(i.readonly,!1)).appendTo(c),!0===bool(i.required,!1)&&a.append($$.sc("ind_required","*")),"object"==typeof i.attr&&l.attr(i.attr),"object"==typeof i.prop&&l.prop(i.prop),"string"==typeof i.class&&l.addClass(i.class),"function"==typeof i.change&&(l.change(i.change),!0===bool(i.applychange,!1)&&void 0!==i.value&&l.change()),""!==(i.note||"")&&$$.dc("form-note",c).rwText(i.note),"function"==typeof i.complete&&i.complete.call(l)})),n},t.initMCE=function(t,e){t=$(t),e=e||{};try{let n={target:t[0],inline:!1,width:e.width||"100%",statusbar:!1,document_base_url:window.location.origin+"/",content_style:"ph:before {content: '«'; color: #BBB; font-style:italic; } ph:after {content: '»'; color: #BBB; font-style:italic; } ph { color: #AAA; font-style:italic; }",relative_urls:!1,remove_script_host:!1};!0===bool(e.hidemenu,!1)&&(n.menubar=!1,n.menu={}),!0===bool(e.hidetoolbar,!1)&&(n.toolbar=!1),$.extend(n,e||{}),tinymce.init(n)}catch(t){alert(t.message)}},t.dlgform=function(e,n){n=n||{};let i,o=$$.dc("frm").append(t.mform(e||[]).addClass("stacked"));n.addcontent instanceof jQuery&&o.append(n.addcontent),"function"==typeof n.submit?i=n.submit:"function"==typeof n.success&&(i=function(e){var i=$(this).ldng(1),o=$.extend({loginaccount:t.auth.account||""},i.serializeObject(bool(n.checkvalidity,!0),{typedvalues:bool(n.typedvalues,!1)}));""!==(n.url||"")?t.postXT({url:n.url,data:o,success:function(t){n.success.call(this,t),i.trigger("modal_close")},error:function(){alert($t.l17)},complete:function(){i.ldng(0)},timeout:6e4}):(n.success.call(this,o),i.trigger("modal_close"))});let r={form:!0,title:n.title||"",button:n.button||$t.submit,confirm:i,size:n.size||[500,600],open:function(){let e=$(this).find(".tinymce");e.length>0&&t.initMCE(e,n.tinymce||{})}};return t.dlg.call(this,o,r)},t.login.dlg=function(e){e=e||{};let n=[{name:"userinfo",label:$t.l1,type:"string",value:t.auth.login,change:t.login.uichange,required:!0},{name:"userlogin",type:"hidden",required:!0,value:t.auth.login},{name:"username",type:"string",label:$t.l4,required:!0,readonly:!0,placeholder:$t.l5,value:t.auth.fullname_rev},{name:"userpass",type:"password",label:$t.l3,required:!0,placeholder:$t.l3}];""===(t.auth.account||"")&&n.unshift({id:"dlg_loginaccount",name:"loginaccount",type:"string",required:!0,value:t.auth.account});let i=$$.dc("frm").append(t.mform(n).addClass("stacked")),o=t.dlg.call(this,i,{form:!0,title:$t.l0,button:$t.submit,confirm:function(n){var i=$(this).ldng(1),o=$.extend({loginaccount:t.auth.account||""},i.serializeObject());t.postXT({url:"/vt/login",data:o,success:function(n){""!==((n||{}).login||"")&&(i.trigger("modal_close"),t.auth=n,"object"==typeof e.ajo&&(e.ajo.islogin,$.ajax(e.ajo)))},error:function(){alert($t.l17)},complete:function(){i.ldng(0)},timeout:6e4})},size:[500,600]}),r=$$.dc("modal-content").css("height","auto").attr("novalidate","true").append($$.dc("modal-header").appendIf($("

          ").text(t.auth.accountname),""!==(t.auth.accountname||"")).append($("

          Vereinsmanager

          ")));o.dlg.prepend(r)},t.addNoEntryInfo=function(t){$(this).append($$.dc("noentryinfo").text(t||$t.t11))}}($ocms),Element.prototype.matches||(Element.prototype.matches=Element.prototype.msMatchesSelector||Element.prototype.webkitMatchesSelector),Element.prototype.closest||(Element.prototype.closest=function(t){var e=this;do{if(Element.prototype.matches.call(e,t))return e;e=e.parentElement||e.parentNode}while(null!==e&&1===e.nodeType);return null}),function(t,e){var n,i;"object"==typeof window&&(window[t]=(n=function(t){var e=window,n=document.body,i=document.documentElement,o=Math.max(0,e.pageXOffset||i.scrollLeft||n.scrollLeft||0)-(i.clientLeft||0),r=Math.max(0,e.pageYOffset||i.scrollTop||n.scrollTop||0)-(i.clientTop||0);return{x:t?Math.max(0,t.pageX||t.clientX||0)-o:0,y:t?Math.max(0,t.pageY||t.clientY||0)-r:0}},(i=function(t,e){t&&t instanceof Element&&(this._container=t,this._options=e||{},this._clickItem=null,this._dragItem=null,this._showDragItem="boolean"!=typeof this._options.dragItem||!1!==this._options.dragItem,this._hovItem=null,this._sortLists=[],this._click={},this._dragging=!1,this._dragHandleClass=this._options.dragHandleClass||"",this._parentident=this._options.parentident||"",this._swapdone="function"==typeof this._options.swapdone?this._options._swapdone:null,this._container.setAttribute("data-is-sortable",1),this._container.classList.add("sortable"),this._container.style.position="static",window.addEventListener("mousedown",this._onPress.bind(this),!0),window.addEventListener("touchstart",this._onPress.bind(this),!0),window.addEventListener("mouseup",this._onRelease.bind(this),!0),window.addEventListener("touchend",this._onRelease.bind(this),!0),window.addEventListener("mousemove",this._onMove.bind(this),!0),window.addEventListener("touchmove",this._onMove.bind(this),!0))}).prototype={constructor:i,toArray:function(t){t=t||"id";for(var e=[],n="",i=0;ii.left&&ei.top&&n-1)&&e.className.indexOf("nosort")<0)&&(t.preventDefault(),this._dragging=!0,this._click=n(t),this._makeDragItem(e),this._onMove(t),!0)}t&&!1===e.call(this,t.target)&&""!==this._parentident&&t.target.closest(this._parentident)&&e.call(this,t.target.closest(this._parentident))},_onRelease:function(t){this._dragging=!1,this._trashDragItem()},_onMove:function(t){if(this._dragItem&&this._dragging){t.preventDefault();var e=n(t),i=this._container;!0===this._showDragItem&&this._moveItem(this._dragItem,e.x-this._click.x,e.y-this._click.y);for(var o=0;o0?a.mousedown(c).addClass("dctrl"):s.mousedown(c).addClass("dctrl"),t(this)}}(jQuery),$(document).ready((function(){$("html").click((function(t){$nuf()})),$("#listframe").click((function(t){t.stopPropagation(),$nuf()})),$("#mainmenu").ocmsmenu($ocms.ocmsmenu),$("#mainmenu").activatemenu()})),$.extend($t,{m_inv:"Rechnungen",m_req:"Aufträge",m_rep:"Berichte",m_todo:"ToDos",m_bcd:"BankBuchungen",rsp:"Passwort ändern",pnm:"Die Passwörter stimmen nicht überein",cps:"Das neue Passwort wurde gespeichert.",pwr:"Bitte wählen Sie ein starkes Passwort (min 8 Zeichen, davon jeweils min 2 Zahlen, kleine und große Buchstaben, Sonderzeichen sind optional).",smsc:"Sie beötigen für diese Funktion einen SMS-Code.\nSoll dieser nun versandt werden?",wdc:"Doppelt klicken, um die Box zu aktualisieren.",wdg:{}}),$t.rspf={sms:"Der SMS-Code konnte nicht bestätigt werden",valid:"Das alte Passwort ist nicht korrekt",requirements:"Das Passwort entspricht nicht den Anforderungen.\n"+$t.pwr},$fd={rsp:new fields_definition("","",[{name:"opw",label:"aktuelles Passwort",type:"password",required:!0,attr:{"auto-complete":"current-password"}},{name:"npw",label:"neues Passwort",type:"password",required:!0,pattern:"(.{6,})",attr:{"auto-complete":"new-password"}},{name:"npwc",label:"neues Passwort (Bestätigung)",type:"password",required:!0,attr:{"auto-complete":"new-password"},note:$t.pwr},{name:"code",label:"SMS-Code",type:"string",required:!0,attr:{"auto-complete":"one-time-code"}}])},$ocms.init=function(t){var e="string"==typeof t?t:(t.data||{}).fn||"";""!==e&&("home"===e?($cfr(),$lfr(),$("#topbar").ocmsmenu([],!0),$("#activemodule").text($t.ov),$fis.ov()):($cfr(),$lfr(),$("#topbar").ocmsmenu([]),$ocms.postXT({url:$ocms.url(e+"/auth"),success:function(t){void 0===$ocms[e]&&($ocms[e]={}),$ocms[e].auth=t,t.manage>0&&$ocms.getScript({module:e,script:["/web/fis",e,$ocms.auth.locale||"de","js"].join("."),css:["/web/fis",e,"css"].join("."),condition:"function"!=typeof $ocms[e].init2},(function(){$ocms[e].init2()}))},error:function(){$("#contentframe").empty()}})))};var $fis={auth:{},db:function(){$("#mainmenu_activemodule").text($t.ov);let t=$(this).empty(),e=$$.d({id:"dashboard_frame"}).appendTo(t);$ocms.postXT({url:$ocms.url("wdg/my"),success:function(t){$.each(t,(function(t,n){var i=$$.dc("wdg_frame",e,{"data-wdg":n}).ldng(1);$ocms.wdg.call(i,{wdg:n})}))},loading:e})},ValidateEmail:function(t){return!!/^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/.test(t)},cf:t=>{let e=$("#contentframe");return!0===bool(t,!1)&&e.empty().rC("hd"),e},lf:t=>{let e=$("#listframe");return!0===bool(t,!1)&&e.empty().aC("hd").rC("fix"),e},frm_edit:function(t){let e=$fis.cf(!1),n=e.children(".cfrm"),i=e.children(".edit_frm");return n.length<1?n=$$.dc("cfrm hd").prependTo(e):!0===bool(t,!1)&&n.empty(),i.length<1&&(i=$$.dc("edit_frm").insertAfter(n)),i.empty()},frm_list:function(t,e){let n=$fis.cf(!1),i=n.children(".cfrm"),o=n.children(".list_frm");return i.length<1?i=$$.dc("cfrm hd").prependTo(n):!0===bool(t,!1)&&i.empty(),!0===bool(e,!1)&&n.children(".edit_frm").remove(),o.length<1&&(o=$$.dc("list_frm").appendTo(n)),o.empty()},lfm:()=>{let t=$fis.lf(!1),e=t.children(".lfrm");return e.length<1&&(e=$$.dc("lfrm").prependTo(t)),e},getAuth:(t,e)=>new Promise(((n,i)=>{$fis.auth[t]&&!1===bool(e,!1)?n($fis.auth[t]||-1):$ocms.postXT({url:$ocms.url("auth"),data:{module:t},success:e=>{$fis.auth[t]=e.auth||-1,n($fis.auth[t]||-1)},error:()=>{i()}})})),prepAuth:t=>new Promise(((e,n)=>{$ocms.postXT({url:$ocms.url("auth"),data:{module:t,array:1},success:t=>{$.extend($fis.auth,t||{})},complete:()=>{e()}})})),isAuth:(t,e)=>($fis.auth[t]||-1)>=(e||1),resetPass:function(t,e){confirm($t.smsc)&&($ocms.postXT({url:$ocms.url("account/sms"),data:{fn:"pwc"}}),$ocms.dlgform($fd.rsp.clone(),{title:$t.rsp||"",submit:function(t){var e=$(this).ldng(1),n=$.extend({loginaccount:$ocms.auth.account||""},e.serializeObject(!0,{typedvalues:!0}));(n.npw||"")!==(n.npwc||"")?e.find('input[name="npw"]:first')[0].setCustomValidity($t.pnm):$ocms.postXT({url:$ocms.url("account/changepassword"),data:n,success:function(t){alert($t.cps),e.trigger("modal_close")},error:function(t){alert($t.rspf[t.getResponseHeader("x-ocms-std")])},complete:function(){e.ldng(0)},timeout:6e4})}}))},wdg:function(t){let e=$(this).empty();$ocms.postXT({url:$ocms.url("wdg/one"),data:{short_name:t.wdg},timeout:9e4,success:function(n,i,o){let r=t.wdg,s=n[r];if(!s)return void e.ldng(0);let a=$.inArrayRegEx("dblwidth",s.rendering_options)>-1,c=$.inArrayRegEx("tiny",s.rendering_options)>-1;e.toggleClass("dbl",a&&!c).toggleClass("tny",c);$$.dc("wdg_hd",e,{title:ne(s.description,$t.wdc)}).toggleClass("dbl",a).text(ne(s.name,t.wdg)).dblclick((function(t){t.stopPropagation(),$fis.wdg.call(e,{wdg:r})}));let l=$$.dc("wdg_cnt",e).toggleClass("dbl",a).hide(),u=$.inArrayRegEx("bgcolor",s.rendering_options);switch(u>-1&&l.css("backgroundColor",s.rendering_options[u].toString().right(":")),s.type){case"table":var d=$$.tblset({},l),h=$$.tr().appendTo(d.hd),p=$t.wdg[r.indexOf("wdg_ev_")>=0?"wdg_ev_":r]||{};$.each(s.columns,(function(t,e){var n=p[e]?p[e].label:e;$$.th().text(n).appendTo(h)})),$.each(s.data,(function(t,e){var n=$$.tr().appendTo(d.bdy);$.each(s.columns,(function(t,i){var o=$$.td().appendTo(n);e[i]instanceof Date||!0===$ocms.isJSONDateString(e[i])?o.text(fdt(e[i],$t.dateformat)):o.rwText(e[i])}))})),$.inArray("firstrow_bold",s.rendering_options)>-1&&h.nextAll("tr:first").css("font-weight","bold");break;case"ind":$$.dc("ind",l).addClass("sts_"+(s.data.status||"")).append([$$.dc("ind").text(s.data.value),$$.lbl(s.data.label)]);break;case"image_url":l.css("background","url('"+s.url+"') no-repeat center center transparent");break;case"image_base64":l.css("background","url('data:image/png;base64,"+s.image+"') no-repeat center center transparent");break;case"html":if(l.html(s.html),$.inArray("reload_10min",s.rendering_options)>-1){var f=l.find("iframe");setTimeout((function(){f.attr("src",(function(t,e){return e}))}),6e5)}}$.inArray("reload_30min",s.rendering_options)>-1&&"html"!==s.type&&setTimeout((function(){$fis.wdg.call(e,{wdg:r})}),18e5),l.slideDown(150)},error:function(t){e.slideUp(150),$fis.failure.call(this,t)},complete:function(){e.ldng(0)}})},ov:function(){$fis.lf(!0);let t=$("#contentframe").empty(),e=$$.d({id:"dashboard_frame"}).appendTo(t);$ocms.postXT({url:$ocms.url("wdg/my"),success:function(t){$.each(t,(function(t,n){var i=$$.dc("wdg_frame",e,{"data-wdg":n}).ldng(1);$fis.wdg.call(i,{wdg:n})}))},loading:e})}};$fis.notifications={connection:null,init:function(){"undefined"!=typeof signalR&&null===this.connection&&$ocms.auth.useraccount_id&&(this.ensureFrame(),this.connection=(new signalR.HubConnectionBuilder).withUrl("/notifications").withAutomaticReconnect().build(),this.connection.on("notification",(t=>{this.push(t)})),this.connection.onclose((()=>{console.warn("Notification connection closed; retrying in 5s."),this.connection=null,setTimeout((()=>this.init()),5e3)})),this.start())},start:function(){this.connection.start().catch((t=>{console.warn("Notification connection failed to start; retrying in 5s.",t),this.connection=null,setTimeout((()=>this.init()),5e3)}))},ensureFrame:function(){$("#notification_frame").length<1&&$("
          ",{id:"notification_frame"}).appendTo($("footer:first").length?"footer:first":"body")},push:function(t){this.ensureFrame(),t=t||{};let e=$("
          ",{class:"notification_item"}).addClass((t.severity||"info").toLowerCase()).append($("
          '),n=e.find(".form-body"),i=null;e.find("form").submit((function(t){t.preventDefault();var o=$(this).serializeObject(!0),r=null===i,s=r?"spwc":"spw";return $ocms.postXT.call(this,{url:$ocms.url(s),data:o,complete:function(){r?(n.append('
          Ihnen wurde ein Code per SMS zugesandt.
          Bitte tragen Sie den hier ein:
          '),i=$('
          ').appendTo(n)):(alert($t.l13),e.remove())},error:()=>{}}),!1})),e.find(".modal-close").click((function(){e.remove()}));var o=[];$.each($t.l7a.split("\n"),((t,e)=>{Array.prototype.push.apply(o,[$("
          "),$("").text(e)])})),e.find(".modal-note").append($('').text($t.alert)).append(o),e.appendTo("body"),setTimeout((function(){$(".modal").find('input[name="lastname"]').focus()}),600)}};var $$={s:function(t){return $("").text(t)},br:function(){return $("
          ")},sc:function(t,e){return $("").addClass(t).text(e)},td:function(t,e){var n=$("");return t instanceof jQuery==!0?n.appendTo(t):"object"==typeof t?n.attr(t):"string"==typeof t&&n.text(t),"object"==typeof e?n.attr(e):"string"==typeof e&&n.text(e),n},th:function(t,e){var n=$("");return t instanceof jQuery==!0?n.appendTo(t):"object"==typeof t?n.attr(t):"string"==typeof t&&n.text(t),"object"==typeof e?n.attr(e):"string"==typeof e&&n.text(e),n},tdc:function(t,e,n){return $$.td(e,n).addClass(t)},td2:function(t){var e=$('');return"string"===$.type(t)?e.text(t):t instanceof jQuery?e.append(t):"function"==typeof t?t.call(e):e.html(" "),e},td3:function(t){var e=$('');return"string"===$.type(t)?e.text(t):t instanceof jQuery?e.append(t):"function"==typeof t?t.call(e):e.html(" "),e},tdtr:function(t,e){var n=$$.tr().appendTo(e);return t instanceof jQuery==!0||"string"==typeof t?t.appendTo($$.td().appendTo(n)):!0===Array.isArray(t)&&$.each(t,(function(t,e){$(e).appendTo($$.td().appendTo(n))})),n},tr:function(t,e){var n=$("");return t instanceof jQuery==!0?n.appendTo(t):"object"==typeof t&&n.attr(t),"object"==typeof e&&n.attr(e),n},trc:function(t,e){var n=$("").addClass(t);return e instanceof jQuery==!0?n.appendTo(e):"object"==typeof e&&n.attr(e),n},d:function(t){return $("
          ").attr(t||{})},dc:function(t,e,n,i){var o=$("
          ").addClass(t);return e instanceof jQuery==!0?o.appendTo(e):"object"==typeof e?o.attr(e):"function"==typeof e?o.click(e):"string"==typeof e&&o.text(e),"string"==typeof n?o.text(n):"object"==typeof n?o.attr(n):"function"==typeof n&&o.click(n),"string"==typeof i?o.text(i):"object"==typeof i?o.attr(i):"function"==typeof i&&o.click(i),o},df:function(t){return $("
           
          ").attr(t||{})},opt:function(t,e,n){var i=$("");return"string"==typeof t?i.attr("value",t):"object"==typeof t&&i.attr(t),"string"==typeof e?i.text(e):"object"==typeof e&&i.attr(e),"object"==typeof n&&i.attr(n),i},eOpt:function(t){var e=$('');return t&&e.attr("selected","selected"),e},tbl:function(t){return $("
          ").attr(t||{})},tblc:function(t){return $("
          ").addClass(t)},thead:function(t){let e=$("");return t instanceof jQuery&&e.prependTo(t),e},tbody:function(t){let e=$("");return t instanceof jQuery&&e.appendTo(t),e},tblset:function(t,e){let n=$$.tbl(t||{});return e instanceof jQuery&&e.append(n),{tbl:n,hd:$$.thead().appendTo(n),bdy:$$.tbody().appendTo(n)}},i:function(t){return $("").attr(t||{})},img:function(t,e){return $("").attr("src",t).attr(e||{})},sel:function(t){return $("").attr(t||{})},btn:function(t){return $("").attr(t||{})},a:function(t){return $("").attr(t||{})},li:function(t){return $("
        • ").attr(t||{})},ul:function(t){return $("
            ").attr(t||{})},nav:function(t){return $("").attr(t||{})},lbl:function(t,e){var n=$("");return"string"==typeof t&&n.text(t),"object"==typeof t?n.attr(t):"object"==typeof e&&n.attr(e),n},txt:function(t){return $("").attr(t||{})},0:function(t,e){return $("<"+t+">").attr(e||{})},bbtn:function(t,e){return $$.btn({type:"button",class:"btn"}).addClass(e).text(t)},svg:t=>$(document.createElementNS("http://www.w3.org/2000/svg",t))};function getMonday(t){var e=(t=new Date(t)).getDay(),n=t.getDate()-e+(0==e?-6:1);return new Date(t.setDate(n))}function $lf(t){var e=void 0===t?null:"number"==typeof t&&1!==t||"boolean"==typeof cl&&!1===t;return $("#listframe").tC("hd",e).is(".hd")}function $nuf(t){if(t&&t.stopPropagation(),!$(this).is(".disabled")){var e=function(t){t.removeClass("vis").find("li.dropdown").removeClass("open").removeClass("vis").attr("aria-expanded","false")},n=$(this).parent("li.dropdown");if(n.length>0){n.tC("open"),navs=!0===n.is(".open")?"true":"false",n.attr("aria-expanded",navs);var i=n.closest("nav");i.find("li.dropdown").not(n.parentsUntil("nav")).not(n).removeClass("open").attr("aria-expanded","false"),!1===n.is(".open")&&n.find("li.dropdown").removeClass("open").attr("aria-expanded","false"),e($("nav").not(i))}else e($("nav"))}}function $tbr(){return $lf(0),$("#topbar").ocmsmenu([])}function $lfr(){return $("#sidebar").empty(),$("#listframe").removeClass("fix").addClass("hd").empty()}function $cfr(){return $tbr(),$("#contentframe").empty()}function jObj(t,e){let n={};if("{"===(t||"").substr(0,1))try{n=JSON.parse(t)}catch(t){n={}}return n[e]||""}function string(t,e){var n,i=t||"";return $.each(e||[],(function(t,e){n=new RegExp("\\{"+t.toString()+"\\}","ig"),i=i.replace(n,e)})),i}function init_tooltip(t){var e=!0===("boolean"==typeof t&&t)&&"mouse";$("[title]").qtip({position:{target:e,adjust:{x:2,y:2},viewport:!0},events:{hidden:$ocms.tooltip_hidden},effect:!1}),$("div.tooltiptext").each((function(){$(this).filter(":not(:empty)").parent().qtip({suppress:!1,content:{text:$(this).clone()},position:{target:e,adjust:{x:2,y:2},viewport:!0},events:{hidden:$ocms.tooltip_hidden}})}))}String.prototype.trim=function(){return this.replace(/^\s+|\s+$/g,"")},String.prototype.left=function(t){if("string"===$.type(t)){var e=this.indexOf(t);return e>0?this.slice(0,e):""}return this.substring(0,t)},String.prototype.right=function(t){if("string"===$.type(t)){var e=this.indexOf(t);return e>0?this.substring(this.length-e):""}return this.substring(this.length-t)},Array.prototype.move=function(t,e){if(e>=this.length)for(var n=e-this.length;1+n--;)this.push(void 0);return this.splice(e,0,this.splice(t,1)[0]),this},function(t){t.fn.appendToIf=function(e,n){var i=t(this),o="function"==typeof n?n(i):n;return!0===("boolean"!=typeof o||o)&&i.appendTo(e),i},t.fn.appendIf=function(e,n){var i=t(this),o="function"==typeof n?n(i):n;return!0===("boolean"!=typeof o||o)&&i.append(e),i},t.fn.rwText=function(e,n,i){var o=t(this).empty();i=t.extend({wrap:!0},i);var r=!0===Array.isArray(e)?e:(null==e?"":String(e)).split("\n");return t.each(r,(function(t,e){""!==(e||"")&&(t>0&&o.append($$.br()),o.append(!0===i.wrap?$$.s(e):e))})),n&&o.attr("title",n),o},t.fn.loadSel=function(e,n,i){if("SELECT"===t(this).prop("tagName").toUpperCase()){var o=t(this);$ocms.postXT.call(this,{url:e,data:n||{},success:function(e){t.each(e,(function(){o.append($$.opt(e.value,e.text))}))},complete:function(){o.ldng(0),"function"==typeof i&&i.call(o)}})}},t.fn.emptyWithEditors=function(e){var n=t(this);return n.find(":input.tinymce").each((function(e,n){try{var i=tinymce.get(t(n).attr("id"));i&&i.remove()}catch(e){t.noop()}})),n.empty()},t.fn.cssValue=function(t){if(this.length>0){var e=this.css(t)||"";if(""===e)return 0;var n=/(^[\d\.]*)(\D{1,3}$)/gi.exec(e);return null!==n?"rem"===n[2]?$ocms.rpx(parseFloat(n[1])):parseFloat(n[1]):!1===isNaN(e)?parseFloat(e):0}return 0},t.fn.veryInnerHeight=function(){let e=e=>t(this).cssValue(e);return t(this).innerHeight()-e("padding-top")-e("padding-bottom")},t.fn.veryInnerWidth=function(){let e=e=>t(this).cssValue(e);return t(this).innerWidth()-e("padding-left")-e("padding-right")},t.fn.marginWidth=function(){let e=e=>t(this).cssValue(e);return e("margin-left")+e("margin-right")},t.fn.marginHeight=function(){let e=e=>t(this).cssValue(e);return e("margin-top")+e("margin-bottom")},t.inArrayRegEx=function(e,n,i){var o="regexp"===t.type(e)?e:new RegExp(e);if(!n)return-1;for(var r=i=i||0;r7){i=e.split(","),o=(n||(t<0?"rgb(0,0,0)":"rgb(255,255,255)")).split(",");var c=a(i[0].slice(4)),l=a(i[1]),u=a(i[2]);return"rgb("+(s((a(o[0].slice(4))-c)*r)+c)+","+(s((a(o[1])-l)*r)+l)+","+(s((a(o[2])-u)*r)+u)+")"}var d=(i=a(e.slice(1),16))>>16,h=i>>8&255,p=255&i;return"#"+(16777216+65536*(s((((o=a((n||(t<0?"#000000":"#FFFFFF")).slice(1),16))>>16)-d)*r)+d)+256*(s(((o>>8&255)-h)*r)+h)+(s(((255&o)-p)*r)+p)).toString(16).slice(1)},t.fn.IN=function(e){return t(this).fadeIn(400,e),t(this)},t.fn.OUT=function(e){return t(this).fadeOut(400,e),t(this)},t.fn.tooltip=function(e,n){var i=!0===("boolean"==typeof e&&e)&&"mouse",o="boolean"==typeof n&&n,r=t(this);return r.each((function(){var e=o?t(this).find(".tooltiptext"):t(this).children(".tooltiptext");t(e).length>0?e.each((function(){var e=t(this);t(this).filter(":not(:empty)").parent().qtip({suppress:!1,content:{text:e.clone()},position:{target:i,adjust:{x:2,y:2},viewport:!0},events:{hidden:$ocms.tooltip_hidden},show:{effect:!1},hide:{effect:!1}}),e.remove()})):t(this).qtip({position:{target:i,adjust:{x:2,y:2},viewport:!0},events:{hidden:$ocms.tooltip_hidden},effect:!1})})),r},t.fn.rC=function(e){return t(this).removeClass(e)},t.fn.aC=function(e){return t(this).addClass(e)},t.fn.tC=function(e,n){return t(this).toggleClass(e,n)}}(jQuery),function(t){t.fn.ocmsmenu=function(e,n){var i=t(this);return $ocms.menu.call(i,e,n),i},t.fn.activatemenu=function(){var e=t(this).filter("nav");return e.find("a").not(".on").addClass("on").click($nuf),e.find(".nav-btn").not(".on").addClass("on").click((function(e){e.stopPropagation();var n=t(this);t(n.attr("data-target")).tC(n.attr("data-toggle"))})),e}}(jQuery);class ObjectArray extends Array{isEmpty(){return 0===this[0].length}static get[Symbol.species](){return Array}filter(t){return"function"==typeof t?new ObjectArray(this[0].filter(t)):this}remove(t){if("function"!=typeof t)return this;{let e=this[0].findIndex(t);for(;e>-1;)this[0].splice(e),e=this[0].findIndex(t)}}sortBy(t){return"function"==typeof t&&this[0].sort(t),this}sortString(t){return this[0].sort(((e,n)=>{let i=(e[t]||"").toString().toUpperCase(),o=(n[t]||"").toString().toUpperCase();return console.debug(i.localeCompare(o)),i.localeCompare(o)})),this}sortNum(t){return this[0].sort(((e,n)=>{let i=e[t],o=n[t];return!0===isNaN(o)&&!1===isNaN(i)||io?1:0})),this}sum(t){return this[0].reduce(((e,n)=>e+(!0===isNaN(n[t])?0:n[t])),0)}groupBy(t){return this[0].reduce((function(e,n){let i=n[t];return e[i]||(e[i]=[]),e[i].push(n),e}),{})}each(t){if("function"==typeof t){let e=!1;this[0].forEach(((n,i,o)=>{if(!1===e){let r=t(n,i,o);"boolean"==typeof r&&!1===r&&(e=!0)}}))}}get toArray(){return this[0]}}class NumArray extends Array{sum(){return this.reduce(((t,e)=>t+e))}first(){return this[0]}last(){return this[this.length-1]}average(){return this.sum()/this.length}range(){let t=this.map((t=>t)).sort();return{min:t[0],max:t[this.length-1]}}static get[Symbol.species](){return Array}}$ocms.ocmsmenu=[{lbl:"",id:"m_home",ico:"glyphicon glyphicon-home",fnc:"init:home"},{fnc:"separator"}],function(t){t.multline=function(t){let e=t.split("\n"),n=$$.d();return $.each(e,((t,e)=>{n.append($$.s(e))})),n.html()},t.tooltip_hidden=function(t,e){$(this).remove(),e.rendered=!1},t.isJSONDateString=function(t){return"string"==typeof t&&/^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})?)?$/.test(t)},t.failure=function(e){11110===(e.internalCode||-1)?t.login.dlg():alert($t.f1+"\n"+(e.internalText||""))},t.getScript=function(e,n){var i=[],o=[],r=function(t){return"string"==typeof t&&""!==(t||"")},s=function(t,e){!0===bool(e.condition,!0)&&(""!==(e.script||"")&&o.push({url:e.script,module:e.module||""}),!0===r(e.css||"")?i.push(e.css):!0===Array.isArray(e.css)&&Array.prototype.push.apply(i,e.css.filter(r)))};!0===r(e||"")?o.push(e):!0===Array.isArray(e)?$.each(e,s):"object"==typeof e&&""!==(e.script||"")&&s(0,e);let a=[];$.each(i,(function(t,e){""!==(e||"")&&a.push(loadCSS(e))}));let c=o.map((function(e,n){let i=e.url,r=e.module||"";if(""===r){let t=new Promise((function(t,e){try{!async function(){$.ajax({url:i,dataType:"script",success:function(){t(o)},error:function(){e(o)},timeout:3e4})}()}catch(t){console.debug(t.message+"%o",t)}}));return t}return t.loadmodule(r,i,e.alias)}));Promise.all(c).then(n)},t.loadmodule=function(e,n,i){let o=new Promise((function(o,r){!async function(){try{let s=(n.startsWith("/")||n.startsWith(".")?"":"/")+n;import(s).then((n=>{t[e]=n[i||"default"],o(e)})).catch((t=>{console.debug(t.message+"%o",t),r(e)}))}catch(t){console.debug(t.message+"%o",t)}}()}));return o},t.ocms_auth=function(e,n,i,o){!1===$.isPlainObject(t.auth.modules)&&(t.auth.modules={});var r=0;t.auth.modules[e+(i||"")]?((r=t.auth.modules[e+(i||"")])<2&&(i||"")===auth.guid&&(r=2),r>=(n||0)&&o(false)):t.postXT({url:t.url("auth"),data:{module:e,person_guid:i||""},success:function(s){r=s[e],t.auth.modules[e+(i||"")]=r,r<2&&(i||"")===t.auth.person_guid&&(r=2),r>=(n||0)&&o(false)},error:function(e){t.failure.call(this,e)}})},t.auth.locale="de",t.ocms_prepauth=function(e,n,i){t.postXT({url:t.url("auth"),data:{fn:"csv",modules:e,person_guid:n||""},success:function(e){t.ocms_regauth(e)},error:function(e){t.failure.call(this,e)},complete:function(){i()}})},t.ocms_regauth=function(t){$.each(t||{},(function(t,e){auth.modules[t]=parseInt(e)}))},t.init=function(e){var n="string"==typeof e?e:(e.data||{}).fn||"";""!==n&&("home"===n?($cfr(),$lfr(),$("#topbar").ocmsmenu([],!0),$("#activemodule").text($t.ov),t.ov.call($("#contentframe"))):($cfr(),$lfr(),$("#topbar").ocmsmenu([]),t.postXT({url:t.url(n+"/auth"),success:function(e){void 0===t[n]&&(t[n]={}),t[n].auth=e,e.manage>0&&t.getScript({module:n,script:["web/imdl",n,t.auth.locale||"de","js"].join("."),css:["web/imdl",n,"css"].join("."),condition:"function"!=typeof t[n].init2},(function(){t[n].init2()}))},error:function(){$("#contentframe").empty()}})))},t.menuarray=function(t){this.array=[],this.sep=function(){this.length>0&&"separator"!==this.array[array.length-1].fnc&&this.push({fnc:"separator"})},this.push=function(t){return void 0===t?null:(!0===Array.isArray(t)?Array.prototype.push.apply(this.array,t):"object"==typeof t&&this.array.push(t),t)},this.unshift=function(t){return void 0===t?null:(!0===Array.isArray(t)?Array.prototype.unshift.apply(this.array,t):"object"==typeof t&&this.array.unshift(t),t)},this.push(t)},t.menu=function(e,n){e=e||[];var i=$(this).removeClass("vis");if(!0===bool(n,!0)&&!1===i.is("#mainmenu")&&i.empty(),!1===bool(n,!1)&&i.is("#sidebar,#topbar")&&(e.unshift({id:"sbctrl",glyph:"glyphicon-th-list",aclass:"fbtn",fnc:function(){$lf()}}),$lf(0)),0===(e||[]).length)i.empty().addClass("hd");else{i.removeClass("hd");var o=!0===i.is("nav")?i:i.children("nav");1!==o.length&&(o=$("").tC("nv",i.is("#sidebar")).tC("ctxt",i.is("#topbar")).appendTo(i));var r,s=$$.ul().appendTo(o),a=function(t,e){var n=$(this).addClass("dropdown submenu");t.append($$.sc("caret dd")).addClass("dds dropdown-toggle").attr({"aria-expanded":"false"}),""!==(e.ico||"")&&t.prepend($$.sc("ico "+e.ico));var i=$$.ul({class:"dropdown-menu",role:"menu"}).appendTo(n);$.each(e.itm||[],(function(t,e){r.call(i,e)}))},c=function(t){$(this).tC("disabled","boolean"==typeof t.disabled?t.disabled:"string"==typeof t.disabled&&"subs"===t.disabled&&0===(t.itm||[]).length)};r=function(e){var n,i=$$.li({id:e.id}).attr(e.attr||{}).addClass(e.lclass).appendTo($(this)),o="string"==typeof e.fnc&&""!==e.fnc?e.fnc.split(":")[0]:"";""!==o&&"init"!==o?i.attr("role",o).appendIf($$.s(e.lbl),""!==ne(e.lbl)):(n=$$.a({class:"on",role:"button"}).addClass(e.aclass).appendTo(i).append($$.s(e.lbl)),c.call(n,e),(e.itm||[]).length>0&&a.call(i,n,e),n.click($nuf),"function"==typeof e.fnc?n.click(e.data||{},e.fnc):"init"===o&&n.click($.extend({},e.data||{},{fn:e.fnc.split(":")[1]}),t.init))},$.each(e,(function(e,n){var i,o=$$.li({id:n.id}).attr(n.attr||{}).addClass(n.lclass),a="string"==typeof n.fnc&&""!==n.fnc?n.fnc.split(":")[0]:"";if(""!==a&&"init"!==a)o.attr("role",a).appendIf($$.s(n.lbl),""!==ne(n.lbl));else{if(i=$$.a({class:"on",role:"button"}).addClass(n.aclass).appendTo(o),c.call(i,n),""!==(n.lbl||"")&&i.append($$.s(n.lbl)),""!==(n.ico||"")&&i.prepend($$.sc("ico "+n.ico)),""!==(n.glyph||"")&&i.prepend($$.sc("glyphicon "+n.glyph)),(n.itm||[]).length>0){o.addClass("dropdown"),i.append($$.sc("caret dd")).addClass("dds dropdown-toggle").attr({"aria-expanded":"false"});var l=$$.ul({class:"dropdown-menu",role:"menu"}).appendTo(o);$.each(n.itm||[],(function(t,e){r.call(l,e)}))}(n.sel||[]).length>0||(i.click($nuf),"function"==typeof n.fnc?i.click(n.data||{},n.fnc):"init"===a&&i.click($.extend({},n.data||{},{fn:n.fnc.split(":")[1]}),t.init))}o.appendTo(s)})),o.activatemenu()}},t.easytbl=(t,e)=>{e=e||{};let n=$$.tbl().addClass(e.class).css("border-collapse","collapse"),i=($$.tbody(n),!0===bool(e.frame,!1)?{padding:"5px",border:"1px solid #727272"}:{});if(!0===Array.isArray(e.header)){let t=$$.thead(n);$.each(e.header,((n,o)=>$$.th(t).css(e.cellcss||i).rwText(o)))}else if(!0===bool(e.header,!1)&&(t||[]).length>0){let o=$$.thead(n);$.each(Object.keys(t[0]),((t,n)=>$$.th(o).css(e.cellcss||i).rwText(n)))}return $.each(t||[],((t,o)=>{let r=$$.tr();$.each(o,((t,n)=>{n=n||"";let o=$$.td(r).css(e.cellcss||i);n instanceof jQuery?o.append(n):"string"==typeof n&&("<"===n.substring(0,1)?o.append(n):o.text(n))})),n.append(r)})),n},t.dlgtbl=(e,n,i)=>{i=i||{};let o=t.easytbl(e,i);t.dlg(o,$.extend({title:n},i))},t.dlg=function(t,n){n=n||{};let i=$("body > .modal").length>0,o=t=>typeof n[t],r=t=>"function"===o(t);if(!0===bool(n.exclusive,!0)&&!0===i)return void alert($t.dbldlg||"Es ist bereits ein Dialog geöffnet");let s=$$.dc("modal",$("body")),a=$$.dc("modal-dialog",s);!1===isNaN(n.zindex)?s.css("zIndex",n.zindex):!0===i&&s.css("zIndex",parseInt($("body > .modal:last").cssValue("zIndex"))+200),!1===isNaN(n.zindex_min)&&s.cssValue("zIndex")').appendTo(d)),""!==ne(n.title)&&(c=$$.dc("modal-header",d),$("

            ").text(n.title).appendTo(c));let p=$$.dc("modal-body",d),f=$$.dc("modal-footer",d);t instanceof jQuery==!0&&p.append(t);let m=function(t){t&&"function"==typeof t.stopPropagation&&t.stopPropagation(),a.removeClass("in"),!0===r("closing")&&n.closing.call(d),p.hide().emptyWithEditors(),s.remove(),!0===r("close")&&n.close.call(d)};if(d.find(":input[required]").length>0&&($$.dc("note_required",f).append($$.sc("ind_required","*")).append($$.s($t.t1||"Eingabe erforderlich")),$$.dc("note_invalid",f).append($$.s($t.t2||"Bitte überprüfen Sie Ihre Eingaben im Formular."))),!0===r("cancel")){$$.bbtn(n.cancelbutton||"Abbrechen","cancel").attr({type:"button",role:"cancel"}).appendTo(f).click((function(t){n.cancel.call(d,t);t.stopPropagation(),m()}))}if(!0===r("confirm")){let t=$$.bbtn(n.button||"OK","confirm").attr({type:!0===bool(n.form,!1)?"submit":"button",role:"confirm"}).appendTo(f);!0===h?(d.submit((function(t){try{n.confirm.call(d,t)}finally{t.preventDefault()}return!1})),d.on("modal_submit",(function(){n.confirm.call(d,e)}))):(t.click((function(t){n.confirm.call(d,t);t.stopPropagation()})),d.on("modal_submit",(function(){t.click()})))}else!0===h&&d.submit((function(t){return t.preventDefault(),!1}));return d.on("modal_close",(function(){m()})),l.click(m),!0===r("opening")&&n.opening.call(d),a.addClass("in"),ne(n.mode).indexOf("maxbody")>-1&&p.css("min-height",(u.height()-c.outerHeight()-f.outerHeight()).toString()+"px"),!0===r("open")&&n.open.call(d),{hd:c,bdy:p,ft:f,ct:u,dlg:a,c:d}},t.mform=function(e){let n=$$.dc("form-body"),i=Array.isArray(e)?e:e instanceof fields_definition?e.fields:[];return $.each(i||[],(function(e,i){let o=i.type||"";if("ignore"===o)return!0;let r=$$.dc("form-group",n),s=i.id||"dlg_"+(i.name||"")+("html"===i.type?"_"+(65536*(1+Math.random())||0).toString(16).substr(9):""),a=$$.lbl(i.label||i.name,{for:s}).appendTo($$.dc("form-itm",r)),c=$$.dc("form-itm",r),l=$$.i({id:s,name:i.name,placeholder:i.placeholder,type:i.type});switch(o){case"email":i.pattern=ne(i.pattern,"[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,}$");break;case"url":i.pattern=ne(i.pattern,"https?://.+");break;case"number":i.pattern=ne(i.pattern,"[-+]?[0-9]*[.,]?[0-9]*"),l.attr("step",i.precision||"any"),l.attr("data-format","float");break;case"integer":case"int":i.pattern=ne(i.pattern,"[-+]?[0-9]*"),l.attr("type","number"),l.attr("data-format","integer");break;case"date":if(""!==ne(i.pattern,$t.datepattern)&&(i.pattern=ne(i.pattern,"("+$t.datepattern+")|([0-9]{4}.(0[1-9]|1[012]).(0[1-9]|1[0-9]|2[0-9]|3[01]))")),""!==ne(i.placeholder,$t.dateplaceholder)&&l.attr("placeholder",ne(i.placeholder,$t.dateplaceholder)),"string"==typeof i.value){var u=i.value.substr(0,10);i.value="date"!==l.prop("type")?fdt(u+"T00:00:00",ne(i.dateformat,$t.dateformat)):u}l.attr("data-format","date:"+ne(i.dateformat,$t.dateformat)+";yyyy-MM-dd");break;case"datetime":l.attr("type","datetime-local"),""!==ne(i.pattern,$t.datetimepattern)&&(i.pattern=ne(i.pattern,"("+$t.datetimepattern+")|([0-9]{4}-(0[1-9]|1[012])-(0[1-9]|1[0-9]|2[0-9]|3[01])\\s([0-5][0-9]):([0-5][0-9]))")),""!==ne(i.placeholder,$t.datetimeplaceholder)&&l.attr("placeholder",ne(i.placeholder,$t.datetimeplaceholder)),"string"==typeof i.value&&"T"===i.value.substr(10,1)&&(i.value="datetime"!==l.prop("type").substr(0,8)?fdt(i.value,ne(i.datetimeformat,$t.datetimeformat)):i.value),l.attr("data-format","datetime:"+ne(i.datetimeformat,$t.datetimeformat)+";yyyy-MM-dd HH:mm:ss");break;case"hidden":r.addClass("hd");break;case"html":case"text":l=$$.txt({id:s,name:i.name,placeholder:i.placeholder,type:i.type}),l.tC("tinymce","html"===i.type);break;case"bool":case"boolean":i.url=[{value:"true",label:($t||{}).true||"Yes"},{value:"false",label:($t||{}).false||"No"}],"boolean"==typeof i.value&&(i.value=i.value?"true":"false");case"select":l=$$.sel({id:s,name:i.name,type:i.type}),!1===bool(i.required,!1)&&$$.eOpt().appendTo(l);try{var d=function(t){!0===Array.isArray(t)&&$.each(t,(function(t,e){"string"==typeof e?$$.opt(e,e).appendTo(l):!0===Array.isArray(e)?$$.opt(e[0],e[1]).appendTo(l):"object"==typeof e&&$$.opt(e.value,e.label||e.text).appendTo(l)}))};!0===Array.isArray(i.url)?d(i.url):"function"==typeof i.url?i.url.call(l):"string"==typeof i.url&&t.postXT({url:i.url,success:d})}catch(t){$.noop()}break;default:""!==ne(i["max-length"])&&l.attr("max-length",i["max-length"])}""!==ne(i.pattern)&&l.attr("pattern",i.pattern),l.val(i.value).change(),l.change((function(){$(this)[0].setCustomValidity("")})),l.addClass("form-control").prop("required",bool(i.required,!1)).prop("readonly",bool(i.readonly,!1)).appendTo(c),!0===bool(i.required,!1)&&a.append($$.sc("ind_required","*")),"object"==typeof i.attr&&l.attr(i.attr),"object"==typeof i.prop&&l.prop(i.prop),"string"==typeof i.class&&l.addClass(i.class),"function"==typeof i.change&&(l.change(i.change),!0===bool(i.applychange,!1)&&void 0!==i.value&&l.change()),""!==(i.note||"")&&$$.dc("form-note",c).rwText(i.note),"function"==typeof i.complete&&i.complete.call(l)})),n},t.initMCE=function(t,e){t=$(t),e=e||{};try{let n={target:t[0],inline:!1,width:e.width||"100%",statusbar:!1,document_base_url:window.location.origin+"/",content_style:"ph:before {content: '«'; color: #BBB; font-style:italic; } ph:after {content: '»'; color: #BBB; font-style:italic; } ph { color: #AAA; font-style:italic; }",relative_urls:!1,remove_script_host:!1};!0===bool(e.hidemenu,!1)&&(n.menubar=!1,n.menu={}),!0===bool(e.hidetoolbar,!1)&&(n.toolbar=!1),$.extend(n,e||{}),tinymce.init(n)}catch(t){alert(t.message)}},t.dlgform=function(e,n){n=n||{};let i,o=$$.dc("frm").append(t.mform(e||[]).addClass("stacked"));n.addcontent instanceof jQuery&&o.append(n.addcontent),"function"==typeof n.submit?i=n.submit:"function"==typeof n.success&&(i=function(e){var i=$(this).ldng(1),o=$.extend({loginaccount:t.auth.account||""},i.serializeObject(bool(n.checkvalidity,!0),{typedvalues:bool(n.typedvalues,!1)}));""!==(n.url||"")?t.postXT({url:n.url,data:o,success:function(t){n.success.call(this,t),i.trigger("modal_close")},error:function(){alert($t.l17)},complete:function(){i.ldng(0)},timeout:6e4}):(n.success.call(this,o),i.trigger("modal_close"))});let r={form:!0,title:n.title||"",button:n.button||$t.submit,confirm:i,size:n.size||[500,600],open:function(){let e=$(this).find(".tinymce");e.length>0&&t.initMCE(e,n.tinymce||{})}};return t.dlg.call(this,o,r)},t.login.dlg=function(e){e=e||{};let n=[{name:"userinfo",label:$t.l1,type:"string",value:t.auth.login,change:t.login.uichange,required:!0},{name:"userlogin",type:"hidden",required:!0,value:t.auth.login},{name:"username",type:"string",label:$t.l4,required:!0,readonly:!0,placeholder:$t.l5,value:t.auth.fullname_rev},{name:"userpass",type:"password",label:$t.l3,required:!0,placeholder:$t.l3}];""===(t.auth.account||"")&&n.unshift({id:"dlg_loginaccount",name:"loginaccount",type:"string",required:!0,value:t.auth.account});let i=$$.dc("frm").append(t.mform(n).addClass("stacked")),o=t.dlg.call(this,i,{form:!0,title:$t.l0,button:$t.submit,confirm:function(n){var i=$(this).ldng(1),o=$.extend({loginaccount:t.auth.account||""},i.serializeObject());t.postXT({url:"/vt/login",data:o,success:function(n){""!==((n||{}).login||"")&&(i.trigger("modal_close"),t.auth=n,"object"==typeof e.ajo&&(e.ajo.islogin,$.ajax(e.ajo)))},error:function(){alert($t.l17)},complete:function(){i.ldng(0)},timeout:6e4})},size:[500,600]}),r=$$.dc("modal-content").css("height","auto").attr("novalidate","true").append($$.dc("modal-header").appendIf($("

            ").text(t.auth.accountname),""!==(t.auth.accountname||"")).append($("

            Vereinsmanager

            ")));o.dlg.prepend(r)},t.addNoEntryInfo=function(t){$(this).append($$.dc("noentryinfo").text(t||$t.t11))}}($ocms),Element.prototype.matches||(Element.prototype.matches=Element.prototype.msMatchesSelector||Element.prototype.webkitMatchesSelector),Element.prototype.closest||(Element.prototype.closest=function(t){var e=this;do{if(Element.prototype.matches.call(e,t))return e;e=e.parentElement||e.parentNode}while(null!==e&&1===e.nodeType);return null}),function(t,e){var n,i;"object"==typeof window&&(window[t]=(n=function(t){var e=window,n=document.body,i=document.documentElement,o=Math.max(0,e.pageXOffset||i.scrollLeft||n.scrollLeft||0)-(i.clientLeft||0),r=Math.max(0,e.pageYOffset||i.scrollTop||n.scrollTop||0)-(i.clientTop||0);return{x:t?Math.max(0,t.pageX||t.clientX||0)-o:0,y:t?Math.max(0,t.pageY||t.clientY||0)-r:0}},(i=function(t,e){t&&t instanceof Element&&(this._container=t,this._options=e||{},this._clickItem=null,this._dragItem=null,this._showDragItem="boolean"!=typeof this._options.dragItem||!1!==this._options.dragItem,this._hovItem=null,this._sortLists=[],this._click={},this._dragging=!1,this._dragHandleClass=this._options.dragHandleClass||"",this._parentident=this._options.parentident||"",this._swapdone="function"==typeof this._options.swapdone?this._options.swapdone:null,this._onend="function"==typeof this._options.onend?this._options.onend:null,this._container.setAttribute("data-is-sortable",1),this._container.classList.add("sortable"),this._container.style.position="static",window.addEventListener("mousedown",this._onPress.bind(this),!0),window.addEventListener("touchstart",this._onPress.bind(this),!0),window.addEventListener("mouseup",this._onRelease.bind(this),!0),window.addEventListener("touchend",this._onRelease.bind(this),!0),window.addEventListener("mousemove",this._onMove.bind(this),!0),window.addEventListener("touchmove",this._onMove.bind(this),!0))}).prototype={constructor:i,toArray:function(t){t=t||"id";for(var e=[],n="",i=0;ii.left&&ei.top&&n-1)&&e.className.indexOf("nosort")<0)&&(t.preventDefault(),this._dragging=!0,this._click=n(t),this._makeDragItem(e),this._onMove(t),!0)}t&&!1===e.call(this,t.target)&&""!==this._parentident&&t.target.closest(this._parentident)&&e.call(this,t.target.closest(this._parentident))},_onRelease:function(t){var e=!0===this._dragging&&null!==this._clickItem;this._dragging=!1,this._trashDragItem(),e&&"function"==typeof this._onend&&this._onend()},_onMove:function(t){if(this._dragItem&&this._dragging){t.preventDefault();var e=n(t),i=this._container;!0===this._showDragItem&&this._moveItem(this._dragItem,e.x-this._click.x,e.y-this._click.y);for(var o=0;o0?a.mousedown(c).addClass("dctrl"):s.mousedown(c).addClass("dctrl"),t(this)}}(jQuery),$(document).ready((function(){$("html").click((function(t){$nuf()})),$("#listframe").click((function(t){t.stopPropagation(),$nuf()})),$("#mainmenu").ocmsmenu($ocms.ocmsmenu),$("#mainmenu").activatemenu()})),$.extend($t,{m_inv:"Rechnungen",m_req:"Aufträge",m_rep:"Berichte",m_todo:"ToDos",m_bcd:"BankBuchungen",rsp:"Passwort ändern",pnm:"Die Passwörter stimmen nicht überein",cps:"Das neue Passwort wurde gespeichert.",pwr:"Bitte wählen Sie ein starkes Passwort (min 8 Zeichen, davon jeweils min 2 Zahlen, kleine und große Buchstaben, Sonderzeichen sind optional).",smsc:"Sie beötigen für diese Funktion einen SMS-Code.\nSoll dieser nun versandt werden?",wdc:"Doppelt klicken, um die Box zu aktualisieren.",wdg:{}}),$t.rspf={sms:"Der SMS-Code konnte nicht bestätigt werden",valid:"Das alte Passwort ist nicht korrekt",requirements:"Das Passwort entspricht nicht den Anforderungen.\n"+$t.pwr},$fd={rsp:new fields_definition("","",[{name:"opw",label:"aktuelles Passwort",type:"password",required:!0,attr:{"auto-complete":"current-password"}},{name:"npw",label:"neues Passwort",type:"password",required:!0,pattern:"(.{6,})",attr:{"auto-complete":"new-password"}},{name:"npwc",label:"neues Passwort (Bestätigung)",type:"password",required:!0,attr:{"auto-complete":"new-password"},note:$t.pwr},{name:"code",label:"SMS-Code",type:"string",required:!0,attr:{"auto-complete":"one-time-code"}}])},$ocms.init=function(t){var e="string"==typeof t?t:(t.data||{}).fn||"";""!==e&&("home"===e?($cfr(),$lfr(),$("#topbar").ocmsmenu([],!0),$("#activemodule").text($t.ov),$fis.ov()):($cfr(),$lfr(),$("#topbar").ocmsmenu([]),$ocms.postXT({url:$ocms.url(e+"/auth"),success:function(t){void 0===$ocms[e]&&($ocms[e]={}),$ocms[e].auth=t,t.manage>0&&$ocms.getScript({module:e,script:["/web/fis",e,$ocms.auth.locale||"de","js"].join("."),css:["/web/fis",e,"css"].join("."),condition:"function"!=typeof $ocms[e].init2},(function(){$ocms[e].init2()}))},error:function(){$("#contentframe").empty()}})))};var $fis={auth:{},db:function(){$("#mainmenu_activemodule").text($t.ov);let t=$(this).empty(),e=$$.d({id:"dashboard_frame"}).appendTo(t);$ocms.postXT({url:$ocms.url("wdg/my"),success:function(t){$.each(t,(function(t,n){var i=$$.dc("wdg_frame",e,{"data-wdg":n}).ldng(1);$ocms.wdg.call(i,{wdg:n})}))},loading:e})},ValidateEmail:function(t){return!!/^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/.test(t)},cf:t=>{let e=$("#contentframe");return!0===bool(t,!1)&&e.empty().rC("hd"),e},lf:t=>{let e=$("#listframe");return!0===bool(t,!1)&&e.empty().aC("hd").rC("fix"),e},frm_edit:function(t){let e=$fis.cf(!1),n=e.children(".cfrm"),i=e.children(".edit_frm");return n.length<1?n=$$.dc("cfrm hd").prependTo(e):!0===bool(t,!1)&&n.empty(),i.length<1&&(i=$$.dc("edit_frm").insertAfter(n)),i.empty()},frm_list:function(t,e){let n=$fis.cf(!1),i=n.children(".cfrm"),o=n.children(".list_frm");return i.length<1?i=$$.dc("cfrm hd").prependTo(n):!0===bool(t,!1)&&i.empty(),!0===bool(e,!1)&&n.children(".edit_frm").remove(),o.length<1&&(o=$$.dc("list_frm").appendTo(n)),o.empty()},lfm:()=>{let t=$fis.lf(!1),e=t.children(".lfrm");return e.length<1&&(e=$$.dc("lfrm").prependTo(t)),e},getAuth:(t,e)=>new Promise(((n,i)=>{$fis.auth[t]&&!1===bool(e,!1)?n($fis.auth[t]||-1):$ocms.postXT({url:$ocms.url("auth"),data:{module:t},success:e=>{$fis.auth[t]=e.auth||-1,n($fis.auth[t]||-1)},error:()=>{i()}})})),prepAuth:t=>new Promise(((e,n)=>{$ocms.postXT({url:$ocms.url("auth"),data:{module:t,array:1},success:t=>{$.extend($fis.auth,t||{})},complete:()=>{e()}})})),isAuth:(t,e)=>($fis.auth[t]||-1)>=(e||1),resetPass:function(t,e){confirm($t.smsc)&&($ocms.postXT({url:$ocms.url("account/sms"),data:{fn:"pwc"}}),$ocms.dlgform($fd.rsp.clone(),{title:$t.rsp||"",submit:function(t){var e=$(this).ldng(1),n=$.extend({loginaccount:$ocms.auth.account||""},e.serializeObject(!0,{typedvalues:!0}));(n.npw||"")!==(n.npwc||"")?e.find('input[name="npw"]:first')[0].setCustomValidity($t.pnm):$ocms.postXT({url:$ocms.url("account/changepassword"),data:n,success:function(t){alert($t.cps),e.trigger("modal_close")},error:function(t){alert($t.rspf[t.getResponseHeader("x-ocms-std")])},complete:function(){e.ldng(0)},timeout:6e4})}}))},wdg:function(t){let e=$(this).empty();$ocms.postXT({url:$ocms.url("wdg/one"),data:{short_name:t.wdg},timeout:9e4,success:function(n,i,o){let r=t.wdg,s=n[r];if(!s)return void e.ldng(0);let a=$.inArrayRegEx("dblwidth",s.rendering_options)>-1,c=$.inArrayRegEx("tiny",s.rendering_options)>-1;e.toggleClass("dbl",a&&!c).toggleClass("tny",c);$$.dc("wdg_hd",e,{title:ne(s.description,$t.wdc)}).toggleClass("dbl",a).text(ne(s.name,t.wdg)).dblclick((function(t){t.stopPropagation(),$fis.wdg.call(e,{wdg:r})}));let l=$$.dc("wdg_cnt",e).toggleClass("dbl",a).hide(),u=$.inArrayRegEx("bgcolor",s.rendering_options);switch(u>-1&&l.css("backgroundColor",s.rendering_options[u].toString().right(":")),s.type){case"table":var d=$$.tblset({},l),h=$$.tr().appendTo(d.hd),p=$t.wdg[r.indexOf("wdg_ev_")>=0?"wdg_ev_":r]||{};$.each(s.columns,(function(t,e){var n=p[e]?p[e].label:e;$$.th().text(n).appendTo(h)})),$.each(s.data,(function(t,e){var n=$$.tr().appendTo(d.bdy);$.each(s.columns,(function(t,i){var o=$$.td().appendTo(n);e[i]instanceof Date||!0===$ocms.isJSONDateString(e[i])?o.text(fdt(e[i],$t.dateformat)):o.rwText(e[i])}))})),$.inArray("firstrow_bold",s.rendering_options)>-1&&h.nextAll("tr:first").css("font-weight","bold");break;case"ind":$$.dc("ind",l).addClass("sts_"+(s.data.status||"")).append([$$.dc("ind").text(s.data.value),$$.lbl(s.data.label)]);break;case"image_url":l.css("background","url('"+s.url+"') no-repeat center center transparent");break;case"image_base64":l.css("background","url('data:image/png;base64,"+s.image+"') no-repeat center center transparent");break;case"html":if(l.html(s.html),$.inArray("reload_10min",s.rendering_options)>-1){var f=l.find("iframe");setTimeout((function(){f.attr("src",(function(t,e){return e}))}),6e5)}}$.inArray("reload_30min",s.rendering_options)>-1&&"html"!==s.type&&setTimeout((function(){$fis.wdg.call(e,{wdg:r})}),18e5),l.slideDown(150)},error:function(t){e.slideUp(150),$fis.failure.call(this,t)},complete:function(){e.ldng(0)}})},ov:function(){$fis.lf(!0);let t=$("#contentframe").empty(),e=$$.d({id:"dashboard_frame"}).appendTo(t);$ocms.postXT({url:$ocms.url("wdg/my"),success:function(t){$.each(t,(function(t,n){var i=$$.dc("wdg_frame",e,{"data-wdg":n}).ldng(1);$fis.wdg.call(i,{wdg:n})}))},loading:e})}};$fis.notifications={connection:null,init:function(){"undefined"!=typeof signalR&&null===this.connection&&$ocms.auth.useraccount_id&&(this.ensureFrame(),this.connection=(new signalR.HubConnectionBuilder).withUrl("/notifications").withAutomaticReconnect().build(),this.connection.on("notification",(t=>{this.push(t)})),this.connection.onclose((()=>{console.warn("Notification connection closed; retrying in 5s."),this.connection=null,setTimeout((()=>this.init()),5e3)})),this.start())},start:function(){this.connection.start().catch((t=>{console.warn("Notification connection failed to start; retrying in 5s.",t),this.connection=null,setTimeout((()=>this.init()),5e3)}))},ensureFrame:function(){$("#notification_frame").length<1&&$("
            ",{id:"notification_frame"}).appendTo($("footer:first").length?"footer:first":"body")},push:function(t){this.ensureFrame(),t=t||{};let e=$("
            ",{class:"notification_item"}).addClass((t.severity||"info").toLowerCase()).append($("