Compare commits
	
		
			18 Commits
		
	
	
		
			v0.1.0
			...
			f5f00a7739
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| f5f00a7739 | |||
| 2de4739e9d | |||
| 47e8ab7e62 | |||
| 8a678509c5 | |||
| de53bfdd2b | |||
| f9d95a48f2 | |||
| be734b880f | |||
| 1c45e95ef3 | |||
| 7086a72fab | |||
| 540236f878 | |||
| aab95ee444 | |||
| 2b7d19199a | |||
| 28b424fe65 | |||
| 324c5db94e | |||
| faaeefe6ce | |||
| b76d43a5ff | |||
| 8b48882c86 | |||
| 286279b89f | 
@@ -137,11 +137,6 @@ namespace Elwig {
 | 
				
			|||||||
            base.OnStartup(evt);
 | 
					            base.OnStartup(evt);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        protected override void OnExit(ExitEventArgs evt) {
 | 
					 | 
				
			||||||
            Utils.RunBackground("PDF Close", () => Documents.Pdf.Close());
 | 
					 | 
				
			||||||
            base.OnExit(evt);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        private void PrintingReadyChanged() {
 | 
					        private void PrintingReadyChanged() {
 | 
				
			||||||
            Dispatcher.BeginInvoke(OnPrintingReadyChanged, new EventArgs());
 | 
					            Dispatcher.BeginInvoke(OnPrintingReadyChanged, new EventArgs());
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,7 +2,7 @@
 | 
				
			|||||||
@inherits TemplatePage<Elwig.Documents.BusinessDocument>
 | 
					@inherits TemplatePage<Elwig.Documents.BusinessDocument>
 | 
				
			||||||
@model Elwig.Documents.BusinessDocument
 | 
					@model Elwig.Documents.BusinessDocument
 | 
				
			||||||
@{ Layout = "Document"; }
 | 
					@{ Layout = "Document"; }
 | 
				
			||||||
 | 
					<link rel="stylesheet" href="file:///@Raw(Model.DataPath)\resources\style-businessdocument.css" />
 | 
				
			||||||
<div class="info-wrapper">
 | 
					<div class="info-wrapper">
 | 
				
			||||||
    <div class="address-wrapper">
 | 
					    <div class="address-wrapper">
 | 
				
			||||||
        <div class="sender">
 | 
					        <div class="sender">
 | 
				
			||||||
@@ -15,6 +15,4 @@
 | 
				
			|||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
    <aside>@Raw(Model.Aside)</aside>
 | 
					    <aside>@Raw(Model.Aside)</aside>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
<main>
 | 
					 | 
				
			||||||
@RenderBody()
 | 
					@RenderBody()
 | 
				
			||||||
</main>
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,7 +13,7 @@ namespace Elwig.Documents {
 | 
				
			|||||||
            Location = App.BranchName;
 | 
					            Location = App.BranchName;
 | 
				
			||||||
            IncludeSender = includeSender;
 | 
					            IncludeSender = includeSender;
 | 
				
			||||||
            var uid = (m.UstIdNr ?? "-") + (m.IsBuchführend ? "" : " <i>(pauschaliert)</i>");
 | 
					            var uid = (m.UstIdNr ?? "-") + (m.IsBuchführend ? "" : " <i>(pauschaliert)</i>");
 | 
				
			||||||
            Aside = $"<table><colgroup><col span='1' style='width: 2.25cm;'/><col span='1' style='width: 100%;'/></colgroup>" +
 | 
					            Aside = $"<table><colgroup><col span='1' style='width: 22.5mm;'/><col span='1' style='width: 42.5mm;'/></colgroup>" +
 | 
				
			||||||
                $"<thead><tr><th colspan='2'>Mitglied</th></tr></thead><tbody>" +
 | 
					                $"<thead><tr><th colspan='2'>Mitglied</th></tr></thead><tbody>" +
 | 
				
			||||||
                $"<tr><th>Mitglieds-Nr.</th><td>{m.MgNr}</td></tr>" +
 | 
					                $"<tr><th>Mitglieds-Nr.</th><td>{m.MgNr}</td></tr>" +
 | 
				
			||||||
                $"<tr><th>Betriebs-Nr.</th><td>{m.LfbisNr}</td></tr>" +
 | 
					                $"<tr><th>Betriebs-Nr.</th><td>{m.LfbisNr}</td></tr>" +
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,7 +2,8 @@
 | 
				
			|||||||
@inherits TemplatePage<Elwig.Documents.BusinessLetter>
 | 
					@inherits TemplatePage<Elwig.Documents.BusinessLetter>
 | 
				
			||||||
@model Elwig.Documents.BusinessLetter
 | 
					@model Elwig.Documents.BusinessLetter
 | 
				
			||||||
@{ Layout = "BusinessDocument"; }
 | 
					@{ Layout = "BusinessDocument"; }
 | 
				
			||||||
 | 
					<main>
 | 
				
			||||||
<p>Sehr geehrtes Mitglied,</p>
 | 
					<p>Sehr geehrtes Mitglied,</p>
 | 
				
			||||||
<p>nein.</p>
 | 
					<p>nein.</p>
 | 
				
			||||||
<p>Mit freundlichen Grüßen<br/>Ihre Winzergenossenschaft</p>
 | 
					<p>Mit freundlichen Grüßen<br/>Ihre Winzergenossenschaft</p>
 | 
				
			||||||
 | 
					</main>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,7 +2,8 @@
 | 
				
			|||||||
@inherits TemplatePage<Elwig.Documents.DeliveryNote>
 | 
					@inherits TemplatePage<Elwig.Documents.DeliveryNote>
 | 
				
			||||||
@model Elwig.Documents.DeliveryNote
 | 
					@model Elwig.Documents.DeliveryNote
 | 
				
			||||||
@{ Layout = "BusinessDocument"; }
 | 
					@{ Layout = "BusinessDocument"; }
 | 
				
			||||||
 | 
					<link rel="stylesheet" href="file:///@Raw(Model.DataPath)\resources\style-deliverynote.css" />
 | 
				
			||||||
 | 
					<main>
 | 
				
			||||||
<div class="date">@Model.Location, am @($"{Model.Date:dd.MM.yyyy}")</div>
 | 
					<div class="date">@Model.Location, am @($"{Model.Date:dd.MM.yyyy}")</div>
 | 
				
			||||||
<h1>@Model.Title</h1>
 | 
					<h1>@Model.Title</h1>
 | 
				
			||||||
@{
 | 
					@{
 | 
				
			||||||
@@ -11,38 +12,49 @@
 | 
				
			|||||||
<script>
 | 
					<script>
 | 
				
			||||||
    document.addEventListener("DOMContentLoaded", () => {
 | 
					    document.addEventListener("DOMContentLoaded", () => {
 | 
				
			||||||
        const hidden = document.getElementsByClassName("hidden")[0];
 | 
					        const hidden = document.getElementsByClassName("hidden")[0];
 | 
				
			||||||
        const bottom = hidden.offsetTop + hidden.offsetHeight;
 | 
					 | 
				
			||||||
        const cm = bottom * 2.54 / 96 * window.devicePixelRatio;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (cm > 25.75) {
 | 
					 | 
				
			||||||
            // force page break
 | 
					 | 
				
			||||||
        const table = document.getElementsByClassName("delivery")[0];
 | 
					        const table = document.getElementsByClassName("delivery")[0];
 | 
				
			||||||
 | 
					        const stats = document.getElementById("delivery-stats");
 | 
				
			||||||
 | 
					        const mm = px2mm(0, hidden.offsetTop + hidden.offsetHeight);
 | 
				
			||||||
 | 
					        const heightTable = px2mm(table.offsetTop, hidden.offsetTop + hidden.offsetHeight);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (mm >= heightA4 - heightFooter) {
 | 
				
			||||||
 | 
					            if (heightTable + 10 >= heightMain) {
 | 
				
			||||||
 | 
					                // force page break in table
 | 
				
			||||||
 | 
					                const tblOff = px2mm(0, table.offsetTop);
 | 
				
			||||||
 | 
					                let last = null;
 | 
				
			||||||
 | 
					                for (const tr of table.getElementsByTagName("tr")) {
 | 
				
			||||||
 | 
					                    if (!tr.classList.contains("main")) continue;
 | 
				
			||||||
 | 
					                    const mm2 = tblOff + px2mm(0, tr.offsetTop);
 | 
				
			||||||
 | 
					                    if (mm2 >= heightA4 - heightFooter) {
 | 
				
			||||||
 | 
					                        last.classList.add("page-break");
 | 
				
			||||||
 | 
					                        break;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    last = tr;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                // force page break
 | 
				
			||||||
                const hr = document.createElement("hr");
 | 
					                const hr = document.createElement("hr");
 | 
				
			||||||
                hr.classList.add("page-break");
 | 
					                hr.classList.add("page-break");
 | 
				
			||||||
                table.before(hr);
 | 
					                table.before(hr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            const stats = document.getElementById("delivery-stats");
 | 
					 | 
				
			||||||
            stats.getElementsByTagName("table")[0].classList.add("expanded");
 | 
					 | 
				
			||||||
            hr.before(stats);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                const p = document.createElement("p");
 | 
					                const p = document.createElement("p");
 | 
				
			||||||
                p.innerText = "Siehe nächste Seite."
 | 
					                p.innerText = "Siehe nächste Seite."
 | 
				
			||||||
            stats.before(p);
 | 
					                hr.before(p);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
<table class="delivery">
 | 
					<table class="delivery">
 | 
				
			||||||
    <colgroup>
 | 
					    <colgroup>
 | 
				
			||||||
        <col style="width: 1cm;"/>
 | 
					        <col style="width: 10.00mm;"/>
 | 
				
			||||||
        <col style="width: 25%;"/>
 | 
					        <col style="width: 21.25mm;"/>
 | 
				
			||||||
        <col style="width: 25%;"/>
 | 
					        <col style="width: 21.25mm;"/>
 | 
				
			||||||
        <col style="width: 25%;"/>
 | 
					        <col style="width: 21.25mm;"/>
 | 
				
			||||||
        <col style="width: 25%;"/>
 | 
					        <col style="width: 21.25mm;"/>
 | 
				
			||||||
        <col style="width: 3cm;"/>
 | 
					        <col style="width: 30.00mm;"/>
 | 
				
			||||||
        <col style="width: 1.25cm;"/>
 | 
					        <col style="width: 12.50mm;"/>
 | 
				
			||||||
        <col style="width: 1.25cm;"/>
 | 
					        <col style="width: 12.50mm;"/>
 | 
				
			||||||
        <col style="width: 1.5cm;"/>
 | 
					        <col style="width: 15.00mm;"/>
 | 
				
			||||||
    </colgroup>
 | 
					    </colgroup>
 | 
				
			||||||
    <thead>
 | 
					    <thead>
 | 
				
			||||||
        <tr>
 | 
					        <tr>
 | 
				
			||||||
@@ -105,13 +117,13 @@
 | 
				
			|||||||
    <div id="delivery-stats">
 | 
					    <div id="delivery-stats">
 | 
				
			||||||
        <table class="delivery-stats">
 | 
					        <table class="delivery-stats">
 | 
				
			||||||
            <colgroup>
 | 
					            <colgroup>
 | 
				
			||||||
                <col style="width: 100%;"/>
 | 
					                <col style="width: 45mm;"/>
 | 
				
			||||||
                <col style="width: 2cm;"/>
 | 
					                <col style="width: 20mm;"/>
 | 
				
			||||||
                <col style="width: 2cm;"/>
 | 
					                <col style="width: 20mm;"/>
 | 
				
			||||||
                <col style="width: 2cm;"/>
 | 
					                <col style="width: 20mm;"/>
 | 
				
			||||||
                <col style="width: 2cm;"/>
 | 
					                <col style="width: 20mm;"/>
 | 
				
			||||||
                <col style="width: 2cm;"/>
 | 
					                <col style="width: 20mm;"/>
 | 
				
			||||||
                <col style="width: 2cm;"/>
 | 
					                <col style="width: 20mm;"/>
 | 
				
			||||||
            </colgroup>
 | 
					            </colgroup>
 | 
				
			||||||
            <thead>
 | 
					            <thead>
 | 
				
			||||||
                <tr>
 | 
					                <tr>
 | 
				
			||||||
@@ -152,6 +164,7 @@
 | 
				
			|||||||
        </table>
 | 
					        </table>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					</main>
 | 
				
			||||||
@for (int i = 0; i < 2; i++) {
 | 
					@for (int i = 0; i < 2; i++) {
 | 
				
			||||||
    <div class="@(i == 0 ? "hidden" : "bottom")">
 | 
					    <div class="@(i == 0 ? "hidden" : "bottom")">
 | 
				
			||||||
        @if (Model.Text != null) {
 | 
					        @if (Model.Text != null) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,26 +1,29 @@
 | 
				
			|||||||
@using RazorLight
 | 
					@using RazorLight
 | 
				
			||||||
@inherits TemplatePage<Elwig.Documents.Document>
 | 
					@inherits TemplatePage<Elwig.Documents.Document>
 | 
				
			||||||
@model Elwig.Documents.Document
 | 
					@model Elwig.Documents.Document
 | 
				
			||||||
 | 
					 | 
				
			||||||
<!DOCTYPE html>
 | 
					<!DOCTYPE html>
 | 
				
			||||||
<html lang="de-AT">
 | 
					<html lang="de-AT">
 | 
				
			||||||
<head>
 | 
					<head>
 | 
				
			||||||
    <title>@Model.Title</title>
 | 
					    <title>@Model.Title</title>
 | 
				
			||||||
 | 
					    <meta name="author" value="@Model.Author"/>
 | 
				
			||||||
    <meta charset="UTF-8"/>
 | 
					    <meta charset="UTF-8"/>
 | 
				
			||||||
    <script>
 | 
					    <script>
 | 
				
			||||||
        window.PagedConfig = { auto: false };
 | 
					        const heightA4 = 297, widhtA4 = 210, heightFooter = 35, heightHeader = 25;
 | 
				
			||||||
        if (!navigator.webdriver) {
 | 
					        const heightMain = heightA4 - heightFooter - heightHeader;
 | 
				
			||||||
            window.addEventListener("beforeprint", async () => { await window.PagedPolyfill.preview(); });
 | 
					        function px2mm(px1, px2) {
 | 
				
			||||||
            window.addEventListener("afterprint", () => { location.reload(); });
 | 
					            return (px2 - px1 + 1) * 2.54 / 96 * window.devicePixelRatio * 10;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    </script>
 | 
					    </script>
 | 
				
			||||||
    <script src="@Raw(Model.DataPath)\resources\paged.polyfill.js"></script>
 | 
					    <link rel="stylesheet" href="file:///@Raw(Model.DataPath)\resources\style.css"/>
 | 
				
			||||||
    <link rel="stylesheet" href="@Raw(Model.DataPath)\resources\style.css" />
 | 
					    <link rel="stylesheet" href="file:///@Raw(Model.DataPath)\resources\style-page.css"/>
 | 
				
			||||||
</head>
 | 
					</head>
 | 
				
			||||||
<body>
 | 
					<body>
 | 
				
			||||||
    <div class="m1"></div>
 | 
					    <div class="m1"></div>
 | 
				
			||||||
    <div class="m2"></div>
 | 
					    <div class="m2"></div>
 | 
				
			||||||
    <div class="m3"></div>
 | 
					    <div class="m3"></div>
 | 
				
			||||||
 | 
					    <div class="m1 r"></div>
 | 
				
			||||||
 | 
					    <div class="m2 r"></div>
 | 
				
			||||||
 | 
					    <div class="m3 r"></div>
 | 
				
			||||||
    <div class="footer-wrapper">
 | 
					    <div class="footer-wrapper">
 | 
				
			||||||
        <div class="pre-footer">
 | 
					        <div class="pre-footer">
 | 
				
			||||||
            <span class="date">@($"{Model.Date:dddd, d. MMMM yyyy}")</span>
 | 
					            <span class="date">@($"{Model.Date:dddd, d. MMMM yyyy}")</span>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,11 +8,21 @@ namespace Elwig.Documents {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        private TempFile? PdfFile = null;
 | 
					        private TempFile? PdfFile = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public string DataPath;
 | 
				
			||||||
 | 
					        public int CurrentNextSeason;
 | 
				
			||||||
 | 
					        public string? DocumentId;
 | 
				
			||||||
 | 
					        public string Title;
 | 
				
			||||||
 | 
					        public string Author;
 | 
				
			||||||
 | 
					        public string Header;
 | 
				
			||||||
 | 
					        public string Footer;
 | 
				
			||||||
 | 
					        public DateTime Date;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public Document(string title) {
 | 
					        public Document(string title) {
 | 
				
			||||||
            var c = App.Client;
 | 
					            var c = App.Client;
 | 
				
			||||||
            DataPath = App.DataPath;
 | 
					            DataPath = App.DataPath;
 | 
				
			||||||
            CurrentNextSeason = Utils.CurrentNextSeason;
 | 
					            CurrentNextSeason = Utils.CurrentNextSeason;
 | 
				
			||||||
            Title = title;
 | 
					            Title = title;
 | 
				
			||||||
 | 
					            Author = App.Client.NameFull;
 | 
				
			||||||
            Header = $"<h1>{c.Name}</h1>";
 | 
					            Header = $"<h1>{c.Name}</h1>";
 | 
				
			||||||
            Footer = Utils.GenerateFooter("<br/>", " \u00b7 ")
 | 
					            Footer = Utils.GenerateFooter("<br/>", " \u00b7 ")
 | 
				
			||||||
                .Item(c.NameFull).NextLine()
 | 
					                .Item(c.NameFull).NextLine()
 | 
				
			||||||
@@ -33,15 +43,7 @@ namespace Elwig.Documents {
 | 
				
			|||||||
            GC.SuppressFinalize(this);
 | 
					            GC.SuppressFinalize(this);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public string DataPath { get; set; }
 | 
					        private Task<string> Render() {
 | 
				
			||||||
        public int CurrentNextSeason { get; set; }
 | 
					 | 
				
			||||||
        public string Title { get; set; }
 | 
					 | 
				
			||||||
        public string Header { get; set; }
 | 
					 | 
				
			||||||
        public string Footer { get; set; }
 | 
					 | 
				
			||||||
        public DateTime Date { get; set; }
 | 
					 | 
				
			||||||
        public string? DocumentId { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        private async Task<string> Render() {
 | 
					 | 
				
			||||||
            string name;
 | 
					            string name;
 | 
				
			||||||
            if (this is BusinessLetter) {
 | 
					            if (this is BusinessLetter) {
 | 
				
			||||||
                name = "BusinessLetter";
 | 
					                name = "BusinessLetter";
 | 
				
			||||||
@@ -50,16 +52,19 @@ namespace Elwig.Documents {
 | 
				
			|||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                throw new InvalidOperationException("Invalid document object");
 | 
					                throw new InvalidOperationException("Invalid document object");
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            return await Html.CompileRenderAsync(name, this);
 | 
					            return Render(name);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private Task<string> Render(string name) {
 | 
				
			||||||
 | 
					            return Html.CompileRenderAsync(name, this);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public async Task Generate() {
 | 
					        public async Task Generate() {
 | 
				
			||||||
            var pdf = new TempFile("pdf");
 | 
					            var pdf = new TempFile("pdf");
 | 
				
			||||||
            using (var tmpHtml = new TempFile("html")) {
 | 
					            using (var tmpHtml = new TempFile("html")) {
 | 
				
			||||||
                await File.WriteAllTextAsync(tmpHtml.FilePath, await Render());
 | 
					                await File.WriteAllTextAsync(tmpHtml.FilePath, await Render(), Utils.UTF8);
 | 
				
			||||||
                await Pdf.Convert(tmpHtml.FilePath, pdf.FilePath);
 | 
					                await Pdf.Convert(tmpHtml.FilePath, pdf.FilePath);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            Pdf.UpdateMetadata(pdf.FilePath, Title, App.Client.NameFull);
 | 
					 | 
				
			||||||
            PdfFile = pdf;
 | 
					            PdfFile = pdf;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,64 +1,45 @@
 | 
				
			|||||||
using System;
 | 
					 | 
				
			||||||
using System.Threading.Tasks;
 | 
					using System.Threading.Tasks;
 | 
				
			||||||
using System.Windows;
 | 
					 | 
				
			||||||
using PdfSharp.Pdf.IO;
 | 
					 | 
				
			||||||
using PuppeteerSharp;
 | 
					 | 
				
			||||||
using Elwig.Helpers;
 | 
					using Elwig.Helpers;
 | 
				
			||||||
using Elwig.Windows;
 | 
					using Elwig.Windows;
 | 
				
			||||||
using System.Diagnostics;
 | 
					using System.Diagnostics;
 | 
				
			||||||
 | 
					using Balbarak.WeasyPrint;
 | 
				
			||||||
 | 
					using System;
 | 
				
			||||||
 | 
					using System.IO;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Elwig.Documents {
 | 
					namespace Elwig.Documents {
 | 
				
			||||||
    public static class Pdf {
 | 
					    public static class Pdf {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private static readonly string Chromium = @"C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe";
 | 
					 | 
				
			||||||
        private static readonly string PdfToPrinter = App.ExePath + "PDFtoPrinter.exe";
 | 
					        private static readonly string PdfToPrinter = App.ExePath + "PDFtoPrinter.exe";
 | 
				
			||||||
        private static IBrowser? Browser = null;
 | 
					        private static readonly FilesManager WeasyPrintManager = new();
 | 
				
			||||||
        public static bool IsReady => Browser != null;
 | 
					        private static string? WeasyPrintPython = null;
 | 
				
			||||||
 | 
					        private static string? WeasyPrintDir => WeasyPrintManager.FolderPath;
 | 
				
			||||||
 | 
					        public static bool IsReady => WeasyPrintPython != null && WeasyPrintDir != null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public static async Task Init(Action evtHandler) {
 | 
					        public static async Task Init(Action evtHandler) {
 | 
				
			||||||
            Browser = await Puppeteer.LaunchAsync(new LaunchOptions {
 | 
					            if (!WeasyPrintManager.IsFilesExsited()) {
 | 
				
			||||||
                Headless = true,
 | 
					                await WeasyPrintManager.InitFilesAsync();
 | 
				
			||||||
                ExecutablePath = Chromium,
 | 
					            }
 | 
				
			||||||
                // paged.js uses XHRs to load styles, so this is needed
 | 
					            WeasyPrintPython = Path.Combine(WeasyPrintManager.FolderPath, "python.exe");
 | 
				
			||||||
                Args = new[] { "--allow-file-access-from-files" },
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
            evtHandler();
 | 
					            evtHandler();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public static async Task Close() {
 | 
					 | 
				
			||||||
            if (Browser == null) return;
 | 
					 | 
				
			||||||
            await Browser.CloseAsync();
 | 
					 | 
				
			||||||
            Browser = null;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public static async Task Convert(string htmlPath, string pdfPath) {
 | 
					        public static async Task Convert(string htmlPath, string pdfPath) {
 | 
				
			||||||
            if (Browser == null) throw new InvalidOperationException("The puppeteer engine has not been initialized yet");
 | 
					            var p = new Process() { StartInfo = new() {
 | 
				
			||||||
            using var page = await Browser.NewPageAsync();
 | 
					                FileName = WeasyPrintPython,
 | 
				
			||||||
            page.Console += OnConsole;
 | 
					                CreateNoWindow = true,
 | 
				
			||||||
            await page.GoToAsync($"file://{htmlPath}");
 | 
					                WorkingDirectory = WeasyPrintDir,
 | 
				
			||||||
            await page.EvaluateFunctionAsync("async () => { await window.PagedPolyfill.preview(); }");
 | 
					                RedirectStandardError = true,
 | 
				
			||||||
            await page.PdfAsync(pdfPath, new() {
 | 
					            } };
 | 
				
			||||||
                PreferCSSPageSize = true,
 | 
					            p.StartInfo.EnvironmentVariables["PATH"] = "Scripts;gtk3;" + Environment.GetEnvironmentVariable("PATH");
 | 
				
			||||||
                //Format = PaperFormat.A4,
 | 
					            p.StartInfo.ArgumentList.Add("scripts/weasyprint.exe");
 | 
				
			||||||
                DisplayHeaderFooter = false,
 | 
					            p.StartInfo.ArgumentList.Add("-e");
 | 
				
			||||||
                MarginOptions = new() {
 | 
					            p.StartInfo.ArgumentList.Add("utf8");
 | 
				
			||||||
                    Top = "0mm",
 | 
					            p.StartInfo.ArgumentList.Add(htmlPath);
 | 
				
			||||||
                    Right = "0mm",
 | 
					            p.StartInfo.ArgumentList.Add(pdfPath);
 | 
				
			||||||
                    Bottom = "0mm",
 | 
					            p.Start();
 | 
				
			||||||
                    Left = "0mm",
 | 
					            await p.WaitForExitAsync();
 | 
				
			||||||
                },
 | 
					            var stderr = await p.StandardError.ReadToEndAsync();
 | 
				
			||||||
            });
 | 
					            if (p.ExitCode != 0) throw new Exception(stderr);
 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        private static void OnConsole(object? sender, ConsoleEventArgs e) {
 | 
					 | 
				
			||||||
            MessageBox.Show(e.Message.Text);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public static void UpdateMetadata(string path, string title, string author) {
 | 
					 | 
				
			||||||
            using var doc = PdfReader.Open(path);
 | 
					 | 
				
			||||||
            doc.Info.Title = title;
 | 
					 | 
				
			||||||
            doc.Info.Author = author;
 | 
					 | 
				
			||||||
            doc.Save(path);
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public static void Show(TempFile file, string title) {
 | 
					        public static void Show(TempFile file, string title) {
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										146
									
								
								Elwig/Documents/style-businessdocument.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										146
									
								
								Elwig/Documents/style-businessdocument.css
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,146 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					header, .address-wrapper, aside, main {
 | 
				
			||||||
 | 
					    overflow: hidden;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					header {
 | 
				
			||||||
 | 
					    height: 45mm;
 | 
				
			||||||
 | 
					    padding: 5mm;
 | 
				
			||||||
 | 
					    position: absolute;
 | 
				
			||||||
 | 
					    top: -25mm;
 | 
				
			||||||
 | 
					    left: 0;
 | 
				
			||||||
 | 
					    right: 0;
 | 
				
			||||||
 | 
					    text-align: center;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					header h1 {
 | 
				
			||||||
 | 
					    font-size: 18pt;
 | 
				
			||||||
 | 
					    margin-top: 10mm;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.spacing {
 | 
				
			||||||
 | 
					    height: 20mm;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.info-wrapper {
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					    height: 45mm;
 | 
				
			||||||
 | 
					    margin: 0 0 2mm 0;
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.address-wrapper {
 | 
				
			||||||
 | 
					    height: 45mm;
 | 
				
			||||||
 | 
					    width: 85mm;
 | 
				
			||||||
 | 
					    margin: 0;
 | 
				
			||||||
 | 
					    padding: 5mm;
 | 
				
			||||||
 | 
					    position: absolute;
 | 
				
			||||||
 | 
					    left: -5mm;
 | 
				
			||||||
 | 
					    top: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.address-wrapper .sender {
 | 
				
			||||||
 | 
					    height: 4em;
 | 
				
			||||||
 | 
					    font-size: 8pt;
 | 
				
			||||||
 | 
					    padding: 1em 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					address {
 | 
				
			||||||
 | 
					    height: 5em;
 | 
				
			||||||
 | 
					    white-space: pre-line;
 | 
				
			||||||
 | 
					    font-size: 12pt;
 | 
				
			||||||
 | 
					    font-style: normal;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					aside {
 | 
				
			||||||
 | 
					    height: 40mm;
 | 
				
			||||||
 | 
					    width: 75mm;
 | 
				
			||||||
 | 
					    margin: 0;
 | 
				
			||||||
 | 
					    position: absolute;
 | 
				
			||||||
 | 
					    left: 100mm;
 | 
				
			||||||
 | 
					    top: 5mm;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					aside table {
 | 
				
			||||||
 | 
					    border-collapse: collapse;
 | 
				
			||||||
 | 
					    border: 0.5pt solid #808080;
 | 
				
			||||||
 | 
					    width: 65mm;
 | 
				
			||||||
 | 
					    margin-right: 10mm;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					aside table thead:not(:first-child) tr {
 | 
				
			||||||
 | 
					    border-top: 0.5pt solid #808080;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					aside table thead th {
 | 
				
			||||||
 | 
					    background-color: #E0E0E0;
 | 
				
			||||||
 | 
					    font-size: 10pt;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					aside table tbody th,
 | 
				
			||||||
 | 
					aside table tbody td {
 | 
				
			||||||
 | 
					    text-align: left;
 | 
				
			||||||
 | 
					    font-size: 10pt;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					aside table tbody th {
 | 
				
			||||||
 | 
					    font-weight: normal;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					main {
 | 
				
			||||||
 | 
					    margin: 2em 0 1em 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					main > *:first-child {
 | 
				
			||||||
 | 
					    margin-top: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.main-wrapper h1, .main-wrapper p {
 | 
				
			||||||
 | 
					    font-size: 12pt;
 | 
				
			||||||
 | 
					    margin: 1em 0;
 | 
				
			||||||
 | 
					    text-align: justify;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.main-wrapper p {
 | 
				
			||||||
 | 
					    widows: 3;
 | 
				
			||||||
 | 
					    orphans: 3;
 | 
				
			||||||
 | 
					    hyphens: manual;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.main-wrapper .date {
 | 
				
			||||||
 | 
					    margin-bottom: 2em;
 | 
				
			||||||
 | 
					    text-align: right;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.main-wrapper h1 {
 | 
				
			||||||
 | 
					    margin-bottom: 2em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.main-wrapper p.comment {
 | 
				
			||||||
 | 
					    font-size: 10pt;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.main-wrapper .hidden {
 | 
				
			||||||
 | 
					    break-before: avoid;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.main-wrapper .bottom {
 | 
				
			||||||
 | 
					    bottom: 0;
 | 
				
			||||||
 | 
					    position: absolute;
 | 
				
			||||||
 | 
					    width: 165mm;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.main-wrapper .signatures {
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    justify-content: space-around;
 | 
				
			||||||
 | 
					    margin: 20mm 0 2mm 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.main-wrapper .signatures > * {
 | 
				
			||||||
 | 
					    width: 50mm;
 | 
				
			||||||
 | 
					    border-top: 0.5pt solid black;
 | 
				
			||||||
 | 
					    padding-top: 1mm;
 | 
				
			||||||
 | 
					    text-align: center;
 | 
				
			||||||
 | 
					    font-size: 10pt;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										95
									
								
								Elwig/Documents/style-deliverynote.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								Elwig/Documents/style-deliverynote.css
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,95 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					table.delivery {
 | 
				
			||||||
 | 
					    margin-bottom: 5mm;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					table.delivery tr:not(.main) {
 | 
				
			||||||
 | 
					    break-before: avoid;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					table.delivery th {
 | 
				
			||||||
 | 
					    font-weight: normal;
 | 
				
			||||||
 | 
					    font-style: italic;
 | 
				
			||||||
 | 
					    font-size: 10pt;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					table.delivery th.main {
 | 
				
			||||||
 | 
					    text-align: left;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					table.delivery tr.main td {
 | 
				
			||||||
 | 
					    font-weight: bold;
 | 
				
			||||||
 | 
					    padding-top: 2mm;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					table.delivery tbody tr:not(.main) td {
 | 
				
			||||||
 | 
					    font-size: 8pt;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					table.delivery tr.tight:not(.first) td {
 | 
				
			||||||
 | 
					    padding-top: 0;
 | 
				
			||||||
 | 
					    padding-bottom: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					table.delivery tr.tight.first td {
 | 
				
			||||||
 | 
					    padding-bottom: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* FIXME update version of WeasyPrint
 | 
				
			||||||
 | 
					table.delivery tr.tight:has(+ tr:not(.tight)) td {
 | 
				
			||||||
 | 
					padding-bottom: 0.5mm !important;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					table.delivery tr.sum {
 | 
				
			||||||
 | 
					    border-top: 0.5pt solid black;
 | 
				
			||||||
 | 
					    break-before: avoid;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					table.delivery tr.sum td {
 | 
				
			||||||
 | 
					    padding-top: 1mm;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					table.delivery-stats {
 | 
				
			||||||
 | 
					    font-size: 8pt;
 | 
				
			||||||
 | 
					    break-inside: avoid;
 | 
				
			||||||
 | 
					    break-after: avoid;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					table.delivery-stats.expanded th,
 | 
				
			||||||
 | 
					table.delivery-stats.expanded td {
 | 
				
			||||||
 | 
					    padding: 0.25mm 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					table.delivery-stats:not(.expanded) th,
 | 
				
			||||||
 | 
					table.delivery-stats:not(.expanded) td {
 | 
				
			||||||
 | 
					    padding: 0.125mm 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					table.delivery-stats:not(.expanded) tr.optional {
 | 
				
			||||||
 | 
					    display: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					table.delivery-stats thead th {
 | 
				
			||||||
 | 
					    font-weight: normal;
 | 
				
			||||||
 | 
					    font-style: italic;
 | 
				
			||||||
 | 
					    text-align: right;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					table.delivery-stats thead th:first-child {
 | 
				
			||||||
 | 
					    text-align: left;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					table.delivery-stats.expanded tbody {
 | 
				
			||||||
 | 
					    font-size: 10pt;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					table.delivery-stats td {
 | 
				
			||||||
 | 
					    text-align: right;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					table.delivery-stats tbody th {
 | 
				
			||||||
 | 
					    font-weight: normal;
 | 
				
			||||||
 | 
					    font-style: italic;
 | 
				
			||||||
 | 
					    text-align: left;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										71
									
								
								Elwig/Documents/style-page.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								Elwig/Documents/style-page.css
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,71 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					.m1, .m2, .m3 {
 | 
				
			||||||
 | 
					    height: 0;
 | 
				
			||||||
 | 
					    width: 10mm;
 | 
				
			||||||
 | 
					    position: fixed;
 | 
				
			||||||
 | 
					    left: -25mm;
 | 
				
			||||||
 | 
					    border-top: 0.5pt solid black;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.m1.r, .m2.r, .m3.r {
 | 
				
			||||||
 | 
					    left: initial;
 | 
				
			||||||
 | 
					    right: -20mm;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.m1 {top: 80mm;}
 | 
				
			||||||
 | 
					.m2 {top: 123.5mm;}
 | 
				
			||||||
 | 
					.m3 {top: 185mm;}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.page-break {
 | 
				
			||||||
 | 
					    break-before: page;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					hr.page-break {
 | 
				
			||||||
 | 
					    display: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@page {
 | 
				
			||||||
 | 
					    size: A4;
 | 
				
			||||||
 | 
					    margin: 25mm 20mm 35mm 25mm;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @bottom-center {
 | 
				
			||||||
 | 
					        content: element(page-footer);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@media screen {
 | 
				
			||||||
 | 
					    body, header, .footer-wrapper {
 | 
				
			||||||
 | 
					        width: 210mm;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    header, .address-wrapper, aside, main {
 | 
				
			||||||
 | 
					        border: 1px solid lightgray;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .m1, .m2, .m3 {
 | 
				
			||||||
 | 
					        display: none;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    header {
 | 
				
			||||||
 | 
					        top: 0;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .spacing {
 | 
				
			||||||
 | 
					        height: 45mm;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .main-wrapper {
 | 
				
			||||||
 | 
					        margin: 0 20mm 40mm 25mm;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .footer-wrapper {
 | 
				
			||||||
 | 
					        position: fixed;
 | 
				
			||||||
 | 
					        bottom: 0;
 | 
				
			||||||
 | 
					        left: 0;
 | 
				
			||||||
 | 
					        right: 0;
 | 
				
			||||||
 | 
					        background: white;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@media print {
 | 
				
			||||||
 | 
					    .page::after {
 | 
				
			||||||
 | 
					        content: "Seite " counter(page) " von " counter(pages) !important;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -12,75 +12,10 @@ body {
 | 
				
			|||||||
    margin: 0;
 | 
					    margin: 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.m1, .m2, .m3 {
 | 
					table {
 | 
				
			||||||
    height: 0;
 | 
					 | 
				
			||||||
    width: 1cm;
 | 
					 | 
				
			||||||
    position: fixed;
 | 
					 | 
				
			||||||
    left: 0;
 | 
					 | 
				
			||||||
    border-top: 1pt solid black;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.m1 {top: 105mm;}
 | 
					 | 
				
			||||||
.m2 {top: 148.5mm;}
 | 
					 | 
				
			||||||
.m3 {top: 210mm;}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
header, .address-wrapper, aside, main {
 | 
					 | 
				
			||||||
    overflow: hidden;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
header {
 | 
					 | 
				
			||||||
    height: 45mm;
 | 
					 | 
				
			||||||
    padding: 5mm;
 | 
					 | 
				
			||||||
    position: absolute;
 | 
					 | 
				
			||||||
    top: -25mm;
 | 
					 | 
				
			||||||
    left: 0;
 | 
					 | 
				
			||||||
    right: 0;
 | 
					 | 
				
			||||||
    text-align: center;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
header h1{
 | 
					 | 
				
			||||||
    font-size: 18pt;
 | 
					 | 
				
			||||||
    margin-top: 1cm;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.spacing {
 | 
					 | 
				
			||||||
    height: 20mm;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.info-wrapper {
 | 
					 | 
				
			||||||
    width: 100%;
 | 
					    width: 100%;
 | 
				
			||||||
    height: 45mm;
 | 
					    border-collapse: collapse;
 | 
				
			||||||
    margin: 0 0 8.46mm 0;
 | 
					    table-layout: fixed;
 | 
				
			||||||
    position: relative;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.address-wrapper {
 | 
					 | 
				
			||||||
    height: 45mm;
 | 
					 | 
				
			||||||
    width: 85mm;
 | 
					 | 
				
			||||||
    margin: 0;
 | 
					 | 
				
			||||||
    padding: 5mm;
 | 
					 | 
				
			||||||
    position: absolute;
 | 
					 | 
				
			||||||
    left: 20mm;
 | 
					 | 
				
			||||||
    top: 0;
 | 
					 | 
				
			||||||
    display: flex;
 | 
					 | 
				
			||||||
    flex-direction: column;
 | 
					 | 
				
			||||||
    justify-content: flex-end;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.address-wrapper .sender {
 | 
					 | 
				
			||||||
    flex: 17.7mm 1 1;
 | 
					 | 
				
			||||||
    font-size: 8pt;
 | 
					 | 
				
			||||||
    display: flex;
 | 
					 | 
				
			||||||
    flex-direction: column;
 | 
					 | 
				
			||||||
    justify-content: flex-end;
 | 
					 | 
				
			||||||
    padding-bottom: 2mm;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
address {
 | 
					 | 
				
			||||||
    flex: 27.3mm 1 1;
 | 
					 | 
				
			||||||
    white-space: pre-line;
 | 
					 | 
				
			||||||
    font-size: 12pt;
 | 
					 | 
				
			||||||
    font-style: normal;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
table td,
 | 
					table td,
 | 
				
			||||||
@@ -88,90 +23,23 @@ table th {
 | 
				
			|||||||
    padding: 0.5mm 1mm;
 | 
					    padding: 0.5mm 1mm;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
aside {
 | 
					table th {
 | 
				
			||||||
    height: 40mm;
 | 
					    text-align: center;
 | 
				
			||||||
    width: 75mm;
 | 
					 | 
				
			||||||
    margin: 0;
 | 
					 | 
				
			||||||
    position: absolute;
 | 
					 | 
				
			||||||
    left: 125mm;
 | 
					 | 
				
			||||||
    top: 5mm;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
aside table {
 | 
					 | 
				
			||||||
    border-collapse: collapse;
 | 
					 | 
				
			||||||
    border: 1pt solid #808080;
 | 
					 | 
				
			||||||
    width: calc(100% - 1cm);
 | 
					 | 
				
			||||||
    margin-right: 1cm;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
aside table thead:not(:first-child) tr {
 | 
					 | 
				
			||||||
    border-top: 1pt solid #808080;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
aside table thead th {
 | 
					 | 
				
			||||||
    background-color: #E0E0E0;
 | 
					 | 
				
			||||||
    font-size: 10pt;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
aside table tbody th,
 | 
					 | 
				
			||||||
aside table tbody td {
 | 
					 | 
				
			||||||
    text-align: left;
 | 
					 | 
				
			||||||
    font-size: 10pt;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
aside table tbody th {
 | 
					 | 
				
			||||||
    font-weight: normal;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
main {
 | 
					 | 
				
			||||||
    margin: 8.46mm 20mm 4.23mm 25mm;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
main :first-child {
 | 
					 | 
				
			||||||
    margin-top: 0;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
main h1, main p {
 | 
					 | 
				
			||||||
    font-size: 12pt;
 | 
					 | 
				
			||||||
    margin: 1em 0;
 | 
					 | 
				
			||||||
    text-align: justify;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
main p {
 | 
					 | 
				
			||||||
    widows: 3;
 | 
					 | 
				
			||||||
    orphans: 3;
 | 
					 | 
				
			||||||
    hyphens: auto;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
main .date {
 | 
					 | 
				
			||||||
    margin-bottom: 2em;
 | 
					 | 
				
			||||||
    text-align: right;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
main h1 {
 | 
					 | 
				
			||||||
    margin-bottom: 2em;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
main p.comment {
 | 
					 | 
				
			||||||
    font-size: 10pt;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.footer-wrapper {
 | 
					.footer-wrapper {
 | 
				
			||||||
    padding: 0 20mm 0 25mm;
 | 
					 | 
				
			||||||
    position: running(page-footer);
 | 
					    position: running(page-footer);
 | 
				
			||||||
    bottom: 0;
 | 
					    width: 165mm;
 | 
				
			||||||
    left: 0;
 | 
					 | 
				
			||||||
    right: 0;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.pre-footer {
 | 
					.pre-footer {
 | 
				
			||||||
    margin: 4.23mm 0;
 | 
					    margin: 1em 0;
 | 
				
			||||||
    font-size: 10pt;
 | 
					    font-size: 10pt;
 | 
				
			||||||
    display: flex;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.pre-footer > * {
 | 
					.pre-footer > * {
 | 
				
			||||||
    flex: 5cm 1 1;
 | 
					    display: inline-block;
 | 
				
			||||||
 | 
					    width: 33%;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.pre-footer .date {
 | 
					.pre-footer .date {
 | 
				
			||||||
@@ -185,183 +53,27 @@ main p.comment {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
.pre-footer .page {
 | 
					.pre-footer .page {
 | 
				
			||||||
    text-align: right;
 | 
					    text-align: right;
 | 
				
			||||||
 | 
					    float: right;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.pre-fotter .page::after {
 | 
					.pre-footer .page::after {
 | 
				
			||||||
    content: "Seite 1 von 1";
 | 
					    content: "Seite 1 von 1";
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
footer {
 | 
					footer {
 | 
				
			||||||
    font-size: 10pt;
 | 
					    font-size: 10pt;
 | 
				
			||||||
    border-top: 1pt solid black;
 | 
					    border-top: 0.5pt solid black;
 | 
				
			||||||
    height: 25mm;
 | 
					    height: 25mm;
 | 
				
			||||||
    padding-top: 1mm;
 | 
					    padding-top: 1mm;
 | 
				
			||||||
    text-align: center;
 | 
					    text-align: center;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
table {
 | 
					 | 
				
			||||||
    width: 100%;
 | 
					 | 
				
			||||||
    border-collapse: collapse;
 | 
					 | 
				
			||||||
    table-layout: fixed;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
table.delivery {
 | 
					 | 
				
			||||||
    margin-bottom: 5mm;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
table.delivery th {
 | 
					 | 
				
			||||||
    font-weight: normal;
 | 
					 | 
				
			||||||
    font-style: italic;
 | 
					 | 
				
			||||||
    font-size: 10pt;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
table.delivery th.main {
 | 
					 | 
				
			||||||
    text-align: left;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
table.delivery tr.main td {
 | 
					 | 
				
			||||||
    font-weight: bold;
 | 
					 | 
				
			||||||
    padding-top: 2mm;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
table.delivery tbody tr:not(.main) td {
 | 
					 | 
				
			||||||
    font-size: 8pt;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
table.delivery tr.tight:not(.first) td {
 | 
					 | 
				
			||||||
    padding-top: 0;
 | 
					 | 
				
			||||||
    padding-bottom: 0;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
table.delivery tr.tight.first td {
 | 
					 | 
				
			||||||
    padding-bottom: 0;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
table.delivery tr.tight:has(+ tr:not(.tight)) td {
 | 
					 | 
				
			||||||
    padding-bottom: 0.5mm !important;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
table.delivery tr.sum {
 | 
					 | 
				
			||||||
    border-top: 1pt solid black;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
table.delivery tr.sum td {
 | 
					 | 
				
			||||||
    padding-top: 1mm;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
table.delivery-stats {
 | 
					 | 
				
			||||||
    font-size: 8pt;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
table.delivery-stats.expanded th,
 | 
					 | 
				
			||||||
table.delivery-stats.expanded td {
 | 
					 | 
				
			||||||
    padding: 0.25mm 0;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
table.delivery-stats:not(.expanded) th,
 | 
					 | 
				
			||||||
table.delivery-stats:not(.expanded) td {
 | 
					 | 
				
			||||||
    padding: 0.125mm 0;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
table.delivery-stats:not(.expanded) tr.optional {
 | 
					 | 
				
			||||||
    display: none;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
table.delivery-stats thead th {
 | 
					 | 
				
			||||||
    font-weight: normal;
 | 
					 | 
				
			||||||
    font-style: italic;
 | 
					 | 
				
			||||||
    text-align: right;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
table.delivery-stats thead th:first-child {
 | 
					 | 
				
			||||||
    text-align: left;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
table.delivery-stats.expanded tbody {
 | 
					 | 
				
			||||||
    font-size: 10pt;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
table.delivery-stats td {
 | 
					 | 
				
			||||||
    text-align: right;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
table.delivery-stats tbody th {
 | 
					 | 
				
			||||||
    font-weight: normal;
 | 
					 | 
				
			||||||
    font-style: italic;
 | 
					 | 
				
			||||||
    text-align: left;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.hidden {
 | 
					.hidden {
 | 
				
			||||||
    visibility: hidden;
 | 
					    visibility: hidden;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
main .bottom {
 | 
					 | 
				
			||||||
    bottom: 0;
 | 
					 | 
				
			||||||
    position: absolute;
 | 
					 | 
				
			||||||
    width: calc(100% - 25mm - 20mm);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
main .signatures {
 | 
					 | 
				
			||||||
    width: 100%;
 | 
					 | 
				
			||||||
    display: flex;
 | 
					 | 
				
			||||||
    justify-content: space-around;
 | 
					 | 
				
			||||||
    margin: 20mm 0 2mm 0;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
main .signatures > * {
 | 
					 | 
				
			||||||
    width: 5cm;
 | 
					 | 
				
			||||||
    border-top: 1pt solid black;
 | 
					 | 
				
			||||||
    padding-top: 1mm;
 | 
					 | 
				
			||||||
    text-align: center;
 | 
					 | 
				
			||||||
    font-size: 10pt;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
hr {
 | 
					hr {
 | 
				
			||||||
    border: none;
 | 
					    border: none;
 | 
				
			||||||
    border-top: 1pt solid black;
 | 
					    border-top: 0.5pt solid black;
 | 
				
			||||||
    margin: 5mm 0;
 | 
					    margin: 5mm 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
hr.page-break {
 | 
					 | 
				
			||||||
    display: none;
 | 
					 | 
				
			||||||
    break-after: page;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@page {
 | 
					 | 
				
			||||||
    size: A4;
 | 
					 | 
				
			||||||
    margin: 25mm 0 35mm 0;
 | 
					 | 
				
			||||||
    @bottom-center {
 | 
					 | 
				
			||||||
        content: element(page-footer);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@media screen {
 | 
					 | 
				
			||||||
    body, header, .footer-wrapper {
 | 
					 | 
				
			||||||
        width: 210mm;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    header, .address-wrapper, aside, main {
 | 
					 | 
				
			||||||
        border: 1pt solid lightgray;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    .m1, .m2, .m3 {display: none;}
 | 
					 | 
				
			||||||
    header {top: 0;}
 | 
					 | 
				
			||||||
    .spacing {height: 45mm;}
 | 
					 | 
				
			||||||
    .main-wrapper {
 | 
					 | 
				
			||||||
        margin-bottom: 40mm;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    .footer-wrapper {
 | 
					 | 
				
			||||||
        position: fixed;
 | 
					 | 
				
			||||||
        bottom: 0;
 | 
					 | 
				
			||||||
        left: 0;
 | 
					 | 
				
			||||||
        right: 0;
 | 
					 | 
				
			||||||
        background: white;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@media print {
 | 
					 | 
				
			||||||
    .page::after {
 | 
					 | 
				
			||||||
        content: "Seite " counter(page) " von " counter(pages);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    .footer-wrapper {
 | 
					 | 
				
			||||||
        display: none;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,14 +16,13 @@
 | 
				
			|||||||
  </ItemGroup>
 | 
					  </ItemGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  <ItemGroup>
 | 
					  <ItemGroup>
 | 
				
			||||||
 | 
					    <PackageReference Include="Balbarak.WeasyPrint" Version="2.0.2" />
 | 
				
			||||||
    <PackageReference Include="Extended.Wpf.Toolkit" Version="4.5.1" />
 | 
					    <PackageReference Include="Extended.Wpf.Toolkit" Version="4.5.1" />
 | 
				
			||||||
    <PackageReference Include="ini-parser" Version="2.5.2" />
 | 
					    <PackageReference Include="ini-parser" Version="2.5.2" />
 | 
				
			||||||
    <PackageReference Include="Microsoft.AspNetCore.Razor.Language" Version="6.0.21" />
 | 
					    <PackageReference Include="Microsoft.AspNetCore.Razor.Language" Version="6.0.21" />
 | 
				
			||||||
    <PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="7.0.10" />
 | 
					    <PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="7.0.10" />
 | 
				
			||||||
    <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.10" />
 | 
					    <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.10" />
 | 
				
			||||||
    <PackageReference Include="Microsoft.Web.WebView2" Version="1.0.1938.49" />
 | 
					    <PackageReference Include="Microsoft.Web.WebView2" Version="1.0.1938.49" />
 | 
				
			||||||
    <PackageReference Include="PdfSharp" Version="1.50.5147" />
 | 
					 | 
				
			||||||
    <PackageReference Include="PuppeteerSharp" Version="11.0.2" />
 | 
					 | 
				
			||||||
    <PackageReference Include="RazorLight" Version="2.3.1" />
 | 
					    <PackageReference Include="RazorLight" Version="2.3.1" />
 | 
				
			||||||
    <PackageReference Include="ScottPlot.WPF" Version="4.1.67" />
 | 
					    <PackageReference Include="ScottPlot.WPF" Version="4.1.67" />
 | 
				
			||||||
    <PackageReference Include="System.IO.Ports" Version="7.0.0" />
 | 
					    <PackageReference Include="System.IO.Ports" Version="7.0.0" />
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -43,6 +43,7 @@ namespace Elwig.Helpers {
 | 
				
			|||||||
        public DbSet<DeliveryPartAttr> DeliveryPartAttributes { get; private set; }
 | 
					        public DbSet<DeliveryPartAttr> DeliveryPartAttributes { get; private set; }
 | 
				
			||||||
        public DbSet<DeliveryPartModifier> DeliveryPartModifiers { get; private set; }
 | 
					        public DbSet<DeliveryPartModifier> DeliveryPartModifiers { get; private set; }
 | 
				
			||||||
        public DbSet<PaymentVar> PaymentVariants { get; private set; }
 | 
					        public DbSet<PaymentVar> PaymentVariants { get; private set; }
 | 
				
			||||||
 | 
					        public DbSet<PaymentMember> MemberPayments { get; private set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private readonly StreamWriter? LogFile = null;
 | 
					        private readonly StreamWriter? LogFile = null;
 | 
				
			||||||
        public static DateTime LastWriteTime => File.GetLastWriteTime(App.Config.DatabaseFile);
 | 
					        public static DateTime LastWriteTime => File.GetLastWriteTime(App.Config.DatabaseFile);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,14 +1,10 @@
 | 
				
			|||||||
using Newtonsoft.Json.Linq;
 | 
					 | 
				
			||||||
using ScottPlot;
 | 
					using ScottPlot;
 | 
				
			||||||
using System;
 | 
					using System;
 | 
				
			||||||
using System.Collections.Generic;
 | 
					using System.Collections.Generic;
 | 
				
			||||||
using System.Diagnostics.Contracts;
 | 
					 | 
				
			||||||
using System.Linq;
 | 
					using System.Linq;
 | 
				
			||||||
using System.Text;
 | 
					using System.Text.Json.Nodes;
 | 
				
			||||||
using System.Threading.Tasks;
 | 
					 | 
				
			||||||
using System.Windows.Markup;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Elwig.Models {
 | 
					namespace Elwig.Helpers.Billing {
 | 
				
			||||||
    public class Graph : ICloneable {
 | 
					    public class Graph : ICloneable {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public string Type { get; set; }
 | 
					        public string Type { get; set; }
 | 
				
			||||||
@@ -30,7 +26,7 @@ namespace Elwig.Models {
 | 
				
			|||||||
            DataY = DataGen.Zeros(MaxX - MinX + 1);
 | 
					            DataY = DataGen.Zeros(MaxX - MinX + 1);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public Graph(string type, int num, JToken graphData, string contracts, int minX, int maxX) {
 | 
					        public Graph(string type, int num, JsonObject graphData, string contracts, int minX, int maxX) {
 | 
				
			||||||
            Type = type;
 | 
					            Type = type;
 | 
				
			||||||
            Num = num;
 | 
					            Num = num;
 | 
				
			||||||
            Contracts =  contracts;
 | 
					            Contracts =  contracts;
 | 
				
			||||||
@@ -52,8 +48,8 @@ namespace Elwig.Models {
 | 
				
			|||||||
            DataY = dataY;
 | 
					            DataY = dataY;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private void ParseGraphData(JToken graphData) {
 | 
					        private void ParseGraphData(JsonObject graphData) {
 | 
				
			||||||
            var GraphPoints = graphData.Children().OfType<JProperty>().ToDictionary(p => int.Parse(p.Name[..^2]), p => (double)p.Value);
 | 
					            var GraphPoints = graphData.ToDictionary(p => int.Parse(p.Key[..^2]), p => (double)p.Value?.AsValue());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (GraphPoints.Keys.Count < 1) {
 | 
					            if (GraphPoints.Keys.Count < 1) {
 | 
				
			||||||
                return;
 | 
					                return;
 | 
				
			||||||
@@ -99,19 +95,19 @@ namespace Elwig.Models {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public JObject ToJson() {
 | 
					        public JsonObject ToJson() {
 | 
				
			||||||
            JObject graph = new();
 | 
					            JsonObject graph = new();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (DataY[0] != DataY[1]) {
 | 
					            if (DataY[0] != DataY[1]) {
 | 
				
			||||||
                graph.Add(new JProperty(DataX[0] + Type.ToLower(), Math.Round(DataY[0], 4)));
 | 
					                graph.Add(new KeyValuePair<string, JsonNode?>(DataX[0] + Type.ToLower(), Math.Round(DataY[0], 4)));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            for (int i = 1; i < DataX.Length - 1; i++) {
 | 
					            for (int i = 1; i < DataX.Length - 1; i++) {
 | 
				
			||||||
                if (Math.Round(DataY[i] - DataY[i - 1], 4) != Math.Round(DataY[i + 1] - DataY[i], 4)) {
 | 
					                if (Math.Round(DataY[i] - DataY[i - 1], 4) != Math.Round(DataY[i + 1] - DataY[i], 4)) {
 | 
				
			||||||
                    graph.Add(new JProperty(DataX[i] + Type.ToLower(), Math.Round(DataY[i], 4)));
 | 
					                    graph.Add(new KeyValuePair<string, JsonNode?>(DataX[i] + Type.ToLower(), Math.Round(DataY[i], 4)));
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            if (DataY[^1] != DataY[^2]) {
 | 
					            if (DataY[^1] != DataY[^2]) {
 | 
				
			||||||
                graph.Add(new JProperty(DataX[^1] + Type.ToLower(), Math.Round(DataY[^1], 4)));
 | 
					                graph.Add(new KeyValuePair<string, JsonNode?>(DataX[^1] + Type.ToLower(), Math.Round(DataY[^1], 4)));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            return graph;
 | 
					            return graph;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
							
								
								
									
										35
									
								
								Elwig/Helpers/Billing/Transaction.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								Elwig/Helpers/Billing/Transaction.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,35 @@
 | 
				
			|||||||
 | 
					using Elwig.Models;
 | 
				
			||||||
 | 
					using System;
 | 
				
			||||||
 | 
					using System.Collections.Generic;
 | 
				
			||||||
 | 
					using System.Linq;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Elwig.Helpers.Billing {
 | 
				
			||||||
 | 
					    public class Transaction {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public readonly Member Member;
 | 
				
			||||||
 | 
					        public readonly long AmountCent;
 | 
				
			||||||
 | 
					        public readonly string Currency;
 | 
				
			||||||
 | 
					        public readonly int Nr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public Transaction(Member m, decimal amount, string currency, int nr) {
 | 
				
			||||||
 | 
					            Member = m;
 | 
				
			||||||
 | 
					            AmountCent = (long)Math.Round(amount * 100);
 | 
				
			||||||
 | 
					            Currency = currency;
 | 
				
			||||||
 | 
					            Nr = nr;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public static IEnumerable<Transaction> FromPaymentVariant(PaymentVar variant) {
 | 
				
			||||||
 | 
					            var last = variant.Season.PaymentVariants.Where(v => v.TransferDate != null).OrderBy(v => v.TransferDate).LastOrDefault();
 | 
				
			||||||
 | 
					            var dict = last?.MemberPayments.ToDictionary(m => m.MgNr, m => m.Amount) ?? new();
 | 
				
			||||||
 | 
					            return variant.MemberPayments
 | 
				
			||||||
 | 
					                .OrderBy(m => m.MgNr)
 | 
				
			||||||
 | 
					                .Select(m => {
 | 
				
			||||||
 | 
					                    var amt = Math.Round(dict.GetValueOrDefault(m.MgNr, 0), 2);
 | 
				
			||||||
 | 
					                    return new Transaction(m.Member, m.Amount - amt, m.Variant.Season.CurrencyCode, m.TgNr ?? 0);
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
 | 
					                .ToList();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public static string FormatAmountCent(long cents) => $"{cents / 100}.{cents % 100:00}";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -7,10 +7,10 @@ using System.Threading.Tasks;
 | 
				
			|||||||
namespace Elwig.Helpers {
 | 
					namespace Elwig.Helpers {
 | 
				
			||||||
    public class ClientParameters {
 | 
					    public class ClientParameters {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public enum Type { Matzen, GWK };
 | 
					        public enum Type { Matzen, Winzerkeller };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public bool IsMatzen => Client == Type.Matzen;
 | 
					        public bool IsMatzen => Client == Type.Matzen;
 | 
				
			||||||
        public bool IsGWK => Client == Type.GWK;
 | 
					        public bool IsWinzerkeller => Client == Type.Winzerkeller;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public string NameToken;
 | 
					        public string NameToken;
 | 
				
			||||||
        public string NameShort;
 | 
					        public string NameShort;
 | 
				
			||||||
@@ -61,7 +61,7 @@ namespace Elwig.Helpers {
 | 
				
			|||||||
                NameType = parameters["CLIENT_NAME_TYPE"] ?? throw new KeyNotFoundException();
 | 
					                NameType = parameters["CLIENT_NAME_TYPE"] ?? throw new KeyNotFoundException();
 | 
				
			||||||
                switch (Name) {
 | 
					                switch (Name) {
 | 
				
			||||||
                    case "Winzergenossenschaft für Matzen und Umgebung": Client = Type.Matzen; break;
 | 
					                    case "Winzergenossenschaft für Matzen und Umgebung": Client = Type.Matzen; break;
 | 
				
			||||||
                    case "Winzerkeller im Weinviertel": Client = Type.GWK; break;
 | 
					                    case "Winzerkeller im Weinviertel": Client = Type.Winzerkeller; break;
 | 
				
			||||||
                };
 | 
					                };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                Plz = int.Parse(parameters["CLIENT_PLZ"] ?? "");
 | 
					                Plz = int.Parse(parameters["CLIENT_PLZ"] ?? "");
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,6 @@
 | 
				
			|||||||
using System.Collections.Generic;
 | 
					using System.Collections.Generic;
 | 
				
			||||||
using System.IO;
 | 
					using System.IO;
 | 
				
			||||||
using System.Linq;
 | 
					using System.Linq;
 | 
				
			||||||
using System.Text;
 | 
					 | 
				
			||||||
using IniParser;
 | 
					using IniParser;
 | 
				
			||||||
using IniParser.Model;
 | 
					using IniParser.Model;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -25,7 +24,7 @@ namespace Elwig.Helpers {
 | 
				
			|||||||
            var parser = new FileIniDataParser();
 | 
					            var parser = new FileIniDataParser();
 | 
				
			||||||
            IniData? ini = null;
 | 
					            IniData? ini = null;
 | 
				
			||||||
            try {
 | 
					            try {
 | 
				
			||||||
                ini = parser.ReadFile(FileName, Encoding.UTF8);
 | 
					                ini = parser.ReadFile(FileName, Utils.UTF8);
 | 
				
			||||||
            } catch {}
 | 
					            } catch {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (ini == null || !ini.TryGetKey("database.file", out string db)) {
 | 
					            if (ini == null || !ini.TryGetKey("database.file", out string db)) {
 | 
				
			||||||
@@ -70,7 +69,7 @@ namespace Elwig.Helpers {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public void Write() {
 | 
					        public void Write() {
 | 
				
			||||||
            using var file = new StreamWriter(FileName, false, Encoding.UTF8);
 | 
					            using var file = new StreamWriter(FileName, false, Utils.UTF8);
 | 
				
			||||||
            file.Write($"\r\n[general]\r\n");
 | 
					            file.Write($"\r\n[general]\r\n");
 | 
				
			||||||
            if (Branch != null) file.Write($"branch = {Branch}\r\n");
 | 
					            if (Branch != null) file.Write($"branch = {Branch}\r\n");
 | 
				
			||||||
            file.Write($"\r\n[database]\r\nfile = {DatabaseFile}\r\n");
 | 
					            file.Write($"\r\n[database]\r\nfile = {DatabaseFile}\r\n");
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,67 @@
 | 
				
			|||||||
 | 
					using System.Collections.Generic;
 | 
				
			||||||
 | 
					using System.Linq;
 | 
				
			||||||
 | 
					using System.Text;
 | 
				
			||||||
 | 
					using System.Threading.Tasks;
 | 
				
			||||||
 | 
					using System;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Elwig.Helpers.Export {
 | 
					namespace Elwig.Helpers.Export {
 | 
				
			||||||
    public class Bki {
 | 
					
 | 
				
			||||||
 | 
					    using Row = Tuple<(string, string?, string?, string?, string, int, string, int), (string, int, string, string, string, int, string, double, double)>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public class Bki : Csv<Row> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private readonly string _clientData;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public Bki(string filename) : base(filename, ';', Encoding.Latin1) {
 | 
				
			||||||
 | 
					            Header = """
 | 
				
			||||||
 | 
					                EDV-Liste zum automatischen Einlesen in die Weindatenbank;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 | 
				
			||||||
 | 
					                ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 | 
				
			||||||
 | 
					                Stammdaten;;;;;;;;;;;;;;Transportschein;;;;;;;;;;;;;;;;
 | 
				
			||||||
 | 
					                Empfänger:;;;;;;;Versender:;;;;;;;;;;;;;;;;;;;;;;;
 | 
				
			||||||
 | 
					                Betriebsnr;Name od. Firmenname;Vorname;Straße;Hausnr;Plz;Ort;Betriebsnr;Name od. Firmenname;Vorname;Straße;Hausnr;Plz;Ort;Datum der Lieferung;Menge in kg;Art;Weiß;Rot;Sorte1;Sorte2;Sorte3;Qualitätsstufe;Jahrgang;Herkunft;°KMW;°Oe;Vollablieferer;Ha Gesamt;Ha Ertragsfähig;Flächenbindung in Ha für AMA
 | 
				
			||||||
 | 
					                """;
 | 
				
			||||||
 | 
					            var c = App.Client;
 | 
				
			||||||
 | 
					            var (a1, a2) = Utils.SplitAddress(c.Address);
 | 
				
			||||||
 | 
					            _clientData = $"{c.LfbisNr};{c.NameFull};;{a1};{a2};{c.Plz};{c.Ort}";
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public async Task ExportAsync(int year) {
 | 
				
			||||||
 | 
					            using var cnx = await AppDbContext.ConnectAsync();
 | 
				
			||||||
 | 
					            using var cmd = cnx.CreateCommand();
 | 
				
			||||||
 | 
					            cmd.CommandText = $"""
 | 
				
			||||||
 | 
					                SELECT lfbis_nr, family_name, name, billing_name, address, plz, ort, area,
 | 
				
			||||||
 | 
					                       date, weight, type, sortid, qualid, year, hkid, kmw, oe
 | 
				
			||||||
 | 
					                FROM v_bki_delivery
 | 
				
			||||||
 | 
					                WHERE year = {year}
 | 
				
			||||||
 | 
					                """;
 | 
				
			||||||
 | 
					            var r = await cmd.ExecuteReaderAsync();
 | 
				
			||||||
 | 
					            List<Row> rows = new();
 | 
				
			||||||
 | 
					            while (await r.ReadAsync()) {
 | 
				
			||||||
 | 
					                rows.Add(new(
 | 
				
			||||||
 | 
					                    (r.GetString(0), r.IsDBNull(1) ? null : r.GetString(1), r.IsDBNull(2) ? null : r.GetString(2), r.IsDBNull(3) ? null : r.GetString(3), r.GetString(4), r.GetInt32(5), r.GetString(6), r.GetInt32(7)),
 | 
				
			||||||
 | 
					                    (r.GetString(8), r.GetInt32(9), r.GetString(10), r.GetString(11), r.GetString(12), r.GetInt32(13), r.GetString(14), r.GetDouble(15), r.GetDouble(16))
 | 
				
			||||||
 | 
					                ));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            await ExportAsync(rows);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public void Export(int year) {
 | 
				
			||||||
 | 
					            ExportAsync(year).GetAwaiter().GetResult();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public override string FormatRow(Row row) {
 | 
				
			||||||
 | 
					            var (member, delivery) = row;
 | 
				
			||||||
 | 
					            var (lfBisNr, familyName, name, billingName, address, plz, ort, area) = member;
 | 
				
			||||||
 | 
					            var (date, weight, type, sortid, qualid, year, hkid, kmw, oe) = delivery;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var (n1, n2) = billingName == null ? (familyName, name) : Utils.SplitName(billingName, familyName);
 | 
				
			||||||
 | 
					            var (a1, a2) = Utils.SplitAddress(address);
 | 
				
			||||||
 | 
					            var memberData = $"{lfBisNr};{n1};{n2};{a1};{a2};{plz};{ort}";
 | 
				
			||||||
 | 
					            var deliveryData = $"{string.Join(".", date.Split("-").Reverse())};{weight};TB;{(type == "W" ? "J" : "")};{(type == "R" ? "J" : "")};{sortid};;;{qualid};{year};{hkid};{kmw:0.0};{oe:0}";
 | 
				
			||||||
 | 
					            var vollData = $"N;;;{area / 10_000.0}";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return $"{_clientData};{memberData};{deliveryData};{vollData}";
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,61 @@
 | 
				
			|||||||
 | 
					using System;
 | 
				
			||||||
 | 
					using System.Collections;
 | 
				
			||||||
 | 
					using System.Collections.Generic;
 | 
				
			||||||
 | 
					using System.IO;
 | 
				
			||||||
 | 
					using System.Linq;
 | 
				
			||||||
 | 
					using System.Text;
 | 
				
			||||||
 | 
					using System.Threading.Tasks;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Elwig.Helpers.Export {
 | 
					namespace Elwig.Helpers.Export {
 | 
				
			||||||
    public class Csv {
 | 
					    public abstract class Csv<T> : IExporter<T> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public static string FileExtension => "csv";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private readonly StreamWriter _writer;
 | 
				
			||||||
 | 
					        protected readonly char Separator;
 | 
				
			||||||
 | 
					        protected string? Header;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public Csv(string filename, char separator = ';') : this(filename, separator, Utils.UTF8) { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public Csv(string filename, char separator, Encoding encoding) {
 | 
				
			||||||
 | 
					            _writer = new StreamWriter(filename, false, encoding);
 | 
				
			||||||
 | 
					            Separator = separator;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public void Dispose() {
 | 
				
			||||||
 | 
					            GC.SuppressFinalize(this);
 | 
				
			||||||
 | 
					            _writer.Dispose();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public ValueTask DisposeAsync() {
 | 
				
			||||||
 | 
					            GC.SuppressFinalize(this);
 | 
				
			||||||
 | 
					            return _writer.DisposeAsync();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public async Task ExportAsync(IEnumerable<T> data, IProgress<double>? progress = null) {
 | 
				
			||||||
 | 
					            progress?.Report(0.0);
 | 
				
			||||||
 | 
					            int count = data.Count() + 2, i = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (Header != null) await _writer.WriteLineAsync(Header);
 | 
				
			||||||
 | 
					            progress?.Report(100.0 * ++i / count);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            foreach (var row in data) {
 | 
				
			||||||
 | 
					                await _writer.WriteLineAsync(FormatRow(row));
 | 
				
			||||||
 | 
					                progress?.Report(100.0 * ++i / count);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            await _writer.FlushAsync();
 | 
				
			||||||
 | 
					            progress?.Report(100.0);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public void Export(IEnumerable<T> data, IProgress<double>? progress = null) {
 | 
				
			||||||
 | 
					            ExportAsync(data, progress).GetAwaiter().GetResult();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public string FormatRow(IEnumerable row) {
 | 
				
			||||||
 | 
					            return string.Join(Separator, row);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public abstract string FormatRow(T row);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,15 +1,109 @@
 | 
				
			|||||||
 | 
					using Elwig.Helpers.Billing;
 | 
				
			||||||
using Elwig.Models;
 | 
					using Elwig.Models;
 | 
				
			||||||
using System;
 | 
					using System;
 | 
				
			||||||
using System.Collections.Generic;
 | 
					using System.Collections.Generic;
 | 
				
			||||||
 | 
					using System.IO;
 | 
				
			||||||
 | 
					using System.Linq;
 | 
				
			||||||
using System.Threading.Tasks;
 | 
					using System.Threading.Tasks;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Elwig.Helpers.Export {
 | 
					namespace Elwig.Helpers.Export {
 | 
				
			||||||
    public class Ebics : IBankingProvider {
 | 
					    public class Ebics : IBankingExporter {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public string FileExtension => "xml";
 | 
					        public static string FileExtension => "xml";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public Task Export(string filename, int avnr, IEnumerable<Member> members) {
 | 
					        private readonly StreamWriter _writer;
 | 
				
			||||||
            throw new NotImplementedException();
 | 
					        private readonly DateOnly _date;
 | 
				
			||||||
 | 
					        private readonly int _year;
 | 
				
			||||||
 | 
					        private readonly string _name;
 | 
				
			||||||
 | 
					        private readonly int _nr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public Ebics(PaymentVar variant, string filename) {
 | 
				
			||||||
 | 
					            _writer = new(filename, false, Utils.UTF8);
 | 
				
			||||||
 | 
					            _date = variant.TransferDate ?? DateOnly.Parse("2021-01-10"); //throw new ArgumentException("TransferDate has to be set in PaymentVar");
 | 
				
			||||||
 | 
					            _year = variant.Year;
 | 
				
			||||||
 | 
					            _name = variant.Name;
 | 
				
			||||||
 | 
					            _nr = variant.AvNr;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public void Dispose() {
 | 
				
			||||||
 | 
					            GC.SuppressFinalize(this);
 | 
				
			||||||
 | 
					            _writer.Dispose();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public ValueTask DisposeAsync() {
 | 
				
			||||||
 | 
					            GC.SuppressFinalize(this);
 | 
				
			||||||
 | 
					            return _writer.DisposeAsync();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public void Export(IEnumerable<Transaction> transactions, IProgress<double>? progress = null) {
 | 
				
			||||||
 | 
					            ExportAsync(transactions, progress).GetAwaiter().GetResult();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public async Task ExportAsync(IEnumerable<Transaction> transactions, IProgress<double>? progress = null) {
 | 
				
			||||||
 | 
					            progress?.Report(0.0);
 | 
				
			||||||
 | 
					            var nbOfTxs = transactions.Count();
 | 
				
			||||||
 | 
					            int count = nbOfTxs + 2, i = 0;
 | 
				
			||||||
 | 
					            var ctrlSum = Transaction.FormatAmountCent(transactions.Sum(tx => tx.AmountCent));
 | 
				
			||||||
 | 
					            var msgId = $"ELWIG-{App.Client.NameToken}-{_year}-AV{_nr:00}";
 | 
				
			||||||
 | 
					            var pmtInfId = $"{msgId}-1";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            await _writer.WriteLineAsync($"""
 | 
				
			||||||
 | 
					                <?xml version="1.0" encoding="UTF-8"?>
 | 
				
			||||||
 | 
					                <Document xmlns="urn:iso:std:iso:20022:tech:xsd:pain.001.001.09"
 | 
				
			||||||
 | 
					                          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 | 
				
			||||||
 | 
					                          xsi:schemaLocation="urn:iso:std:iso:20022:tech:xsd:pain.001.001.09 pain.001.001.09.xsd">
 | 
				
			||||||
 | 
					                  <CstmrCdtTrfInitn>
 | 
				
			||||||
 | 
					                    <GrpHdr>
 | 
				
			||||||
 | 
					                      <MsgId>{msgId}</MsgId>
 | 
				
			||||||
 | 
					                      <CreDtTm>{DateTime.UtcNow:o}</CreDtTm>
 | 
				
			||||||
 | 
					                      <NbOfTxs>{nbOfTxs}</NbOfTxs>
 | 
				
			||||||
 | 
					                      <CtrlSum>{ctrlSum}</CtrlSum>
 | 
				
			||||||
 | 
					                      <InitgPty><Nm>{App.Client.NameFull}</Nm></InitgPty>
 | 
				
			||||||
 | 
					                    </GrpHdr>
 | 
				
			||||||
 | 
					                    <PmtInf>
 | 
				
			||||||
 | 
					                      <PmtInfId>{pmtInfId}</PmtInfId>
 | 
				
			||||||
 | 
					                      <PmtMtd>TRF</PmtMtd>
 | 
				
			||||||
 | 
					                      <NbOfTxs>{nbOfTxs}</NbOfTxs>
 | 
				
			||||||
 | 
					                      <CtrlSum>{ctrlSum}</CtrlSum>
 | 
				
			||||||
 | 
					                      <ReqdExctnDt><Dt>{_date:yyyy-MM-dd}</Dt></ReqdExctnDt>
 | 
				
			||||||
 | 
					                      <Dbtr><Nm>{App.Client.NameFull}</Nm></Dbtr>
 | 
				
			||||||
 | 
					                      <DbtrAcct><Id><IBAN>{App.Client.Iban?.Replace(" ", "")}</IBAN></Id></DbtrAcct>
 | 
				
			||||||
 | 
					                      <DbtrAgt><FinInstnId><BICFI>{App.Client.Bic ?? "NOTPROVIDED"}</BICFI></FinInstnId></DbtrAgt>
 | 
				
			||||||
 | 
					                """);
 | 
				
			||||||
 | 
					            progress?.Report(100.0 * ++i / count);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            foreach (var tx in transactions) {
 | 
				
			||||||
 | 
					                var a = (IAddress?)tx.Member.BillingAddress ?? tx.Member;
 | 
				
			||||||
 | 
					                var (a1, a2) = Utils.SplitAddress(a.Address);
 | 
				
			||||||
 | 
					                var id = $"ELWIG-{App.Client.NameToken}-{_year}-TG{tx.Nr:0000}";
 | 
				
			||||||
 | 
					                var info = $"{_name} - Traubengutschrift {_year}/{tx.Nr:000}";
 | 
				
			||||||
 | 
					                await _writer.WriteLineAsync($"""
 | 
				
			||||||
 | 
					                          <CdtTrfTxInf>
 | 
				
			||||||
 | 
					                            <PmtId><EndToEndId>{id}</EndToEndId></PmtId>
 | 
				
			||||||
 | 
					                            <Amt><InstdAmt Ccy="{tx.Currency}">{Transaction.FormatAmountCent(tx.AmountCent)}</InstdAmt></Amt>
 | 
				
			||||||
 | 
					                            <Cdtr>
 | 
				
			||||||
 | 
					                              <Nm>{a.Name}</Nm>
 | 
				
			||||||
 | 
					                              <PstlAdr>
 | 
				
			||||||
 | 
					                                <StrtNm>{a1}</StrtNm><BldgNb>{a2}</BldgNb>
 | 
				
			||||||
 | 
					                                <PstCd>{a.PostalDest.AtPlz?.Plz}</PstCd><TwnNm>{a.PostalDest.AtPlz?.Ort.Name}</TwnNm>
 | 
				
			||||||
 | 
					                                <Ctry>{a.PostalDest.Country.Alpha2}</Ctry>
 | 
				
			||||||
 | 
					                              </PstlAdr>
 | 
				
			||||||
 | 
					                            </Cdtr>
 | 
				
			||||||
 | 
					                            <CdtrAcct><Id><IBAN>{tx.Member.Iban}</IBAN></Id><CdtrAcct>
 | 
				
			||||||
 | 
					                            <CdtrAgt><FinInstnId><BICFI>{tx.Member.Bic ?? "NOTPROVIDED"}</BICFI></FinInstnId></CdtrAgt>
 | 
				
			||||||
 | 
					                            <RmtInf><Ustrd>{info}</Ustrd></RmtInf>
 | 
				
			||||||
 | 
					                          </CdtTrfTxInf>
 | 
				
			||||||
 | 
					                    """);
 | 
				
			||||||
 | 
					                progress?.Report(100.0 * ++i / count);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            await _writer.WriteLineAsync("""
 | 
				
			||||||
 | 
					                    </PmtInf>
 | 
				
			||||||
 | 
					                  </CstmrCdtTrfInitn>
 | 
				
			||||||
 | 
					                </Document>
 | 
				
			||||||
 | 
					                """);
 | 
				
			||||||
 | 
					            await _writer.FlushAsync();
 | 
				
			||||||
 | 
					            progress?.Report(100.0);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,14 +1,14 @@
 | 
				
			|||||||
using Elwig.Models;
 | 
					using Elwig.Helpers.Billing;
 | 
				
			||||||
using System;
 | 
					using System;
 | 
				
			||||||
using System.Collections.Generic;
 | 
					 | 
				
			||||||
using System.Threading.Tasks;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Elwig.Helpers.Export {
 | 
					namespace Elwig.Helpers.Export {
 | 
				
			||||||
    public class Elba : IBankingProvider {
 | 
					    public class Elba : Csv<Transaction>, IBankingExporter {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public string FileExtension => "elba";
 | 
					        public static new string FileExtension => "elba";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public Task Export(string filename, int avnr, IEnumerable<Member> members) {
 | 
					        public Elba(string filename) : base(filename) { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public override string FormatRow(Transaction row) {
 | 
				
			||||||
            throw new NotImplementedException();
 | 
					            throw new NotImplementedException();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										8
									
								
								Elwig/Helpers/Export/IBankingExporter.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								Elwig/Helpers/Export/IBankingExporter.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
				
			|||||||
 | 
					using Elwig.Helpers.Billing;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Elwig.Helpers.Export {
 | 
				
			||||||
 | 
					    /// <summary>
 | 
				
			||||||
 | 
					    /// Interface for exporting banking data
 | 
				
			||||||
 | 
					    /// </summary>
 | 
				
			||||||
 | 
					    public interface IBankingExporter : IExporter<Transaction> { }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,24 +0,0 @@
 | 
				
			|||||||
using Elwig.Models;
 | 
					 | 
				
			||||||
using System.Collections.Generic;
 | 
					 | 
				
			||||||
using System.Threading.Tasks;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace Elwig.Helpers.Export {
 | 
					 | 
				
			||||||
    /// <summary>
 | 
					 | 
				
			||||||
    /// Interface for exporting banking data
 | 
					 | 
				
			||||||
    /// </summary>
 | 
					 | 
				
			||||||
    public interface IBankingProvider {
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// The default file extension of the exported files to be used (whithout a preceding ".")
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        string FileExtension { get; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Export the member payment data of the given payment variant to the given file.
 | 
					 | 
				
			||||||
        /// (The amount of the last payed variant is deducted from the calculated amount.)
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <param name="filename">The file to export the data to</param>
 | 
					 | 
				
			||||||
        /// <param name="avnr">The number of the payment variant to export</param>
 | 
					 | 
				
			||||||
        /// <param name="members">The members whose data to include in the export</param>
 | 
					 | 
				
			||||||
        Task Export(string filename, int avnr, IEnumerable<Member> members);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										26
									
								
								Elwig/Helpers/Export/IExporter.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								Elwig/Helpers/Export/IExporter.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,26 @@
 | 
				
			|||||||
 | 
					using System;
 | 
				
			||||||
 | 
					using System.Collections.Generic;
 | 
				
			||||||
 | 
					using System.Threading.Tasks;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Elwig.Helpers.Export {
 | 
				
			||||||
 | 
					    public interface IExporter<T> : IDisposable, IAsyncDisposable {
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// The default file extension of the exported files to be used (whithout a preceding ".")
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        static abstract string FileExtension { get; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Export the given data to the given file.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="data">The data to be exported</param>
 | 
				
			||||||
 | 
					        /// <param name="progress">The progress object to report to</param>
 | 
				
			||||||
 | 
					        void Export(IEnumerable<T> data, IProgress<double>? progress = null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Asynchronosly export the given data to the given file.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="data">The data to be exported</param>
 | 
				
			||||||
 | 
					        /// <param name="progress">The progress object to report to</param>
 | 
				
			||||||
 | 
					        Task ExportAsync(IEnumerable<T> data, IProgress<double>? progress = null);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										9
									
								
								Elwig/Helpers/IAddress.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								Elwig/Helpers/IAddress.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					using Elwig.Models;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Elwig.Helpers {
 | 
				
			||||||
 | 
					    public interface IAddress {
 | 
				
			||||||
 | 
					        string Name { get; }
 | 
				
			||||||
 | 
					        string Address { get; }
 | 
				
			||||||
 | 
					        PostalDest PostalDest { get; }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -15,6 +15,8 @@ using Elwig.Models;
 | 
				
			|||||||
namespace Elwig.Helpers {
 | 
					namespace Elwig.Helpers {
 | 
				
			||||||
    public static partial class Utils {
 | 
					    public static partial class Utils {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public static readonly Encoding UTF8 = new UTF8Encoding(false, true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public static int CurrentYear => DateTime.Now.Year;
 | 
					        public static int CurrentYear => DateTime.Now.Year;
 | 
				
			||||||
        public static int CurrentNextSeason => DateTime.Now.Year - (DateTime.Now.Month <= 3 ? 1 : 0);
 | 
					        public static int CurrentNextSeason => DateTime.Now.Year - (DateTime.Now.Month <= 3 ? 1 : 0);
 | 
				
			||||||
        public static int CurrentLastSeason => DateTime.Now.Year - (DateTime.Now.Month <= 7 ? 1 : 0);
 | 
					        public static int CurrentLastSeason => DateTime.Now.Year - (DateTime.Now.Month <= 7 ? 1 : 0);
 | 
				
			||||||
@@ -25,6 +27,7 @@ namespace Elwig.Helpers {
 | 
				
			|||||||
        public static readonly Regex PartialDateRegex = GeneratedPartialDateRegex();
 | 
					        public static readonly Regex PartialDateRegex = GeneratedPartialDateRegex();
 | 
				
			||||||
        public static readonly Regex FromToRegex = GeneratedFromToRegex();
 | 
					        public static readonly Regex FromToRegex = GeneratedFromToRegex();
 | 
				
			||||||
        public static readonly Regex FromToTimeRegex = GeneratedFromToTimeRegex();
 | 
					        public static readonly Regex FromToTimeRegex = GeneratedFromToTimeRegex();
 | 
				
			||||||
 | 
					        public static readonly Regex AddressRegex = GeneratedAddressRegex();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        [GeneratedRegex("^serial://([A-Za-z0-9]+):([0-9]+)(,([5-9]),([NOEMSnoems]),(0|1|1\\.5|2|))?$", RegexOptions.Compiled)]
 | 
					        [GeneratedRegex("^serial://([A-Za-z0-9]+):([0-9]+)(,([5-9]),([NOEMSnoems]),(0|1|1\\.5|2|))?$", RegexOptions.Compiled)]
 | 
				
			||||||
        private static partial Regex GeneratedSerialRegex();
 | 
					        private static partial Regex GeneratedSerialRegex();
 | 
				
			||||||
@@ -41,6 +44,9 @@ namespace Elwig.Helpers {
 | 
				
			|||||||
        [GeneratedRegex(@"^([0-9]{1,2}:[0-9]{2})?-([0-9]{1,2}:[0-9]{2})?$", RegexOptions.Compiled)]
 | 
					        [GeneratedRegex(@"^([0-9]{1,2}:[0-9]{2})?-([0-9]{1,2}:[0-9]{2})?$", RegexOptions.Compiled)]
 | 
				
			||||||
        private static partial Regex GeneratedFromToTimeRegex();
 | 
					        private static partial Regex GeneratedFromToTimeRegex();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [GeneratedRegex(@"^(.*?) +([0-9].*)$", RegexOptions.Compiled)]
 | 
				
			||||||
 | 
					        private static partial Regex GeneratedAddressRegex();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private static readonly ushort[] Crc16ModbusTable = {
 | 
					        private static readonly ushort[] Crc16ModbusTable = {
 | 
				
			||||||
            0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241,
 | 
					            0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241,
 | 
				
			||||||
            0xC601, 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440,
 | 
					            0xC601, 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440,
 | 
				
			||||||
@@ -292,5 +298,30 @@ namespace Elwig.Helpers {
 | 
				
			|||||||
        public static string GenerateLsNr(Delivery d) => GenerateLsNr(d.Date, d.ZwstId, d.LNr);
 | 
					        public static string GenerateLsNr(Delivery d) => GenerateLsNr(d.Date, d.ZwstId, d.LNr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public static string GenerateLsNr(DateOnly date, string zwstid, int lnr) => $"{date:yyyyMMdd}{zwstid}{lnr:000}";
 | 
					        public static string GenerateLsNr(DateOnly date, string zwstid, int lnr) => $"{date:yyyyMMdd}{zwstid}{lnr:000}";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public static (string, string?) SplitAddress(string address) {
 | 
				
			||||||
 | 
					            var m = AddressRegex.Match(address);
 | 
				
			||||||
 | 
					            return (m.Groups[1].Value, m.Groups[2].Value);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public static (string, string?) SplitName(string fullName, string? familyName) {
 | 
				
			||||||
 | 
					            if (familyName == null) return (fullName, null);
 | 
				
			||||||
 | 
					            var p0 = fullName.ToLower().IndexOf(familyName.ToLower());
 | 
				
			||||||
 | 
					            if (p0 == -1) return (fullName, null);
 | 
				
			||||||
 | 
					            var p1 = fullName.IndexOf(" und ");
 | 
				
			||||||
 | 
					            var p2 = fullName.ToLower().LastIndexOf(" und ");
 | 
				
			||||||
 | 
					            if (p1 != p2) {
 | 
				
			||||||
 | 
					                if (p0 > p1) {
 | 
				
			||||||
 | 
					                    // A und B familyName [und ...]
 | 
				
			||||||
 | 
					                    return (fullName[p0..^0], fullName[0..(p0 - 1)]);
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    // familyName und ... A und B
 | 
				
			||||||
 | 
					                    var p3 = fullName.LastIndexOf(' ', p2 - 1);
 | 
				
			||||||
 | 
					                    return (fullName[0..p3], fullName[(p3 + 1)..^0]);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                return (familyName, fullName.Replace(familyName, "").Replace("  ", " ").Trim());
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,9 +1,10 @@
 | 
				
			|||||||
 | 
					using Elwig.Helpers;
 | 
				
			||||||
using Microsoft.EntityFrameworkCore;
 | 
					using Microsoft.EntityFrameworkCore;
 | 
				
			||||||
using System.ComponentModel.DataAnnotations.Schema;
 | 
					using System.ComponentModel.DataAnnotations.Schema;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Elwig.Models {
 | 
					namespace Elwig.Models {
 | 
				
			||||||
    [Table("member_billing_address"), PrimaryKey("MgNr")]
 | 
					    [Table("member_billing_address"), PrimaryKey("MgNr")]
 | 
				
			||||||
    public class BillingAddr {
 | 
					    public class BillingAddr : IAddress {
 | 
				
			||||||
        [Column("mgnr")]
 | 
					        [Column("mgnr")]
 | 
				
			||||||
        public int MgNr { get; set; }
 | 
					        public int MgNr { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -19,12 +19,8 @@ namespace Elwig.Models {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        [NotMapped]
 | 
					        [NotMapped]
 | 
				
			||||||
        public DateOnly Date {
 | 
					        public DateOnly Date {
 | 
				
			||||||
            get {
 | 
					            get => DateOnly.ParseExact(DateString, "yyyy-MM-dd");
 | 
				
			||||||
                return DateOnly.ParseExact(DateString, "yyyy-MM-dd");
 | 
					            set => DateString = value.ToString("yyyy-MM-dd");
 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            set {
 | 
					 | 
				
			||||||
                DateString = value.ToString("yyyy-MM-dd");
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        [Column("time")]
 | 
					        [Column("time")]
 | 
				
			||||||
@@ -32,19 +28,13 @@ namespace Elwig.Models {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        [NotMapped]
 | 
					        [NotMapped]
 | 
				
			||||||
        public TimeOnly? Time {
 | 
					        public TimeOnly? Time {
 | 
				
			||||||
            get {
 | 
					            get => (TimeString == null) ? null : TimeOnly.ParseExact(TimeString, "HH:mm:ss");
 | 
				
			||||||
                return (TimeString == null) ? null : TimeOnly.ParseExact(TimeString, "HH:mm:ss");
 | 
					            set => TimeString = value?.ToString("HH:mm:ss");
 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            set {
 | 
					 | 
				
			||||||
                TimeString = value?.ToString("HH:mm:ss");
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        [NotMapped]
 | 
					        [NotMapped]
 | 
				
			||||||
        public DateTime DateTime {
 | 
					        public DateTime DateTime {
 | 
				
			||||||
            get {
 | 
					            get => Date.ToDateTime(Time ?? TimeOnly.MinValue);
 | 
				
			||||||
                return Date.ToDateTime(Time ?? TimeOnly.MinValue);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            set {
 | 
					            set {
 | 
				
			||||||
                Date = DateOnly.FromDateTime(value);
 | 
					                Date = DateOnly.FromDateTime(value);
 | 
				
			||||||
                Time = TimeOnly.FromDateTime(value);
 | 
					                Time = TimeOnly.FromDateTime(value);
 | 
				
			||||||
@@ -72,6 +62,9 @@ namespace Elwig.Models {
 | 
				
			|||||||
        [Column("comment")]
 | 
					        [Column("comment")]
 | 
				
			||||||
        public string? Comment { get; set; }
 | 
					        public string? Comment { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [ForeignKey("Year")]
 | 
				
			||||||
 | 
					        public virtual Season Season { get; private set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        [InverseProperty("Delivery")]
 | 
					        [InverseProperty("Delivery")]
 | 
				
			||||||
        public virtual ISet<DeliveryPart> Parts { get; private set; }
 | 
					        public virtual ISet<DeliveryPart> Parts { get; private set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,7 +7,7 @@ using System.Linq;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
namespace Elwig.Models {
 | 
					namespace Elwig.Models {
 | 
				
			||||||
    [Table("member"), PrimaryKey("MgNr")]
 | 
					    [Table("member"), PrimaryKey("MgNr")]
 | 
				
			||||||
    public class Member {
 | 
					    public class Member : IAddress {
 | 
				
			||||||
        [Column("mgnr")]
 | 
					        [Column("mgnr")]
 | 
				
			||||||
        public int MgNr { get; set; }
 | 
					        public int MgNr { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										34
									
								
								Elwig/Models/PaymentMember.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								Elwig/Models/PaymentMember.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,34 @@
 | 
				
			|||||||
 | 
					using Microsoft.EntityFrameworkCore;
 | 
				
			||||||
 | 
					using System.ComponentModel.DataAnnotations.Schema;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Elwig.Models {
 | 
				
			||||||
 | 
					    [Table("payment_member"), PrimaryKey("Year", "AvNr", "MgNr"), Index("Year", "TgNr", IsUnique = true)]
 | 
				
			||||||
 | 
					    public class PaymentMember {
 | 
				
			||||||
 | 
					        [Column("year")]
 | 
				
			||||||
 | 
					        public int Year { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [Column("avnr")]
 | 
				
			||||||
 | 
					        public int AvNr { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [Column("mgnr")]
 | 
				
			||||||
 | 
					        public int MgNr { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [Column("amount")]
 | 
				
			||||||
 | 
					        public long AmountValue { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [Column("tgnr")]
 | 
				
			||||||
 | 
					        public int? TgNr { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [NotMapped]
 | 
				
			||||||
 | 
					        public decimal Amount {
 | 
				
			||||||
 | 
					            get => Variant.Season.DecFromDb(AmountValue);
 | 
				
			||||||
 | 
					            set => AmountValue = Variant.Season.DecToDb(value);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [ForeignKey("Year, AvNr")]
 | 
				
			||||||
 | 
					        public virtual PaymentVar Variant { get; private set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [ForeignKey("MgNr")]
 | 
				
			||||||
 | 
					        public virtual Member Member { get; private set; }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,5 +1,6 @@
 | 
				
			|||||||
using Microsoft.EntityFrameworkCore;
 | 
					using Microsoft.EntityFrameworkCore;
 | 
				
			||||||
using System;
 | 
					using System;
 | 
				
			||||||
 | 
					using System.Collections.Generic;
 | 
				
			||||||
using System.ComponentModel.DataAnnotations.Schema;
 | 
					using System.ComponentModel.DataAnnotations.Schema;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Elwig.Models {
 | 
					namespace Elwig.Models {
 | 
				
			||||||
@@ -19,12 +20,17 @@ namespace Elwig.Models {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        [NotMapped]
 | 
					        [NotMapped]
 | 
				
			||||||
        public DateOnly Date {
 | 
					        public DateOnly Date {
 | 
				
			||||||
            get {
 | 
					            get => DateOnly.ParseExact(DateString, "yyyy-MM-dd");
 | 
				
			||||||
                return DateOnly.ParseExact(DateString, "yyyy-MM-dd");
 | 
					            set => DateString = value.ToString("yyyy-MM-dd");
 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            set {
 | 
					 | 
				
			||||||
                DateString = value.ToString("yyyy-MM-dd");
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [Column("transfer_date")]
 | 
				
			||||||
 | 
					        public string? TransferDateString { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [NotMapped]
 | 
				
			||||||
 | 
					        public DateOnly? TransferDate {
 | 
				
			||||||
 | 
					            get => TransferDateString != null ? DateOnly.ParseExact(TransferDateString, "yyyy-MM-dd") : null;
 | 
				
			||||||
 | 
					            set => TransferDateString = value?.ToString("yyyy-MM-dd");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        [Column("test_variant")]
 | 
					        [Column("test_variant")]
 | 
				
			||||||
@@ -65,5 +71,11 @@ namespace Elwig.Models {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        [Column("data")]
 | 
					        [Column("data")]
 | 
				
			||||||
        public string Data { get; set; }
 | 
					        public string Data { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [ForeignKey("Year")]
 | 
				
			||||||
 | 
					        public virtual Season Season { get; private set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [InverseProperty("Variant")]
 | 
				
			||||||
 | 
					        public virtual ISet<PaymentMember> MemberPayments { get; private set; }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -48,6 +48,12 @@ namespace Elwig.Models {
 | 
				
			|||||||
        [InverseProperty("Season")]
 | 
					        [InverseProperty("Season")]
 | 
				
			||||||
        public virtual ISet<Modifier> Modifiers { get; private set; }
 | 
					        public virtual ISet<Modifier> Modifiers { get; private set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [InverseProperty("Season")]
 | 
				
			||||||
 | 
					        public virtual ISet<PaymentVar> PaymentVariants { get; private set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [InverseProperty("Season")]
 | 
				
			||||||
 | 
					        public virtual ISet<Delivery> Deliveries { get; private set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public decimal DecFromDb(long value) {
 | 
					        public decimal DecFromDb(long value) {
 | 
				
			||||||
            return Utils.DecFromDb(value, Precision);
 | 
					            return Utils.DecFromDb(value, Precision);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -18,6 +18,9 @@ namespace Elwig.Models {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        public string CommentFormat => (Comment != null) ? $" ({Comment})" : "";
 | 
					        public string CommentFormat => (Comment != null) ? $" ({Comment})" : "";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public bool IsRed => Type == "R";
 | 
				
			||||||
 | 
					        public bool IsWhite => Type == "W";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public override string ToString() {
 | 
					        public override string ToString() {
 | 
				
			||||||
            return Name;
 | 
					            return Name;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -212,7 +212,7 @@ namespace Elwig.Windows {
 | 
				
			|||||||
            p.Address = ClientAddressInput.Text;
 | 
					            p.Address = ClientAddressInput.Text;
 | 
				
			||||||
            p.Plz = int.Parse(ClientPlzInput.Text);
 | 
					            p.Plz = int.Parse(ClientPlzInput.Text);
 | 
				
			||||||
            p.Ort = ClientOrtInput.Text;
 | 
					            p.Ort = ClientOrtInput.Text;
 | 
				
			||||||
            p.Iban = ClientIbanInput.Text.Length > 0 ? ClientIbanInput.Text.Replace(" ", "") : null;
 | 
					            p.Iban = ClientIbanInput.Text.Length > 0 ? ClientIbanInput.Text : null;
 | 
				
			||||||
            p.Bic = ClientBicInput.Text.Length > 0 ? ClientBicInput.Text : null;
 | 
					            p.Bic = ClientBicInput.Text.Length > 0 ? ClientBicInput.Text : null;
 | 
				
			||||||
            p.UstIdNr = ClientUstIdNrInput.Text.Length > 0 ? ClientUstIdNrInput.Text : null;
 | 
					            p.UstIdNr = ClientUstIdNrInput.Text.Length > 0 ? ClientUstIdNrInput.Text : null;
 | 
				
			||||||
            p.LfbisNr = ClientLfbisNrInput.Text.Length > 0 ? ClientLfbisNrInput.Text : null;
 | 
					            p.LfbisNr = ClientLfbisNrInput.Text.Length > 0 ? ClientLfbisNrInput.Text : null;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,21 +1,17 @@
 | 
				
			|||||||
using System;
 | 
					using System;
 | 
				
			||||||
using System.Collections.Generic;
 | 
					using System.Collections.Generic;
 | 
				
			||||||
using System.ComponentModel;
 | 
					 | 
				
			||||||
using System.Diagnostics;
 | 
					 | 
				
			||||||
using System.Diagnostics.Contracts;
 | 
					 | 
				
			||||||
using System.Drawing;
 | 
					using System.Drawing;
 | 
				
			||||||
using System.Linq;
 | 
					using System.Linq;
 | 
				
			||||||
 | 
					using System.Text.Json.Nodes;
 | 
				
			||||||
using System.Threading.Tasks;
 | 
					using System.Threading.Tasks;
 | 
				
			||||||
using System.Windows;
 | 
					using System.Windows;
 | 
				
			||||||
using System.Windows.Controls;
 | 
					using System.Windows.Controls;
 | 
				
			||||||
using System.Windows.Data;
 | 
					 | 
				
			||||||
using System.Windows.Documents;
 | 
					 | 
				
			||||||
using System.Windows.Input;
 | 
					using System.Windows.Input;
 | 
				
			||||||
using Elwig.Helpers;
 | 
					using Elwig.Helpers;
 | 
				
			||||||
 | 
					using Elwig.Helpers.Billing;
 | 
				
			||||||
using Elwig.Models;
 | 
					using Elwig.Models;
 | 
				
			||||||
using Microsoft.EntityFrameworkCore;
 | 
					using Microsoft.EntityFrameworkCore;
 | 
				
			||||||
using Microsoft.EntityFrameworkCore.ChangeTracking;
 | 
					using Microsoft.EntityFrameworkCore.ChangeTracking;
 | 
				
			||||||
using Newtonsoft.Json.Linq;
 | 
					 | 
				
			||||||
using ScottPlot;
 | 
					using ScottPlot;
 | 
				
			||||||
using ScottPlot.Plottable;
 | 
					using ScottPlot.Plottable;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -69,14 +65,14 @@ namespace Elwig.Windows {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            PaymentVar paymentVar = paymentVars[0];
 | 
					            PaymentVar paymentVar = paymentVars[0];
 | 
				
			||||||
            var data = JToken.Parse(paymentVar.Data);
 | 
					            var data = JsonNode.Parse(paymentVar.Data).AsObject();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var auszahlungsSorten = data["AuszahlungSorten"];
 | 
					            var auszahlungsSorten = data["AuszahlungSorten"]?.AsObject();
 | 
				
			||||||
            if (auszahlungsSorten == null) {
 | 
					            if (auszahlungsSorten == null) {
 | 
				
			||||||
                return;
 | 
					                return;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var Graphs = auszahlungsSorten["Kurven"];
 | 
					            var Graphs = auszahlungsSorten["Kurven"]?.AsArray();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (Graphs == null) {
 | 
					            if (Graphs == null) {
 | 
				
			||||||
                return;
 | 
					                return;
 | 
				
			||||||
@@ -86,7 +82,7 @@ namespace Elwig.Windows {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            int i = 1;
 | 
					            int i = 1;
 | 
				
			||||||
            foreach (var graph in Graphs) {
 | 
					            foreach (var graph in Graphs) {
 | 
				
			||||||
                GraphsList.Add(new Graph("Oe", i, graph, ParseContracts(auszahlungsSorten, i - 1), 50, 140));
 | 
					                GraphsList.Add(new Graph("Oe", i, graph?.AsObject(), ParseContracts(auszahlungsSorten, i - 1), 50, 140));
 | 
				
			||||||
                i++;
 | 
					                i++;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -98,17 +94,15 @@ namespace Elwig.Windows {
 | 
				
			|||||||
            RefreshInputs();
 | 
					            RefreshInputs();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private String ParseContracts(JToken auszahlungsSorten, int num) {
 | 
					        private String ParseContracts(JsonObject auszahlungsSorten, int num) {
 | 
				
			||||||
            List<string> contracts = new();
 | 
					            List<string> contracts = new();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            foreach (var sorte in auszahlungsSorten.Children().OfType<JToken>()) {
 | 
					            foreach (var sorte in auszahlungsSorten) {
 | 
				
			||||||
                if (((JProperty)sorte).Name == "Kurven") {
 | 
					                if (sorte.Key == "Kurven") continue;
 | 
				
			||||||
                    continue;
 | 
					                foreach (var attribut in sorte.Value.AsObject()) {
 | 
				
			||||||
                }
 | 
					                    foreach (var bindung in attribut.Value.AsObject()) {
 | 
				
			||||||
                foreach (var attribut in sorte.Values().OfType<JToken>()) {
 | 
					                        if ((int)bindung.Value.AsValue() == num) {
 | 
				
			||||||
                    foreach (var bindung in attribut.Values().OfType<JProperty>()) {
 | 
					                            contracts.Add($"{sorte.Key}/{attribut.Key}/{bindung.Key}");
 | 
				
			||||||
                        if ((int)(bindung).Value == num) {
 | 
					 | 
				
			||||||
                            contracts.Add($"{((JProperty)sorte).Name}/{((JProperty)attribut).Name}/{bindung.Name}");
 | 
					 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
@@ -125,15 +119,14 @@ namespace Elwig.Windows {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            PaymentVar paymentVar = paymentVars[0];
 | 
					            PaymentVar paymentVar = paymentVars[0];
 | 
				
			||||||
            var data = JToken.Parse(paymentVar.Data);
 | 
					            var data = JsonNode.Parse(paymentVar.Data).AsObject();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var auszahlungsSorten = data["AuszahlungSorten"];
 | 
					            var auszahlungsSorten = data["AuszahlungSorten"]?.AsObject();
 | 
				
			||||||
            if (auszahlungsSorten == null) {
 | 
					            if (auszahlungsSorten == null) {
 | 
				
			||||||
                return false;
 | 
					                return false;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var Graphs = auszahlungsSorten["Kurven"];
 | 
					            var Graphs = auszahlungsSorten["Kurven"]?.AsObject();
 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (Graphs == null) {
 | 
					            if (Graphs == null) {
 | 
				
			||||||
                return false;
 | 
					                return false;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@@ -141,26 +134,24 @@ namespace Elwig.Windows {
 | 
				
			|||||||
            int i = 1;
 | 
					            int i = 1;
 | 
				
			||||||
            foreach (var graph in Graphs) {
 | 
					            foreach (var graph in Graphs) {
 | 
				
			||||||
                if (i == num) {
 | 
					                if (i == num) {
 | 
				
			||||||
                    graph.Remove();
 | 
					                    Graphs.Remove(graph.Key);
 | 
				
			||||||
                    break;
 | 
					                    break;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                i++;
 | 
					                i++;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            foreach (var sorte in auszahlungsSorten.Children().OfType<JToken>()) {
 | 
					            foreach (var sorte in auszahlungsSorten) {
 | 
				
			||||||
                if (((JProperty)sorte).Name == "Kurven") {
 | 
					                if (sorte.Key == "Kurven") continue;
 | 
				
			||||||
                    continue;
 | 
					                foreach (var attribut in sorte.Value.AsObject()) {
 | 
				
			||||||
                }
 | 
					                    var bindungen = attribut.Value.AsObject();
 | 
				
			||||||
                foreach (var attribut in sorte.Values().OfType<JToken>()) {
 | 
					                    foreach (var bindung in bindungen) {
 | 
				
			||||||
                    List<JProperty> itemsToRemove = new();
 | 
					                        int v = (int)bindung.Value;
 | 
				
			||||||
                    foreach (var bindung in attribut.Values().OfType<JProperty>()) {
 | 
					                        if (v == num - 1) {
 | 
				
			||||||
                        if ((int)bindung.Value == num - 1) {
 | 
					                            bindungen.Remove(bindung.Key);
 | 
				
			||||||
                            itemsToRemove.Add(bindung);
 | 
					                        } else if (v > num - 1) {
 | 
				
			||||||
                        } else if ((int)bindung.Value > num - 1) {
 | 
					                            bindungen[bindung.Key] = v - 1;
 | 
				
			||||||
                            bindung.Value = (int)bindung.Value - 1;
 | 
					 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    itemsToRemove.ForEach(i => i.Remove());
 | 
					 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -231,6 +222,7 @@ namespace Elwig.Windows {
 | 
				
			|||||||
        private void InitPlot() {
 | 
					        private void InitPlot() {
 | 
				
			||||||
            OechslePricePlotScatter = OechslePricePlot.Plot.AddScatter(Graph.DataX, Graph.DataY);
 | 
					            OechslePricePlotScatter = OechslePricePlot.Plot.AddScatter(Graph.DataX, Graph.DataY);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            OechslePricePlot.Configuration.DoubleClickBenchmark = false;
 | 
				
			||||||
            OechslePricePlotScatter.LineColor = Color.Blue;
 | 
					            OechslePricePlotScatter.LineColor = Color.Blue;
 | 
				
			||||||
            OechslePricePlotScatter.MarkerColor = Color.Blue;
 | 
					            OechslePricePlotScatter.MarkerColor = Color.Blue;
 | 
				
			||||||
            OechslePricePlotScatter.MarkerSize = 9;
 | 
					            OechslePricePlotScatter.MarkerSize = 9;
 | 
				
			||||||
@@ -398,7 +390,7 @@ namespace Elwig.Windows {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                    PriceInput.Text = Graph.DataY[PrimaryMarkedPointIndex].ToString();
 | 
					                    PriceInput.Text = Graph.DataY[PrimaryMarkedPointIndex].ToString();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    EnableActionButtons();
 | 
					                    if (IsEditing || IsCreating) EnableActionButtons();
 | 
				
			||||||
                    OechslePricePlot.Render();
 | 
					                    OechslePricePlot.Render();
 | 
				
			||||||
                    return;
 | 
					                    return;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
@@ -430,6 +422,8 @@ namespace Elwig.Windows {
 | 
				
			|||||||
                return;
 | 
					                return;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            FlattenGraph(0, PrimaryMarkedPointIndex, Graph.DataY[PrimaryMarkedPointIndex]);
 | 
					            FlattenGraph(0, PrimaryMarkedPointIndex, Graph.DataY[PrimaryMarkedPointIndex]);
 | 
				
			||||||
 | 
					            SaveButton.IsEnabled = true;
 | 
				
			||||||
 | 
					            ResetButton.IsEnabled = true;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private void RightFlatButton_Click(object sender, RoutedEventArgs evt) {
 | 
					        private void RightFlatButton_Click(object sender, RoutedEventArgs evt) {
 | 
				
			||||||
@@ -437,6 +431,8 @@ namespace Elwig.Windows {
 | 
				
			|||||||
                return;
 | 
					                return;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            FlattenGraph(PrimaryMarkedPointIndex, Graph.DataY.Length - 1, Graph.DataY[PrimaryMarkedPointIndex]);
 | 
					            FlattenGraph(PrimaryMarkedPointIndex, Graph.DataY.Length - 1, Graph.DataY[PrimaryMarkedPointIndex]);
 | 
				
			||||||
 | 
					            SaveButton.IsEnabled = true;
 | 
				
			||||||
 | 
					            ResetButton.IsEnabled = true;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private void InterpolateButton_Click(object sender, RoutedEventArgs evt) {
 | 
					        private void InterpolateButton_Click(object sender, RoutedEventArgs evt) {
 | 
				
			||||||
@@ -451,6 +447,8 @@ namespace Elwig.Windows {
 | 
				
			|||||||
            for (int i = lowIndex; i < highIndex - 1; i++) {
 | 
					            for (int i = lowIndex; i < highIndex - 1; i++) {
 | 
				
			||||||
                Graph.DataY[i + 1] = Math.Round(Graph.DataY[i] + step, 4); // TODO richtig runden
 | 
					                Graph.DataY[i + 1] = Math.Round(Graph.DataY[i] + step, 4); // TODO richtig runden
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					            SaveButton.IsEnabled = true;
 | 
				
			||||||
 | 
					            ResetButton.IsEnabled = true;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private void LinearIncreaseButton_Click(object sender, RoutedEventArgs e) { 
 | 
					        private void LinearIncreaseButton_Click(object sender, RoutedEventArgs e) { 
 | 
				
			||||||
@@ -462,6 +460,8 @@ namespace Elwig.Windows {
 | 
				
			|||||||
                return;
 | 
					                return;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            LinearIncreaseGraph(PrimaryMarkedPointIndex, Graph.DataY.Length - 1, priceIncrease.Value);
 | 
					            LinearIncreaseGraph(PrimaryMarkedPointIndex, Graph.DataY.Length - 1, priceIncrease.Value);
 | 
				
			||||||
 | 
					            SaveButton.IsEnabled = true;
 | 
				
			||||||
 | 
					            ResetButton.IsEnabled = true;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private void OechslePricePlot_MouseDown(object sender, MouseEventArgs e) {
 | 
					        private void OechslePricePlot_MouseDown(object sender, MouseEventArgs e) {
 | 
				
			||||||
@@ -662,23 +662,22 @@ namespace Elwig.Windows {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            PaymentVar paymentVar = paymentVars[0];
 | 
					            PaymentVar paymentVar = paymentVars[0];
 | 
				
			||||||
            var data = JToken.Parse(paymentVar.Data);
 | 
					            var data = JsonNode.Parse(paymentVar.Data).AsObject();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var auszahlungsSorten = data["AuszahlungSorten"];
 | 
					            var auszahlungsSorten = data["AuszahlungSorten"];
 | 
				
			||||||
            if (auszahlungsSorten == null) {
 | 
					            if (auszahlungsSorten == null) {
 | 
				
			||||||
                return null;
 | 
					                return null;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var Graphs = auszahlungsSorten["Kurven"];
 | 
					            var Graphs = auszahlungsSorten["Kurven"].AsArray();
 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (Graphs == null) {
 | 
					            if (Graphs == null) {
 | 
				
			||||||
                return null;
 | 
					                return null;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (IsEditing) {
 | 
					            if (IsEditing) {
 | 
				
			||||||
                ((JArray)Graphs)[g.Num - 1] = g.ToJson();
 | 
					                Graphs[g.Num - 1] = g.ToJson();
 | 
				
			||||||
            } else if(IsCreating) {
 | 
					            } else if(IsCreating) {
 | 
				
			||||||
                ((JArray)Graphs).Add(g.ToJson());
 | 
					                Graphs.Add(g.ToJson());
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                return null;
 | 
					                return null;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -102,15 +102,19 @@ namespace Elwig.Windows {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        private async void Menu_Print_ShowDeliveryNote_Click(object sender, RoutedEventArgs evt) {
 | 
					        private async void Menu_Print_ShowDeliveryNote_Click(object sender, RoutedEventArgs evt) {
 | 
				
			||||||
            if (DeliveryList.SelectedItem is not Delivery d) return;
 | 
					            if (DeliveryList.SelectedItem is not Delivery d) return;
 | 
				
			||||||
 | 
					            Mouse.OverrideCursor = Cursors.Wait;
 | 
				
			||||||
            using var doc = new DeliveryNote(d, Context);
 | 
					            using var doc = new DeliveryNote(d, Context);
 | 
				
			||||||
            await doc.Generate();
 | 
					            await doc.Generate();
 | 
				
			||||||
            doc.Show();
 | 
					            doc.Show();
 | 
				
			||||||
 | 
					            Mouse.OverrideCursor = null;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private async void Menu_Print_PrintDeliveryNote_Click(object sender, RoutedEventArgs evt) {
 | 
					        private async void Menu_Print_PrintDeliveryNote_Click(object sender, RoutedEventArgs evt) {
 | 
				
			||||||
            if (DeliveryList.SelectedItem is not Delivery d) return;
 | 
					            if (DeliveryList.SelectedItem is not Delivery d) return;
 | 
				
			||||||
 | 
					            Mouse.OverrideCursor = Cursors.Wait;
 | 
				
			||||||
            using var doc = new DeliveryNote(d, Context);
 | 
					            using var doc = new DeliveryNote(d, Context);
 | 
				
			||||||
            await doc.Generate();
 | 
					            await doc.Generate();
 | 
				
			||||||
 | 
					            Mouse.OverrideCursor = null;
 | 
				
			||||||
            await doc.Print();
 | 
					            await doc.Print();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -702,26 +706,36 @@ namespace Elwig.Windows {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private async void NewDeliveryPartButton_Click(object sender, RoutedEventArgs evt) {
 | 
					        private async void NewDeliveryPartButton_Click(object sender, RoutedEventArgs evt) {
 | 
				
			||||||
 | 
					            FinishButton.IsEnabled = false;
 | 
				
			||||||
 | 
					            NewDeliveryPartButton.IsEnabled = false;
 | 
				
			||||||
 | 
					            NewDeliveryPartButton.Cursor = Cursors.Wait;
 | 
				
			||||||
            DeliveryPartList.IsEnabled = false;
 | 
					            DeliveryPartList.IsEnabled = false;
 | 
				
			||||||
            var p = await UpdateDeliveryPart(DeliveryList.SelectedItem as Delivery, DeliveryPartList.SelectedItem as DeliveryPart);
 | 
					            var p = await UpdateDeliveryPart(DeliveryList.SelectedItem as Delivery, DeliveryPartList.SelectedItem as DeliveryPart);
 | 
				
			||||||
            await RefreshDeliveryList();
 | 
					            await RefreshDeliveryList();
 | 
				
			||||||
            await RefreshDeliveryParts();
 | 
					            await RefreshDeliveryParts();
 | 
				
			||||||
 | 
					            NewDeliveryPartButton.Cursor = null;
 | 
				
			||||||
            DeliveryList.SelectedItem = p?.Delivery;
 | 
					            DeliveryList.SelectedItem = p?.Delivery;
 | 
				
			||||||
            DeliveryPartList.SelectedItem = null;
 | 
					            DeliveryPartList.SelectedItem = null;
 | 
				
			||||||
            InitialInputs();
 | 
					            InitialInputs();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private async void FinishButton_Click(object sender, RoutedEventArgs evt) {
 | 
					        private async void FinishButton_Click(object sender, RoutedEventArgs evt) {
 | 
				
			||||||
 | 
					            FinishButton.IsEnabled = false;
 | 
				
			||||||
 | 
					            NewDeliveryPartButton.IsEnabled = false;
 | 
				
			||||||
 | 
					            FinishButton.Cursor = Cursors.Wait;
 | 
				
			||||||
            DeliveryPartList.IsEnabled = false;
 | 
					            DeliveryPartList.IsEnabled = false;
 | 
				
			||||||
            var p = await UpdateDeliveryPart(DeliveryList.SelectedItem as Delivery, DeliveryPartList.SelectedItem as DeliveryPart);
 | 
					            var p = await UpdateDeliveryPart(DeliveryList.SelectedItem as Delivery, DeliveryPartList.SelectedItem as DeliveryPart);
 | 
				
			||||||
            await RefreshDeliveryList();
 | 
					            await RefreshDeliveryList();
 | 
				
			||||||
            await RefreshDeliveryParts();
 | 
					            await RefreshDeliveryParts();
 | 
				
			||||||
            if (p?.Delivery != null) {
 | 
					            if (p?.Delivery != null) {
 | 
				
			||||||
 | 
					                Mouse.OverrideCursor = Cursors.Wait;
 | 
				
			||||||
                using var doc = new DeliveryNote(p.Delivery, Context);
 | 
					                using var doc = new DeliveryNote(p.Delivery, Context);
 | 
				
			||||||
                await doc.Generate();
 | 
					                await doc.Generate();
 | 
				
			||||||
 | 
					                Mouse.OverrideCursor = Cursors.Wait;
 | 
				
			||||||
                doc.Show();
 | 
					                doc.Show();
 | 
				
			||||||
                //await doc.Print(2);
 | 
					                //await doc.Print(2);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					            FinishButton.Cursor = null;
 | 
				
			||||||
            DeliveryList.SelectedItem = null;
 | 
					            DeliveryList.SelectedItem = null;
 | 
				
			||||||
            InitInputs();
 | 
					            InitInputs();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@@ -769,6 +783,7 @@ namespace Elwig.Windows {
 | 
				
			|||||||
            try {
 | 
					            try {
 | 
				
			||||||
                if (res == null || res <= 0)
 | 
					                if (res == null || res <= 0)
 | 
				
			||||||
                    return;
 | 
					                    return;
 | 
				
			||||||
 | 
					                Mouse.OverrideCursor = Cursors.Wait;
 | 
				
			||||||
                ClearOriginalValues();
 | 
					                ClearOriginalValues();
 | 
				
			||||||
                if (res >= p.Weight) {
 | 
					                if (res >= p.Weight) {
 | 
				
			||||||
                    ControlUtils.SelectComboBoxItem(WineQualityLevelInput, q => (q as WineQualLevel)?.QualId, "WEI");
 | 
					                    ControlUtils.SelectComboBoxItem(WineQualityLevelInput, q => (q as WineQualLevel)?.QualId, "WEI");
 | 
				
			||||||
@@ -797,6 +812,7 @@ namespace Elwig.Windows {
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
                await Context.SaveChangesAsync();
 | 
					                await Context.SaveChangesAsync();
 | 
				
			||||||
                await RefreshDeliveryParts();
 | 
					                await RefreshDeliveryParts();
 | 
				
			||||||
 | 
					                Mouse.OverrideCursor = null;
 | 
				
			||||||
                FinishInputFilling();
 | 
					                FinishInputFilling();
 | 
				
			||||||
            } catch (Exception exc) {
 | 
					            } catch (Exception exc) {
 | 
				
			||||||
                if (entry1 != null) {
 | 
					                if (entry1 != null) {
 | 
				
			||||||
@@ -849,21 +865,28 @@ namespace Elwig.Windows {
 | 
				
			|||||||
                $"Soll die Lieferung {d.LsNr} ({d.Member.AdministrativeName}, MgNr. {d.Member.MgNr}) wirklich unwiderruflich gelöscht werden?",
 | 
					                $"Soll die Lieferung {d.LsNr} ({d.Member.AdministrativeName}, MgNr. {d.Member.MgNr}) wirklich unwiderruflich gelöscht werden?",
 | 
				
			||||||
                "Lieferung löschen", MessageBoxButton.YesNo, MessageBoxImage.Warning, MessageBoxResult.No);
 | 
					                "Lieferung löschen", MessageBoxButton.YesNo, MessageBoxImage.Warning, MessageBoxResult.No);
 | 
				
			||||||
            if (r == MessageBoxResult.Yes) {
 | 
					            if (r == MessageBoxResult.Yes) {
 | 
				
			||||||
 | 
					                Mouse.OverrideCursor = Cursors.Wait;
 | 
				
			||||||
                Context.Remove(d);
 | 
					                Context.Remove(d);
 | 
				
			||||||
                await Context.SaveChangesAsync();
 | 
					                await Context.SaveChangesAsync();
 | 
				
			||||||
                await RefreshDeliveryList();
 | 
					                await RefreshDeliveryList();
 | 
				
			||||||
                await RefreshDeliveryParts();
 | 
					                await RefreshDeliveryParts();
 | 
				
			||||||
 | 
					                Mouse.OverrideCursor = null;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private async void SaveButton_Click(object sender, RoutedEventArgs evt) {
 | 
					        private async void SaveButton_Click(object sender, RoutedEventArgs evt) {
 | 
				
			||||||
            DeliveryPart p = await UpdateDeliveryPart(DeliveryList.SelectedItem as Delivery, DeliveryPartList.SelectedItem as DeliveryPart);
 | 
					            SaveButton.IsEnabled = false;
 | 
				
			||||||
 | 
					            SaveButton.Cursor = Cursors.Wait;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            IsEditing = false;
 | 
					            IsEditing = false;
 | 
				
			||||||
            IsCreating = false;
 | 
					            IsCreating = false;
 | 
				
			||||||
            DeliveryList.IsEnabled = true;
 | 
					            DeliveryList.IsEnabled = true;
 | 
				
			||||||
            DeliveryPartList.IsEnabled = true;
 | 
					            DeliveryPartList.IsEnabled = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            DeliveryPart p = await UpdateDeliveryPart(DeliveryList.SelectedItem as Delivery, DeliveryPartList.SelectedItem as DeliveryPart);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            SaveButton.Cursor = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            HideSaveResetCancelButtons();
 | 
					            HideSaveResetCancelButtons();
 | 
				
			||||||
            ShowNewEditDeleteButtons();
 | 
					            ShowNewEditDeleteButtons();
 | 
				
			||||||
            LockInputs();
 | 
					            LockInputs();
 | 
				
			||||||
@@ -925,12 +948,12 @@ namespace Elwig.Windows {
 | 
				
			|||||||
                .ToListAsync();
 | 
					                .ToListAsync();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var res = Utils.ShowDeliveryExtractionDialog($"{delivery.LsNr}/{p.DPNr}", delivery.Member.AdministrativeName, count == 1, lsnrs);
 | 
					            var res = Utils.ShowDeliveryExtractionDialog($"{delivery.LsNr}/{p.DPNr}", delivery.Member.AdministrativeName, count == 1, lsnrs);
 | 
				
			||||||
 | 
					            if (res == null) return;
 | 
				
			||||||
            EntityEntry<Delivery>? entry = null;
 | 
					            EntityEntry<Delivery>? entry = null;
 | 
				
			||||||
            try {
 | 
					            try {
 | 
				
			||||||
                Delivery? d = null;
 | 
					                Delivery? d = null;
 | 
				
			||||||
                if (res == null) {
 | 
					                Mouse.OverrideCursor = Cursors.Wait;
 | 
				
			||||||
                    return;
 | 
					                if (res == "new") {
 | 
				
			||||||
                } else if (res == "new") {
 | 
					 | 
				
			||||||
                    d = Context.CreateProxy<Delivery>();
 | 
					                    d = Context.CreateProxy<Delivery>();
 | 
				
			||||||
                    d.Date = delivery.Date;
 | 
					                    d.Date = delivery.Date;
 | 
				
			||||||
                    d.Time = delivery.Time;
 | 
					                    d.Time = delivery.Time;
 | 
				
			||||||
@@ -958,6 +981,7 @@ namespace Elwig.Windows {
 | 
				
			|||||||
                await Context.Entry(p).ReloadAsync();
 | 
					                await Context.Entry(p).ReloadAsync();
 | 
				
			||||||
                await Context.Entry(delivery).ReloadAsync();
 | 
					                await Context.Entry(delivery).ReloadAsync();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                Mouse.OverrideCursor = null;
 | 
				
			||||||
                await RefreshDeliveryList();
 | 
					                await RefreshDeliveryList();
 | 
				
			||||||
                DeliveryList.SelectedItem = d;
 | 
					                DeliveryList.SelectedItem = d;
 | 
				
			||||||
            } catch (Exception exc) {
 | 
					            } catch (Exception exc) {
 | 
				
			||||||
@@ -979,9 +1003,11 @@ namespace Elwig.Windows {
 | 
				
			|||||||
                $"Soll die Teillieferung Nr. {p.DPNr} wirklich unwiderruflich gelöscht werden?",
 | 
					                $"Soll die Teillieferung Nr. {p.DPNr} wirklich unwiderruflich gelöscht werden?",
 | 
				
			||||||
                "Lieferung löschen", MessageBoxButton.YesNo, MessageBoxImage.Warning, MessageBoxResult.No);
 | 
					                "Lieferung löschen", MessageBoxButton.YesNo, MessageBoxImage.Warning, MessageBoxResult.No);
 | 
				
			||||||
            if (r == MessageBoxResult.Yes) {
 | 
					            if (r == MessageBoxResult.Yes) {
 | 
				
			||||||
 | 
					                Mouse.OverrideCursor = Cursors.Wait;
 | 
				
			||||||
                Context.Remove(p);
 | 
					                Context.Remove(p);
 | 
				
			||||||
                await Context.SaveChangesAsync();
 | 
					                await Context.SaveChangesAsync();
 | 
				
			||||||
                await RefreshDeliveryParts();
 | 
					                await RefreshDeliveryParts();
 | 
				
			||||||
 | 
					                Mouse.OverrideCursor = null;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,9 @@
 | 
				
			|||||||
using System.Linq;
 | 
					using System.Linq;
 | 
				
			||||||
using System.Windows;
 | 
					using System.Windows;
 | 
				
			||||||
 | 
					using System.Windows.Input;
 | 
				
			||||||
using Elwig.Documents;
 | 
					using Elwig.Documents;
 | 
				
			||||||
using Elwig.Helpers;
 | 
					using Elwig.Helpers;
 | 
				
			||||||
 | 
					using Microsoft.EntityFrameworkCore;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Elwig.Windows {
 | 
					namespace Elwig.Windows {
 | 
				
			||||||
    public partial class MainWindow : Window {
 | 
					    public partial class MainWindow : Window {
 | 
				
			||||||
@@ -38,13 +40,13 @@ namespace Elwig.Windows {
 | 
				
			|||||||
            // TODO
 | 
					            // TODO
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private void PdfButton_Click(object sender, RoutedEventArgs evt) {
 | 
					        private async void PdfButton_Click(object sender, RoutedEventArgs evt) {
 | 
				
			||||||
            Utils.RunBackground("PDF Generation", async () => {
 | 
					            Mouse.OverrideCursor = Cursors.Wait;
 | 
				
			||||||
            using var ctx = new AppDbContext();
 | 
					            using var ctx = new AppDbContext();
 | 
				
			||||||
                using var doc = new DeliveryNote(ctx.Deliveries.OrderBy(d => d.Parts.Count).ThenBy(d => d.Year).ThenBy(d => d.DId).Last(), ctx);
 | 
					            using var doc = new DeliveryNote(await ctx.Deliveries.OrderBy(d => d.Parts.Count).ThenBy(d => d.Year).ThenBy(d => d.DId).LastAsync(), ctx);
 | 
				
			||||||
            await doc.Generate();
 | 
					            await doc.Generate();
 | 
				
			||||||
            doc.Show();
 | 
					            doc.Show();
 | 
				
			||||||
            });
 | 
					            Mouse.OverrideCursor = null;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private void TestWindowButton_Click(object sender, RoutedEventArgs evt) {
 | 
					        private void TestWindowButton_Click(object sender, RoutedEventArgs evt) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,5 @@
 | 
				
			|||||||
::mkdir "C:\Program Files\Elwig"
 | 
					::mkdir "C:\Program Files\Elwig"
 | 
				
			||||||
::curl -s "http://www.columbia.edu/~em36/PDFtoPrinter.exe" -z "C:\Program Files\Elwig\PDFtoPrinter.exe" -o "C:\Program Files\Elwig\PDFtoPrinter.exe"
 | 
					::curl -s "http://www.columbia.edu/~em36/PDFtoPrinter.exe" -z "C:\Program Files\Elwig\PDFtoPrinter.exe" -o "C:\Program Files\Elwig\PDFtoPrinter.exe"
 | 
				
			||||||
mkdir "C:\ProgramData\Elwig\resources"
 | 
					mkdir "C:\ProgramData\Elwig\resources"
 | 
				
			||||||
curl -s -L "https://unpkg.com/pagedjs/dist/paged.polyfill.js" -o "C:\ProgramData\Elwig\resources\paged.polyfill.js"
 | 
					copy /b /y Documents\*.css "C:\ProgramData\Elwig\resources"
 | 
				
			||||||
copy /b /y "Documents\style.css" "C:\ProgramData\Elwig\resources\style.css"
 | 
					 | 
				
			||||||
copy /b /y Documents\*.cshtml "C:\ProgramData\Elwig\resources"
 | 
					copy /b /y Documents\*.cshtml "C:\ProgramData\Elwig\resources"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -68,5 +68,37 @@ namespace Tests {
 | 
				
			|||||||
                Assert.Throws<ArgumentException>(() => Utils.Modulo("789", -1));
 | 
					                Assert.Throws<ArgumentException>(() => Utils.Modulo("789", -1));
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [Test]
 | 
				
			||||||
 | 
					        public void Test_SplitAddress() {
 | 
				
			||||||
 | 
					            Assert.Multiple(() => {
 | 
				
			||||||
 | 
					                Assert.That(Utils.SplitAddress("Winzerstra<72>e 1"), Is.EqualTo(("Winzerstra<72>e", "1")));
 | 
				
			||||||
 | 
					                Assert.That(Utils.SplitAddress("Auf dem Feld 12"), Is.EqualTo(("Auf dem Feld", "12")));
 | 
				
			||||||
 | 
					                Assert.That(Utils.SplitAddress("Winzerstra<72>e 5a"), Is.EqualTo(("Winzerstra<72>e", "5a")));
 | 
				
			||||||
 | 
					                Assert.That(Utils.SplitAddress("Winzerstra<72>e 1-3/2"), Is.EqualTo(("Winzerstra<72>e", "1-3/2")));
 | 
				
			||||||
 | 
					                Assert.That(Utils.SplitAddress("Winzerstra<72>e 3/4/5"), Is.EqualTo(("Winzerstra<72>e", "3/4/5")));
 | 
				
			||||||
 | 
					                Assert.That(Utils.SplitAddress("Winzerstra<72>e 7/2/4/77"), Is.EqualTo(("Winzerstra<72>e", "7/2/4/77")));
 | 
				
			||||||
 | 
					                Assert.That(Utils.SplitAddress("Winzerstra<72>e 95b"), Is.EqualTo(("Winzerstra<72>e", "95b")));
 | 
				
			||||||
 | 
					                Assert.That(Utils.SplitAddress("Winzerstra<72>e 1, TOP 3"), Is.EqualTo(("Winzerstra<72>e", "1, TOP 3")));
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [Test]
 | 
				
			||||||
 | 
					        public void Test_SplitName() {
 | 
				
			||||||
 | 
					            Assert.Multiple(() => {
 | 
				
			||||||
 | 
					                Assert.That(Utils.SplitName("Max Bauer", "Bauer"), Is.EqualTo(("Bauer", "Max")));
 | 
				
			||||||
 | 
					                Assert.That(Utils.SplitName("Bauer Max", "Bauer"), Is.EqualTo(("Bauer", "Max")));
 | 
				
			||||||
 | 
					                Assert.That(Utils.SplitName("Max und Moritz Bauer", "Bauer"), Is.EqualTo(("Bauer", "Max und Moritz")));
 | 
				
			||||||
 | 
					                Assert.That(Utils.SplitName("Bauer Max und Moritz", "Bauer"), Is.EqualTo(("Bauer", "Max und Moritz")));
 | 
				
			||||||
 | 
					                Assert.That(Utils.SplitName("Bauer GesbR", "Bauer"), Is.EqualTo(("Bauer", "GesbR")));
 | 
				
			||||||
 | 
					                Assert.That(Utils.SplitName("Max und Moritz Bauer GesbR", "Bauer"), Is.EqualTo(("Bauer", "Max und Moritz GesbR")));
 | 
				
			||||||
 | 
					                Assert.That(Utils.SplitName("Bauer Max und Moritz GesbR", "Bauer"), Is.EqualTo(("Bauer", "Max und Moritz GesbR")));
 | 
				
			||||||
 | 
					                Assert.That(Utils.SplitName("Weingut Bauer", "Bauer"), Is.EqualTo(("Bauer", "Weingut")));
 | 
				
			||||||
 | 
					                Assert.That(Utils.SplitName("Bauer Weingut", "Bauer"), Is.EqualTo(("Bauer", "Weingut")));
 | 
				
			||||||
 | 
					                Assert.That(Utils.SplitName("Max und Moritz Bauer und Mustermann", "Bauer"), Is.EqualTo(("Bauer und Mustermann", "Max und Moritz")));
 | 
				
			||||||
 | 
					                Assert.That(Utils.SplitName("Bauer und Mustermann Max und Moritz", "Bauer"), Is.EqualTo(("Bauer und Mustermann", "Max und Moritz")));
 | 
				
			||||||
 | 
					                Assert.That(Utils.SplitName("ABC GesbR", "Bauer"), Is.EqualTo(((string, string?))("ABC GesbR", null)));
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user