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 Credit? Credit; public string? Text; public string CurrencySymbol; public int Precision; public string? MemberModifier; public List<(string Name, int Kg, decimal Amount)>? MemberUnderDeliveries; public decimal MemberTotalUnderDelivery; public int MemberAutoBusinessShares; public decimal MemberAutoBusinessSharesAmount; public PaymentCustom? CustomPayment; protected bool ConsiderContractPenalties; protected bool ConsiderTotalPenalty; protected bool ConsiderAutoBusinessShares; protected bool ConsiderCustomModifiers; private CreditNoteDeliveryData? _data; private Dictionary? _underDeliveries; public CreditNote(PaymentMember p, DateOnly? dateFrom, 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, dateFrom) { UseBillingAddress = true; ShowDateAndLocation = true; Payment = p; Credit = p.Credit; 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, DateOnly? dateFrom, 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, dateFrom, 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); } _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.FetchModifiers(season.Year).Where(m => m.Name.StartsWith("Treue")).FirstOrDefaultAsync() : null; if (CustomPayment?.ModComment != null) { MemberModifier = CustomPayment.ModComment; } else if (mod != null) { MemberModifier = $"{mod.Name} ({mod.PublicValueStr})"; } else { MemberModifier = "Sonstige Zu-/Abschläge"; } 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 * Member.BusinessShares ?? 0); } if (ConsiderAutoBusinessShares) { var fromDate = $"{season.Year}-01-01"; var toDate = $"{season.Year}-12-31"; 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) .SumAsync(h => h.BusinessShares); MemberAutoBusinessSharesAmount = MemberAutoBusinessShares * (-season.BusinessShareValue ?? 0); } if (ConsiderContractPenalties) { var varieties = await ctx.FetchWineVarieties().ToDictionaryAsync(v => v.SortId, v => v); var attributes = await ctx.FetchWineAttributes().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 : ""), u.Value.Diff, u.Value.Diff * (comTypes[u.Key].PenaltyPerKg ?? 0) - (comTypes[u.Key].PenaltyAmount ?? 0) - ((u.Value.Weight == 0 ? comTypes[u.Key].PenaltyNone : null) ?? 0))) .Where(u => u.Item3 != 0) .ToList(); } } protected override void BeforeRenderBody(iText.Layout.Document doc, PdfDocument pdf) { base.BeforeRenderBody(doc, pdf); Aside?.AddCell(NewAsideCell("Gutschrift", 2)) .AddCell(NewAsideCell("TG-Nr.:", isName: true)).AddCell(NewAsideCell(Payment?.Credit != null ? $"{Payment.Credit.Year}/{Payment.Credit.TgNr:000}" : "-")) .AddCell(NewAsideCell("Datum:", isName: true)).AddCell(NewAsideCell($"{Payment?.Variant.Date:dd.MM.yyyy}")) .AddCell(NewAsideCell("Überw. am:", isName: true)).AddCell(NewAsideCell($"{Payment?.Variant.TransferDate:dd.MM.yyyy}")); } 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)); var div = new Table(ColsMM(60, 105)) .SetWidth(UnitValue.CreatePercentValue(100)).SetFixedLayout() .SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE); var hint = new KernedParagraph(8) .Add(Italic("Hinweis:\n" + $"Die Summe der Lieferungen und die Summe der anfal\u00adlenden Pönalen werden mit " + $"{Payment?.Variant.Season.Precision} Nach\u00adkomma-stellen berechnent, " + $"erst das Ergebnis wird kauf-männisch auf 2 Nach\u00adkomma\u00adstellen gerundet.")) .SetWidth(56 * PtInMM).SetMarginsMM(4, 2, 4, 2); div.AddCell(new Cell(1, 2).SetPadding(0).SetBorder(Border.NO_BORDER).SetBorderTop(new SolidBorder(BorderThickness))); div.AddCell(new Cell(3, 1).SetPadding(0).SetBorder(Border.NO_BORDER).Add(hint).SetKeepTogether(true)); var tbl1 = new Table(ColsMM(70, 5, 5, 25)) .SetWidth(UnitValue.CreatePercentValue(100)).SetFixedLayout() .SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE) .SetKeepTogether(true); var tbl2 = new Table(ColsMM(70, 5, 5, 25)) .SetWidth(UnitValue.CreatePercentValue(100)).SetFixedLayout() .SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE) .SetKeepTogether(true); var tbl3 = new Table(ColsMM(70, 5, 5, 25)) .SetWidth(UnitValue.CreatePercentValue(100)).SetFixedLayout() .SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE) .SetKeepTogether(true); var sum = _data.Rows.Sum(p => p.Amount); if (Payment == null) { tbl1.AddCells(FormatRow("Gesamt", sum, bold: true, noTopBorder: true)); } else { var noBorder = true; 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)); } if (Credit == null) { tbl1.AddCells(FormatRow("Gesamtbetrag", Payment.Amount, bold: true, noTopBorder: noBorder)); // TODO Mock VAT } else { var hasPrev = Credit.PrevNetAmount != null; tbl1.AddCells(FormatRow(hasPrev ? "Gesamtbetrag" : "Nettobetrag", Credit.NetAmount, bold: true, noTopBorder: noBorder)); if (hasPrev) { tbl1.AddCells(FormatRow("Bisher berücksichtigt", -Credit.PrevNetAmount, add: true)); tbl1.AddCells(FormatRow("Nettobetrag", Credit.NetAmount - (Credit.PrevNetAmount ?? 0))); } tbl1.AddCells(FormatRow($"Mehrwertsteuer ({Credit.Vat * 100} %)", Credit.VatAmount, add: true)); tbl1.AddCells(FormatRow("Bruttobetrag", Credit.GrossAmount, bold: true)); } } decimal penalty = 0; string? comment = null; if (MemberUnderDeliveries != null && MemberUnderDeliveries.Count > 0) { tbl2.AddCell(NewTd("Anfallende Pönalen durch Unterlieferungen:", colspan: 2).SetPaddingTopMM(5)) .AddCell(NewCell(colspan: 2)); foreach (var u in MemberUnderDeliveries) { tbl2.AddCells(FormatRow($"{u.Name} ({u.Kg:N0} kg)", u.Amount, add: true, subCat: true)); penalty += u.Amount; } penalty = Math.Round(penalty, 2, MidpointRounding.AwayFromZero); } if (MemberTotalUnderDelivery != 0) { tbl2.AddCells(FormatRow("Unterlieferung (GA)", MemberTotalUnderDelivery, add: true)); penalty += MemberTotalUnderDelivery; } if (MemberAutoBusinessSharesAmount != 0) { tbl2.AddCells(FormatRow($"Autom. Nachz. von GA ({MemberAutoBusinessShares})", MemberAutoBusinessSharesAmount, add: true)); penalty += MemberAutoBusinessSharesAmount; } if (CustomPayment?.Amount != null) { comment = CustomPayment.Comment; string text = (CustomPayment.Amount.Value < 0 ? "Weitere Abzüge" : "Weitere Zuschläge") + (comment != null ? "*" : ""); if (comment != null && comment!.Length <= 30) { text = comment; comment = null; } tbl2.AddCells(FormatRow(text, CustomPayment.Amount.Value, add: true)); penalty += CustomPayment.Amount.Value; } if (Credit == null) { tbl3.AddCells(FormatRow("Auszahlungsbetrag", (Payment?.Amount + penalty) ?? (sum + penalty), bold: true)); } else { var diff = Credit.Modifiers - penalty; if (diff != 0) { tbl3.AddCells(FormatRow(diff < 0 ? "Sonstige Abzüge" : "Sonstige Zuschläge", diff, add: true)); } if (Credit.PrevModifiers != null && Credit.PrevModifiers != 0) { tbl3.AddCells(FormatRow("Bereits berücksichtigte Abzüge", -Credit.PrevModifiers, add: true)); } tbl3.AddCells(FormatRow("Auszahlungsbetrag", Credit.Amount, bold: true)); } div.AddCell(new Cell().SetPadding(0).SetBorder(Border.NO_BORDER).Add(tbl1)); div.AddCell(new Cell().SetPadding(0).SetBorder(Border.NO_BORDER).Add(tbl2)); div.AddCell(new Cell().SetPadding(0).SetBorder(Border.NO_BORDER).Add(tbl3)); doc.Add(div); if (comment != null) { doc.Add(new KernedParagraph($"*{comment}", 12).SetMarginTopMM(10)); } doc.Add(new KernedParagraph($"Überweisung erfolgt auf Konto {Utils.FormatIban(Member.Iban ?? "-")}.", 12).SetPaddingTopMM(10)); if (Text != null) { doc.Add(new KernedParagraph(Text, 12).SetMarginTop(12).SetKeepTogether(true)); } } protected Cell[] FormatRow(string name, decimal? value, bool add = false, bool bold = false, bool subCat = false, bool noTopBorder = false) { float textSize = subCat ? 8 : !add ? 12 : 10; float numSize = subCat ? 8 : 12; var border = !add && !noTopBorder; var pad = !add && !noTopBorder ? 1 : 0.5f; return [ NewTd($"{name}:", textSize, bold: bold, borderTop: border).SetPaddingTopMM(pad), NewTd(value < 0 ? "–" : (add ? "+" : ""), numSize, right: true, bold: bold, borderTop: border).SetPaddingTopMM(pad), NewTd(CurrencySymbol, numSize, bold: bold, borderTop: border).SetPaddingTopMM(pad), NewTd($"{Math.Abs(value ?? 0):N2}", numSize, right: true, bold: bold, borderTop: border).SetPaddingTopMM(pad), ]; } protected Table NewCreditTable(CreditNoteDeliveryData data) { var tbl = new Table(ColsMM(25, 6, 36, 10, 10, 15, 12, 13, 5, 17, 16), true) .SetWidth(UnitValue.CreatePercentValue(100)).SetFixedLayout() .SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE); tbl.AddHeaderCell(NewTh("Lieferschein-Nr.", rowspan: 2, left: true)) .AddHeaderCell(NewTh("Pos.", rowspan: 2).SetPaddingLeft(0).SetPaddingRight(0)) .AddHeaderCell(NewTh("Sorte/Attribut/Bewirtschaftg.\nZu-/Abschlag", rowspan: 2, left: true)) .AddHeaderCell(NewTh("Gradation", colspan: 2)) .AddHeaderCell(NewTh("Flächenbindung", colspan: 2)) .AddHeaderCell(NewTh("Preis")) .AddHeaderCell(NewTh("Rbl.").SetPaddingLeft(0).SetPaddingRight(0)) .AddHeaderCell(NewTh("Zu-/Abschläge").SetPaddingLeft(0).SetPaddingRight(0)) .AddHeaderCell(NewTh("Betrag")) .AddHeaderCell(NewTh("[°Oe]")) .AddHeaderCell(NewTh("[°KMW]").SetPaddingLeft(0).SetPaddingRight(0)) .AddHeaderCell(NewTh("[kg]", colspan: 2)) .AddHeaderCell(NewTh($"[{CurrencySymbol}/kg]")) .AddHeaderCell(NewTh("[%]").SetPaddingLeft(0).SetPaddingRight(0)) .AddHeaderCell(NewTh($"[{CurrencySymbol}]")) .AddHeaderCell(NewTh($"[{CurrencySymbol}]")); foreach (var p in data.Rows) { var sub = new Table(ColsMM(25, 6, 36, 10, 10, 15, 12, 13, 5, 17, 16)) .SetWidth(UnitValue.CreatePercentValue(100)).SetFixedLayout() .SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE) .SetKeepTogether(true); var attr = p.Attribute != null || p.Cultivation != null || p.QualId == "WEI"; var rows = Math.Max(p.Buckets.Length, 1 + (attr ? 1 : 0) + p.Modifiers.Length); for (int i = 0; i < rows; i++) { if (i == 0) { sub.AddCell(NewTd(p.LsNr)) .AddCell(NewTd($"{p.DPNr:N0}", center: true).SetPaddingLeft(0).SetPaddingRight(0)) .AddCell(NewTd(p.Variety)) .AddCell(NewTd($"{p.Gradation.Oe:N0}", center: true)) .AddCell(NewTd($"{p.Gradation.Kmw:N1}", center: true)); } else if (i == 1 && attr) { var varibute = new KernedParagraph(8); if (p.Attribute != null) varibute.Add(Normal(p.Attribute)); if (p.Attribute != null && p.Cultivation != null) varibute.Add(Normal(" / ")); if (p.Cultivation != null) varibute.Add(Normal(p.Cultivation)); if ((p.Attribute != null || p.Cultivation != null) && p.QualId == "WEI") varibute.Add(Normal(" / ")); if (p.QualId == "WEI") varibute.Add(Italic("abgew.")); sub.AddCell(NewCell(colspan: 2)) .AddCell(NewTd(varibute, colspan: 3).SetPaddingTop(0)); } else if (i - (rows - p.Modifiers.Length) < p.Modifiers.Length) { sub.AddCell(NewCell(colspan: 2)) .AddCell(NewTd(p.Modifiers[i - (rows - p.Modifiers.Length)], 8, colspan: 3).SetPaddingTop(0).SetPaddingLeftMM(5)); } else { sub.AddCell(NewCell(colspan: 5)); } if (i < p.Buckets.Length) { var bucket = p.Buckets[i]; var pad = i == 0 ? 0.5f : 0; sub.AddCell(NewTd($"{bucket.Name}:", 8) .SetPaddingTopMM(pad)) .AddCell(NewTd($"{bucket.Value:N0}", right: true) .SetPaddingTopMM(pad)) .AddCell(NewTd($"{bucket.Price:N4}", right: true) .SetPaddingTopMM(pad)); } else { sub.AddCell(NewCell(colspan: 3)); } if (i == p.Buckets.Length - 1) { var rebelMod = p.WeighingModifier * 100; var totalMod = p.TotalModifiers ?? 0; var pad = i == 0 ? 0.5f : 0; sub.AddCell(NewTd(rebelMod == 0 ? "-" : (Utils.GetSign(rebelMod) + $"{Math.Abs(rebelMod):0.0##}"), 6, center: true) .SetPaddingTopMM(pad)) .AddCell(NewTd(totalMod == 0 ? "-" : Utils.GetSign(totalMod) + $"{Math.Abs(totalMod):N2}", center: totalMod == 0, right: true) .SetPaddingTopMM(pad)) .AddCell(NewTd($"{p.Amount:N2}", right: true) .SetPaddingTopMM(pad)); } else { sub.AddCell(NewCell(colspan: 3)); } } tbl.AddCell(new Cell(1, 12).SetPadding(0).SetBorder(Border.NO_BORDER).Add(sub)); } return tbl; } } }