diff --git a/.idea/.idea.TinyInvoice/.idea/riderPublish.xml b/.idea/.idea.TinyInvoice/.idea/riderPublish.xml
new file mode 100644
index 0000000..8e8355d
--- /dev/null
+++ b/.idea/.idea.TinyInvoice/.idea/riderPublish.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Server/Components/Pages/InvoiceListPage.razor b/Server/Components/Pages/InvoiceListPage.razor
index e812110..f17c2f0 100644
--- a/Server/Components/Pages/InvoiceListPage.razor
+++ b/Server/Components/Pages/InvoiceListPage.razor
@@ -21,9 +21,10 @@
@($"{invoice.TotalNetto:N2} €") Netto
Bearbeiten
+
@if (invoice.DeletionAllowed)
{
-
+
}
diff --git a/Server/Components/Pages/InvoiceListPage.razor.cs b/Server/Components/Pages/InvoiceListPage.razor.cs
index f3b4d4c..44aadc2 100644
--- a/Server/Components/Pages/InvoiceListPage.razor.cs
+++ b/Server/Components/Pages/InvoiceListPage.razor.cs
@@ -1,4 +1,6 @@
using Microsoft.AspNetCore.Components;
+using QuestPDF.Fluent;
+using QuestPDF.Infrastructure;
using Server.Data;
using Server.Model;
@@ -19,6 +21,7 @@ namespace Server.Components.Pages
{
parameters.SetParameterProperties(this);
+ await SettingsData.LoadAsync();
Invoices = await InvoiceData.LoadAllAsync();
await base.SetParametersAsync(ParameterView.Empty);
@@ -41,5 +44,22 @@ namespace Server.Components.Pages
}
#endregion
+
+ #region Private Method GenerateDocumentAsync
+
+ ///
+ /// Generates a PDF document asynchronously for the given invoice.
+ ///
+ /// The invoice model for which to generate the document.
+ /// A task representing the asynchronous document generation operation.
+ private Task GenerateDocumentAsync(InvoiceModel invoice)
+ {
+ QuestPDF.Settings.License = LicenseType.Community;
+ var doc = new InvoiceDocument(invoice);
+ doc.GeneratePdfAndShow();
+ return Task.CompletedTask;
+ }
+
+ #endregion
}
}
\ No newline at end of file
diff --git a/Server/Extensions.cs b/Server/Extensions.cs
index c36a9d7..5d3507c 100644
--- a/Server/Extensions.cs
+++ b/Server/Extensions.cs
@@ -1,7 +1,25 @@
-namespace Server
+using QuestPDF.Fluent;
+using QuestPDF.Infrastructure;
+
+namespace Server
{
public static class Extensions
{
- public static bool IsNullOrWhiteSpace(this string? value) => string.IsNullOrWhiteSpace(value);
+ #region Public Method TextSmall
+
+ /// Adds a small text element to the specified container with an optional bold style.
+ /// The container to which the text element will be added.
+ /// The text content to be displayed.
+ /// Indicates whether the text should be displayed in bold. Defaults to false.
+ public static void TextSmall(this IContainer container, string? text, bool bold = false)
+ {
+ container.Text(txt =>
+ {
+ var spanDescriptor = txt.Span(text).FontSize(8);
+ if (bold) spanDescriptor.SemiBold();
+ });
+ }
+
+ #endregion
}
}
\ No newline at end of file
diff --git a/Server/Model/InvoiceDocument.cs b/Server/Model/InvoiceDocument.cs
index 8563513..9ad5481 100644
--- a/Server/Model/InvoiceDocument.cs
+++ b/Server/Model/InvoiceDocument.cs
@@ -1,5 +1,6 @@
using System.Globalization;
using System.Net.Mime;
+using QuestPDF.Drawing;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
@@ -13,13 +14,19 @@ namespace Server.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(12).FontFamily(Fonts.Calibri).Light());
+ page.DefaultTextStyle(x => x
+ .FontSize(10)
+ .FontFamily("NotoSans")
+ .Light());
page.Header().Element(ComposeHeader);
page.Content().Element(ComposeContent);
@@ -30,38 +37,126 @@ namespace Server.Model
private void ComposeContent(IContainer container)
{
- container.PaddingVertical(40).Column(column =>
+ container.PaddingVertical(40).Column(outerColumn =>
{
- column.Spacing(5);
+ 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);
+ });
+
+ row.AutoItem().Column(col =>
+ {
+ col.Item().Element(ComposeTaxTable);
+ });
+ });
+
+ outerColumn.Spacing(5);
+ });
+ }
- column.Item().Element(ComposeTable);
+ 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);
+ });
- if (!string.IsNullOrWhiteSpace(Model.Comment))
- column.Item().PaddingTop(25).Element(ComposeComments);
+ 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 ComposeTable(IContainer container)
+ private void ComposeItemTable(IContainer container)
{
container.Table(table =>
{
// Define columns
table.ColumnsDefinition(columns =>
{
- columns.ConstantColumn(30, Unit.Millimetre);
columns.RelativeColumn(3);
- columns.RelativeColumn();
- columns.RelativeColumn();
+ 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().Element(CellStyle).AlignCenter().Text("Menge");
- header.Cell().Element(CellStyle).Text("Bezeichnung");
- header.Cell().Element(CellStyle).AlignRight().Text("Einzelpreis");
- header.Cell().Element(CellStyle).AlignRight().Text("Gesamtpreis");
-
+
+ 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);
@@ -71,54 +166,28 @@ namespace Server.Model
// Describe content
foreach (var item in Model.Items ?? [])
{
- table.Cell().Element(CellStyle).AlignCenter().Text(txt => txt.Span($"{item.Quantity:N2}"));
+
+ // Beschreibung
table.Cell().Element(CellStyle).Column(column =>
{
column.Item().Text(item.Name);
- if(!string.IsNullOrWhiteSpace(item.Description)) column.Item().Text(txt =>
- {
- txt.Span(item.Description).FontSize(8);
- });
+ 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().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;
}
-
- // Gesamt Netto
- table.Cell().ColumnSpan(3).PaddingTop(10).Text("Gesamt Netto");
- table.Cell().PaddingTop(10).AlignRight().Text($"{Model.TotalNetto:N2} €");
- // 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;
-
- table.Cell().ColumnSpan(2).Text("zzgl. 7% USt. auf");
- table.Cell().AlignRight().Text($"{tax7Netto:N2} €");
- table.Cell().AlignRight().Text($"{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;
- table.Cell().ColumnSpan(2).Text("zzgl. 19% USt. auf");
- table.Cell().AlignRight().Text($"{tax19Netto:N2} €");
- table.Cell().AlignRight().Text($"{tax19Tax:N2} €");
- }
-
-
-
- table.Cell().ColumnSpan(3).Element(CellStyle).PaddingTop(10).Text("Rechnungsbetrag").Medium();
+ 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;
@@ -140,37 +209,27 @@ namespace Server.Model
{
container.Column(outerColumn =>
{
- outerColumn.Item().DefaultTextStyle(style => style.FontSize(10)).Row(row =>
+ outerColumn.Item().Row(row =>
{
row.RelativeItem().Column(column =>
{
- column.Item().Text(txt => txt.Span(Model.Seller?.Name).Bold());
- column.Item().Text(txt => txt.Span(Model.Seller?.Street));
- column.Item().Text(txt => txt.Span($"{Model.Seller?.Zip} {Model.Seller?.City}"));
+ column.Item().TextSmall(Model.Seller?.Name, true);
+ column.Item().TextSmall(Model.Seller?.Street);
+ column.Item().TextSmall($"{Model.Seller?.Zip} {Model.Seller?.City}");
});
row.RelativeItem().Column(column =>
{
- if(!string.IsNullOrWhiteSpace(Model.Seller?.Phone))
- column.Item().Text(txt => txt.Span($"Tel.: {Model.Seller?.Phone}"));
-
- if(!string.IsNullOrWhiteSpace(Model.Seller?.Email))
- column.Item().Text(txt => txt.Span($"E-Mail: {Model.Seller?.Email}"));
-
- if(!string.IsNullOrWhiteSpace(Model.Seller?.Web))
- column.Item().Text(txt => txt.Span($"Web: {Model.Seller?.Web}"));
+ if(!string.IsNullOrWhiteSpace(Model.Seller?.Phone)) column.Item().TextSmall($"Tel.: {Model.Seller?.Phone}");
+ if(!string.IsNullOrWhiteSpace(Model.Seller?.Email)) column.Item().TextSmall($"E-Mail: {Model.Seller?.Email}");
+ if(!string.IsNullOrWhiteSpace(Model.Seller?.Web)) column.Item().TextSmall($"Web: {Model.Seller?.Web}");
});
row.RelativeItem().Column(column =>
{
- if(!string.IsNullOrWhiteSpace(Model.PaymentData?.BankName))
- column.Item().Text(txt => txt.Span(Model.PaymentData?.BankName));
-
- if(!string.IsNullOrWhiteSpace(Model.PaymentData?.Iban))
- column.Item().Text(txt => txt.Span($"IBAN: {Model.PaymentData?.Iban}"));
-
- if(!string.IsNullOrWhiteSpace(Model.PaymentData?.Bic))
- column.Item().Text(txt => txt.Span($"BIC: {Model.PaymentData?.Bic}"));
+ 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}");
});
});
@@ -187,9 +246,9 @@ namespace Server.Model
{
container.Column(outerColumn =>
{
- outerColumn.Item().AlignCenter().Row(row =>
+ outerColumn.Item().Row(row =>
{
- row.RelativeItem().AlignCenter().Column(col =>
+ row.RelativeItem().Column(col =>
{
if (!string.IsNullOrWhiteSpace(SettingsData.Instance.Logo) && File.Exists(SettingsData.Instance.Logo))
@@ -198,13 +257,18 @@ namespace Server.Model
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().Text(Model.Seller?.ToString()).Style(new TextStyle().FontSize(8).SemiBold().Underline());
+ column.Item().PaddingBottom(5).TextSmall(Model.Seller?.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);
@@ -213,27 +277,24 @@ namespace Server.Model
row.RelativeItem();
- row.AutoItem().DefaultTextStyle(style => style.FontSize(10)).Column(column =>
+ row.AutoItem().Column(col =>
{
- column.Item().Text(txt =>
+ col.Item().Table(table =>
{
- txt.Span("Steuer-Nr:").Bold().Underline();
- txt.Span(" ");
- txt.Span(Model.Seller?.TaxId);
- });
+ table.ColumnsDefinition(c =>
+ {
+ c.ConstantColumn(50);
+ c.ConstantColumn(50);
+ });
- column.Item().Text(txt =>
- {
- txt.Span("Datum:").Bold().Underline();
- txt.Span(" ");
- txt.Span(Model.IssueDate.ToShortDateString());
- });
-
- column.Item().Text(txt =>
- {
- txt.Span("Rechnung:").Bold();
- txt.Span(" ");
- txt.Span($"#{Model.InvoiceId}");
+ table.Cell().TextSmall("Steuer-Nr:", true);
+ table.Cell().TextSmall(Model.Seller?.TaxId);
+
+ table.Cell().TextSmall("Datum:", true);
+ table.Cell().TextSmall(Model.IssueDate.ToShortDateString());
+
+ table.Cell().TextSmall("Rechnung:", true);
+ table.Cell().TextSmall($"#{Model.InvoiceId}");
});
});
});
diff --git a/Server/Model/InvoiceModel.cs b/Server/Model/InvoiceModel.cs
index 415ea8d..e974293 100644
--- a/Server/Model/InvoiceModel.cs
+++ b/Server/Model/InvoiceModel.cs
@@ -69,6 +69,7 @@
public enum TaxType
{
- Tax19, Tax7
+ Tax19 = 19,
+ Tax7 = 7
}
}
\ No newline at end of file
diff --git a/Server/NotoSans-VariableFont_wdth,wght.ttf b/Server/NotoSans-VariableFont_wdth,wght.ttf
new file mode 100644
index 0000000..ceb7b2f
Binary files /dev/null and b/Server/NotoSans-VariableFont_wdth,wght.ttf differ