[#15] MailWindow: Add Rundschreiben-Funktion

This commit is contained in:
2024-02-29 10:48:48 +01:00
parent 92c3ed991b
commit e5c462b43f
10 changed files with 922 additions and 50 deletions

View File

@ -0,0 +1,557 @@
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,
];
protected Season? Season;
public ObservableCollection<SelectedDoc> SelectedDocs = [];
public IEnumerable<Member> Recipients = [];
protected Document? PrintDocument;
protected Dictionary<Member, List<Document>>? 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() {
InitializeComponent();
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() {
Season = await Context.Seasons.OrderBy(s => s.Year).LastOrDefaultAsync();
var l = new List<string> {
MemberDataSheet.Name
};
if (Season != null) {
l.Add($"{DeliveryConfirmation.Name} {Season.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, (Season!.Year, DocumentNonDeliverersInput.IsChecked == true)));
} else if (idx >= 2) {
var name = s.Split(" ")[^1];
var pv = Context.PaymentVariants.Single(v => v.Year == Season!.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-Datei (*.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<Member>().ToList();
} else {
// FIXME NOT WORKING ON SECOND OPENING OF WINDOW
var year = (!await Context.Deliveries.AnyAsync()) ? 0 : await Context.Deliveries.Select(d => d.Year).MaxAsync();
IQueryable<Member> query = Context.Members.Where(m => m.IsActive);
if (MemberBranchInput.SelectedItems.Count != MemberBranchInput.Items.Count) {
var zwst = MemberBranchInput.SelectedItems.Cast<Branch>().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<AT_Kg>().Select(k => k.KgNr).ToList();
query = query.Where(m => kgs.Contains((int)m.DefaultKgNr));
}
if (RecipientsAreaComMembersInput.IsChecked == true) {
var vtrg = MemberAreaComInput.SelectedItems.Cast<AreaComType>().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<Member> 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<int, IDictionary<int, DeliveryConfirmationDeliveryData>> dcData = [];
Dictionary<(int, int), (IDictionary<int, CreditNoteDeliveryData>, IDictionary<int, PaymentMember>, 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<SelectedDoc, GeneratedDoc>(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<double>(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<double>(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);
}
}
}
}