using Elwig.Helpers; 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 System; using System.Collections.Generic; using System.Linq; namespace Elwig.Documents { public class CreditNote : BusinessDocument { public new static string Name => "Traubengutschrift"; public PaymentMember? Payment; public Credit? Credit; public CreditNoteDeliveryData Data; 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; public CreditNote( AppDbContext ctx, PaymentMember p, CreditNoteDeliveryData data, bool considerContractPenalties, bool considerTotalPenalty, bool considerAutoBusinessShares, bool considerCustomModifiers, 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); } var mod = App.Client.IsMatzen ? ctx.Modifiers.Where(m => m.Year == season.Year && m.Name.StartsWith("Treue")).FirstOrDefault() : null; if (CustomPayment?.ModComment != null) { MemberModifier = CustomPayment.ModComment; } else if (mod != null) { MemberModifier = $"{mod.Name} ({mod.PublicValueStr})"; } 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; 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); } 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") .Where(h => h.DateString.CompareTo(fromDate) >= 0 && h.DateString.CompareTo(toDate) <= 0) .Sum(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? .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 RenderHeader(iText.Layout.Document doc, PdfDocument pdf) { base.RenderHeader(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) { 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; } } }