From e983ef918d050dfd7f87b120b940271da46570e2 Mon Sep 17 00:00:00 2001 From: Lorenz Stechauner Date: Wed, 1 Apr 2026 16:24:03 +0200 Subject: [PATCH] [WIP] Models/Entities: Remove EF proxies --- Elwig/Documents/BusinessDocument.cs | 8 +- Elwig/Documents/CreditNote.cs | 102 +++++++++++------- Elwig/Documents/DeliveryConfirmation.cs | 36 +++++-- Elwig/Documents/DeliveryNote.cs | 38 ++++++- Elwig/Documents/Document.cs | 6 +- Elwig/Documents/MemberDataSheet.cs | 49 ++++++--- Elwig/Documents/PaymentVariantSummary.cs | 94 +++++++++------- Elwig/Elwig.csproj | 1 - Elwig/Helpers/AppDbContext.cs | 48 ++++++++- Elwig/Helpers/Utils.cs | 16 ++- Elwig/Models/Dtos/CreditNoteDeliveryData.cs | 4 +- .../Dtos/DeliveryConfirmationDeliveryData.cs | 2 +- Elwig/Models/Dtos/MemberListData.cs | 2 +- Elwig/Models/Entities/Delivery.cs | 15 ++- Elwig/Models/Entities/DeliveryPart.cs | 2 +- Elwig/Models/Entities/WineOrigin.cs | 2 +- Elwig/Services/DeliveryService.cs | 48 ++++++--- Elwig/Services/MemberService.cs | 27 ++--- Elwig/Services/PaymentVariantService.cs | 4 +- Elwig/ViewModels/MemberAdminViewModel.cs | 2 +- Elwig/Windows/DeliveryAdminWindow.xaml.cs | 53 ++++----- Elwig/Windows/MailWindow.xaml.cs | 19 ++-- Elwig/Windows/MemberAdminWindow.xaml.cs | 7 +- Tests/Resources/Sql/DocumentInsert.sql | 4 + .../UnitTests/DocumentTests/CreditNoteTest.cs | 10 +- .../DocumentTests/DeliveryConfirmationTest.cs | 2 +- .../DocumentTests/DeliveryNoteTest.cs | 57 +++++++--- .../DocumentTests/MemberDataSheetTest.cs | 5 +- .../PaymentVariantSummaryTest.cs | 11 +- Tests/UnitTests/DocumentTests/Utils.cs | 5 +- 30 files changed, 428 insertions(+), 251 deletions(-) diff --git a/Elwig/Documents/BusinessDocument.cs b/Elwig/Documents/BusinessDocument.cs index 8901917..dc4e993 100644 --- a/Elwig/Documents/BusinessDocument.cs +++ b/Elwig/Documents/BusinessDocument.cs @@ -261,7 +261,7 @@ namespace Elwig.Documents { } protected Table NewBucketTable( - Season season, Dictionary buckets, + Season season, Dictionary buckets, int deliveredWeight, bool includeDelivery = true, bool includePayment = false, bool isTiny = false, IEnumerable? filter = null ) { @@ -315,10 +315,8 @@ namespace Elwig.Documents { .OrderBy(b => b.Value.Name); tbl.AddCell(NewBucketTh("Gesamtlieferung lt. gez. GA", isTiny: isTiny)); - tbl.AddCells(FormatRow(Member.BusinessShares * season.MinKgPerBusinessShare, - Member.BusinessShares * season.MaxKgPerBusinessShare, - season.Deliveries.Where(d => d.MgNr == Member.MgNr).Sum(d => d.Weight), - isGa: true, showPayment: includePayment, showArea: !includeDelivery, isTiny: isTiny)); + tbl.AddCells(FormatRow(Member.BusinessShares * season.MinKgPerBusinessShare, Member.BusinessShares * season.MaxKgPerBusinessShare, + deliveredWeight, isGa: true, showPayment: includePayment, showArea: !includeDelivery, isTiny: isTiny)); if (fbs.Any()) { tbl.AddCell(NewBucketSubHdr("Flächenbindungen" + (vtr.Any() ? " (inkl. Verträge)" : "") + ":", includePayment ? 8 : 7, isTiny: isTiny)); diff --git a/Elwig/Documents/CreditNote.cs b/Elwig/Documents/CreditNote.cs index 3b5bd57..a41360c 100644 --- a/Elwig/Documents/CreditNote.cs +++ b/Elwig/Documents/CreditNote.cs @@ -1,55 +1,82 @@ using Elwig.Helpers; +using Elwig.Helpers.Billing; using Elwig.Models.Dtos; using Elwig.Models.Entities; using iText.Kernel.Pdf; using iText.Layout.Borders; using iText.Layout.Element; using iText.Layout.Properties; +using Microsoft.EntityFrameworkCore; using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; namespace Elwig.Documents { public class CreditNote : BusinessDocument { public new static string Name => "Traubengutschrift"; - public PaymentMember? Payment; + public PaymentMember Payment; public Credit? Credit; - public CreditNoteDeliveryData Data; public string? Text; public string CurrencySymbol; public int Precision; - public string MemberModifier; + public string? MemberModifier; public List<(string Name, int Kg, decimal Amount)>? MemberUnderDeliveries; public decimal MemberTotalUnderDelivery; public int MemberAutoBusinessShares; public decimal MemberAutoBusinessSharesAmount; public PaymentCustom? CustomPayment; - public CreditNote( - AppDbContext ctx, - PaymentMember p, - CreditNoteDeliveryData data, - bool considerContractPenalties, - bool considerTotalPenalty, - bool considerAutoBusinessShares, - bool considerCustomModifiers, - Dictionary? underDeliveries = null - ) : + protected bool ConsiderContractPenalties; + protected bool ConsiderTotalPenalty; + protected bool ConsiderAutoBusinessShares; + protected bool ConsiderCustomModifiers; + + private CreditNoteDeliveryData? _data; + private Dictionary? _underDeliveries; + + public CreditNote(PaymentMember p, BillingData? billingData = null, CreditNoteDeliveryData? data = null, Dictionary? underDeliveries = null) : base($"{Name} {(p.Credit != null ? $"Nr. {p.Credit.Year}/{p.Credit.TgNr:000}" : p.Member.FullName)} – {p.Variant.Name}", p.Member) { UseBillingAddress = true; ShowDateAndLocation = true; - Data = data; Payment = p; Credit = p.Credit; - IsPreview = Payment == null || Credit == null; - var season = p.Variant.Season; - if (considerCustomModifiers) { - CustomPayment = ctx.CustomPayments.Find(p.Year, p.MgNr); + Text = App.Client.TextCreditNote; + DocumentId = $"Tr.-Gutschr. " + (Credit != null ? $"{Credit.Year}/{Credit.TgNr:000}" : Payment.MgNr); + IsPreview = Credit == null; + _data = data; + _underDeliveries = underDeliveries; + CurrencySymbol = Payment.Variant.Season.Currency.Symbol ?? Payment.Variant.Season.Currency.Code; + Precision = Payment.Variant.Season.Precision; + + billingData ??= BillingData.FromJson(Payment.Variant.Data); + ConsiderContractPenalties = billingData.ConsiderContractPenalties; + ConsiderTotalPenalty = billingData.ConsiderTotalPenalty; + ConsiderAutoBusinessShares = billingData.ConsiderAutoBusinessShares; + ConsiderCustomModifiers = billingData.ConsiderCustomModifiers; + } + + public static async Task Initialize(int year, int avnr, int mgnr, BillingData? billingData = null, CreditNoteDeliveryData? data = null, Dictionary? underDeliveries = null) { + using var ctx = new AppDbContext(); + var p = await ctx.MemberPayments + .Where(p => p.Year == year && p.AvNr == avnr && p.MgNr == mgnr) + .SingleAsync(); + return new CreditNote(p, billingData, data, underDeliveries); + } + + protected override async Task LoadData(AppDbContext ctx) { + await base.LoadData(ctx); + var season = Payment.Variant.Season; + if (ConsiderCustomModifiers) { + CustomPayment = await ctx.CustomPayments.FindAsync(Payment.Year, Payment.MgNr); } - var mod = App.Client.IsMatzen ? ctx.Modifiers.Where(m => m.Year == season.Year && m.Name.StartsWith("Treue")).FirstOrDefault() : null; + _data ??= (await CreditNoteDeliveryData.ForPaymentVariant(ctx.CreditNoteDeliveryRows, ctx.PaymentVariants, Payment.Year, Payment.AvNr))[Member.MgNr]; + _underDeliveries ??= await ctx.GetMemberUnderDelivery(Payment.Year, Member.MgNr); + + var mod = App.Client.IsMatzen ? await ctx.Modifiers.Where(m => m.Year == season.Year && m.Name.StartsWith("Treue")).FirstOrDefaultAsync() : null; if (CustomPayment?.ModComment != null) { MemberModifier = CustomPayment.ModComment; } else if (mod != null) { @@ -57,32 +84,28 @@ namespace Elwig.Documents { } else { MemberModifier = "Sonstige Zu-/Abschläge"; } - Text = App.Client.TextCreditNote; - DocumentId = $"Tr.-Gutschr. " + (p.Credit != null ? $"{p.Credit.Year}/{p.Credit.TgNr:000}" : p.MgNr); - CurrencySymbol = season.Currency.Symbol ?? season.Currency.Code; - Precision = season.Precision; - if (considerTotalPenalty) { - var total = data.Rows.SelectMany(r => r.Buckets).Sum(b => b.Value); - var totalUnderDelivery = total - p.Member.BusinessShares * season.MinKgPerBusinessShare; + if (ConsiderTotalPenalty) { + var total = _data.Rows.SelectMany(r => r.Buckets).Sum(b => b.Value); + var totalUnderDelivery = total - Member.BusinessShares * season.MinKgPerBusinessShare; MemberTotalUnderDelivery = totalUnderDelivery < 0 ? totalUnderDelivery * (season.PenaltyPerKg ?? 0) - (season.PenaltyAmount ?? 0) - (season.PenaltyPerBsAmount * Math.Floor(-(decimal)totalUnderDelivery / season.MinKgPerBusinessShare) ?? 0) : 0; if (total == 0) - MemberTotalUnderDelivery -= (season.PenaltyNone ?? 0) + (season.PenaltyPerBsNone * p.Member.BusinessShares ?? 0); + MemberTotalUnderDelivery -= (season.PenaltyNone ?? 0) + (season.PenaltyPerBsNone * Member.BusinessShares ?? 0); } - if (considerAutoBusinessShares) { + if (ConsiderAutoBusinessShares) { var fromDate = $"{season.Year}-01-01"; var toDate = $"{season.Year}-12-31"; - MemberAutoBusinessShares = ctx.MemberHistory - .Where(h => h.MgNr == p.Member.MgNr && h.Type == "auto") + MemberAutoBusinessShares = await ctx.MemberHistory + .Where(h => h.MgNr == Member.MgNr && h.Type == "auto") .Where(h => h.DateString.CompareTo(fromDate) >= 0 && h.DateString.CompareTo(toDate) <= 0) - .Sum(h => h.BusinessShares); + .SumAsync(h => h.BusinessShares); MemberAutoBusinessSharesAmount = MemberAutoBusinessShares * (-season.BusinessShareValue ?? 0); } - if (considerContractPenalties) { - var varieties = ctx.WineVarieties.ToDictionary(v => v.SortId, v => v); - var attributes = ctx.WineAttributes.ToDictionary(a => a.AttrId, a => a); - var comTypes = ctx.AreaCommitmentTypes.ToDictionary(t => t.VtrgId, t => t); - MemberUnderDeliveries = underDeliveries? + if (ConsiderContractPenalties) { + var varieties = await ctx.WineVarieties.ToDictionaryAsync(v => v.SortId, v => v); + var attributes = await ctx.WineAttributes.ToDictionaryAsync(a => a.AttrId, a => a); + var comTypes = await ctx.AreaCommitmentTypes.ToDictionaryAsync(t => t.VtrgId, t => t); + MemberUnderDeliveries = _underDeliveries? .OrderBy(u => u.Key) .Select(u => ( varieties[u.Key[..2]].Name + (u.Key.Length > 2 ? " " + attributes[u.Key[2..]].Name : ""), @@ -104,8 +127,9 @@ namespace Elwig.Documents { } protected override void RenderBody(iText.Layout.Document doc, PdfDocument pdf) { + if (_data == null) throw new Exception("Call LoadData before RenderBody"); base.RenderBody(doc, pdf); - doc.Add(NewCreditTable(Data)); + doc.Add(NewCreditTable(_data)); var div = new Table(ColsMM(60, 105)) .SetWidth(UnitValue.CreatePercentValue(100)).SetFixedLayout() @@ -131,7 +155,7 @@ namespace Elwig.Documents { .SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE) .SetKeepTogether(true); - var sum = Data.Rows.Sum(p => p.Amount); + var sum = _data.Rows.Sum(p => p.Amount); if (Payment == null) { tbl1.AddCells(FormatRow("Gesamt", sum, bold: true, noTopBorder: true)); } else { @@ -139,7 +163,7 @@ namespace Elwig.Documents { if (Payment.NetAmount != Payment.Amount) { tbl1.AddCells(FormatRow("Zwischensumme", Payment.NetAmount, noTopBorder: noBorder)); noBorder = false; - tbl1.AddCells(FormatRow(MemberModifier, Payment.Amount - Payment.NetAmount, add: true)); + tbl1.AddCells(FormatRow(MemberModifier ?? "", Payment.Amount - Payment.NetAmount, add: true)); } if (Credit == null) { tbl1.AddCells(FormatRow("Gesamtbetrag", Payment.Amount, bold: true, noTopBorder: noBorder)); diff --git a/Elwig/Documents/DeliveryConfirmation.cs b/Elwig/Documents/DeliveryConfirmation.cs index 38c32ce..5e828c2 100644 --- a/Elwig/Documents/DeliveryConfirmation.cs +++ b/Elwig/Documents/DeliveryConfirmation.cs @@ -5,33 +5,48 @@ using iText.Kernel.Pdf; using iText.Layout.Borders; using iText.Layout.Element; using iText.Layout.Properties; +using Microsoft.EntityFrameworkCore; using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; namespace Elwig.Documents { public class DeliveryConfirmation : BusinessDocument { public new static string Name => "Anlieferungsbestätigung"; - public Season Season; - public DeliveryConfirmationDeliveryData Data; + private readonly int _year; + public Season? Season; + public int MemberDeliveredWeight; + public DeliveryConfirmationDeliveryData? Data; public string? Text = App.Client.TextDeliveryConfirmation; - public Dictionary MemberBuckets; - public List MemberStats; + public Dictionary MemberBuckets = []; + public List MemberStats = []; - public DeliveryConfirmation(AppDbContext ctx, int year, Member m, DeliveryConfirmationDeliveryData data) : + public DeliveryConfirmation(int year, Member m, DeliveryConfirmationDeliveryData? data = null) : base($"{Name} {year}", m) { - Season = ctx.Seasons.Find(year) ?? throw new ArgumentException("invalid season"); + _year = year; ShowDateAndLocation = true; UseBillingAddress = true; - DocumentId = $"Anl.-Best. {Season.Year}/{m.MgNr}"; + DocumentId = $"Anl.-Best. {_year}/{m.MgNr}"; Data = data; - MemberBuckets = ctx.GetMemberBuckets(Season.Year, m.MgNr).GetAwaiter().GetResult(); - MemberStats = AppDbContext.GetMemberStats(Season.Year, m.MgNr).GetAwaiter().GetResult(); + } + + protected override async Task LoadData(AppDbContext ctx) { + await base.LoadData(ctx); + Season = ctx.Seasons.Find(_year) ?? throw new ArgumentException("invalid season"); + MemberDeliveredWeight = await ctx.Deliveries + .Where(d => d.Year == Season.Year && d.MgNr == Member.MgNr) + .SelectMany(d => d.Parts) + .SumAsync(p => p.Weight); + MemberBuckets = await ctx.GetMemberBuckets(Season.Year, Member.MgNr); + MemberStats = await AppDbContext.GetMemberStats(Season.Year, Member.MgNr); + Data ??= await DeliveryConfirmationDeliveryData.ForMember(ctx.DeliveryParts, Season.Year, Member); } protected override void BeforeRenderBody(iText.Layout.Document doc, PdfDocument pdf) { + if (Data == null) throw new Exception("Call LoadData before BeforeRenderBody"); base.BeforeRenderBody(doc, pdf); var firstDay = Data.Rows.MinBy(r => r.Date)?.Date; var lastDay = Data.Rows.MaxBy(r => r.Date)?.Date; @@ -42,12 +57,13 @@ namespace Elwig.Documents { } protected override void RenderBody(iText.Layout.Document doc, PdfDocument pdf) { + if (Season == null || Data == null) throw new Exception("Call LoadData before RenderBody"); base.RenderBody(doc, pdf); doc.Add(NewDeliveryListTable(Data)); doc.Add(NewWeightsTable(MemberStats) .SetMarginTopMM(10).SetKeepTogether(true)); - doc.Add(NewBucketTable(Season, MemberBuckets, includePayment: true) + doc.Add(NewBucketTable(Season, MemberBuckets, MemberDeliveredWeight, includePayment: true) .SetMarginTopMM(10).SetKeepTogether(true)); if (Text != null) { diff --git a/Elwig/Documents/DeliveryNote.cs b/Elwig/Documents/DeliveryNote.cs index d3e8c23..93f87dc 100644 --- a/Elwig/Documents/DeliveryNote.cs +++ b/Elwig/Documents/DeliveryNote.cs @@ -5,10 +5,12 @@ using iText.Layout.Borders; using iText.Layout.Element; using iText.Layout.Layout; using iText.Layout.Properties; +using Microsoft.EntityFrameworkCore; using System; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; +using System.Threading.Tasks; namespace Elwig.Documents { public class DeliveryNote : BusinessDocument { @@ -17,7 +19,8 @@ namespace Elwig.Documents { public Delivery Delivery; public string? Text; - public Dictionary MemberBuckets; + public int MemberDeliveredWeight; + public Dictionary MemberBuckets = []; // 0 - none // 1 - GA only @@ -25,7 +28,7 @@ namespace Elwig.Documents { // 3 - full public int DisplayStats = App.Client.ModeDeliveryNoteStats; - public DeliveryNote(Delivery d, AppDbContext? ctx = null) : + public DeliveryNote(Delivery d) : base($"{Name} Nr. {d.LsNr}", d.Member) { UseBillingAddress = true; ShowDateAndLocation = true; @@ -34,7 +37,34 @@ namespace Elwig.Documents { DocumentId = d.LsNr; Date = DateOnly.FromDateTime(d.ModifiedAt); IsDoublePaged = true; - MemberBuckets = ctx?.GetMemberBuckets(d.Year, d.Member.MgNr).GetAwaiter().GetResult() ?? []; + } + + public static async Task Initialize(int year, int did) { + using var ctx = new AppDbContext(); + await ctx.WineOrigins.LoadAsync(); + var d = await ctx.Deliveries + .Where(d => d.Year == year && d.DId == did) + .Include(d => d.Parts).ThenInclude(p => p.PartModifiers) + .SingleAsync(); + return new DeliveryNote(d); + } + + public static async Task Initialize(string lsnr) { + using var ctx = new AppDbContext(); + await ctx.WineOrigins.LoadAsync(); + var d = await ctx.Deliveries + .Where(d => d.LsNr == lsnr) + .Include(d => d.Parts).ThenInclude(p => p.PartModifiers) + .SingleAsync(); + return new DeliveryNote(d); + } + + protected override async Task LoadData(AppDbContext ctx) { + await base.LoadData(ctx); + MemberDeliveredWeight = await ctx.DeliveryParts + .Where(d => d.Year == Delivery.Year && d.Delivery.MgNr == Member.MgNr) + .SumAsync(p => p.Weight); + MemberBuckets = await ctx.GetMemberBuckets(Delivery.Year, Member.MgNr) ?? []; } protected override void BeforeRenderBody(iText.Layout.Document doc, PdfDocument pdf) { @@ -53,7 +83,7 @@ namespace Elwig.Documents { doc.Add(new KernedParagraph($"Anmerkung zur Lieferung: {Delivery.Comment}", 10).SetMarginsMM(5, 0, 0, 0)); } if (DisplayStats > 0) { - doc.Add(NewBucketTable(Delivery.Season, MemberBuckets, isTiny: true, + doc.Add(NewBucketTable(Delivery.Season, MemberBuckets, MemberDeliveredWeight, isTiny: true, filter: DisplayStats > 2 ? null : DisplayStats == 1 ? [] : Delivery.Parts.Select(p => p.SortId).Distinct().ToList()) .SetKeepTogether(true) .SetMarginsMM(5, 0, 0, 0)); diff --git a/Elwig/Documents/Document.cs b/Elwig/Documents/Document.cs index 9be1be1..78836da 100644 --- a/Elwig/Documents/Document.cs +++ b/Elwig/Documents/Document.cs @@ -128,6 +128,8 @@ namespace Elwig.Documents { } } + protected virtual async Task LoadData(AppDbContext ctx) { } + protected virtual void BeforeRenderBody(iText.Layout.Document doc, PdfDocument pdf) { } protected virtual void RenderBody(iText.Layout.Document doc, PdfDocument pdf) { } @@ -136,7 +138,7 @@ namespace Elwig.Documents { return new KernedParagraph(App.Client.NameFull, 10); } - public async Task Generate(CancellationToken? cancelToken = null, IProgress? progress = null) { + public async Task Generate(AppDbContext ctx, CancellationToken? cancelToken = null, IProgress? progress = null) { if (_pdfFile != null) return; progress?.Report(0.0); @@ -181,6 +183,7 @@ namespace Elwig.Documents { merger.Merge(src, 1, src.GetNumberOfPages()); p += src.GetNumberOfPages(); } else { + await doc.LoadData(ctx); int pageNum = doc.Render(tmpPdf.FilePath); if (IsDoublePaged && doc is Letterhead) { using var reader = new PdfReader(tmpPdf.FilePath); @@ -233,6 +236,7 @@ namespace Elwig.Documents { throw new OperationCanceledException("Dokumentenerzeugung abgebrochen!"); var pdf = new TempFile("pdf"); try { + await LoadData(ctx); TotalPages = Render(pdf.FilePath); } catch { pdf.Dispose(); diff --git a/Elwig/Documents/MemberDataSheet.cs b/Elwig/Documents/MemberDataSheet.cs index 50a63e5..a11a4ff 100644 --- a/Elwig/Documents/MemberDataSheet.cs +++ b/Elwig/Documents/MemberDataSheet.cs @@ -5,32 +5,51 @@ using iText.Kernel.Pdf; using iText.Layout.Borders; using iText.Layout.Element; using iText.Layout.Properties; +using Microsoft.EntityFrameworkCore; using System; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; +using System.Threading.Tasks; namespace Elwig.Documents { public class MemberDataSheet : BusinessDocument { public new static string Name => "Stammdatenblatt"; - public Season Season; - public Dictionary MemberBuckets; - public List ActiveAreaCommitments; + public Season? Season; + public int MemberDeliveredWeight; + public Dictionary MemberBuckets = []; + public List ActiveAreaCommitments = []; - public MemberDataSheet(Member m, AppDbContext ctx) : + public MemberDataSheet(Member m) : base($"{Name} {m.AdministrativeName}", m) { DocumentId = $"{Name} {m.MgNr}"; - Season = ctx.Seasons.ToList().MaxBy(s => s.Year) ?? throw new ArgumentException("invalid season"); - MemberBuckets = ctx.GetMemberBuckets(Utils.CurrentYear, m.MgNr).GetAwaiter().GetResult(); - ActiveAreaCommitments = [.. m.ActiveAreaCommitments(ctx)]; + } + + public static async Task Initialize(int mgnr) { + using var ctx = new AppDbContext(); + return new MemberDataSheet(await ctx.Members.Include(m => m.EmailAddresses).Include(m => m.TelephoneNumbers).Where(m => m.MgNr == mgnr).SingleAsync()); + } + + protected override async Task LoadData(AppDbContext ctx) { + await base.LoadData(ctx); + Season = await ctx.Seasons.OrderBy(s => s.Year).LastOrDefaultAsync() ?? throw new ArgumentException("invalid season"); + MemberBuckets = await ctx.GetMemberBuckets(Utils.CurrentYear, Member.MgNr); + ActiveAreaCommitments = await Member.ActiveAreaCommitments(ctx) + .Include(c => c.Contract).ThenInclude(c => c.Revisions) + .ToListAsync(); + MemberDeliveredWeight = await ctx.Deliveries + .Where(d => d.Year == Season.Year && d.MgNr == Member.MgNr) + .SelectMany(d => d.Parts) + .SumAsync(p => p.Weight); } protected override void RenderBody(iText.Layout.Document doc, PdfDocument pdf) { + if (Season == null) throw new Exception("Call LoadData before RenderBody"); base.RenderBody(doc, pdf); - doc.Add(NewMemberData().SetMarginBottomMM(5)); - doc.Add(NewBucketTable(Season, MemberBuckets, includeDelivery: false)); + doc.Add(NewMemberData(Season).SetMarginBottomMM(5)); + doc.Add(NewBucketTable(Season, MemberBuckets, MemberDeliveredWeight, includeDelivery: false)); if (ActiveAreaCommitments.Count != 0) { bool firstOnPage = false; if (pdf.GetNumberOfPages() == 1) { @@ -38,7 +57,7 @@ namespace Elwig.Documents { firstOnPage = true; } doc.Add(new KernedParagraph(12).Add(Bold($"Flächenbindungen per {Date:dd.MM.yyyy}")).SetMargins(firstOnPage ? 0 : 24, 0, 12, 0)); - doc.Add(NewAreaComTable()); + doc.Add(NewAreaComTable(ActiveAreaCommitments)); } } @@ -52,7 +71,7 @@ namespace Elwig.Documents { .SetPaddingRightMM(0); } - protected Table NewMemberData() { + protected Table NewMemberData(Season season) { var tbl = new Table(ColsMM(30.0, 51.5, 20.0, 12.0, 18.0, 31.5)) .SetWidth(UnitValue.CreatePercentValue(100)).SetFixedLayout() .SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE) @@ -117,7 +136,7 @@ namespace Elwig.Documents { .AddCell(NewDataTh("UID:", colspan: 2)).AddCell(NewTd(Member.UstIdNr, colspan: 2)) .AddCell(NewDataTh("Stammgemeinde:")).AddCell(NewTd(Member.DefaultKg?.Name)) .AddCell(NewDataTh("Buchführend:", colspan: 2)).AddCell(NewTd(new KernedParagraph(Member.IsBuchführend ? "Ja " : "Nein ", 10) - .Add(Normal($"({(Member.IsBuchführend ? Season.VatNormal : Season.VatFlatrate) * 100:N0}% USt.)", 8)), colspan: 2)) + .Add(Normal($"({(Member.IsBuchführend ? season.VatNormal : season.VatFlatrate) * 100:N0}% USt.)", 8)), colspan: 2)) .AddCell(NewDataTh("(Katastralgemeinde mit dem größten Anteil an Weinbauflächen)", 8, colspan: 2)) .AddCell(NewDataTh("Bio:", colspan: 2)).AddCell(NewTd(Member.IsOrganic ? "Ja" : "Nein", colspan: 2)) .AddCell(NewDataHdr("Genossenschaft", colspan: 6)) @@ -134,8 +153,8 @@ namespace Elwig.Documents { return tbl; } - protected Table NewAreaComTable() { - var areaComs = ActiveAreaCommitments.GroupBy(a => a.AreaComType).Select(group => new { + protected Table NewAreaComTable(IEnumerable activeAreaComs) { + var areaComs = activeAreaComs.GroupBy(a => a.AreaComType).Select(group => new { Type = group.Key, AreaComs = group.OrderBy(c => c.Contract.Kg.AtKg.Name).ToList(), Size = group.Sum(c => c.Area) @@ -177,7 +196,7 @@ namespace Elwig.Documents { } tbl.AddCell(NewTd("Gesamt:", 12, colspan: 2, bold: true, borderTop: true).SetPaddingsMM(1, 1, 1, 1)); - tbl.AddCell(NewTd($"{ActiveAreaCommitments.Sum(a => a.Area):N0}", 12, colspan: 2, right: true, bold: true, borderTop: true).SetPaddingsMM(1, 1, 1, 1)); + tbl.AddCell(NewTd($"{activeAreaComs.Sum(a => a.Area):N0}", 12, colspan: 2, right: true, bold: true, borderTop: true).SetPaddingsMM(1, 1, 1, 1)); tbl.AddCell(NewTd(colspan: 2, borderTop: true).SetPaddingsMM(1, 1, 1, 1)); return tbl; diff --git a/Elwig/Documents/PaymentVariantSummary.cs b/Elwig/Documents/PaymentVariantSummary.cs index a994097..2b156aa 100644 --- a/Elwig/Documents/PaymentVariantSummary.cs +++ b/Elwig/Documents/PaymentVariantSummary.cs @@ -7,40 +7,60 @@ using iText.Kernel.Pdf; using iText.Layout.Borders; using iText.Layout.Element; using iText.Layout.Properties; +using Microsoft.EntityFrameworkCore; using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; namespace Elwig.Documents { public class PaymentVariantSummary : Document { public new static string Name => "Auszahlungsvariante"; - public PaymentVariantSummaryData Data; + public PaymentVariantSummaryData? Data; public PaymentVar Variant; public BillingData BillingData; public string CurrencySymbol; public int MemberNum; public int DeliveryNum; public int DeliveryPartNum; - public List ModifierStat; - public Dictionary Modifiers; + public List? ModifierStat; + public Dictionary? Modifiers; - public PaymentVariantSummary(PaymentVar v, PaymentVariantSummaryData data) : + private List _credits = []; + private List _parts = []; + + public PaymentVariantSummary(PaymentVar v, PaymentVariantSummaryData? data = null) : base($"{Name} {v.Year} - {v.Name}") { Variant = v; BillingData = BillingData.FromJson(v.Data); Data = data; CurrencySymbol = v.Season.Currency.Symbol ?? v.Season.Currency.Code; - MemberNum = v.Credits.Count; + } + + public static async Task Initialize(int year, int avnr, PaymentVariantSummaryData? data = null) { + using var ctx = new AppDbContext(); + var v = await ctx.PaymentVariants + .Where(v => v.Year == year && v.AvNr == avnr) + .SingleAsync(); + return new PaymentVariantSummary(v, data); + } + + protected override async Task LoadData(AppDbContext ctx) { + _credits = await ctx.Credits.Where(c => c.Year == Variant.Year && c.AvNr == Variant.AvNr).ToListAsync(); + _parts = await ctx.PaymentDeliveryParts.Where(p => p.Year == Variant.Year && p.AvNr == Variant.AvNr).ToListAsync(); + MemberNum = _credits.Count; IsPreview = MemberNum == 0; - DeliveryNum = v.DeliveryPartPayments.DistinctBy(p => p.DeliveryPart.Delivery).Count(); - DeliveryPartNum = v.DeliveryPartPayments.Count; - ModifierStat = AppDbContext.GetModifierStats(v.Year, v.AvNr).GetAwaiter().GetResult(); - Modifiers = v.Season.Modifiers.ToDictionary(m => m.ModId); + DeliveryNum = await ctx.Deliveries.Where(d => d.Year == Variant.Year).CountAsync(); + DeliveryPartNum = await ctx.DeliveryParts.Where(d => d.Year == Variant.Year).CountAsync(); + Data ??= await PaymentVariantSummaryData.ForPaymentVariant(Variant, ctx.PaymentVariantSummaryRows); + ModifierStat = await AppDbContext.GetModifierStats(Variant.Year, Variant.AvNr); + Modifiers = await ctx.Modifiers.Where(m => m.Year == Variant.Year).ToDictionaryAsync(m => m.ModId); } protected override void RenderBody(iText.Layout.Document doc, PdfDocument pdf) { + if (Data == null || Modifiers == null || ModifierStat == null) throw new Exception("Call LoadData before RenderBody"); base.RenderBody(doc, pdf); doc.Add(new KernedParagraph($"{Name} Lese {Variant.Year}", 24) .SetTextAlignment(TextAlignment.CENTER).SetFont(BF) @@ -48,10 +68,10 @@ namespace Elwig.Documents { doc.Add(new KernedParagraph(Variant.Name, 14) .SetTextAlignment(TextAlignment.CENTER).SetFont(BF) .SetMarginsMM(0, 0, 10, 0)); - doc.Add(NewVariantStatTable().SetMarginBottomMM(10)); - doc.Add(NewModifierStatTable()); + doc.Add(NewVariantStatTable(Data).SetMarginBottomMM(10)); + doc.Add(NewModifierStatTable(Modifiers, ModifierStat)); doc.Add(new AreaBreak(AreaBreakType.NEXT_PAGE)); - doc.Add(NewPriceTable()); + doc.Add(NewPriceTable(Data)); } protected Cell NewSectionHdr(string text, int colspan = 1, bool borderLeft = false) { @@ -67,33 +87,33 @@ namespace Elwig.Documents { .SetBorderLeft(borderLeft ? new SolidBorder(BorderThickness) : Border.NO_BORDER); } - protected Table NewVariantStatTable() { + protected Table NewVariantStatTable(PaymentVariantSummaryData data) { var tbl = new Table(ColsMM(20, 30, 4.5, 4.5, 23.5, 47.5, 15, 20)) .SetWidth(UnitValue.CreatePercentValue(100)).SetFixedLayout() .SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE) .SetBorder(new SolidBorder(BorderThickness)); - //var sum1 = Variant.DeliveryPartPayments.Sum(p => p.NetAmount); - //var sum2 = Variant.Credits.Sum(p => p.); //Variant.MemberPayments.Sum(p => p.Amount); - var deliveryModifiers = Variant.DeliveryPartPayments.Sum(p => p.Amount - p.NetAmount); - var memberModifiers = Variant.Credits.Sum(c => c.Payment.Amount - c.Payment.NetAmount); - var sum2 = Variant.Credits.Sum(p => p.NetAmount); + //var sum1 = _parts.Sum(p => p.NetAmount); + //var sum2 = _credits.Sum(p => p.); //Variant.MemberPayments.Sum(p => p.Amount); + var deliveryModifiers = _parts.Sum(p => p.Amount - p.NetAmount); + var memberModifiers = _credits.Sum(c => c.Payment.Amount - c.Payment.NetAmount); + var sum2 = _credits.Sum(p => p.NetAmount); var sum1 = sum2 - deliveryModifiers - memberModifiers; - var payed = -Variant.Credits.Sum(p => p.PrevNetAmount ?? 0m); - var netSum = Variant.Credits.Sum(p => p.NetAmount) - Variant.Credits.Sum(p => p.PrevNetAmount ?? 0m); - var vat = Variant.Credits.Sum(p => p.VatAmount); - var grossSum = Variant.Credits.Sum(p => p.GrossAmount); - var totalMods = Variant.Credits.Sum(p => p.Modifiers ?? 0m); - var considered = -Variant.Credits.Sum(p => p.PrevModifiers ?? 0m); - var totalSum = Variant.Credits.Sum(p => p.Amount); + var payed = -_credits.Sum(p => p.PrevNetAmount ?? 0m); + var netSum = _credits.Sum(p => p.NetAmount) - _credits.Sum(p => p.PrevNetAmount ?? 0m); + var vat = _credits.Sum(p => p.VatAmount); + var grossSum = _credits.Sum(p => p.GrossAmount); + var totalMods = _credits.Sum(p => p.Modifiers ?? 0m); + var considered = -_credits.Sum(p => p.PrevModifiers ?? 0m); + var totalSum = _credits.Sum(p => p.Amount); - var weiRows = Data.Rows.Where(r => r.QualityLevel == "Wein"); + var weiRows = data.Rows.Where(r => r.QualityLevel == "Wein"); var minWei = weiRows.Min(r => r.Ungeb.MinPrice); var maxWei = weiRows.Max(r => r.Ungeb.MaxPrice); - var quwRows = Data.Rows.Where(r => r.QualityLevel != "Wein"); + var quwRows = data.Rows.Where(r => r.QualityLevel != "Wein"); var minPrice = quwRows.Min(r => r.Ungeb.MinPrice); var maxPrice = quwRows.Max(r => r.Ungeb.MaxPrice); - var gebRows = Data.Rows + var gebRows = data.Rows .Where(r => r.Geb.MaxPrice != null && r.Ungeb.MinPrice != null) .Select(r => r.Geb.MaxPrice - r.Ungeb.MinPrice); var minGeb = gebRows.Min(); @@ -191,26 +211,26 @@ namespace Elwig.Documents { .AddCell(NewTd(CurrencySymbol)) .AddCell(NewTd($"{Math.Abs(totalMods):N2}", right: true)) .AddCell(NewSectionTh("Menge (ungebunden):", borderLeft: true, borderTop: true)) - .AddCell(NewTd($"{Data.Rows.Sum(r => r.Ungeb.Weight):N0} kg", colspan: 2, right: true, borderTop: true)) + .AddCell(NewTd($"{data.Rows.Sum(r => r.Ungeb.Weight):N0} kg", colspan: 2, right: true, borderTop: true)) .AddCell(NewSectionTh("Bereits berücksichtigte Abzüge:", colspan: 2)) .AddCell(NewTd(Utils.GetSign(considered))) .AddCell(NewTd(CurrencySymbol)) .AddCell(NewTd($"{Math.Abs(considered):N2}", right: true)) .AddCell(NewSectionTh("Menge (gebunden):", borderLeft: true)) - .AddCell(NewTd($"{Data.Rows.Sum(r => r.Geb.Weight + r.LowGeb.Weight):N0} kg", colspan: 2, right: true)) + .AddCell(NewTd($"{data.Rows.Sum(r => r.Geb.Weight + r.LowGeb.Weight):N0} kg", colspan: 2, right: true)) .AddCell(NewSectionTh("Auszahlungsbetrag:", colspan: 2)) .AddCell(NewTd(borderTop: true)) .AddCell(NewTd(CurrencySymbol, borderTop: true)) .AddCell(NewTd($"{totalSum:N2}", right: true, borderTop: true)) .AddCell(NewSectionTh("Gesamtmenge:", borderLeft: true)) - .AddCell(NewTd($"{Data.Rows.Sum(r => r.Ungeb.Weight + r.LowGeb.Weight + r.Geb.Weight):N0} kg", colspan: 2, right: true, borderTop: true)); + .AddCell(NewTd($"{data.Rows.Sum(r => r.Ungeb.Weight + r.LowGeb.Weight + r.Geb.Weight):N0} kg", colspan: 2, right: true, borderTop: true)); return tbl; } - protected Table NewModifierStatTable() { + protected Table NewModifierStatTable(Dictionary modifiers, IEnumerable modStat) { var tbl = new Table(ColsMM(35, 30, 25, 25, 25, 25)) .SetWidth(UnitValue.CreatePercentValue(100)).SetFixedLayout() .SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE) @@ -228,8 +248,8 @@ namespace Elwig.Documents { .AddCell(NewTh($"[{CurrencySymbol}]")) .AddCell(NewTh($"[{CurrencySymbol}]")); - foreach (var m in ModifierStat) { - var mod = Modifiers[m.ModId]; + foreach (var m in modStat) { + var mod = modifiers[m.ModId]; tbl.AddCell(NewTd(mod.Name, italic: true)) .AddCell(NewTd(mod.ValueStr, right: true)) .AddCell(NewTd($"{m.Count:N0}", right: true)) @@ -241,7 +261,7 @@ namespace Elwig.Documents { return tbl; } - protected Table NewPriceTable() { + protected Table NewPriceTable(PaymentVariantSummaryData data) { var tbl = new Table(ColsMM(25, 19, 18, 15, 18, 15, 18, 15, 22)) .SetWidth(UnitValue.CreatePercentValue(100)).SetFixedLayout() .SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE); @@ -262,10 +282,10 @@ namespace Elwig.Documents { .AddHeaderCell(NewTh($"[{CurrencySymbol}]")); string? lastHdr = null; - foreach (var row in Data.Rows) { + foreach (var row in data.Rows) { var hdr = $"{row.Variety}{(row.Attribute != null ? " / " : "")}{row.Attribute}{(row.Cultivation != null ? " / " : "")}{row.Cultivation}"; if (lastHdr != hdr) { - var rows = Data.Rows + var rows = data.Rows .Where(r => r.Variety == row.Variety && r.Attribute == row.Attribute && r.Cultivation == row.Cultivation) .ToList(); var border = lastHdr != null; diff --git a/Elwig/Elwig.csproj b/Elwig/Elwig.csproj index b3fe799..766e1f3 100644 --- a/Elwig/Elwig.csproj +++ b/Elwig/Elwig.csproj @@ -29,7 +29,6 @@ - diff --git a/Elwig/Helpers/AppDbContext.cs b/Elwig/Helpers/AppDbContext.cs index 6006449..89b30a8 100644 --- a/Elwig/Helpers/AppDbContext.cs +++ b/Elwig/Helpers/AppDbContext.cs @@ -119,11 +119,51 @@ namespace Elwig.Helpers { protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseSqlite(ConnectionString); - optionsBuilder.UseLazyLoadingProxies(); optionsBuilder.LogTo(Log, LogLevel.Information); base.OnConfiguring(optionsBuilder); } + protected override void OnModelCreating(ModelBuilder modelBuilder) { + modelBuilder.Entity().Navigation(k => k.AtKg).AutoInclude(); + modelBuilder.Entity().Navigation(k => k.Gl).AutoInclude(); + modelBuilder.Entity().Navigation(k => k.Gem).AutoInclude(); + modelBuilder.Entity().Navigation(p => p.Country).AutoInclude(); + modelBuilder.Entity().Navigation(p => p.AtPlz).AutoInclude(); + modelBuilder.Entity().Navigation(p => p.AtPlz).AutoInclude(); + modelBuilder.Entity().Navigation(p => p.Ort).AutoInclude(); + modelBuilder.Entity().Navigation(m => m.DefaultWbKg).AutoInclude(); + modelBuilder.Entity().Navigation(m => m.Country).AutoInclude(); + modelBuilder.Entity().Navigation(m => m.PostalDest).AutoInclude(); + modelBuilder.Entity().Navigation(m => m.BillingAddress).AutoInclude(); + modelBuilder.Entity().Navigation(a => a.Country).AutoInclude(); + modelBuilder.Entity().Navigation(a => a.PostalDest).AutoInclude(); + modelBuilder.Entity().Navigation(m => m.Season).AutoInclude(); + modelBuilder.Entity().Navigation(s => s.Currency).AutoInclude(); + modelBuilder.Entity().Navigation(v => v.Season).AutoInclude(); + modelBuilder.Entity().Navigation(p => p.Variant).AutoInclude(); + modelBuilder.Entity().Navigation(c => c.Payment).AutoInclude(); + modelBuilder.Entity().Navigation(d => d.Member).AutoInclude(); + modelBuilder.Entity().Navigation(d => d.Season).AutoInclude(); + modelBuilder.Entity().Navigation(d => d.Branch).AutoInclude(); + modelBuilder.Entity().Navigation(p => p.Quality).AutoInclude(); + modelBuilder.Entity().Navigation(p => p.Variety).AutoInclude(); + modelBuilder.Entity().Navigation(p => p.Attribute).AutoInclude(); + modelBuilder.Entity().Navigation(p => p.Cultivation).AutoInclude(); + modelBuilder.Entity().Navigation(p => p.Kg).AutoInclude(); + modelBuilder.Entity().Navigation(p => p.Rd).AutoInclude(); + modelBuilder.Entity().Navigation(m => m.Modifier).AutoInclude(); + modelBuilder.Entity().Navigation(c => c.Kg).AutoInclude(); + modelBuilder.Entity().Navigation(c => c.Rd).AutoInclude(); + modelBuilder.Entity().Navigation(c => c.Contract).AutoInclude(); + modelBuilder.Entity().Navigation(c => c.WineCult).AutoInclude(); + modelBuilder.Entity().Navigation(c => c.AreaComType).AutoInclude(); + modelBuilder.Entity().Navigation(c => c.WineVar).AutoInclude(); + modelBuilder.Entity().Navigation(c => c.WineAttr).AutoInclude(); + modelBuilder.Entity().Navigation(c => c.Credit).AutoInclude(); + modelBuilder.Entity().Navigation(c => c.Member).AutoInclude(); + modelBuilder.Entity().Navigation(c => c.Variant).AutoInclude(); + } + public override void Dispose() { base.Dispose(); LogFile?.Dispose(); @@ -399,10 +439,12 @@ namespace Elwig.Helpers { var paymentBuckets = await GetMemberPaymentBuckets(year, mgnr, cnx); if (ownCnx) await cnx.DisposeAsync(); + var varieties = await WineVarieties.ToDictionaryAsync(v => v.SortId); + var attributes = await WineAttributes.ToDictionaryAsync(a => a.AttrId); var buckets = new Dictionary(); foreach (var id in rightsAndObligations.Keys.Union(deliveryBuckets.Keys).Union(paymentBuckets.Keys)) { - var variety = await WineVarieties.FindAsync(id[..2]); - var attribute = await WineAttributes.FindAsync(id[2..]); + var variety = varieties.GetValueOrDefault(id[..2]); + var attribute = attributes.GetValueOrDefault(id[2..]); var name = (variety?.Name ?? "") + (id[2..] == "_" ? " (kein Qual.Wein)" : attribute != null ? $" ({attribute})" : ""); buckets[id] = new( name, diff --git a/Elwig/Helpers/Utils.cs b/Elwig/Helpers/Utils.cs index f48a2ea..681ec3b 100644 --- a/Elwig/Helpers/Utils.cs +++ b/Elwig/Helpers/Utils.cs @@ -557,7 +557,9 @@ namespace Elwig.Helpers { "Vorläufiges Dokument", MessageBoxButton.OK, MessageBoxImage.Error); return; } - await doc.Generate(); + using (var ctx = new AppDbContext()) { + await doc.Generate(ctx); + } await doc.Print(); } else if (mode == ExportMode.Email && emailData is (Member, string, string) e) { if (doc.IsPreview) { @@ -565,7 +567,9 @@ namespace Elwig.Helpers { "Vorläufiges Dokument", MessageBoxButton.OK, MessageBoxImage.Error); return; } - await doc.Generate(); + using (var ctx = new AppDbContext()) { + await doc.Generate(ctx); + } var success = await SendEmail(e.Member, e.Subject, e.Text, [doc]); if (success) MessageBox.Show("Die E-Mail wurde erfolgreich verschickt!\n\nEs kann einige Minuten dauern, bis die E-Mail im Posteingang des Empfängers aufscheint.", "E-Mail verschickt", @@ -582,12 +586,16 @@ namespace Elwig.Helpers { Title = $"{doc.Title} speichern unter - Elwig" }; if (d.ShowDialog() == true) { - await doc.Generate(); + using (var ctx = new AppDbContext()) { + await doc.Generate(ctx); + } doc.SaveTo(d.FileName); Process.Start("explorer.exe", d.FileName); } } else { - await doc.Generate(); + using (var ctx = new AppDbContext()) { + await doc.Generate(ctx); + } doc.Show(); } } diff --git a/Elwig/Models/Dtos/CreditNoteDeliveryData.cs b/Elwig/Models/Dtos/CreditNoteDeliveryData.cs index eabff29..7ad0b3d 100644 --- a/Elwig/Models/Dtos/CreditNoteDeliveryData.cs +++ b/Elwig/Models/Dtos/CreditNoteDeliveryData.cs @@ -28,9 +28,9 @@ namespace Elwig.Models.Dtos { } public static async Task> ForPaymentVariant(DbSet table, DbSet paymentVariants, int year, int avnr) { - var variant = await paymentVariants.FindAsync(year, avnr); + var variant = await paymentVariants.Include(v => v.Season.Modifiers).SingleAsync(v => v.Year == year && v.AvNr == avnr); BillingData? varData = null; - try { varData = variant != null ? BillingData.FromJson(variant.Data) : null; } catch { } + try { varData = variant.Data != null ? BillingData.FromJson(variant.Data) : null; } catch { } return (await FromDbSet(table, year, avnr)) .GroupBy( r => new { r.Year, r.AvNr, r.MgNr, r.TgNr, r.DId, r.DPNr }, diff --git a/Elwig/Models/Dtos/DeliveryConfirmationDeliveryData.cs b/Elwig/Models/Dtos/DeliveryConfirmationDeliveryData.cs index 4dac291..1bb8a50 100644 --- a/Elwig/Models/Dtos/DeliveryConfirmationDeliveryData.cs +++ b/Elwig/Models/Dtos/DeliveryConfirmationDeliveryData.cs @@ -65,7 +65,7 @@ namespace Elwig.Models.Dtos { JOIN delivery_part p ON (p.year, p.did, p.dpnr) = (v.year, v.did, v.dpnr) WHERE (p.year = {y} OR {y} IS NULL) AND (v.mgnr = {m} OR {m} IS NULL) ORDER BY p.year, v.mgnr, v.sortid, v.abgewertet ASC, v.attribute_prio DESC, COALESCE(v.attrid, '~'), v.kmw DESC, v.lsnr, v.dpnr - """).ToListAsync(); + """).IgnoreAutoIncludes().ToListAsync(); } } diff --git a/Elwig/Models/Dtos/MemberListData.cs b/Elwig/Models/Dtos/MemberListData.cs index e276e67..aed84a1 100644 --- a/Elwig/Models/Dtos/MemberListData.cs +++ b/Elwig/Models/Dtos/MemberListData.cs @@ -47,7 +47,7 @@ namespace Elwig.Models.Dtos { } public static async Task FromQuery(IQueryable query, List filterNames, IEnumerable filterAreaCom) { - var areaComs = await query.ToDictionaryAsync(m => m.MgNr, m => Utils.ActiveAreaCommitments(m.AreaCommitments)); + var areaComs = await query.Include(m => m.AreaCommitments).ToDictionaryAsync(m => m.MgNr, m => Utils.ActiveAreaCommitments(m.AreaCommitments)); return new((await query .Include(m => m.DefaultWbKg!.AtKg) .Include(m => m.Branch) diff --git a/Elwig/Models/Entities/Delivery.cs b/Elwig/Models/Entities/Delivery.cs index 0abe63e..34ecf48 100644 --- a/Elwig/Models/Entities/Delivery.cs +++ b/Elwig/Models/Entities/Delivery.cs @@ -106,21 +106,18 @@ namespace Elwig.Models.Entities { [InverseProperty(nameof(DeliveryPart.Delivery))] public virtual ICollection Parts { get; private set; } = null!; [NotMapped] - public IEnumerable FilteredParts => PartFilter == null ? Parts : Parts.Where(p => PartFilter(p)); + public IEnumerable FilteredParts { get; set; } = []; - [NotMapped] - public Predicate? PartFilter { get; set; } - - public int Weight => Parts.Select(p => p.Weight).Sum(); - public int FilteredWeight => FilteredParts.Select(p => p.Weight).Sum(); + public int Weight => Parts.Sum(p => p.Weight); + public int FilteredWeight => FilteredParts.Sum(p => p.Weight); public IEnumerable Vaributes => Parts .GroupBy(p => (p.SortId, p.AttrId, p.CultId)) - .OrderByDescending(g => g.Select(p => p.Weight).Sum()) + .OrderByDescending(g => g.Sum(p => p.Weight)) .Select(g => new RawVaribute(g.Key.SortId, g.Key.AttrId, g.Key.CultId)); public IEnumerable FilteredVaributes => FilteredParts .GroupBy(p => (p.SortId, p.AttrId, p.CultId)) - .OrderByDescending(g => g.Select(p => p.Weight).Sum()) + .OrderByDescending(g => g.Sum(p => p.Weight)) .Select(g => new RawVaribute(g.Key.SortId, g.Key.AttrId, g.Key.CultId)); public string VaributeString => string.Join(", ", Vaributes); public string FilteredVaributeString => string.Join(", ", FilteredVaributes); @@ -153,7 +150,7 @@ namespace Elwig.Models.Entities { Member.Name, Member.MiddleName, Member.GivenName, Member.BillingAddress?.FullName, Comment }.ToList(); - list.AddRange(Parts.Select(p => p.Comment).Distinct()); + list.AddRange(FilteredParts.Select(p => p.Comment).Distinct()); return Utils.GetSearchScore(list, keywords); } } diff --git a/Elwig/Models/Entities/DeliveryPart.cs b/Elwig/Models/Entities/DeliveryPart.cs index 0eb5d82..019d81a 100644 --- a/Elwig/Models/Entities/DeliveryPart.cs +++ b/Elwig/Models/Entities/DeliveryPart.cs @@ -184,7 +184,7 @@ namespace Elwig.Models.Entities { public virtual ICollection PartModifiers { get; private set; } = null!; [NotMapped] - public IEnumerable Modifiers => PartModifiers.Select(m => m.Modifier).OrderBy(m => m.Ordering); + public IEnumerable Modifiers => []; // PartModifiers.Select(m => m.Modifier).OrderBy(m => m.Ordering); [InverseProperty(nameof(PaymentDeliveryPart.DeliveryPart))] public virtual PaymentDeliveryPart? Payment { get; private set; } diff --git a/Elwig/Models/Entities/WineOrigin.cs b/Elwig/Models/Entities/WineOrigin.cs index 6e52d1f..5d2e5f4 100644 --- a/Elwig/Models/Entities/WineOrigin.cs +++ b/Elwig/Models/Entities/WineOrigin.cs @@ -33,7 +33,7 @@ namespace Elwig.Models.Entities { public string HkIdLevel => $"{new string(' ', Level * 2)}{HkId}"; - public int TotalChildNum => 1 + Children.Select(c => c.TotalChildNum).Sum(); + public int TotalChildNum => 1 + Children.Sum(c => c.TotalChildNum); private int SortKey1 => (Parent?.SortKey1 ?? 0) | (TotalChildNum << ((3 - Level) * 8)); public int SortKey => SortKey1 | ((Level < 3) ? (-1 >>> (Level * 8 + 8)) : 0); diff --git a/Elwig/Services/DeliveryService.cs b/Elwig/Services/DeliveryService.cs index b1e5a55..8b715da 100644 --- a/Elwig/Services/DeliveryService.cs +++ b/Elwig/Services/DeliveryService.cs @@ -472,6 +472,7 @@ namespace Elwig.Services { DeliveryPart p; using var ctx = new AppDbContext(); + using var tx = await ctx.Database.BeginTransactionAsync(); int year = oldYear ?? Utils.CurrentYear; int did = oldDid ?? await ctx.NextDId(year); int dpnr = oldDpnr ?? await ctx.NextDPNr(year, did); @@ -556,13 +557,13 @@ namespace Elwig.Services { if (originalMgNr != null && originalMgNr.Value != d.MgNr) { // update origin (KgNr), if default is selected var newKgNr = (await ctx.Members.FindAsync(d.MgNr))?.DefaultKgNr; - foreach (var part in d.Parts.Where(part => part.DPNr != dpnr && part.KgNr == originalMemberKgNr)) { - part.KgNr = newKgNr; - ctx.Update(part); - } + await ctx.DeliveryParts + .Where(p => p.Year == d.Year && p.DId == d.DId && p.DPNr != dpnr && p.KgNr == originalMemberKgNr) + .ExecuteUpdateAsync(u => u.SetProperty(p => p.KgNr, newKgNr)); } await ctx.SaveChangesAsync(); + await tx.CommitAsync(); return p; }); @@ -574,7 +575,10 @@ namespace Elwig.Services { using var ctx = new AppDbContext(); bool anyLeft = false; - var d = (await ctx.Deliveries.FindAsync(year, did))!; + var d = await ctx.Deliveries + .Where(d => d.Year == year && d.DId == did) + .Include(d => d.Parts).ThenInclude(p => p.PartModifiers) + .SingleAsync(); var lnr = await ctx.NextLNr(d.Date, d.ZwstId); n = new Delivery { Year = year, @@ -601,7 +605,11 @@ namespace Elwig.Services { anyLeft = true; p.Weight -= w; ctx.Update(p); - var s = ctx.CreateProxy(); + var s = new DeliveryPart { + SortId = null!, + QualId = null!, + HkId = null!, + }; var values = ctx.Entry(p).CurrentValues; ctx.Entry(s).CurrentValues.SetValues(values); s.Year = n.Year; @@ -633,7 +641,10 @@ namespace Elwig.Services { using var ctx = new AppDbContext(); var anyLeft = false; n = (await ctx.Deliveries.FirstAsync(d => d.LsNr == lsnr))!; - var d = (await ctx.Deliveries.FindAsync(year, did))!; + var d = await ctx.Deliveries + .Where(d => d.Year == year && d.DId == did) + .Include(d => d.Parts).ThenInclude(p => p.PartModifiers) + .SingleAsync(); var dpnr = await ctx.NextDPNr(n.Year, n.DId); foreach (var (p, w) in d.Parts.ToList().Zip(weights)) { if (w <= 0) { @@ -645,7 +656,11 @@ namespace Elwig.Services { anyLeft = true; p.Weight -= w; ctx.Update(p); - var s = ctx.CreateProxy(); + var s = new DeliveryPart { + SortId = null!, + QualId = null!, + HkId = null!, + }; var values = ctx.Entry(p).CurrentValues; ctx.Entry(s).CurrentValues.SetValues(values); s.Year = n.Year; @@ -674,7 +689,10 @@ namespace Elwig.Services { public static async Task DepreciateDelivery(int year, int did, int[] weights) { await Task.Run(async () => { using var ctx = new AppDbContext(); - var d = (await ctx.Deliveries.FindAsync(year, did))!; + var d = await ctx.Deliveries + .Where(d => d.Year == year && d.DId == did) + .Include(d => d.Parts).ThenInclude(p => p.PartModifiers) + .SingleAsync(); var dpnr = await ctx.NextDPNr(year, did); foreach (var (p, w) in d.Parts.ToList().Zip(weights)) { if (w <= 0) { @@ -686,7 +704,11 @@ namespace Elwig.Services { } else { p.Weight -= w; ctx.Update(p); - var n = ctx.CreateProxy(); + var n = new DeliveryPart { + SortId = null!, + QualId = null!, + HkId = null!, + }; var values = ctx.Entry(p).CurrentValues; ctx.Entry(n).CurrentValues.SetValues(values); n.DPNr = dpnr++; @@ -711,10 +733,8 @@ namespace Elwig.Services { Mouse.OverrideCursor = Cursors.Wait; await Task.Run(async () => { try { - using var ctx = new AppDbContext(); - var d = (await ctx.Deliveries.FindAsync(year, did))!; - using var doc = new DeliveryNote(d, ctx); - await Utils.ExportDocument(doc, mode, d.LsNr, (d.Member, $"{DeliveryNote.Name} Nr. {d.LsNr}", $"Im Anhang finden Sie den {DeliveryNote.Name} Nr. {d.LsNr}")); + using var doc = await DeliveryNote.Initialize(year, did); + await Utils.ExportDocument(doc, mode, doc.Delivery.LsNr, (doc.Member, $"{DeliveryNote.Name} Nr. {doc.Delivery.LsNr}", $"Im Anhang finden Sie den {DeliveryNote.Name} Nr. {doc.Delivery.LsNr}")); } catch (Exception exc) { MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error); } diff --git a/Elwig/Services/MemberService.cs b/Elwig/Services/MemberService.cs index 849c7cf..45b9a03 100644 --- a/Elwig/Services/MemberService.cs +++ b/Elwig/Services/MemberService.cs @@ -395,8 +395,7 @@ namespace Elwig.Services { Mouse.OverrideCursor = Cursors.Wait; await Task.Run(async () => { try { - using var ctx = new AppDbContext(); - using var doc = new MemberDataSheet(m, ctx); + using var doc = new MemberDataSheet(m); await Utils.ExportDocument(doc, mode, emailData: (m, MemberDataSheet.Name, "Im Anhang finden Sie das aktuelle Stammdatenblatt")); } catch (Exception exc) { MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error); @@ -414,9 +413,7 @@ namespace Elwig.Services { await b.CalculateBuckets(); App.HintContextChange(); - using var ctx = new AppDbContext(); - var data = await DeliveryConfirmationDeliveryData.ForMember(ctx.DeliveryParts, year, m); - using var doc = new DeliveryConfirmation(ctx, year, m, data); + using var doc = new DeliveryConfirmation(year, m); await Utils.ExportDocument(doc, mode, emailData: (m, $"{DeliveryConfirmation.Name} {year}", $"Im Anhang finden Sie die Anlieferungsbestätigung {year}")); } catch (Exception exc) { MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error); @@ -429,16 +426,8 @@ namespace Elwig.Services { Mouse.OverrideCursor = Cursors.Wait; await Task.Run(async () => { try { - using var ctx = new AppDbContext(); - var v = (await ctx.PaymentVariants.FindAsync(year, avnr))!; - var data = await CreditNoteDeliveryData.ForPaymentVariant(ctx.CreditNoteDeliveryRows, ctx.PaymentVariants, year, avnr); - var p = (await ctx.MemberPayments.FindAsync(year, avnr, m.MgNr))!; - var b = BillingData.FromJson((await ctx.PaymentVariants.FindAsync(year, avnr))!.Data); - - using var doc = new CreditNote(ctx, p, data[m.MgNr], - b.ConsiderContractPenalties, b.ConsiderTotalPenalty, b.ConsiderAutoBusinessShares, b.ConsiderCustomModifiers, - await ctx.GetMemberUnderDelivery(year, m.MgNr)); - await Utils.ExportDocument(doc, mode, emailData: (m, $"{CreditNote.Name} {v.Name}", $"Im Anhang finden Sie die Traubengutschrift {v.Name}")); + using var doc = await CreditNote.Initialize(year, avnr, m.MgNr); + await Utils.ExportDocument(doc, mode, emailData: (m, $"{CreditNote.Name} {doc.Payment.Variant.Name}", $"Im Anhang finden Sie die Traubengutschrift {doc.Payment.Variant.Name}")); } catch (Exception exc) { MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error); } @@ -725,18 +714,20 @@ namespace Elwig.Services { public static async Task DeleteMember(int mgnr, bool deletePaymentData, bool deleteDeliveries, bool deleteAreaComs) { await Task.Run(async () => { using var ctx = new AppDbContext(); + using var tx = await ctx.Database.BeginTransactionAsync(); var l = (await ctx.Members.FindAsync(mgnr))!; if (deletePaymentData) { - ctx.RemoveRange(l.Credits); + await ctx.Credits.Where(c => c.MgNr == mgnr).ExecuteDeleteAsync(); } if (deleteDeliveries) { - ctx.RemoveRange(l.Deliveries); + await ctx.Deliveries.Where(c => c.MgNr == mgnr).ExecuteDeleteAsync(); } if (deleteAreaComs) { - ctx.RemoveRange(l.AreaCommitments); + await ctx.AreaCommitments.Where(c => c.MgNr == mgnr).ExecuteDeleteAsync(); } ctx.Remove(l); await ctx.SaveChangesAsync(); + await tx.CommitAsync(); }); } } diff --git a/Elwig/Services/PaymentVariantService.cs b/Elwig/Services/PaymentVariantService.cs index c6cd7b6..4e17014 100644 --- a/Elwig/Services/PaymentVariantService.cs +++ b/Elwig/Services/PaymentVariantService.cs @@ -214,9 +214,7 @@ namespace Elwig.Services { Mouse.OverrideCursor = Cursors.Wait; await Task.Run(async () => { try { - using var ctx = new AppDbContext(); - var data = await PaymentVariantSummaryData.ForPaymentVariant(v, ctx.PaymentVariantSummaryRows); - using var doc = new PaymentVariantSummary((await ctx.PaymentVariants.FindAsync(v.Year, v.AvNr))!, data); + using var doc = await PaymentVariantSummary.Initialize(v.Year, v.AvNr); await Utils.ExportDocument(doc, mode); } catch (Exception exc) { MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error); diff --git a/Elwig/ViewModels/MemberAdminViewModel.cs b/Elwig/ViewModels/MemberAdminViewModel.cs index 8064cd0..bd453b0 100644 --- a/Elwig/ViewModels/MemberAdminViewModel.cs +++ b/Elwig/ViewModels/MemberAdminViewModel.cs @@ -20,7 +20,7 @@ namespace Elwig.ViewModels { public List TextFilter => [.. SearchQuery?.ToLower().Split(' ').ToList().FindAll(e => e.Length > 0) ?? []]; [ObservableProperty] - private bool _showOnlyActiveMembers; + private bool _showOnlyActiveMembers = true; [ObservableProperty] private Member? _selectedMember; diff --git a/Elwig/Windows/DeliveryAdminWindow.xaml.cs b/Elwig/Windows/DeliveryAdminWindow.xaml.cs index 9f56b7f..686411a 100644 --- a/Elwig/Windows/DeliveryAdminWindow.xaml.cs +++ b/Elwig/Windows/DeliveryAdminWindow.xaml.cs @@ -423,12 +423,7 @@ namespace Elwig.Windows { using var ctx = new AppDbContext(); var (_, deliveryQuery, deliveryPartsQuery, predicate, filter) = await ViewModel.GetFilters(ctx); var deliveries = await deliveryQuery - .Include(d => d.Parts).ThenInclude(p => p.PartModifiers).ThenInclude(m => m.Modifier) - .Include(d => d.Parts).ThenInclude(p => p.Attribute) - .Include(d => d.Parts).ThenInclude(p => p.Cultivation) - .Include(d => d.Parts).ThenInclude(p => p.Variety) .Include(d => d.Member.EmailAddresses) - .AsSplitQuery() .ToListAsync(); deliveries.Reverse(); @@ -437,19 +432,22 @@ namespace Elwig.Windows { .ToDictionary(d => d, d => d.SearchScore(ViewModel.TextFilter)) .OrderByDescending(a => a.Value) .ThenBy(a => a.Key.DateTime); - var threshold = dict.Select(a => a.Value).Max() * 3 / 4; - deliveries = dict + var threshold = dict.Max(a => a.Value) * 3 / 4; + deliveries = [.. dict .Where(a => a.Value > threshold) - .Select(a => a.Key) - .ToList(); + .Select(a => a.Key)]; } - deliveries.ForEach(d => { d.PartFilter = predicate; }); + var filteredParts = await deliveryPartsQuery + .Include(p => p.PartModifiers).ThenInclude(p => p.Modifier) + .GroupBy(p => new { p.Year, p.DId }) + .ToDictionaryAsync(g => (g.Key.Year, g.Key.DId), g => g.ToList()); + deliveries.ForEach(d => { d.FilteredParts = filteredParts.GetValueOrDefault((d.Year, d.DId)) ?? []; }); ControlUtils.RenewItemsSource(DeliveryList, deliveries, DeliveryList_SelectionChanged, filter.Count > 0 ? ControlUtils.RenewSourceDefault.IfOnly : ControlUtils.RenewSourceDefault.None, !updateSort); await RefreshDeliveryParts(); - var members = deliveries.Select(d => d.Member).DistinctBy(m => m.MgNr).ToList(); + var members = await deliveryQuery.Select(d => d.Member).Distinct().IgnoreAutoIncludes().ToListAsync(); ViewModel.StatusMembers = $"{members.Count:N0}" + (members.Count > 0 && members.Count <= 4 ? $" ({string.Join(", ", members.Select(m => m.AdministrativeName))})" : ""); ViewModel.StatusDeliveries = $"{deliveries.Count:N0}"; @@ -486,7 +484,7 @@ namespace Elwig.Windows { int year = 0; Menu_Bki_SaveList.Items.Clear(); - foreach (var s in await ctx.Seasons.OrderByDescending(s => s.Year).ToListAsync()) { + foreach (var s in await ctx.Seasons.OrderByDescending(s => s.Year).IgnoreAutoIncludes().ToListAsync()) { if (s.Year > year) year = s.Year; var i = new MenuItem { Header = $"Saison {s.Year}", @@ -503,7 +501,7 @@ namespace Elwig.Windows { }; noAttr.Click += Menu_BulkAction_SetAttribute_Click; Menu_BulkAction_SetAttribute.Items.Add(noAttr); - foreach (var attr in await ctx.WineAttributes.OrderBy(a => a.AttrId).ToListAsync()) { + foreach (var attr in await ctx.WineAttributes.OrderBy(a => a.AttrId).IgnoreAutoIncludes().ToListAsync()) { var i = new MenuItem { Header = attr.Name, }; @@ -513,7 +511,7 @@ namespace Elwig.Windows { Menu_BulkAction_AddModifier.Items.Clear(); Menu_BulkAction_RemoveModifier.Items.Clear(); - foreach (var mod in await ctx.Modifiers.Where(m => m.Year == year).OrderBy(m => m.ModId).ToListAsync()) { + foreach (var mod in await ctx.Modifiers.Where(m => m.Year == year).OrderBy(m => m.ModId).IgnoreAutoIncludes().ToListAsync()) { var i1 = new MenuItem { Header = mod.Name, }; @@ -551,7 +549,7 @@ namespace Elwig.Windows { .OrderBy(m => m.Ordering) .Include(m => m.Season.Currency) .ToListAsync()); - ControlUtils.RenewItemsSource(WineOriginInput, (await ctx.WineOrigins.ToListAsync()).OrderByDescending(o => o.SortKey).ThenBy(o => o.HkId)); + ControlUtils.RenewItemsSource(WineOriginInput, (await ctx.WineOrigins.Include(o => o.Parent).Include(o => o.Children).ToListAsync()).OrderByDescending(o => o.SortKey).ThenBy(o => o.HkId)); var kgList = (await ctx.Katastralgemeinden .Where(k => k.WbKg != null) .Include(k => k.WbKg) @@ -581,14 +579,16 @@ namespace Elwig.Windows { ControlUtils.RenewItemsSource(ModifiersInput, await ctx.Modifiers .Where(m => m.Year == d.Year && (!IsCreating || m.IsActive)) .OrderBy(m => m.Ordering) - .Include(m => m.Season.Currency) .ToListAsync()); - ControlUtils.RenewItemsSource(DeliveryPartList, d.FilteredParts.OrderBy(p => p.DPNr).ToList(), DeliveryPartList_SelectionChanged, ControlUtils.RenewSourceDefault.First); + ControlUtils.RenewItemsSource(DeliveryPartList, await ctx.DeliveryParts + .Where(p => p.Year == d.Year && p.DId == d.DId) + .OrderBy(p => p.DPNr) + .Include(p => p.PartModifiers) + .ToListAsync(), DeliveryPartList_SelectionChanged, ControlUtils.RenewSourceDefault.First); } else { ControlUtils.RenewItemsSource(ModifiersInput, await ctx.Modifiers .Where(m => m.Year == ViewModel.FilterSeason && (!IsCreating || m.IsActive)) .OrderBy(m => m.Ordering) - .Include(m => m.Season.Currency) .ToListAsync()); DeliveryPartList.ItemsSource = null; } @@ -596,16 +596,17 @@ namespace Elwig.Windows { private void RefreshInputs(bool validate = false) { ClearInputStates(); - if (DeliveryPartList.SelectedItem is DeliveryPart p) { - FillInputs(p); - } else if (DeliveryList.SelectedItem is Delivery d) { + if (DeliveryList.SelectedItem is Delivery d) { FillInputs(d); + if (DeliveryPartList.SelectedItem is DeliveryPart p) { + FillInputs(p); + } } else { ClearOriginalValues(); ClearDefaultValues(); ClearInputs(validate); ClearInputStates(); - } + } GC.Collect(); } @@ -617,7 +618,6 @@ namespace Elwig.Windows { } private void FillInputs(DeliveryPart p) { - FillInputs(p.Delivery); ClearOriginalValues(); ClearDefaultValues(); ViewModel.FillInputs(p); @@ -852,9 +852,10 @@ namespace Elwig.Windows { await EnsureContextRenewed(); if (p?.Delivery != null) { try { - using var ctx = new AppDbContext(); - using var doc = new DeliveryNote((await ctx.Deliveries.FindAsync(p.Year, p.DId))!, ctx); - await doc.Generate(); + using var doc = await DeliveryNote.Initialize(p.Year, p.DId); + using (var ctx = new AppDbContext()) { + await doc.Generate(ctx); + } if (App.Config.Debug) { doc.Show(); } else { diff --git a/Elwig/Windows/MailWindow.xaml.cs b/Elwig/Windows/MailWindow.xaml.cs index cbaa424..f729c47 100644 --- a/Elwig/Windows/MailWindow.xaml.cs +++ b/Elwig/Windows/MailWindow.xaml.cs @@ -155,7 +155,7 @@ namespace Elwig.Windows { } protected override async Task OnRenewContext(AppDbContext ctx) { - var season = await ctx.Seasons.FindAsync(Year); + var season = await ctx.Seasons.Include(s => s.PaymentVariants).SingleAsync(s => s.Year == Year); var l = new List { MemberDataSheet.Name }; @@ -758,7 +758,7 @@ namespace Elwig.Windows { if (doc.Type == DocType.Custom) { return [new GeneratedDoc((string)doc.Details!)]; } else if (doc.Type == DocType.MemberDataSheet) { - return [new GeneratedDoc(new MemberDataSheet(m, ctx) { Date = postalDate })]; + return [new GeneratedDoc(new MemberDataSheet(m) { Date = postalDate })]; } else if (doc.Type == DocType.DeliveryConfirmation) { var year = (int)doc.Details!; DeliveryConfirmationDeliveryData data; @@ -769,21 +769,14 @@ namespace Elwig.Windows { } else { return []; } - return [new GeneratedDoc(new DeliveryConfirmation(ctx, year, m, data) { Date = postalDate })]; + return [new GeneratedDoc(new DeliveryConfirmation(year, m, data) { Date = postalDate })]; } 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( - ctx, data.Item2[m.MgNr], data.Item1[m.MgNr], - data.Item3.ConsiderContractPenalties, - data.Item3.ConsiderTotalPenalty, - data.Item3.ConsiderAutoBusinessShares, - data.Item3.ConsiderCustomModifiers, - ctx.GetMemberUnderDelivery(year, m.MgNr).GetAwaiter().GetResult() - ) { Date = postalDate })]; + return [new GeneratedDoc(new CreditNote(data.Item2[m.MgNr], data.Item3, data.Item1[m.MgNr]) { Date = postalDate })]; } catch (Exception) { return []; } @@ -827,7 +820,7 @@ namespace Elwig.Windows { 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(CancelGeneration?.Token, new Progress(v => App.MainDispatcher.Invoke(() => { + await item2.Doc.Generate(ctx, CancelGeneration?.Token, new Progress(v => App.MainDispatcher.Invoke(() => { ProgressBar.Value = offset + v * (item2.Index + 1) / item1.Value.Count / totalNum + 100.0 * item1.Index / totalNum; }))); } @@ -861,7 +854,7 @@ namespace Elwig.Windows { if (printDocs.Count > 0) { var print = Document.Merge(printDocs); - await print.Generate(CancelGeneration?.Token, new Progress(v => App.MainDispatcher.Invoke(() => { + await print.Generate(ctx, CancelGeneration?.Token, new Progress(v => App.MainDispatcher.Invoke(() => { ProgressBar.Value = offset + v * printNum / totalNum; }))); PrintDocument = print; diff --git a/Elwig/Windows/MemberAdminWindow.xaml.cs b/Elwig/Windows/MemberAdminWindow.xaml.cs index e9d37cc..00b0221 100644 --- a/Elwig/Windows/MemberAdminWindow.xaml.cs +++ b/Elwig/Windows/MemberAdminWindow.xaml.cs @@ -189,7 +189,7 @@ namespace Elwig.Windows { var font = new System.Windows.Media.FontFamily("Segoe MDL2 Assets"); MenuItem? temp = null; - var seasons = await ctx.Seasons.OrderByDescending(s => s.Year).ToListAsync(); + var seasons = await ctx.Seasons.Include(s => s.PaymentVariants).OrderByDescending(s => s.Year).ToListAsync(); Menu_DeliveryConfirmation.Items.Clear(); foreach (var s in seasons) { var i = new MenuItem { @@ -328,6 +328,7 @@ namespace Elwig.Windows { } private async void ActiveMemberInput_Changed(object sender, RoutedEventArgs evt) { + if (!IsInitialized) return; await RefreshList(); } @@ -498,7 +499,9 @@ namespace Elwig.Windows { try { await Task.Run(async () => { using var doc = new Letterhead(m); - await doc.Generate(); + using (var ctx = new AppDbContext()) { + await doc.Generate(ctx); + } if (!App.Config.Debug) { await doc.Print(); } else { diff --git a/Tests/Resources/Sql/DocumentInsert.sql b/Tests/Resources/Sql/DocumentInsert.sql index 543dab4..76490cd 100644 --- a/Tests/Resources/Sql/DocumentInsert.sql +++ b/Tests/Resources/Sql/DocumentInsert.sql @@ -64,6 +64,10 @@ INSERT INTO delivery_part (year, did, dpnr, sortid, attrid, cultid, weight, kmw, (2020, 12, 1, 'BP', NULL, NULL, 2410, 18.0, 'KAB', 'WLNO', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL), (2020, 12, 2, 'BP', NULL, NULL, 2313, 18.1, 'KAB', 'WLNO', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL); +INSERT INTO delivery_part_modifier (year, did, dpnr, modid) VALUES +(2020, 2, 1, 'S'), +(2020, 2, 2, 'A'); + INSERT INTO delivery_part_bucket (year, did, dpnr, bktnr, discr, value) VALUES (2020, 1, 1, 0, '_', 3219), (2020, 3, 1, 0, '_', 2561), diff --git a/Tests/UnitTests/DocumentTests/CreditNoteTest.cs b/Tests/UnitTests/DocumentTests/CreditNoteTest.cs index 996fe1e..1cf4db4 100644 --- a/Tests/UnitTests/DocumentTests/CreditNoteTest.cs +++ b/Tests/UnitTests/DocumentTests/CreditNoteTest.cs @@ -1,7 +1,4 @@ using Elwig.Documents; -using Elwig.Helpers; -using Elwig.Models.Dtos; -using Microsoft.EntityFrameworkCore; namespace Tests.UnitTests.DocumentTests { [TestFixture] @@ -9,12 +6,7 @@ namespace Tests.UnitTests.DocumentTests { [Test] public async Task Test_01_VirtualCreditNote() { - using var ctx = new AppDbContext(); - var m = await ctx.Members.FindAsync(101); - var p = await ctx.MemberPayments.Where(p => p.Year == 2020 && p.AvNr == 1).SingleAsync(); - var data = await CreditNoteDeliveryData.ForPaymentVariant(ctx.CreditNoteDeliveryRows, ctx.PaymentVariants, 2020, 1); - using var doc = new CreditNote(ctx, p, data[m!.MgNr], false, false, false, false, - ctx.GetMemberUnderDelivery(2020, m!.MgNr).GetAwaiter().GetResult()); + using var doc = await CreditNote.Initialize(2020, 1, 101); var text = await Utils.GeneratePdfText(doc); Assert.Multiple(() => { Assert.That(text, Contains.Substring(""" diff --git a/Tests/UnitTests/DocumentTests/DeliveryConfirmationTest.cs b/Tests/UnitTests/DocumentTests/DeliveryConfirmationTest.cs index e8af381..f87bb81 100644 --- a/Tests/UnitTests/DocumentTests/DeliveryConfirmationTest.cs +++ b/Tests/UnitTests/DocumentTests/DeliveryConfirmationTest.cs @@ -11,7 +11,7 @@ namespace Tests.UnitTests.DocumentTests { using var ctx = new AppDbContext(); var m = await ctx.Members.FindAsync(101); var data = await DeliveryConfirmationDeliveryData.ForMember(ctx.DeliveryParts, 2020, m!); - using var doc = new DeliveryConfirmation(ctx, 2020, m!, data); + using var doc = new DeliveryConfirmation(2020, m!, data); var text = await Utils.GeneratePdfText(doc); Assert.Multiple(() => { Assert.That(text, Contains.Substring(""" diff --git a/Tests/UnitTests/DocumentTests/DeliveryNoteTest.cs b/Tests/UnitTests/DocumentTests/DeliveryNoteTest.cs index 722ef13..55c06ad 100644 --- a/Tests/UnitTests/DocumentTests/DeliveryNoteTest.cs +++ b/Tests/UnitTests/DocumentTests/DeliveryNoteTest.cs @@ -1,5 +1,4 @@ using Elwig.Documents; -using Elwig.Helpers; namespace Tests.UnitTests.DocumentTests { [TestFixture] @@ -7,9 +6,7 @@ namespace Tests.UnitTests.DocumentTests { [Test] public async Task Test_01_OneDeliveryPart() { - using var ctx = new AppDbContext(); - var d = await ctx.Deliveries.FindAsync(2020, 1); - using var doc = new DeliveryNote(d!, ctx); + using var doc = await DeliveryNote.Initialize(2020, 1); var text = await Utils.GeneratePdfText(doc); Assert.Multiple(() => { Assert.That(text, Contains.Substring(""" @@ -19,7 +16,7 @@ namespace Tests.UnitTests.DocumentTests { """)); Assert.That(text, Contains.Substring("0123463")); // Betriebsnummer Assert.That(text, Contains.Substring("pauschaliert")); - Assert.That(text, Contains.Substring($"Wolkersdorf, am {Elwig.Helpers.Utils.Today:dd.MM.yyyy}")); + Assert.That(text, Contains.Substring($"Wolkersdorf, am {DateTime.Now:dd.MM.yyyy}")); Assert.That(text, Contains.Substring("Traubenübernahmeschein Nr. 20201001X001")); Assert.That(text, Contains.Substring("Das Mitglied erklärt, dass die gelieferte Ware dem österreichischen Weingesetz entspricht")); Assert.That(text, Contains.Substring(""" @@ -34,9 +31,7 @@ namespace Tests.UnitTests.DocumentTests { [Test] public async Task Test_02_TwoDeliveryParts() { - using var ctx = new AppDbContext(); - var d = await ctx.Deliveries.FindAsync(2020, 4); - using var doc = new DeliveryNote(d!, ctx); + using var doc = await DeliveryNote.Initialize(2020, 4); var text = await Utils.GeneratePdfText(doc); Assert.Multiple(() => { Assert.That(text, Contains.Substring(""" @@ -47,7 +42,7 @@ namespace Tests.UnitTests.DocumentTests { """)); Assert.That(text, Contains.Substring("0123471")); // Betriebsnummer Assert.That(text, Contains.Substring("pauschaliert")); - Assert.That(text, Contains.Substring($"Wolkersdorf, am {Elwig.Helpers.Utils.Today:dd.MM.yyyy}")); + Assert.That(text, Contains.Substring($"Wolkersdorf, am {DateTime.Now:dd.MM.yyyy}")); Assert.That(text, Contains.Substring("Traubenübernahmeschein Nr. 20201001X004")); Assert.That(text, Contains.Substring("Das Mitglied erklärt, dass die gelieferte Ware dem österreichischen Weingesetz entspricht")); Assert.That(text, Contains.Substring(""" @@ -68,9 +63,7 @@ namespace Tests.UnitTests.DocumentTests { [Test] public async Task Test_03_DeliveryPartsWithAttribute() { - using var ctx = new AppDbContext(); - var d = await ctx.Deliveries.FindAsync(2020, 3); - using var doc = new DeliveryNote(d!, ctx); + using var doc = await DeliveryNote.Initialize(2020, 3); var text = await Utils.GeneratePdfText(doc); Assert.Multiple(() => { Assert.That(text, Contains.Substring(""" @@ -80,7 +73,7 @@ namespace Tests.UnitTests.DocumentTests { """)); Assert.That(text, Contains.Substring("0123463")); // Betriebsnummer Assert.That(text, Contains.Substring("pauschaliert")); - Assert.That(text, Contains.Substring($"Wolkersdorf, am {Elwig.Helpers.Utils.Today:dd.MM.yyyy}")); + Assert.That(text, Contains.Substring($"Wolkersdorf, am {DateTime.Now:dd.MM.yyyy}")); Assert.That(text, Contains.Substring("Traubenübernahmeschein Nr. 20201001X003")); Assert.That(text, Contains.Substring("Das Mitglied erklärt, dass die gelieferte Ware dem österreichischen Weingesetz entspricht")); Assert.That(text, Contains.Substring(""" @@ -107,9 +100,7 @@ namespace Tests.UnitTests.DocumentTests { [Test] public async Task Test_04_DeliveryPartsWithCultivation() { - using var ctx = new AppDbContext(); - var d = await ctx.Deliveries.FindAsync(2020, 7); - using var doc = new DeliveryNote(d!, ctx); + using var doc = await DeliveryNote.Initialize(2020, 7); var text = await Utils.GeneratePdfText(doc); Assert.Multiple(() => { Assert.That(text, Contains.Substring(""" @@ -119,7 +110,7 @@ namespace Tests.UnitTests.DocumentTests { """)); Assert.That(text, Contains.Substring("0123480")); // Betriebsnummer Assert.That(text, Contains.Substring("pauschaliert")); - Assert.That(text, Contains.Substring($"Wolkersdorf, am {Elwig.Helpers.Utils.Today:dd.MM.yyyy}")); + Assert.That(text, Contains.Substring($"Wolkersdorf, am {DateTime.Now:dd.MM.yyyy}")); Assert.That(text, Contains.Substring("Traubenübernahmeschein Nr. 20201002X001")); Assert.That(text, Contains.Substring("Das Mitglied erklärt, dass die gelieferte Ware dem österreichischen Weingesetz entspricht")); Assert.That(text, Contains.Substring(""" @@ -139,5 +130,37 @@ namespace Tests.UnitTests.DocumentTests { Assert.That(text, Contains.Substring("Gesamt: 78 15,9 5 332")); }); } + + [Test] + public async Task Test_05_DeliveryPartsWithModifier() { + using var doc = await DeliveryNote.Initialize(2020, 2); + var text = await Utils.GeneratePdfText(doc); + Assert.Multiple(() => { + Assert.That(text, Contains.Substring(""" + W&B Weinbauer GesbR + WEINBAUER Wernhardt + Winzerstraße 2 + 2223 Hohenruppersdorf + """)); + Assert.That(text, Contains.Substring("0123471")); // Betriebsnummer + Assert.That(text, Contains.Substring("pauschaliert")); + Assert.That(text, Contains.Substring($"Wolkersdorf, am {DateTime.Now:dd.MM.yyyy}")); + Assert.That(text, Contains.Substring("Traubenübernahmeschein Nr. 20201001X002")); + Assert.That(text, Contains.Substring("Das Mitglied erklärt, dass die gelieferte Ware dem österreichischen Weingesetz entspricht")); + Assert.That(text, Contains.Substring(""" + 1 Grüner Veltliner Kabinett Kabinett 86 17,5 2 987 + Herkunft: Österreich / Weinland / Niederösterreich + / Matzner Hügel / Hohenruppersdorf / KG Hohenruppersdorf + Zu-/Abschläge: Geschädigte Trauben −10,00 % + Waage: ?, ID: ? (gerebelt gewogen) + 2 Grüner Veltliner Kabinett Kabinett 87 17,7 1 873 + Herkunft: Österreich / Weinland / Niederösterreich + / Matzner Hügel / Hohenruppersdorf / KG Hohenruppersdorf + Zu-/Abschläge: Keine Voranmeldung −0,1000 €/kg + Waage: ?, ID: ? (gerebelt gewogen) + Gesamt: 87 17,6 4 860 + """)); + }); + } } } diff --git a/Tests/UnitTests/DocumentTests/MemberDataSheetTest.cs b/Tests/UnitTests/DocumentTests/MemberDataSheetTest.cs index 379239d..f3a3fe9 100644 --- a/Tests/UnitTests/DocumentTests/MemberDataSheetTest.cs +++ b/Tests/UnitTests/DocumentTests/MemberDataSheetTest.cs @@ -1,5 +1,4 @@ using Elwig.Documents; -using Elwig.Helpers; namespace Tests.UnitTests.DocumentTests { [TestFixture] @@ -7,9 +6,7 @@ namespace Tests.UnitTests.DocumentTests { [Test] public async Task Test_01_SimpleMember() { - using var ctx = new AppDbContext(); - var m = await ctx.Members.FindAsync(104); - using var doc = new MemberDataSheet(m!, ctx); + using var doc = await MemberDataSheet.Initialize(104); var text = await Utils.GeneratePdfText(doc); Assert.Multiple(() => { Assert.That(text, Contains.Substring(""" diff --git a/Tests/UnitTests/DocumentTests/PaymentVariantSummaryTest.cs b/Tests/UnitTests/DocumentTests/PaymentVariantSummaryTest.cs index 385fa32..92ec95e 100644 --- a/Tests/UnitTests/DocumentTests/PaymentVariantSummaryTest.cs +++ b/Tests/UnitTests/DocumentTests/PaymentVariantSummaryTest.cs @@ -1,6 +1,4 @@ using Elwig.Documents; -using Elwig.Helpers; -using Elwig.Models.Dtos; namespace Tests.UnitTests.DocumentTests { [TestFixture] @@ -8,16 +6,13 @@ namespace Tests.UnitTests.DocumentTests { [Test] public async Task Test_01_PaymentVariant2020() { - using var ctx = new AppDbContext(); - var v = (await ctx.PaymentVariants.FindAsync(2020, 1))!; - var data = await PaymentVariantSummaryData.ForPaymentVariant(v, ctx.PaymentVariantSummaryRows); - using var doc = new PaymentVariantSummary(v, data); + using var doc = await PaymentVariantSummary.Initialize(2020, 1); var text = await Utils.GeneratePdfText(doc, true); var table = Utils.ExtractTable(text); Assert.Multiple(() => { Assert.That(text, Contains.Substring("Auszahlungsvariante")); - Assert.That(text, Contains.Substring(v.Name)); - Assert.That(table.Skip(17).ToArray(), Is.EqualTo(new string[][] { + Assert.That(text, Contains.Substring(doc.Variant.Name)); + Assert.That(table.Skip(19).ToArray(), Is.EqualTo(new string[][] { ["Sorte/Attr./Bewirt.", "Gradation", "ungebunden", "attributlos gebunden", "gebunden", "Gesamt" ], ["Qualitätsstufe", "[°Oe]", "[kg]", "[€/kg]", "[kg]", "[€/kg]", "[kg]", "[€/kg]", "[€]" ], ["Grüner Veltliner", "3 219", "0", "0", "1 609,50"], diff --git a/Tests/UnitTests/DocumentTests/Utils.cs b/Tests/UnitTests/DocumentTests/Utils.cs index 9bc84b2..f60a447 100644 --- a/Tests/UnitTests/DocumentTests/Utils.cs +++ b/Tests/UnitTests/DocumentTests/Utils.cs @@ -1,4 +1,5 @@ using Elwig.Documents; +using Elwig.Helpers; using NReco.PdfRenderer; using System.Text.RegularExpressions; @@ -8,7 +9,9 @@ namespace Tests.UnitTests.DocumentTests { private static readonly string FileName = Path.Combine(Path.GetTempPath(), "test_document.pdf"); public static async Task GeneratePdfText(Document doc, bool preserveLayout = false) { - await doc.Generate(); + using (var ctx = new AppDbContext()) { + await doc.Generate(ctx); + } try { doc.SaveTo(FileName); var conv = new PdfToTextConverter { CustomArgs = preserveLayout ? "-layout " : "-raw " };