372 lines
17 KiB
C#
372 lines
17 KiB
C#
using System.Globalization;
|
|
using System.Net.Mime;
|
|
using QuestPDF;
|
|
using QuestPDF.Drawing;
|
|
using QuestPDF.Fluent;
|
|
using QuestPDF.Helpers;
|
|
using QuestPDF.Infrastructure;
|
|
using Server.Data;
|
|
|
|
namespace Server.Model
|
|
{
|
|
public class InvoiceDocument(InvoiceModel model) : IDocument
|
|
{
|
|
private InvoiceModel Model { get; } = model;
|
|
|
|
public void Compose(IDocumentContainer container)
|
|
{
|
|
if(File.Exists("NotoSans-VariableFont_wdth,wght.ttf"))
|
|
FontManager.RegisterFont(File.OpenRead("NotoSans-VariableFont_wdth,wght.ttf"));
|
|
|
|
container.Page(page =>
|
|
{
|
|
page.Size(PageSizes.A4);
|
|
page.Margin(10, Unit.Millimetre);
|
|
page.MarginLeft(20, Unit.Millimetre);
|
|
page.PageColor(Colors.White);
|
|
page.DefaultTextStyle(x => x
|
|
.FontSize(10)
|
|
.FontFamily("NotoSans")
|
|
.Light());
|
|
|
|
page.Header().Element(ComposeHeader);
|
|
page.Content().Element(ComposeContent);
|
|
page.Footer().Element(ComposeFooter);
|
|
|
|
});
|
|
}
|
|
|
|
private void ComposeContent(IContainer container)
|
|
{
|
|
container.PaddingVertical(40).Column(outerColumn =>
|
|
{
|
|
if (SettingsData.Instance.Kleinunternehmer)
|
|
{
|
|
outerColumn.Item().Element(ComposeItemTableKleinunternehmer);
|
|
outerColumn.Item().PaddingTop(10).Text("Im Sinne der Kleinunternehmerregelung nach § 19 UStG. enthält der ausgewiesene Betrag keine Umsatzsteuer.");
|
|
}
|
|
else
|
|
{
|
|
outerColumn.Item().Element(ComposeItemTable);
|
|
}
|
|
|
|
|
|
outerColumn.Item().Row(row =>
|
|
{
|
|
row.RelativeItem().PaddingRight(10).Column(col =>
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(Model.Comment))
|
|
col.Item().PaddingTop(25).Element(ComposeComments);
|
|
});
|
|
|
|
if (!SettingsData.Instance.Kleinunternehmer)
|
|
{
|
|
row.AutoItem().Column(col =>
|
|
{
|
|
col.Item().Element(ComposeTaxTable);
|
|
});
|
|
}
|
|
|
|
});
|
|
|
|
outerColumn.Spacing(5);
|
|
});
|
|
}
|
|
|
|
private void ComposeTaxTable(IContainer container)
|
|
{
|
|
container.PaddingTop(25).Table(table =>
|
|
{
|
|
table.ColumnsDefinition(c =>
|
|
{
|
|
c.ConstantColumn(70);
|
|
c.ConstantColumn(25);
|
|
c.ConstantColumn(70);
|
|
c.ConstantColumn(45);
|
|
});
|
|
|
|
table.Header(header =>
|
|
{
|
|
header.Cell();
|
|
header.Cell().AlignRight().TextSmall("", true);
|
|
header.Cell().AlignRight().TextSmall("Zwischensumme", true);
|
|
header.Cell().AlignRight().TextSmall("", true);
|
|
|
|
header.Cell().Element(c => c.BorderBottom(1).BorderColor(Colors.Black));
|
|
header.Cell().Element(c => c.BorderBottom(1).BorderColor(Colors.Black)).AlignRight().TextSmall("USt. %", true);
|
|
header.Cell().Element(c => c.BorderBottom(1).BorderColor(Colors.Black)).AlignRight().TextSmall("(ohne USt.)", true);
|
|
header.Cell().Element(c => c.BorderBottom(1).BorderColor(Colors.Black)).AlignRight().TextSmall("USt.", true);
|
|
});
|
|
|
|
var totalTax = 0d;
|
|
|
|
// 7%
|
|
var tax7Items = Model.Items?.Where(x => x.TaxType == TaxType.Tax7).ToList() ?? [];
|
|
if (tax7Items.Count > 0)
|
|
{
|
|
var tax7Netto = tax7Items.Sum(x => x.PriceNetto * x.Quantity);
|
|
var tax7Tax = tax7Netto * 0.07;
|
|
totalTax += tax7Tax;
|
|
|
|
table.Cell();
|
|
table.Cell().AlignRight().TextSmall("7%");
|
|
table.Cell().AlignRight().TextSmall($"{tax7Netto:N2} €");
|
|
table.Cell().AlignRight().TextSmall($"{tax7Tax:N2} €");
|
|
}
|
|
|
|
// 19%
|
|
var tax19Items = Model.Items?.Where(x => x.TaxType == TaxType.Tax19).ToList() ?? [];
|
|
if (tax19Items.Count > 0)
|
|
{
|
|
var tax19Netto = tax19Items.Sum(x => x.PriceNetto * x.Quantity);
|
|
var tax19Tax = tax19Netto * 0.19;
|
|
totalTax += tax19Tax;
|
|
|
|
table.Cell();
|
|
table.Cell().AlignRight().TextSmall("19%");
|
|
table.Cell().AlignRight().TextSmall($"{tax19Netto:N2} €");
|
|
table.Cell().AlignRight().TextSmall($"{tax19Tax:N2} €");
|
|
}
|
|
|
|
// SUMME
|
|
table.Cell().Element(c => c.BorderTop(1).BorderColor(Colors.Black)).TextSmall("USt. Gesamt");
|
|
table.Cell().Element(c => c.BorderTop(1).BorderColor(Colors.Black));
|
|
table.Cell().Element(c => c.BorderTop(1).BorderColor(Colors.Black)).AlignRight().TextSmall($"{Model.Items?.Sum(x => x.PriceNetto * x.Quantity):N2} €");
|
|
table.Cell().Element(c => c.BorderTop(1).BorderColor(Colors.Black)).AlignRight().TextSmall($"{totalTax:N2} €");
|
|
});
|
|
}
|
|
|
|
private void ComposeItemTableKleinunternehmer(IContainer container)
|
|
{
|
|
container.Table(table =>
|
|
{
|
|
// Define columns
|
|
table.ColumnsDefinition(columns =>
|
|
{
|
|
columns.RelativeColumn(3); // Beschreibung
|
|
columns.ConstantColumn(20, Unit.Millimetre); // Menge
|
|
columns.ConstantColumn(24, Unit.Millimetre); // Einzelpreis
|
|
columns.ConstantColumn(24, Unit.Millimetre); // Gesamtpreis
|
|
});
|
|
|
|
// Describe header
|
|
table.Header(header =>
|
|
{
|
|
header.Cell().Element(CellStyle).Text("Beschreibung").SemiBold();
|
|
header.Cell().Element(CellStyle).AlignCenter().Text("Menge").SemiBold();
|
|
header.Cell().Element(CellStyle).AlignRight().Text("Einzelpreis").SemiBold();
|
|
header.Cell().Element(CellStyle).AlignRight().Text("Gesamtpreis").SemiBold();
|
|
|
|
static IContainer CellStyle(IContainer container)
|
|
{
|
|
return container.DefaultTextStyle(x => x.SemiBold()).BorderBottom(1).BorderColor(Colors.Black);
|
|
}
|
|
});
|
|
|
|
// Describe content
|
|
foreach (var item in Model.Items ?? [])
|
|
{
|
|
|
|
// Beschreibung
|
|
table.Cell().Element(CellStyle).Column(column =>
|
|
{
|
|
column.Item().Text(item.Name);
|
|
if(!string.IsNullOrWhiteSpace(item.Description))
|
|
column.Item().TextSmall(item.Description);
|
|
});
|
|
|
|
table.Cell().Element(CellStyle).AlignCenter().Text(txt => txt.Span($"{item.Quantity:N2}"));
|
|
|
|
// string as currency format
|
|
table.Cell().Element(CellStyle).AlignRight().Text($"{item.PriceNetto:N2} €");
|
|
table.Cell().Element(CellStyle).AlignRight().Text($"{item.PriceNetto * item.Quantity:N2} €");
|
|
}
|
|
|
|
table.Cell().ColumnSpan(3).Element(CellStyle).PaddingTop(10).Text("Rechnungsbetrag").Medium();
|
|
table.Cell().Element(CellStyle).PaddingTop(10).AlignRight().Text($"{Model.Items.Sum(x=> x.PriceNetto*x.Quantity):N2} €").Medium();
|
|
|
|
return;
|
|
|
|
static IContainer CellStyle(IContainer container) => container.BorderBottom(1).BorderColor(Colors.Grey.Lighten2).PaddingVertical(5);
|
|
});
|
|
}
|
|
|
|
private void ComposeItemTable(IContainer container)
|
|
{
|
|
container.Table(table =>
|
|
{
|
|
// Define columns
|
|
table.ColumnsDefinition(columns =>
|
|
{
|
|
columns.RelativeColumn(3);
|
|
columns.ConstantColumn(20, Unit.Millimetre);
|
|
|
|
columns.ConstantColumn(24, Unit.Millimetre);
|
|
columns.ConstantColumn(15, Unit.Millimetre);
|
|
columns.ConstantColumn(24, Unit.Millimetre);
|
|
columns.ConstantColumn(24, Unit.Millimetre);
|
|
});
|
|
|
|
// Describe header
|
|
table.Header(header =>
|
|
{
|
|
header.Cell().Text("").SemiBold();
|
|
header.Cell().Text("").SemiBold();
|
|
header.Cell().AlignRight().Text("Einzelpreis").SemiBold();
|
|
header.Cell().Text("").SemiBold();
|
|
header.Cell().AlignRight().Text("Einzelpreis").SemiBold();
|
|
header.Cell().AlignRight().Text("Gesamtpreis").SemiBold();
|
|
|
|
|
|
header.Cell().Element(CellStyle).Text("Beschreibung").SemiBold();
|
|
header.Cell().Element(CellStyle).AlignCenter().Text("Menge").SemiBold();
|
|
header.Cell().Element(CellStyle).AlignRight().Text("(ohne USt.)").SemiBold();
|
|
header.Cell().Element(CellStyle).AlignRight().Text("USt. %").SemiBold();
|
|
header.Cell().Element(CellStyle).AlignRight().Text("(inkl USt.)").SemiBold();
|
|
header.Cell().Element(CellStyle).AlignRight().Text("(inkl USt.)").SemiBold();
|
|
|
|
static IContainer CellStyle(IContainer container)
|
|
{
|
|
return container.DefaultTextStyle(x => x.SemiBold()).BorderBottom(1).BorderColor(Colors.Black);
|
|
}
|
|
});
|
|
|
|
// Describe content
|
|
foreach (var item in Model.Items ?? [])
|
|
{
|
|
|
|
// Beschreibung
|
|
table.Cell().Element(CellStyle).Column(column =>
|
|
{
|
|
column.Item().Text(item.Name);
|
|
if(!string.IsNullOrWhiteSpace(item.Description))
|
|
column.Item().TextSmall(item.Description);
|
|
});
|
|
|
|
table.Cell().Element(CellStyle).AlignCenter().Text(txt => txt.Span($"{item.Quantity:N2}"));
|
|
|
|
// string as currency format
|
|
table.Cell().Element(CellStyle).AlignRight().Text($"{item.PriceNetto:N2} €");
|
|
table.Cell().Element(CellStyle).AlignRight().Text($"{(int)item.TaxType}%");
|
|
table.Cell().Element(CellStyle).AlignRight().Text($"{item.PriceBrutto:N2} €");
|
|
table.Cell().Element(CellStyle).AlignRight().Text($"{item.PriceBrutto * item.Quantity:N2} €");
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
table.Cell().ColumnSpan(5).Element(CellStyle).PaddingTop(10).Text("Rechnungsbetrag").Medium();
|
|
table.Cell().Element(CellStyle).PaddingTop(10).AlignRight().Text($"{Model.Items.Sum(x=> x.PriceBrutto*x.Quantity):N2} €").Medium();
|
|
|
|
return;
|
|
|
|
static IContainer CellStyle(IContainer container) => container.BorderBottom(1).BorderColor(Colors.Grey.Lighten2).PaddingVertical(5);
|
|
});
|
|
}
|
|
|
|
private void ComposeComments(IContainer container)
|
|
{
|
|
container.Background(Colors.Grey.Lighten3).Padding(10).Column(column =>
|
|
{
|
|
column.Spacing(5);
|
|
column.Item().Text(Model.Comment);
|
|
});
|
|
}
|
|
|
|
private void ComposeFooter(IContainer container)
|
|
{
|
|
container.Column(outerColumn =>
|
|
{
|
|
outerColumn.Item().Row(row =>
|
|
{
|
|
row.RelativeItem().Column(column =>
|
|
{
|
|
column.Item().TextSmall(SettingsData.Instance.SellerAddress.Name, true);
|
|
column.Item().TextSmall(SettingsData.Instance.SellerAddress.Street);
|
|
column.Item().TextSmall($"{SettingsData.Instance.SellerAddress.Zip} {SettingsData.Instance.SellerAddress.City}");
|
|
});
|
|
|
|
row.RelativeItem().Column(column =>
|
|
{
|
|
if(!string.IsNullOrWhiteSpace(SettingsData.Instance.SellerAddress.Phone)) column.Item().TextSmall($"Tel.: {SettingsData.Instance.SellerAddress.Phone}");
|
|
if(!string.IsNullOrWhiteSpace(SettingsData.Instance.SellerAddress.Email)) column.Item().TextSmall($"E-Mail: {SettingsData.Instance.SellerAddress.Email}");
|
|
if(!string.IsNullOrWhiteSpace(SettingsData.Instance.SellerAddress.Web)) column.Item().TextSmall($"Web: {SettingsData.Instance.SellerAddress.Web}");
|
|
});
|
|
|
|
row.RelativeItem().Column(column =>
|
|
{
|
|
if(!string.IsNullOrWhiteSpace(Model.PaymentData?.BankName)) column.Item().TextSmall(Model.PaymentData?.BankName);
|
|
if(!string.IsNullOrWhiteSpace(Model.PaymentData?.Iban)) column.Item().TextSmall($"IBAN: {Model.PaymentData?.Iban}");
|
|
if(!string.IsNullOrWhiteSpace(Model.PaymentData?.Bic)) column.Item().TextSmall($"BIC: {Model.PaymentData?.Bic}");
|
|
});
|
|
});
|
|
|
|
outerColumn.Item().PaddingTop(3, Unit.Millimetre).AlignCenter().Text(txt =>
|
|
{
|
|
txt.CurrentPageNumber();
|
|
txt.Span(" / ");
|
|
txt.TotalPages();
|
|
});
|
|
});
|
|
}
|
|
|
|
private void ComposeHeader(IContainer container)
|
|
{
|
|
container.Column(outerColumn =>
|
|
{
|
|
outerColumn.Item().Row(row =>
|
|
{
|
|
row.RelativeItem().Column(col =>
|
|
{
|
|
|
|
if (!string.IsNullOrWhiteSpace(SettingsData.Instance.Logo) && File.Exists(SettingsData.Instance.Logo))
|
|
col.Item().Height(25, Unit.Millimetre).Image(SettingsData.Instance.Logo);
|
|
else
|
|
col.Item().Height(25, Unit.Millimetre);
|
|
|
|
});
|
|
|
|
row.RelativeItem().AlignMiddle().PaddingRight(10, Unit.Millimetre).Background(Colors.Red.Medium).Column(col =>
|
|
{
|
|
//col.Item().AlignRight().Text("www.example.com");
|
|
});
|
|
});
|
|
|
|
outerColumn.Item().PaddingTop(15, Unit.Millimetre).Row(row =>
|
|
{
|
|
row.AutoItem().Column(column =>
|
|
{
|
|
column.Item().PaddingBottom(5).TextSmall(SettingsData.Instance.SellerAddress.ToString(), true);
|
|
if(!string.IsNullOrWhiteSpace(Model.Customer?.Name)) column.Item().Text(Model.Customer?.Name);
|
|
if(!string.IsNullOrWhiteSpace(Model.Customer?.Name2)) column.Item().Text(Model.Customer?.Name2);
|
|
column.Item().Text(Model.Customer?.Street);
|
|
column.Item().Text($"{Model.Customer?.Zip} {Model.Customer?.City}");
|
|
});
|
|
|
|
row.RelativeItem();
|
|
|
|
row.AutoItem().Column(col =>
|
|
{
|
|
col.Item().Table(table =>
|
|
{
|
|
table.ColumnsDefinition(c =>
|
|
{
|
|
c.ConstantColumn(50);
|
|
c.ConstantColumn(65);
|
|
});
|
|
|
|
table.Cell().TextSmall("Steuer-Nr:", true);
|
|
table.Cell().TextSmall(SettingsData.Instance.SellerAddress.TaxId);
|
|
|
|
table.Cell().TextSmall("Datum:", true);
|
|
table.Cell().TextSmall(Model.IssueDate.ToShortDateString());
|
|
|
|
table.Cell().TextSmall("Rechnung:", true);
|
|
table.Cell().TextSmall($"#{Model.InvoiceId}");
|
|
});
|
|
});
|
|
});
|
|
});
|
|
}
|
|
}
|
|
} |