using Elwig.Helpers; using Elwig.Models.Dtos; using Elwig.Models.Entities; using Microsoft.EntityFrameworkCore; using System; using System.Linq; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using Elwig.Helpers.Billing; using Elwig.Helpers.Export; using Microsoft.Win32; using System.Text.Json; namespace Elwig.Windows { public partial class PaymentVariantsWindow : ContextWindow { public readonly int Year; public readonly bool SeasonLocked; private bool DataValid, DataChanged, NameChanged, CommentChanged, TransferDateValid, TransferDateChanged; private BillingData? BillingData; private bool WeightModifierChanged = false; private static readonly JsonSerializerOptions JsonOpt = new() { WriteIndented = true }; public PaymentVariantsWindow(int year) { InitializeComponent(); Year = year; using (var ctx = new AppDbContext()) { SeasonLocked = ctx.Seasons.Find(Year + 1) != null; } Title = $"Auszahlungsvarianten - Lese {Year} - Elwig"; if (!App.Config.Debug) { DataInput.Visibility = Visibility.Hidden; } } protected override async Task OnRenewContext() { using var ctx = new AppDbContext(); ControlUtils.RenewItemsSource(PaymentVariantList, await ctx.PaymentVariants .Where(v => v.Year == Year) .OrderBy(v => v.AvNr) .Include(v => v.Season.Currency) .ToListAsync()); await Update(); } private async Task Update() { if (PaymentVariantList.SelectedItem is PaymentVar v) { var locked = !v.TestVariant; DeleteButton.IsEnabled = !locked; CalculateButton.IsEnabled = !locked; CommitButton.IsEnabled = !locked && !SeasonLocked; CommitButton.Visibility = !locked ? Visibility.Visible : Visibility.Hidden; RevertButton.IsEnabled = locked && !SeasonLocked; RevertButton.Visibility = locked ? Visibility.Visible : Visibility.Hidden; Arrow3.Content = locked ? "\xF0B0" : "\xF0AF"; CopyButton.IsEnabled = true; EditButton.Content = locked ? "Ansehen" : "Bearbeiten"; EditButton.IsEnabled = true; SaveButton.IsEnabled = !locked; MailButton.IsEnabled = true; ExportButton.IsEnabled = locked; TransactionButton.IsEnabled = locked; NameInput.Text = v.Name; NameInput.IsReadOnly = false; CommentInput.Text = v.Comment; CommentInput.IsReadOnly = false; DateInput.Text = $"{v.Date:dd.MM.yyyy}"; DateInput.IsReadOnly = false; TransferDateInput.Text = $"{v.TransferDate:dd.MM.yyyy}"; TransferDateInput.IsReadOnly = false; try { BillingData = BillingData.FromJson(v.Data); ConsiderModifiersInput.IsChecked = BillingData.ConsiderDelieryModifiers; ConsiderPenaltiesInput.IsChecked = BillingData.ConsiderContractPenalties; ConsiderPenaltyInput.IsChecked = BillingData.ConsiderTotalPenalty; ConsiderAutoInput.IsChecked = BillingData.ConsiderAutoBusinessShares; if (BillingData.NetWeightModifier != 0) { WeightModifierInput.Text = $"{Math.Round(BillingData.NetWeightModifier * 100.0, 8)}"; } else if (BillingData.GrossWeightModifier != 0) { WeightModifierInput.Text = $"{Math.Round(BillingData.GrossWeightModifier * 100.0, 8)}"; } else { WeightModifierInput.Text = ""; } DataInput.Text = JsonSerializer.Serialize(BillingData.Data, JsonOpt); } catch { BillingData = null; ConsiderModifiersInput.IsChecked = false; ConsiderPenaltiesInput.IsChecked = false; ConsiderPenaltyInput.IsChecked = false; ConsiderAutoInput.IsChecked = false; WeightModifierInput.Text = ""; DataInput.Text = v.Data; } WeightModifierInput.TextBox.IsReadOnly = false; ConsiderModifiersInput.IsEnabled = !locked; ConsiderPenaltiesInput.IsEnabled = !locked; ConsiderPenaltyInput.IsEnabled = !locked; ConsiderAutoInput.IsEnabled = !locked; DataInput.IsReadOnly = locked; } else { EditButton.Content = "Bearbeiten"; EditButton.IsEnabled = false; SaveButton.IsEnabled = false; CopyButton.IsEnabled = false; CalculateButton.IsEnabled = false; CommitButton.IsEnabled = false; CommitButton.Visibility = Visibility.Visible; RevertButton.IsEnabled = false; RevertButton.Visibility = Visibility.Hidden; Arrow3.Content = "\xF0AF"; DeleteButton.IsEnabled = false; MailButton.IsEnabled = false; ExportButton.IsEnabled = false; TransactionButton.IsEnabled = false; BillingData = null; NameInput.Text = ""; NameInput.IsReadOnly = true; CommentInput.Text = ""; CommentInput.IsReadOnly = true; DateInput.Text = ""; DateInput.IsReadOnly = true; TransferDateInput.Text = ""; TransferDateInput.IsReadOnly = true; WeightModifierInput.Text = ""; WeightModifierInput.TextBox.IsReadOnly = true; ConsiderModifiersInput.IsChecked = false; ConsiderModifiersInput.IsEnabled = false; ConsiderPenaltiesInput.IsChecked = false; ConsiderPenaltiesInput.IsEnabled = false; ConsiderPenaltyInput.IsChecked = false; ConsiderPenaltyInput.IsEnabled = false; ConsiderAutoInput.IsChecked = false; ConsiderAutoInput.IsEnabled = false; DataInput.Text = ""; DataInput.IsReadOnly = true; } await UpdateSums(); UpdateSaveButton(); } private void UpdateSaveButton() { SaveButton.IsEnabled = PaymentVariantList.SelectedItem != null && ((DataChanged && DataValid) || NameChanged || CommentChanged || (TransferDateChanged && TransferDateValid) || (ConsiderModifiersInput.IsChecked != BillingData?.ConsiderDelieryModifiers) || (ConsiderPenaltiesInput.IsChecked != BillingData?.ConsiderContractPenalties) || (ConsiderPenaltyInput.IsChecked != BillingData?.ConsiderTotalPenalty) || (ConsiderAutoInput.IsChecked != BillingData?.ConsiderAutoBusinessShares) || WeightModifierChanged); CalculateButton.IsEnabled = !SaveButton.IsEnabled && PaymentVariantList.SelectedItem is PaymentVar { TestVariant: true }; CommitButton.IsEnabled = CalculateButton.IsEnabled; } private async Task UpdateSums() { if (PaymentVariantList.SelectedItem is PaymentVar v) { using var ctx = new AppDbContext(); var sym = v.Season.Currency.Symbol; var modSum = await ctx.PaymentDeliveryParts .Where(p => p.Year == v.Year && p.AvNr == v.AvNr) .SumAsync(p => p.AmountValue - p.NetAmountValue); ModifierSum.Text = $"{v.Season.DecFromDb(modSum):N2} {sym}"; var totalSum = await ctx.MemberPayments .Where(p => p.Year == v.Year && p.AvNr == v.AvNr) .SumAsync(p => p.AmountValue); TotalSum.Text = $"{v.Season.DecFromDb(totalSum):N2} {sym}"; var credits = ctx.Credits.Where(c => c.Year == v.Year && c.AvNr == v.AvNr); if (!credits.Any()) { VatSum.Text = $"- {sym}"; DeductionSum.Text = $"- {sym}"; PaymentSum.Text = $"- {sym}"; } else { VatSum.Text = $"{v.Season.DecFromDb(await credits.SumAsync(c => c.VatAmountValue)):N2} {sym}"; DeductionSum.Text = $"{-v.Season.DecFromDb(await credits.SumAsync(c => c.ModifiersValue ?? 0)):N2} {sym}"; PaymentSum.Text = $"{v.Season.DecFromDb(await credits.SumAsync(c => c.AmountValue)):N2} {sym}"; } } else { ModifierSum.Text = "-"; TotalSum.Text = "-"; VatSum.Text = "-"; DeductionSum.Text = "-"; PaymentSum.Text = "-"; } } private async void PaymentVariantList_SelectionChanged(object sender, SelectionChangedEventArgs evt) { await Update(); } private async void AddButton_Click(object sender, RoutedEventArgs evt) { try { using var ctx = new AppDbContext(); var v = new PaymentVar { Year = Year, AvNr = await ctx.NextAvNr(Year), Name = "Neue Auszahlungsvariante", TestVariant = true, DateString = $"{DateTime.Today:yyyy-MM-dd}", Data = "{\"mode\": \"elwig\", \"version\": 1, \"payment\": {}, \"curves\": []}", }; ctx.Add(v); await ctx.SaveChangesAsync(); await App.HintContextChange(); ControlUtils.SelectItem(PaymentVariantList, v); } catch (Exception exc) { var str = "Der Eintrag konnte nicht in der Datenbank aktualisiert werden!\n\n" + exc.Message; if (exc.InnerException != null) str += "\n\n" + exc.InnerException.Message; MessageBox.Show(str, "Auszahlungsvariante erstellen", MessageBoxButton.OK, MessageBoxImage.Error); } } private async void CopyButton_Click(object sender, RoutedEventArgs evt) { if (PaymentVariantList.SelectedItem is not PaymentVar orig) return; try { using var ctx = new AppDbContext(); var n = new PaymentVar { Year = orig.Year, AvNr = await ctx.NextAvNr(Year), Name = $"{orig.Name} (Kopie)", TestVariant = true, DateString = $"{DateTime.Today:yyyy-MM-dd}", Data = orig.Data, }; ctx.Add(n); await ctx.SaveChangesAsync(); await App.HintContextChange(); ControlUtils.SelectItem(PaymentVariantList, n); } catch (Exception exc) { var str = "Der Eintrag konnte nicht in der Datenbank aktualisiert werden!\n\n" + exc.Message; if (exc.InnerException != null) str += "\n\n" + exc.InnerException.Message; MessageBox.Show(str, "Auszahlungsvariante kopieren", MessageBoxButton.OK, MessageBoxImage.Error); } } private async void DeleteButton_Click(object sender, RoutedEventArgs evt) { if (PaymentVariantList.SelectedItem is not PaymentVar v || !v.TestVariant) return; try { using var ctx = new AppDbContext(); ctx.Remove(v); await ctx.SaveChangesAsync(); await App.HintContextChange(); } catch (Exception exc) { var str = "Der Eintrag konnte nicht in der Datenbank aktualisiert werden!\n\n" + exc.Message; if (exc.InnerException != null) str += "\n\n" + exc.InnerException.Message; MessageBox.Show(str, "Auszahlungsvariante löschen", MessageBoxButton.OK, MessageBoxImage.Error); } } private async void CalculateButton_Click(object sender, RoutedEventArgs evt) { if (PaymentVariantList.SelectedValue is not PaymentVar v) return; CalculateButton.IsEnabled = false; Mouse.OverrideCursor = Cursors.AppStarting; try { var b = new BillingVariant(v.Year, v.AvNr); await b.Calculate(); } catch (Exception exc) { MessageBox.Show(exc.Message, "Berechnungsfehler", MessageBoxButton.OK, MessageBoxImage.Error); } await App.HintContextChange(); Mouse.OverrideCursor = null; CalculateButton.IsEnabled = true; } private void EditButton_Click(object sender, RoutedEventArgs evt) { if (PaymentVariantList.SelectedItem is not PaymentVar v) return; App.FocusChartWindow(v.Year, v.AvNr); } private async void MailButton_Click(object sender, RoutedEventArgs evt) { if (PaymentVariantList.SelectedItem is not PaymentVar pv) return; using var ctx = new AppDbContext(); var vars = await ctx.PaymentVariants .Where(v => pv.Year == v.Year) .OrderBy(v => v.AvNr) .Select(v => v.AvNr) .ToArrayAsync(); var w = App.FocusMailWindow(pv.Year); w.AddCreditNote(Array.IndexOf(vars, pv.AvNr)); } private async void CommitButton_Click(object sender, RoutedEventArgs evt) { if (PaymentVariantList.SelectedValue is not PaymentVar v) return; CommitButton.IsEnabled = false; Mouse.OverrideCursor = Cursors.AppStarting; try { var b = new BillingVariant(v.Year, v.AvNr); await b.Commit(); } catch (Exception exc) { MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error); } Mouse.OverrideCursor = null; RevertButton.IsEnabled = true; await App.HintContextChange(); } private async void RevertButton_Click(object sender, RoutedEventArgs evt) { if (PaymentVariantList.SelectedValue is not PaymentVar v) return; var res = MessageBox.Show( "Sollen wirklich alle festgesetzten Traubengutschriften der ausgewählten Auszahlungsvariante unwiderruflich gelöscht werden?\n\n" + "Dies ist im Allgemeinen nie empfohlen. Handelt es sich um die aktuellste Auszahlungsvariante könnte das eine Ausnahme sein.", "Traubengutschriften löschen", MessageBoxButton.YesNo, MessageBoxImage.Warning, MessageBoxResult.No); if (res != MessageBoxResult.Yes) return; RevertButton.IsEnabled = false; Mouse.OverrideCursor = Cursors.AppStarting; var b = new BillingVariant(v.Year, v.AvNr); await b.Revert(); await App.HintContextChange(); Mouse.OverrideCursor = null; CommitButton.IsEnabled = true; } private async void ExportButton_Click(object sender, RoutedEventArgs evt) { if (PaymentVariantList.SelectedValue is not PaymentVar v) { return; } else if (v.TransferDate == null) { MessageBox.Show("Überweisungsdatum muss gesetzt sein!", "Exportieren nicht möglich", MessageBoxButton.OK, MessageBoxImage.Error); return; } var withoutIban = v.Credits.Count(c => c.Member.Iban == null); if (withoutIban > 0) { var r = MessageBox.Show($"Achtung: Für {withoutIban} Mitglieder ist kein IBAN hinterlegt.\nDiese werden NICHT exportiert.", "Mitglieder ohne IBAN", MessageBoxButton.OKCancel, MessageBoxImage.Warning, MessageBoxResult.Cancel); if (r != MessageBoxResult.OK) return; } var d = new SaveFileDialog() { FileName = $"{App.Client.NameToken}-Überweisungsdaten-{v.Year}-{v.Name.Trim().Replace(' ', '-')}.{Ebics.FileExtension}", DefaultExt = Ebics.FileExtension, Filter = "EBICS-Datei (*.xml)|*.xml", Title = $"Überweisungsdaten speichern unter - Elwig", }; if (d.ShowDialog() == true) { ExportButton.IsEnabled = false; Mouse.OverrideCursor = Cursors.AppStarting; try { using var e = new Ebics(v, d.FileName, 9); await e.ExportAsync(Transaction.FromPaymentVariant(v)); } catch (Exception exc) { MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error); } Mouse.OverrideCursor = null; ExportButton.IsEnabled = true; } } private async void TransactionButton_Click(object sender, RoutedEventArgs evt) { if (PaymentVariantList.SelectedValue is not PaymentVar v) { return; } var d = new SaveFileDialog() { FileName = $"{App.Client.NameToken}-Buchungsliste-{v.Year}-{v.Name.Trim().Replace(' ', '-')}.ods", DefaultExt = "ods", Filter = "OpenDocument Format Spreadsheet (*.ods)|*.ods", Title = $"Buchungsliste speichern unter - Elwig" }; if (d.ShowDialog() == true) { TransactionButton.IsEnabled = false; Mouse.OverrideCursor = Cursors.AppStarting; try { using var ctx = new AppDbContext(); var tbl = await CreditNoteData.ForPaymentVariant(ctx, v.Year, v.AvNr); using var ods = new OdsFile(d.FileName); await ods.AddTable(tbl); } catch (Exception exc) { MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error); } Mouse.OverrideCursor = null; TransactionButton.IsEnabled = true; } } private async void SaveButton_Click(object sender, RoutedEventArgs evt) { if (PaymentVariantList.SelectedItem is not PaymentVar v || BillingData == null) return; try { v.Name = NameInput.Text; v.Comment = (CommentInput.Text != "") ? CommentInput.Text : null; v.TransferDateString = (TransferDateInput.Text != "") ? string.Join("-", TransferDateInput.Text.Split(".").Reverse()) : null; var d = App.Config.Debug ? BillingData.FromJson(DataInput.Text) : BillingData; d.ConsiderDelieryModifiers = ConsiderModifiersInput.IsChecked ?? false; d.ConsiderContractPenalties = ConsiderPenaltiesInput.IsChecked ?? false; d.ConsiderTotalPenalty = ConsiderPenaltyInput.IsChecked ?? false; d.ConsiderAutoBusinessShares = ConsiderAutoInput.IsChecked ?? false; var modVal = WeightModifierInput.Text.Length > 0 ? double.Parse(WeightModifierInput.Text) : 0; d.NetWeightModifier = modVal > 0 ? modVal / 100.0 : 0; d.GrossWeightModifier = modVal < 0 ? modVal / 100.0 : 0; WeightModifierChanged = false; v.Data = JsonSerializer.Serialize(d.Data); using (var ctx = new AppDbContext()) { ctx.Update(v); await ctx.SaveChangesAsync(); } await App.HintContextChange(); CommentInput_TextChanged(null, null); ConsiderModifiersInput_Changed(null, null); ConsiderPenaltiesInput_Changed(null, null); ConsiderPenaltyInput_Changed(null, null); ConsiderAutoInput_Changed(null, null); WeightModifierInput_TextChanged(null, null); } catch (Exception exc) { await HintContextChange(); var str = "Der Eintrag konnte nicht in der Datenbank aktualisiert werden!\n\n" + exc.Message; if (exc.InnerException != null) str += "\n\n" + exc.InnerException.Message; MessageBox.Show(str, "Auszahlungsvariante aktualisieren", MessageBoxButton.OK, MessageBoxImage.Error); } } private void ModifierButton_Click(object sender, RoutedEventArgs evt) { App.FocusBaseDataSeason(Year); } private void NameInput_TextChanged(object sender, TextChangedEventArgs evt) { if (PaymentVariantList.SelectedItem is not PaymentVar v) { ControlUtils.ClearInputState(NameInput); return; } if (NameInput.Text != v.Name) { ControlUtils.SetInputChanged(NameInput); NameChanged = true; } else { ControlUtils.ClearInputState(NameInput); NameChanged = false; } UpdateSaveButton(); } private void CommentInput_TextChanged(object? sender, TextChangedEventArgs? evt) { if (PaymentVariantList.SelectedItem is not PaymentVar v) { ControlUtils.ClearInputState(CommentInput); return; } if (CommentInput.Text != (v.Comment ?? "")) { ControlUtils.SetInputChanged(CommentInput); CommentChanged = true; } else { ControlUtils.ClearInputState(CommentInput); CommentChanged = false; } UpdateSaveButton(); } private void TransferDateInput_TextChanged(object sender, TextChangedEventArgs evt) { if (PaymentVariantList.SelectedItem is not PaymentVar v) { ControlUtils.ClearInputState(TransferDateInput); return; } var res = Validator.CheckDate(TransferDateInput, false); if (!res.IsValid) { ControlUtils.SetInputInvalid(TransferDateInput); TransferDateValid = false; } else if (TransferDateInput.Text != $"{v.TransferDate:dd.MM.yyyy}") { ControlUtils.SetInputChanged(TransferDateInput); TransferDateValid = true; TransferDateChanged = true; } else { ControlUtils.ClearInputState(TransferDateInput); TransferDateValid = true; TransferDateChanged = false; } UpdateSaveButton(); } private void DataInput_TextChanged(object sender, TextChangedEventArgs evt) { if (PaymentVariantList.SelectedItem is not PaymentVar v) { ControlUtils.ClearInputState(DataInput); return; } try { var data = BillingData.FromJson(DataInput.Text); var origJson = v.Data; try { origJson = JsonSerializer.Serialize(BillingData.FromJson(v.Data).Data); } catch { } DataValid = true; if (JsonSerializer.Serialize(data.Data) != origJson) { ControlUtils.SetInputChanged(DataInput); DataChanged = true; } else { ControlUtils.ClearInputState(DataInput); DataChanged = false; } } catch { ControlUtils.SetInputInvalid(DataInput); DataValid = false; } UpdateSaveButton(); } private void ConsiderModifiersInput_Changed(object? sender, RoutedEventArgs? evt) { if (BillingData == null) { ControlUtils.ClearInputState(ConsiderModifiersInput); return; } if (BillingData.ConsiderDelieryModifiers != ConsiderModifiersInput.IsChecked) { ControlUtils.SetInputChanged(ConsiderModifiersInput); } else { ControlUtils.ClearInputState(ConsiderModifiersInput); } UpdateSaveButton(); } private void ConsiderPenaltiesInput_Changed(object? sender, RoutedEventArgs? evt) { if (BillingData == null) { ControlUtils.ClearInputState(ConsiderPenaltiesInput); return; } if (BillingData.ConsiderContractPenalties != ConsiderPenaltiesInput.IsChecked) { ControlUtils.SetInputChanged(ConsiderPenaltiesInput); } else { ControlUtils.ClearInputState(ConsiderPenaltiesInput); } UpdateSaveButton(); } private void ConsiderPenaltyInput_Changed(object? sender, RoutedEventArgs? evt) { if (BillingData == null) { ControlUtils.ClearInputState(ConsiderPenaltyInput); return; } if (BillingData.ConsiderTotalPenalty != ConsiderPenaltyInput.IsChecked) { ControlUtils.SetInputChanged(ConsiderPenaltyInput); } else { ControlUtils.ClearInputState(ConsiderPenaltyInput); } UpdateSaveButton(); } private void ConsiderAutoInput_Changed(object? sender, RoutedEventArgs? evt) { if (BillingData == null) { ControlUtils.ClearInputState(ConsiderAutoInput); return; } if (BillingData.ConsiderAutoBusinessShares != ConsiderAutoInput.IsChecked) { ControlUtils.SetInputChanged(ConsiderAutoInput); } else { ControlUtils.ClearInputState(ConsiderAutoInput); } UpdateSaveButton(); } private void WeightModifierInput_TextChanged(object? sender, TextChangedEventArgs? evt) { var res = Validator.CheckDecimal(WeightModifierInput.TextBox, false, 3, 2, true); if (BillingData == null) { ControlUtils.ClearInputState(WeightModifierInput.TextBox); return; } var val = WeightModifierInput.Text.Length > 0 && res.IsValid ? double.Parse(WeightModifierInput.Text) : 0; WeightModifierChanged = (val != Math.Round(BillingData.NetWeightModifier * 100.0, 8) && val != Math.Round(BillingData.GrossWeightModifier * 100.0, 8)) || (val == 0 && (BillingData.NetWeightModifier != 0 || BillingData.GrossWeightModifier != 0)); if (WeightModifierChanged) { ControlUtils.SetInputChanged(WeightModifierInput.TextBox); } else { ControlUtils.ClearInputState(WeightModifierInput.TextBox); } UpdateSaveButton(); } private void WeightModifierInput_LostFocus(object sender, RoutedEventArgs evt) { if (WeightModifierInput.Text.EndsWith(',')) WeightModifierInput.Text = WeightModifierInput.Text[..^1]; } } }