using Elwig.Documents; using Elwig.Helpers; using Elwig.Helpers.Billing; using Elwig.Models.Dtos; using Elwig.Models.Entities; using Microsoft.EntityFrameworkCore; using Microsoft.Win32; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.IO; using System.Linq; using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Input; namespace Elwig.Windows { public partial class MailWindow : ContextWindow { // used for document sorting while generating! public enum DocType { Undefined, Custom, MemberDataSheet, DeliveryConfirmation, CreditNote } public class SelectedDoc(DocType type, string name, object? details = null) { public DocType Type = type; public string Name { get; set; } = name; public object? Details = details; } public class GeneratedDoc { public DocType Type; public Document Doc; public GeneratedDoc(string pdfPath) { Type = DocType.Custom; Doc = Document.FromPdf(pdfPath); } public GeneratedDoc(Document doc) { Type = doc is MemberDataSheet ? DocType.MemberDataSheet : doc is DeliveryConfirmation ? DocType.DeliveryConfirmation : doc is CreditNote ? DocType.CreditNote : DocType.Undefined; Doc = doc; } } public static readonly string[] AvaiableDocuments = [ MemberDataSheet.Name, DeliveryConfirmation.Name, CreditNote.Name, ]; public readonly int? Year; public ObservableCollection SelectedDocs = []; public IEnumerable Recipients = []; protected Document? PrintDocument; protected Dictionary>? EmailDocuments; public static readonly DependencyProperty PostalAllCountProperty = DependencyProperty.Register("PostalAllCount", typeof(int), typeof(MailWindow)); public int PostalAllCount { get => (int)GetValue(PostalAllCountProperty); private set => SetValue(PostalAllCountProperty, value); } public static readonly DependencyProperty PostalWishCountProperty = DependencyProperty.Register("PostalWishCount", typeof(int), typeof(MailWindow)); public int PostalWishCount { get => (int)GetValue(PostalWishCountProperty); private set => SetValue(PostalWishCountProperty, value); } public static readonly DependencyProperty PostalNoEmailCountProperty = DependencyProperty.Register("PostalNoEmailCount", typeof(int), typeof(MailWindow)); public int PostalNoEmailCount { get => (int)GetValue(PostalNoEmailCountProperty); private set => SetValue(PostalNoEmailCountProperty, value); } public static readonly DependencyProperty EmailAllCountProperty = DependencyProperty.Register("EmailAllCount", typeof(int), typeof(MailWindow)); public int EmailAllCount { get => (int)GetValue(EmailAllCountProperty); private set => SetValue(EmailAllCountProperty, value); } public static readonly DependencyProperty EmailWishCountProperty = DependencyProperty.Register("EmailWishCount", typeof(int), typeof(MailWindow)); public int EmailWishCount { get => (int)GetValue(EmailWishCountProperty); private set => SetValue(EmailWishCountProperty, value); } private ICommand _deleteCommand; public ICommand DeleteCommand => _deleteCommand ??= new ActionCommand(() => { var idx = SelectedDocumentsList.SelectedIndex; if (idx == -1) return; SelectedDocs.RemoveAt(SelectedDocumentsList.SelectedIndex); SelectedDocumentsList.SelectedIndex = idx < SelectedDocumentsList.Items.Count ? idx : idx - 1; }); // powershell -Command "$(Get-WmiObject -Class Win32_Printer | Where-Object {$_.Default -eq $True}).Name" public MailWindow(int? year = null) { InitializeComponent(); Year = year ?? Context.Seasons.OrderBy(s => s.Year).LastOrDefault()?.Year; Title = $"Rundschreiben - Lese {Year} - Elwig"; AvaiableDocumentsList.ItemsSource = AvaiableDocuments; SelectedDocumentsList.ItemsSource = SelectedDocs; DocumentNonDeliverersInput.Visibility = Visibility.Hidden; DocumentFooterLabel.Visibility = Visibility.Hidden; DeliveryConfirmationFooterInput.Visibility = Visibility.Hidden; CreditNoteFooterInput.Visibility = Visibility.Hidden; RecipientsActiveMembersInput.IsChecked = true; DeliveryConfirmationFooterInput.Text = App.Client.TextDeliveryConfirmation; CreditNoteFooterInput.Text = App.Client.TextCreditNote; PostalSender1.Text = App.Client.Sender1; PostalSender2.Text = App.Client.Sender2; EmailSubjectInput.Text = App.Client.TextEmailSubject ?? "Rundschreiben"; EmailBodyInput.Text = App.Client.TextEmailBody ?? "Sehr geehrtes Mitglied,\n\nim Anhang finden Sie das aktuelle Rundschreiben.\n\nIhre Winzergenossenschaft\n"; } protected override async Task OnRenewContext() { var season = await Context.Seasons.FindAsync(Year); var l = new List { MemberDataSheet.Name }; if (season != null) { l.Add($"{DeliveryConfirmation.Name} {Year}"); l.AddRange(season.PaymentVariants.Where(v => !v.TestVariant).OrderBy(v => v.AvNr).Select(v => $"{CreditNote.Name} – {v.Name}")); } AvaiableDocumentsList.ItemsSource = l; ControlUtils.RenewItemsSource(MemberBranchInput, await Context.Branches .Where(b => b.Members.Any()) .OrderBy(b => b.Name) .ToListAsync(), b => (b as Branch)?.ZwstId); if (MemberBranchInput.SelectedItems.Count == 0) MemberBranchInput.SelectAll(); ControlUtils.RenewItemsSource(MemberKgInput, await Context.Katastralgemeinden .Where(k => k.WbKg.Members.Any()) .OrderBy(k => k.Name) .ToListAsync(), k => (k as AT_Kg)?.KgNr); if (MemberKgInput.SelectedItems.Count == 0) MemberKgInput.SelectAll(); ControlUtils.RenewItemsSource(MemberAreaComInput, await Context.AreaCommitmentTypes .OrderBy(a => a.VtrgId) .ToListAsync(), a => (a as AreaComType)?.VtrgId); if (MemberAreaComInput.SelectedItems.Count == 0) MemberAreaComInput.SelectAll(); ControlUtils.RenewItemsSource(MemberCustomInput, await Context.Members .Where(m => m.IsActive) .OrderBy(m => m.FamilyName) .ThenBy(m => m.GivenName) .ToListAsync(), m => (m as Member)?.MgNr); if (MemberCustomInput.SelectedItems.Count == 0) MemberCustomInput.SelectAll(); await UpdateRecipients(); } private void ContinueButton_Click(object sender, RoutedEventArgs evt) { TabControl.SelectedIndex = 1; TabControl.AllowDrop = false; } private void BackButton_Click(object sender, RoutedEventArgs evt) { TabControl.SelectedIndex = 0; TabControl.AllowDrop = true; } private void Document_Drop(object sender, DragEventArgs evt) { if (evt.Data.GetDataPresent(DataFormats.FileDrop)) { var files = (string[])evt.Data.GetData(DataFormats.FileDrop); foreach (var file in files) { if (Path.GetExtension(file) == ".pdf") { SelectedDocs.Add(new(DocType.Custom, Path.GetFileName(file), file)); } } } } private void Document_PreviwDragOver(object sender, DragEventArgs evt) { evt.Handled = TabControl.SelectedIndex == 0; } private void AvaiableDocumentsList_SelectionChanged(object sender, RoutedEventArgs evt) { DocumentAddButton.IsEnabled = AvaiableDocumentsList.SelectedIndex != -1; } private void SelectedDocumentsList_SelectionChanged(object sender, RoutedEventArgs evt) { DocumentRemoveButton.IsEnabled = SelectedDocumentsList.SelectedIndex != -1; if (SelectedDocumentsList.SelectedItem is SelectedDoc doc) { DocumentBox.Header = doc.Name; if (doc.Type == DocType.DeliveryConfirmation) { DocumentNonDeliverersInput.Visibility = Visibility.Visible; DocumentFooterLabel.Visibility = Visibility.Visible; DeliveryConfirmationFooterInput.Visibility = Visibility.Visible; CreditNoteFooterInput.Visibility = Visibility.Hidden; DocumentFooterLabel.Margin = new(10, 40, 0, 10); } else if (doc.Type == DocType.CreditNote) { DocumentNonDeliverersInput.Visibility = Visibility.Hidden; DocumentFooterLabel.Visibility = Visibility.Visible; DeliveryConfirmationFooterInput.Visibility = Visibility.Hidden; CreditNoteFooterInput.Visibility = Visibility.Visible; DocumentFooterLabel.Margin = new(10, 10, 0, 10); } else { DocumentNonDeliverersInput.Visibility = Visibility.Hidden; DocumentFooterLabel.Visibility = Visibility.Hidden; DeliveryConfirmationFooterInput.Visibility = Visibility.Hidden; CreditNoteFooterInput.Visibility = Visibility.Hidden; } } else { DocumentBox.Header = "Dokument"; DocumentNonDeliverersInput.Visibility = Visibility.Hidden; DocumentFooterLabel.Visibility = Visibility.Hidden; DeliveryConfirmationFooterInput.Visibility = Visibility.Hidden; CreditNoteFooterInput.Visibility = Visibility.Hidden; } } private void DocumentAddButton_Click(object sender, RoutedEventArgs evt) { var idx = AvaiableDocumentsList.SelectedIndex; if (AvaiableDocumentsList.SelectedItem is not string s) return; if (idx == 0) { SelectedDocs.Add(new(DocType.MemberDataSheet, s, null)); } else if (idx == 1) { SelectedDocs.Add(new(DocType.DeliveryConfirmation, s, ((int)Year!, DocumentNonDeliverersInput.IsChecked == true))); } else if (idx >= 2) { var name = s.Split(" – ")[^1]; var pv = Context.PaymentVariants.Single(v => v.Year == Year && v.Name == name)!; SelectedDocs.Add(new(DocType.CreditNote, s, (pv.Year, pv.AvNr))); } SelectedDocumentsList.SelectedIndex = SelectedDocs.Count - 1; } private void DocumentRemoveButton_Click(object sender, RoutedEventArgs evt) { DeleteCommand.Execute(null); } private void SelectDocumentButton_Click(object sender, RoutedEventArgs evt) { var d = new OpenFileDialog() { Title = "Dokument auswählen - Elwig", DefaultExt = ".pdf", Filter = "PDF-Dokument (*.pdf)|*.pdf", Multiselect = true, }; if (d.ShowDialog() == true) { foreach (var file in d.FileNames) { if (Path.GetExtension(file) == ".pdf") { SelectedDocs.Add(new(DocType.Custom, Path.GetFileName(file), file)); } } } } private async void RecipientsInput_Changed(object sender, RoutedEventArgs evt) { var vis = RecipientsCustomInput.IsChecked == true ? Visibility.Hidden : Visibility.Visible; MemberBranchLabel.Visibility = vis; MemberBranchInput.Visibility = vis; MemberKgLabel.Visibility = vis; MemberKgInput.Visibility = vis; MemberAreaComInput.Visibility = RecipientsAreaComMembersInput.IsChecked == true ? Visibility.Visible : Visibility.Hidden; MemberAreaComLabel.Visibility = RecipientsAreaComMembersInput.IsChecked == true ? Visibility.Visible : Visibility.Hidden; MemberCustomInput.Visibility = RecipientsCustomInput.IsChecked == true ? Visibility.Visible : Visibility.Hidden; await UpdateRecipients(); } private async void MemberInput_SelectionChanged(object sender, RoutedEventArgs evt) { await UpdateRecipients(); } private async Task UpdateRecipients() { if (RecipientsCustomInput.IsChecked == true) { Recipients = MemberCustomInput.SelectedItems.Cast().ToList(); } else { var year = (!await Context.Deliveries.AnyAsync()) ? 0 : await Context.Deliveries.Select(d => d.Year).MaxAsync(); IQueryable query = Context.Members.Where(m => m.IsActive); if (MemberBranchInput.SelectedItems.Count != MemberBranchInput.Items.Count) { var zwst = MemberBranchInput.SelectedItems.Cast().Select(b => b.ZwstId).ToList(); query = query.Where(m => zwst.Contains(m.ZwstId)); } if (MemberKgInput.SelectedItems.Count != MemberKgInput.Items.Count) { var kgs = MemberKgInput.SelectedItems.Cast().Select(k => k.KgNr).ToList(); query = query.Where(m => kgs.Contains((int)m.DefaultKgNr)); } if (RecipientsAreaComMembersInput.IsChecked == true) { var vtrg = MemberAreaComInput.SelectedItems.Cast().Select(a => a.VtrgId).ToList(); query = query.Where(m => m.AreaCommitments.Any(a => a.YearFrom <= year && (a.YearTo == null || a.YearTo >= year) && vtrg.Contains(a.VtrgId))); } else if (year > 0 && RecipientsDeliveryMembersInput.IsChecked == true) { query = query.Where(m => m.Deliveries.Any(d => d.Year == year)); } else if (year > 0 && RecipientsNonDeliveryMembersInput.IsChecked == true) { query = query.Where(m => !m.Deliveries.Any(d => d.Year == year)); } Recipients = await query.ToListAsync(); } UpdatePostalEmailRecipients(); } private void EmailInput_Changed(object sender, RoutedEventArgs evt) { UpdatePostalEmailRecipients(); } private void UpdatePostalEmailRecipients() { EmailAllCount = Recipients.Count(m => m.EmailAddresses.Count > 0); EmailWishCount = Recipients.Count(m => m.EmailAddresses.Count > 0 && m.ContactViaEmail); PostalAllCount = Recipients.Count(); PostalWishCount = Recipients.Count(m => m.ContactViaPost); var m = EmailAllInput.IsChecked == true ? 3 : EmailWishInput.IsChecked == true ? 2 : 1; PostalNoEmailCount = PostalAllCount - (m == 3 ? EmailAllCount : m == 2 ? EmailWishCount : 0); } private async Task UpdateTextParameters() { var changed = false; var dcText = DeliveryConfirmationFooterInput.Text.Trim(); if (dcText.Length == 0) dcText = null; if (dcText != App.Client.TextDeliveryConfirmation) { App.Client.TextDeliveryConfirmation = dcText; changed = true; } var cdText = CreditNoteFooterInput.Text.Trim(); if (cdText.Length == 0) cdText = null; if (cdText != App.Client.TextCreditNote) { App.Client.TextCreditNote = cdText; changed = true; } var emailSubject = EmailSubjectInput.Text.Trim(); if (emailSubject.Length == 0) emailSubject = null; if (emailSubject != App.Client.TextEmailSubject) { App.Client.TextEmailSubject = emailSubject; changed = true; } var emailBody = EmailBodyInput.Text.Trim(); if (emailBody.Length == 0) emailBody = null; if (emailBody != App.Client.TextEmailBody) { App.Client.TextEmailBody = emailBody; changed = true; } if (changed) await App.Client.UpdateValues(); } private void DisposeDocs() { PrintDocument?.Dispose(); PrintDocument = null; if (EmailDocuments != null) { foreach (var (m, docs) in EmailDocuments) { foreach (var d in docs) { d.Dispose(); } } EmailDocuments = null; } } private void Window_Closed(object sender, EventArgs evt) { DisposeDocs(); } private async void GenerateButton_Click(object sender, RoutedEventArgs evt) { PreviewButton.IsEnabled = false; PrintButton.IsEnabled = false; EmailButton.IsEnabled = false; Mouse.OverrideCursor = Cursors.AppStarting; GenerateButton.IsEnabled = false; DisposeDocs(); await UpdateTextParameters(); IEnumerable recipients = Recipients; if (OrderMgNrInput.IsChecked == true) { recipients = recipients .OrderBy(m => m.MgNr) .ToList(); } else if (OrderNameInput.IsChecked == true) { recipients = recipients .OrderBy(m => m.FamilyName) .ThenBy(m => m.GivenName) .ThenBy(m => m.MgNr) .ToList(); } else if (OrderPlzInput.IsChecked == true) { recipients = recipients .OrderBy(m => m.PostalDest.AtPlz.Plz) .ThenBy(m => m.PostalDest.AtPlz.Ort.Name) .ThenBy(m => m.FamilyName) .ThenBy(m => m.GivenName) .ThenBy(m => m.MgNr) .ToList(); } var doublePaged = DoublePagedInput.IsChecked == true; var docs = SelectedDocs.OrderByDescending(d => d.Type).ToList(); Dictionary> dcData = []; Dictionary<(int, int), (IDictionary, IDictionary, BillingData)> cnData = []; foreach (var doc in docs) { if (doc.Type == DocType.DeliveryConfirmation) { var details = ((int, bool))doc.Details!; var year = details.Item1; dcData[year] = await DeliveryConfirmationDeliveryData.ForSeason(Context.DeliveryParts, year); } else if (doc.Type == DocType.CreditNote) { var details = ((int, int))doc.Details!; var year = details.Item1; var avnr = details.Item2; try { cnData[(year, avnr)] = ( await CreditNoteDeliveryData.ForPaymentVariant(Context.CreditNoteDeliveryRows, Context.Seasons, year, avnr), await Context.MemberPayments.Where(p => p.Year == year && p.AvNr == avnr).ToDictionaryAsync(c => c.MgNr), BillingData.FromJson((await Context.PaymentVariants.FindAsync(year, avnr))!.Data) ); } catch (Exception exc) { MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error); GenerateButton.IsEnabled = true; Mouse.OverrideCursor = null; return; } await Context.GetMemberAreaCommitmentBuckets(year, 0); } } var memberDocs = recipients.Select(m => new { Member = m, Docs = docs.SelectMany(doc => { if (doc.Type == DocType.Custom) { return [new GeneratedDoc((string)doc.Details!)]; } else if (doc.Type == DocType.MemberDataSheet) { return [new GeneratedDoc(new MemberDataSheet(m, Context))]; } else if (doc.Type == DocType.DeliveryConfirmation) { var details = ((int, bool))doc.Details!; var year = details.Item1; var include = details.Item2; DeliveryConfirmationDeliveryData data; if (dcData[year].TryGetValue(m.MgNr, out var d)) { data = d; } else if (include) { data = DeliveryConfirmationDeliveryData.CreateEmpty(year, m); } else { return []; } return [new GeneratedDoc(new DeliveryConfirmation(Context, year, m, data))]; } else if (doc.Type == DocType.CreditNote) { var details = ((int, int))doc.Details!; var year = details.Item1; var avnr = details.Item2; var data = cnData[(year, avnr)]; try { return [new GeneratedDoc(new CreditNote( Context, data.Item2[m.MgNr], data.Item1[m.MgNr], data.Item3.ConsiderContractPenalties, data.Item3.ConsiderTotalPenalty, data.Item3.ConsiderAutoBusinessShares, Context.GetMemberUnderDelivery(year, m.MgNr).GetAwaiter().GetResult() ))]; } catch (Exception) { return []; } } else { throw new NotImplementedException("Invalid DocType"); } }).ToList() }).ToList(); var printMode = PostalAllInput.IsChecked == true ? 3 : PostalWishInput.IsChecked == true ? 2 : PostalNoEmailInput.IsChecked == true ? 1 : 0; var emailMode = EmailAllInput.IsChecked == true ? 2 : EmailWishInput.IsChecked == true ? 1 : 0; double printNum = printMode == 3 ? PostalAllCount : printMode == 2 ? PostalWishCount : printMode == 2 ? PostalNoEmailCount : 0; double emailNum = emailMode == 2 ? EmailAllCount : emailMode == 1 ? EmailWishCount : 0; double totalNum = printNum + emailNum; var email = memberDocs .Where(d => d.Docs.Count > 0 && d.Member.EmailAddresses.Any() && (emailMode == 2 || (emailMode == 1 && d.Member.ContactViaEmail))) .ToDictionary(d => d.Member, m => { var docs = m.Docs.Select(d => d.Doc).ToList(); foreach (var doc in docs) { doc!.DoubleSided = false; if (doc is BusinessDocument b) b.IncludeSender = false; }; return docs; }); var emailRecipients = email.Select(d => d.Key.MgNr).ToHashSet(); foreach (var item1 in email.Select((e, i) => new { Index = i, e.Key, e.Value })) { foreach (var item2 in item1.Value.Select((d, i) => new { Index = i, Doc = d})) { await item2.Doc.Generate(new Progress(v => { ProgressBar.Value = v * (item2.Index + 1) / item1.Value.Count / totalNum + 100.0 * item1.Index / totalNum; })); } } if (email.Count > 0) { EmailDocuments = email; } var printDocs = memberDocs .Where(d => printMode == 3 || (printMode == 2 && d.Member.ContactViaPost) || (printMode == 1 && !emailRecipients.Contains(d.Member.MgNr))) .SelectMany(m => { var docs = m.Docs.Select(d => d.Doc).ToList(); if (docs.Count == 0 || m.Docs[0].Type == DocType.Custom) { docs.Insert(0, new Letterhead(m.Member)); } docs.ForEach(doc => doc.DoubleSided = doublePaged); if (docs.Count > 0 && docs[0] is BusinessDocument b) b.IncludeSender = true; return docs; }) .ToList(); if (printDocs.Count > 0) { var print = Document.Merge(printDocs); print.DoubleSided = doublePaged; await print.Generate(new Progress(v => { ProgressBar.Value = 100.0 * emailNum / totalNum + v * printNum / totalNum; })); PrintDocument = print; } ProgressBar.Value = 100.0; GenerateButton.IsEnabled = true; Mouse.OverrideCursor = null; PreviewButton.IsEnabled = true; //PrintButton.IsEnabled = true; //EmailButton.IsEnabled = true; } private void PreviewButton_Click(object sender, RoutedEventArgs evt) { var d = new OpenFolderDialog() { Title = "Ordner auswählen - Elwig", }; if (d.ShowDialog() == true) { Mouse.OverrideCursor = Cursors.AppStarting; PrintDocument?.SaveTo($"{d.FolderName}/Print.pdf"); if (EmailDocuments != null) { foreach (var (m, docs) in EmailDocuments) { var folder = $"{d.FolderName}/E-Mail/{m.AdministrativeName}"; Directory.CreateDirectory(folder); foreach (var item in docs.Select((d, i) => new { Index = i, Doc = d })) { var doc = item.Doc; var name = Regex.Replace(doc.Title.Replace('/', '-'), @"[^A-Za-z0-9ÄÜÖẞäöüß-]+", "_"); doc.SaveTo($"{folder}/{item.Index + 1:00}.{name}.pdf"); } } } Mouse.OverrideCursor = null; Process.Start("explorer.exe", d.FolderName); } } public void AddDeliveryConfirmation() { AvaiableDocumentsList.SelectedIndex = 1; if (AvaiableDocumentsList.SelectedItem is not string s || SelectedDocs.Any(d => d.Type == DocType.DeliveryConfirmation)) return; SelectedDocs.Add(new(DocType.DeliveryConfirmation, s, ((int)Year!, DocumentNonDeliverersInput.IsChecked == true))); SelectedDocumentsList.SelectedIndex = SelectedDocs.Count - 1; } public void AddCreditNote(int index) { AvaiableDocumentsList.SelectedIndex = 2 + index; if (AvaiableDocumentsList.SelectedItem is not string s || SelectedDocs.Any(d => d.Type == DocType.CreditNote)) return; var name = s.Split(" – ")[^1]; var pv = Context.PaymentVariants.Single(v => v.Year == Year && v.Name == name)!; SelectedDocs.Add(new(DocType.CreditNote, s, (pv.Year, pv.AvNr))); SelectedDocumentsList.SelectedIndex = SelectedDocs.Count - 1; } } }