Compare commits

..

12 Commits

85 changed files with 591 additions and 257 deletions
CHANGELOG.md
Elwig
Installer
Tests

@ -3,6 +3,19 @@ Changelog
=========
[v0.13.9][v0.13.9] (2025-05-05) {#v0.13.9}
------------------------------------------
### Sonstiges {#v0.13.9-misc}
* Abhängigkeiten aktualisiert. (bf0db37872, 4af2fa256e, d1c07ee92a, 41c5288fc5)
* Automatisches Aktualisieren der Synchroisations-URL (`https://elwig.at/clients/` -> `https://sync.elwig.at/`). (e50e7337e6)
[v0.13.9]: https://git.necronda.net/winzer/elwig/releases/tag/v0.13.9
[v0.13.8][v0.13.8] (2025-02-21) {#v0.13.8}
------------------------------------------

@ -25,10 +25,13 @@ namespace Elwig {
private readonly DispatcherTimer _autoUpdateTimer = new() { Interval = TimeSpan.FromHours(1) };
public static readonly string DataPath = @"C:\ProgramData\Elwig\";
public static readonly string DataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "Elwig");
public static readonly string MailsPath = Path.Combine(DataPath, "mails");
public static readonly string ConfigPath = Path.Combine(DataPath, "config.ini");
public static readonly string ExePath = @"C:\Program Files\Elwig\";
public static readonly string InstallPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "Elwig");
public static readonly string DocumentsPath = (Assembly.GetEntryAssembly()?.Location.Contains(@"\bin\") ?? false) ?
Path.Combine(Assembly.GetEntryAssembly()!.Location.Split(@"\bin\")[0], "../Elwig/Documents") :
Path.Combine(InstallPath, "resources/Documents");
public static readonly string TempPath = Path.Combine(Path.GetTempPath(), "Elwig");
public static Config Config { get; private set; } = new(ConfigPath);

@ -2,7 +2,7 @@
@inherits TemplatePage<Elwig.Documents.BusinessDocument>
@model Elwig.Documents.BusinessDocument
@{ Layout = "Document"; }
<link rel="stylesheet" href="file:///@Raw(Model.DataPath)\resources\BusinessDocument.css"/>
<link rel="stylesheet" href="file:///@Raw(Model.DocumentsPath)\BusinessDocument.css"/>
<div class="info-wrapper">
<div class="address-wrapper">
<div class="sender">

@ -3,7 +3,7 @@
@inherits TemplatePage<Elwig.Documents.CreditNote>
@model Elwig.Documents.CreditNote
@{ Layout = "BusinessDocument"; }
<link rel="stylesheet" href="file:///@Raw(Model.DataPath)\resources\CreditNote.css"/>
<link rel="stylesheet" href="file:///@Raw(Model.DocumentsPath)\CreditNote.css" />
<main>
<h1>@Model.Title</h1>
<table class="credit">

@ -2,7 +2,7 @@
@inherits TemplatePage<Elwig.Documents.DeliveryAncmtList>
@model Elwig.Documents.DeliveryAncmtList
@{ Layout = "Document"; }
<link rel="stylesheet" href="file:///@Raw(Model.DataPath)\resources\DeliveryAncmtList.css" />
<link rel="stylesheet" href="file:///@Raw(Model.DocumentsPath)\DeliveryAncmtList.css" />
<main>
<h1>Anmeldeliste</h1>
<h2>@Model.Filter</h2>
@ -24,7 +24,7 @@
<th rowspan="2" style="text-align: left;">Ort</th>
<th rowspan="2" style="text-align: left;">Sorte</th>
<th rowspan="2">Anmldg.</th>
<th>Gewicht</th>
<th>Menge</th>
</tr>
<tr>
<th class="unit">[kg]</th>

@ -3,7 +3,7 @@
@inherits TemplatePage<Elwig.Documents.DeliveryConfirmation>
@model Elwig.Documents.DeliveryConfirmation
@{ Layout = "BusinessDocument"; }
<link rel="stylesheet" href="file:///@Raw(Model.DataPath)\resources\DeliveryConfirmation.css"/>
<link rel="stylesheet" href="file:///@Raw(Model.DocumentsPath)\DeliveryConfirmation.css" />
<main>
<h1>@Model.Title</h1>
<table class="delivery-confirmation">
@ -30,7 +30,7 @@
<th rowspan="2" style="text-align: left;">Qualitätsstufe</th>
<th colspan="2">Gradation</th>
<th colspan="2">Flächenbindung</th>
<th>Gewicht</th>
<th>Menge</th>
<th rowspan="3" style="padding: 0;">
<svg width="10" height="40" xmlns="http://www.w3.org/2000/svg">
<text x="-40" y="4" transform="rotate(270)" font-size="8pt" font-style="italic" font-family="Times New Roman"

@ -2,7 +2,7 @@
@inherits TemplatePage<Elwig.Documents.DeliveryDepreciationList>
@model Elwig.Documents.DeliveryDepreciationList
@{ Layout = "Document"; }
<link rel="stylesheet" href="file:///@Raw(Model.DataPath)\resources\DeliveryDepreciationList.css" />
<link rel="stylesheet" href="file:///@Raw(Model.DocumentsPath)\DeliveryDepreciationList.css" />
<main>
<h1>Abwertungsliste</h1>
<h2>@Model.Filter</h2>
@ -27,7 +27,7 @@
<th rowspan="2" style="text-align: left;">Sorte</th>
<th rowspan="2" style="text-align: left;">Attr./Bewirt.</th>
<th colspan="2">Gradation</th>
<th>Gewicht</th>
<th>Menge</th>
</tr>
<tr>
<th class="unit">[°Oe]</th>

@ -2,7 +2,7 @@
@inherits TemplatePage<Elwig.Documents.DeliveryJournal>
@model Elwig.Documents.DeliveryJournal
@{ Layout = "Document"; }
<link rel="stylesheet" href="file:///@Raw(Model.DataPath)\resources\DeliveryJournal.css"/>
<link rel="stylesheet" href="file:///@Raw(Model.DocumentsPath)\DeliveryJournal.css"/>
<main>
<h1>Lieferjournal</h1>
<h2>@Model.Filter</h2>
@ -29,7 +29,7 @@
<th rowspan="2" style="text-align: left;">Mitglied</th>
<th rowspan="2" style="text-align: left;">Sorte</th>
<th colspan="2">Gradation</th>
<th>Gewicht</th>
<th>Menge</th>
</tr>
<tr>
<th class="unit">[°Oe]</th>

@ -2,7 +2,7 @@
@inherits TemplatePage<Elwig.Documents.DeliveryNote>
@model Elwig.Documents.DeliveryNote
@{ Layout = "BusinessDocument"; }
<link rel="stylesheet" href="file:///@Raw(Model.DataPath)\resources\DeliveryNote.css" />
<link rel="stylesheet" href="file:///@Raw(Model.DocumentsPath)\DeliveryNote.css" />
<main>
<h1>@Model.Title</h1>
<table class="delivery large">
@ -24,7 +24,7 @@
<th class="main" rowspan="2" colspan="2">Attribut</th>
<th class="main" rowspan="2">Qualitätsstufe</th>
<th colspan="2">Gradation</th>
<th>Gewicht</th>
<th>Menge</th>
</tr>
<tr>
<th class="unit">[°Oe]</th>

@ -23,7 +23,7 @@ namespace Elwig.Documents {
public bool ShowFoldMarks = App.Config.Debug;
public bool DoublePaged = false;
public string DataPath;
public string DocumentsPath;
public int CurrentNextSeason;
public string? DocumentId;
public string Title;
@ -34,7 +34,7 @@ namespace Elwig.Documents {
public Document(string title) {
var c = App.Client;
DataPath = App.DataPath;
DocumentsPath = App.DocumentsPath;
CurrentNextSeason = Utils.CurrentNextSeason;
Title = title;
Author = c.NameFull;

@ -7,9 +7,9 @@
<title>@Model.Title</title>
<meta name="author" value="@Model.Author"/>
<meta charset="UTF-8"/>
<link rel="stylesheet" href="file:///@Raw(Model.DataPath)\resources\Document.css"/>
<link rel="stylesheet" href="file:///@Raw(Model.DataPath)\resources\Document.Page.css"/>
<link rel="stylesheet" href="file:///@Raw(Model.DataPath)\resources\Document.Table.css"/>
<link rel="stylesheet" href="file:///@Raw(Model.DocumentsPath)\Document.css" />
<link rel="stylesheet" href="file:///@Raw(Model.DocumentsPath)\Document.Page.css" />
<link rel="stylesheet" href="file:///@Raw(Model.DocumentsPath)\Document.Table.css" />
@if (Model.DoublePaged) {
<style>
@@page :left {

@ -3,7 +3,7 @@
@inherits TemplatePage<Elwig.Documents.MemberDataSheet>
@model Elwig.Documents.MemberDataSheet
@{ Layout = "BusinessDocument"; }
<link rel="stylesheet" href="file:///@Raw(Model.DataPath)\resources\MemberDataSheet.css" />
<link rel="stylesheet" href="file:///@Raw(Model.DocumentsPath)\MemberDataSheet.css" />
<main>
<h1>@Model.Title</h1>
<table class="member border">

@ -2,7 +2,7 @@
@inherits TemplatePage<Elwig.Documents.MemberList>
@model Elwig.Documents.MemberList
@{ Layout = "Document"; }
<link rel="stylesheet" href="file:///@Raw(Model.DataPath)\resources\MemberList.css"/>
<link rel="stylesheet" href="file:///@Raw(Model.DocumentsPath)\MemberList.css" />
<main>
<h1>Mitgliederliste</h1>
<h2>@Model.Filter</h2>

@ -3,7 +3,7 @@
@inherits TemplatePage<Elwig.Documents.PaymentVariantSummary>
@model Elwig.Documents.PaymentVariantSummary
@{ Layout = "Document"; }
<link rel="stylesheet" href="file:///@Raw(Model.DataPath)\resources\PaymentVariantSummary.css" />
<link rel="stylesheet" href="file:///@Raw(Model.DocumentsPath)\PaymentVariantSummary.css" />
<main>
<h1>Auszahlungsvariante Lese @Model.Variant.Year</h1>
<h2>@Model.Variant.Name</h2>
@ -111,8 +111,8 @@
<td class="number"><span class="fleft">@Model.CurrencySymbol</span>@($"{Math.Abs(payed):N2}")</td>
@{
var weiRows = Model.Data.Rows.Where(r => r.QualityLevel == "Wein");
var minWei = weiRows.Min(r => r.Ungeb.Price);
var maxWei = weiRows.Max(r => r.Ungeb.Price);
var minWei = weiRows.Min(r => r.Ungeb.MinPrice);
var maxWei = weiRows.Max(r => r.Ungeb.MaxPrice);
}
<th class="lborder tborder">Preis (abgewertet):</th>
<td colspan="2" class="center tborder">@(minWei != maxWei ? $"{minWei:N4}{maxWei:N4}" : $"{minWei:N4}") @Model.CurrencySymbol/kg</td>
@ -123,8 +123,8 @@
<td class="number tborder"><span class="fleft">@Model.CurrencySymbol</span>@($"{netSum:N2}")</td>
@{
var quwRows = Model.Data.Rows.Where(r => r.QualityLevel != "Wein");
var minPrice = quwRows.Min(r => r.Ungeb.Price);
var maxPrice = quwRows.Max(r => r.Ungeb.Price);
var minPrice = quwRows.Min(r => r.Ungeb.MinPrice);
var maxPrice = quwRows.Max(r => r.Ungeb.MaxPrice);
}
<th class="lborder">Preis (ungeb., nicht abgew.):</th>
<td colspan="2" class="center">@(minPrice != maxPrice ? $"{minPrice:N4}{maxPrice:N4}" : $"{minPrice:N4}") @Model.CurrencySymbol/kg</td>
@ -135,8 +135,8 @@
<td class="number"><span class="fleft">@Model.CurrencySymbol</span>@($"{Math.Abs(vat):N2}")</td>
@{
var gebRows = Model.Data.Rows
.Where(r => r.Geb.Price != null && r.Ungeb.Price != null)
.Select(r => r.Geb.Price - r.Ungeb.Price);
.Where(r => r.Geb.MaxPrice != null && r.Ungeb.MinPrice != null)
.Select(r => r.Geb.MaxPrice - r.Ungeb.MinPrice);
var minGeb = gebRows.Min();
var maxGeb = gebRows.Max();
}
@ -219,19 +219,22 @@
</table>
<table class="payment-variant-data">
<colgroup>
<col style="width: 30mm;"/>
<col style="width: 20mm;"/>
<col style="width: 25mm;"/>
<col style="width: 20mm;"/>
<col style="width: 25mm;"/>
<col style="width: 20mm;"/>
<col style="width: 25mm;"/>
<col style="width: 19mm;"/>
<col style="width: 18mm;"/>
<col style="width: 15mm;"/>
<col style="width: 18mm;"/>
<col style="width: 15mm;"/>
<col style="width: 18mm;"/>
<col style="width: 15mm;"/>
<col style="width: 22mm;"/>
</colgroup>
<thead>
<tr>
<th rowspan="2" style="text-align: left;">Qualitätsstufe</th>
<th>Gradation</th>
<th colspan="2">ungebunden</th>
<th colspan="2">attributlos gebunden</th>
<th colspan="2">gebunden</th>
<th>Gesamt</th>
</tr>
@ -241,6 +244,8 @@
<th>[@(Model.CurrencySymbol)/kg]</th>
<th>[kg]</th>
<th>[@(Model.CurrencySymbol)/kg]</th>
<th>[kg]</th>
<th>[@(Model.CurrencySymbol)/kg]</th>
<th>[@(Model.CurrencySymbol)]</th>
</tr>
</thead>
@ -258,6 +263,8 @@
<th colspan="2">@hdr</th>
<td class="number">@($"{rows.Sum(r => r.Ungeb.Weight):N0}")</td>
<td></td>
<td class="number">@($"{rows.Sum(r => r.LowGeb.Weight):N0}")</td>
<td></td>
<td class="number">@($"{rows.Sum(r => r.Geb.Weight):N0}")</td>
<td></td>
<td class="number">@($"{rows.Sum(r => r.Amount):N2}")</td>
@ -267,9 +274,11 @@
<td>@(row.QualityLevel)</td>
<td class="center">@($"{row.Oe:N0}")</td>
<td class="number">@(row.Ungeb.Weight != 0 ? $"{row.Ungeb.Weight:N0}" : "-")</td>
<td class="number">@(row.Ungeb.Price != null ? $"{row.Ungeb.Price:N4}" : "-")</td>
<td class="number">@(row.Ungeb.MaxPrice != null ? $"{row.Ungeb.MaxPrice:N4}" : "-")</td>
<td class="number">@(row.LowGeb.Weight != 0 ? $"{row.LowGeb.Weight:N0}" : "-")</td>
<td class="number">@(row.LowGeb.MaxPrice != null ? $"{row.LowGeb.MaxPrice:N4}" : "-")</td>
<td class="number">@(row.Geb.Weight != 0 ? $"{row.Geb.Weight:N0}" : "-")</td>
<td class="number">@(row.Geb.Price != null ? $"{row.Geb.Price:N4}" : "-")</td>
<td class="number">@(row.Geb.MaxPrice != null ? $"{row.Geb.MaxPrice:N4}" : "-")</td>
<td class="number">@($"{row.Amount:N2}")</td>
</tr>
lastHdr = hdr;

@ -3,7 +3,7 @@
@inherits TemplatePage<Elwig.Documents.WineQualityStatistics>
@model Elwig.Documents.WineQualityStatistics
@{ Layout = "Document"; }
<link rel="stylesheet" href="file:///@Raw(Model.DataPath)\resources\WineQualityStatistics.css"/>
<link rel="stylesheet" href="file:///@Raw(Model.DocumentsPath)\WineQualityStatistics.css" />
<main>
<h1>Qualitätsstatistik</h1>
<h2>@Model.Filter</h2>

@ -7,7 +7,7 @@
<UseWPF>true</UseWPF>
<PreserveCompilationContext>true</PreserveCompilationContext>
<ApplicationIcon>Resources\Images\Elwig.ico</ApplicationIcon>
<Version>0.13.8</Version>
<Version>0.13.9</Version>
<SatelliteResourceLanguages>de-AT</SatelliteResourceLanguages>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<ApplicationManifest>app.manifest</ApplicationManifest>
@ -20,20 +20,16 @@
<EmbeddedResource Include="Resources\Sql\*" />
</ItemGroup>
<Target Name="PreBuild" BeforeTargets="PreBuildEvent">
<Exec Command="call fetch-resources.bat" />
</Target>
<ItemGroup>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
<PackageReference Include="LinqKit" Version="1.3.8" />
<PackageReference Include="MailKit" Version="4.11.0" />
<PackageReference Include="MailKit" Version="4.12.0" />
<PackageReference Include="Microsoft.AspNetCore.Razor.Language" Version="6.0.36" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="9.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.4" />
<PackageReference Include="Microsoft.Extensions.Configuration.Ini" Version="9.0.4" />
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.3179.45" />
<PackageReference Include="NJsonSchema" Version="11.2.0" />
<PackageReference Include="NJsonSchema" Version="11.3.2" />
<PackageReference Include="PdfiumViewer" Version="2.13.0" />
<PackageReference Include="PdfiumViewer.Native.x86_64.no_v8-no_xfa" Version="2018.4.8.256" />
<PackageReference Include="RazorLight" Version="2.3.1" />

@ -9,7 +9,7 @@ namespace Elwig.Helpers {
public static class AppDbUpdater {
// Don't forget to update value in Tests/fetch-resources.bat!
public static readonly int RequiredSchemaVersion = 31;
public static readonly int RequiredSchemaVersion = 32;
private static int VersionOffset = 0;

@ -1,14 +1,15 @@
using System.IO.Compression;
using System.IO;
using System.Threading.Tasks;
using Elwig.Models.Entities;
using System.Collections.Generic;
using System;
using System.Text.Json.Nodes;
using System.Linq;
using System.Windows;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Threading.Tasks;
using System.Windows;
namespace Elwig.Helpers.Export {
public static class ElwigData {
@ -63,7 +64,8 @@ namespace Elwig.Helpers.Export {
List<WbRd> Riede,
List<Delivery> Deliveries,
List<DeliveryPart> DeliveryParts,
List<DeliveryPartModifier> Modifiers)>();
List<DeliveryPartModifier> Modifiers,
Dictionary<string, List<(int Id1, int Id2, DateTime CreatedAt, DateTime ModifiedAt)>> Timestamps)>();
var metaData = new List<(string FileName, string ZwstId, string Device,
int? MemberNum, string? MemberFilters,
@ -94,7 +96,11 @@ namespace Elwig.Helpers.Export {
areaComCount, areaComFilters != null ? string.Join(" / ", areaComFilters) : null,
deliveryCount, deliveryFilters != null ? string.Join(" / ", deliveryFilters) : null));
data.Add(new([], [], [], [], [], [], [], new([], [])));
data.Add(new([], [], [], [], [], [], [], new([], [], new() {
["member"] = [],
["area_commitment"] = [],
["delivery"] = [],
})));
var r = data[^1];
var membersJson = zip.GetEntry("members.json");
@ -103,11 +109,13 @@ namespace Elwig.Helpers.Export {
string? line;
while ((line = await reader.ReadLineAsync()) != null) {
var obj = JsonNode.Parse(line)!.AsObject();
var (m, b, telNrs, emailAddrs) = obj.ToMember(kgs);
var (m, b, telNrs, emailAddrs, timestamps) = obj.ToMember(kgs);
r.Members.Add(m);
if (b != null) r.BillingAddresses.Add(b);
r.TelephoneNumbers.AddRange(telNrs);
r.EmailAddresses.AddRange(emailAddrs);
if (timestamps.HasValue)
r.Timestamps["member"].Add((m.MgNr, 0, timestamps.Value.CreatedAt, timestamps.Value.ModifiedAt));
}
}
@ -117,12 +125,14 @@ namespace Elwig.Helpers.Export {
string? line;
while ((line = await reader.ReadLineAsync()) != null) {
var obj = JsonNode.Parse(line)!.AsObject();
var (areaCom, wbrd) = obj.ToAreaCom(kgs, currentWbRde);
var (areaCom, wbrd, timestamps) = obj.ToAreaCom(kgs, currentWbRde);
r.AreaCommitments.Add(areaCom);
if (wbrd != null) {
currentWbRde[wbrd.KgNr].Add(wbrd);
r.Riede.Add(wbrd);
}
if (timestamps.HasValue)
r.Timestamps["area_commitment"].Add((areaCom.FbNr, 0, timestamps.Value.CreatedAt, timestamps.Value.ModifiedAt));
}
}
@ -132,10 +142,12 @@ namespace Elwig.Helpers.Export {
string? line;
while ((line = await reader.ReadLineAsync()) != null) {
var obj = JsonNode.Parse(line)!.AsObject();
var (d, parts, mods) = obj.ToDelivery(currentLsNrs, currentDids);
var (d, parts, mods, timestamps) = obj.ToDelivery(currentLsNrs, currentDids);
r.Deliveries.Add(d);
r.DeliveryParts.AddRange(parts);
r.Modifiers.AddRange(mods);
if (timestamps.HasValue)
r.Timestamps["delivery"].Add((d.Year, d.DId, timestamps.Value.CreatedAt, timestamps.Value.ModifiedAt));
}
}
}
@ -144,7 +156,7 @@ namespace Elwig.Helpers.Export {
var importedAreaComs = new List<(string FileName, string ZwstId, string Device, int Imported, int NotImported, string Filters)>();
var importedDeliveries = new List<(string FileName, string ZwstId, string Device, int New, int Overwritten, int NotImported, string Filters)>();
foreach (var ((members, billingAddresses, telephoneNumbers, emailAddresses, areaCommitments, riede, deliveries, deliveryParts, modifiers), meta) in data.Zip(metaData)) {
foreach (var ((members, billingAddresses, telephoneNumbers, emailAddresses, areaCommitments, riede, deliveries, deliveryParts, modifiers, timestamps), meta) in data.Zip(metaData)) {
var branch = branches[meta.ZwstId];
var device = meta.Device;
@ -272,6 +284,26 @@ namespace Elwig.Helpers.Export {
}
await ctx.SaveChangesAsync();
var primaryKeys = new Dictionary<string, string>() {
["member"] = "mgnr, 0",
["area_commitment"] = "fbnr, 0",
["delivery"] = "year, did",
};
var updateStmts = timestamps
.SelectMany(e => e.Value.Select(m => $"UPDATE {e.Key} " +
$"SET ctime = {((DateTimeOffset)m.CreatedAt.ToUniversalTime()).ToUnixTimeSeconds()}, " +
$"mtime = {((DateTimeOffset)m.ModifiedAt.ToUniversalTime()).ToUnixTimeSeconds()} " +
$"WHERE ({primaryKeys[e.Key]}) = ({m.Id1}, {m.Id2});"));
using var cnx = AppDbContext.Connect();
await AppDbContext.ExecuteBatch(cnx, $"""
BEGIN;
UPDATE client_parameter SET value = '0' WHERE param = 'ENABLE_TIME_TRIGGERS';
{string.Join("\n", updateStmts)}
UPDATE client_parameter SET value = '1' WHERE param = 'ENABLE_TIME_TRIGGERS';
COMMIT;
""");
await AddImportedFiles(Path.GetFileName(meta.FileName));
}
App.HintContextChange();
@ -460,15 +492,19 @@ namespace Elwig.Helpers.Export {
return obj;
}).ToArray()),
["comment"] = m.Comment,
["created_at"] = $"{m.CreatedAt:yyyy-MM-ddTHH:mm:ssK}",
["modified_at"] = $"{m.ModifiedAt:yyyy-MM-ddTHH:mm:ssK}",
};
}
public static (Member, BillingAddr?, List<MemberTelNr>, List<MemberEmailAddr>) ToMember(this JsonNode json, Dictionary<int, AT_Kg> kgs) {
public static (Member, BillingAddr?, List<MemberTelNr>, List<MemberEmailAddr>, (DateTime CreatedAt, DateTime ModifiedAt)?) ToMember(this JsonNode json, Dictionary<int, AT_Kg> kgs) {
var mgnr = json["mgnr"]!.AsValue().GetValue<int>();
var kgnr = json["default_kgnr"]?.AsValue().GetValue<int>();
if (kgnr != null && !kgs.Values.Any(k => k.WbKg?.KgNr == kgnr)) {
throw new ArgumentException($"Für KG {(kgs.TryGetValue(kgnr.Value, out var k) ? k.Name : "?")} ({kgnr:00000}) ist noch keine Großlage festgelegt!\n(Stammdaten → Herkunftshierarchie)");
}
var createdAt = json["created_at"]?.AsValue().GetValue<string>();
var modifiedAt = json["modified_at"]?.AsValue().GetValue<string>();
return (new Member {
MgNr = mgnr,
PredecessorMgNr = json["predecessor_mgnr"]?.AsValue().GetValue<int>(),
@ -502,6 +538,7 @@ namespace Elwig.Helpers.Export {
ContactViaPost = json["contact_postal"]?.AsValue().GetValue<bool>() ?? false,
ContactViaEmail = json["contact_email"]?.AsValue().GetValue<bool>() ?? false,
Comment = json["comment"]?.AsValue().GetValue<string>(),
ImportedAt = DateTime.Now,
}, json["billing_address"] is JsonObject a ? new BillingAddr {
MgNr = mgnr,
FullName = a["name"]!.AsValue().GetValue<string>(),
@ -519,7 +556,10 @@ namespace Elwig.Helpers.Export {
Nr = i + 1,
Address = a["address"]!.AsValue().GetValue<string>(),
Comment = a["comment"]?.AsValue().GetValue<string>(),
}).ToList());
}).ToList(),
createdAt == null || modifiedAt == null ? null :
(DateTime.ParseExact(createdAt, "yyyy-MM-ddTHH:mm:ssK", CultureInfo.InvariantCulture, DateTimeStyles.None),
DateTime.ParseExact(modifiedAt, "yyyy-MM-ddTHH:mm:ssK", CultureInfo.InvariantCulture, DateTimeStyles.None)));
}
public static JsonObject ToJson(this AreaCom c) {
@ -535,10 +575,12 @@ namespace Elwig.Helpers.Export {
["year_from"] = c.YearFrom,
["year_to"] = c.YearTo,
["comment"] = c.Comment,
["created_at"] = $"{c.CreatedAt:yyyy-MM-ddTHH:mm:ssK}",
["modified_at"] = $"{c.ModifiedAt:yyyy-MM-ddTHH:mm:ssK}",
};
}
public static (AreaCom, WbRd?) ToAreaCom(this JsonNode json, Dictionary<int, AT_Kg> kgs, Dictionary<int, List<WbRd>> riede) {
public static (AreaCom, WbRd?, (DateTime CreatedAt, DateTime ModifiedAt)?) ToAreaCom(this JsonNode json, Dictionary<int, AT_Kg> kgs, Dictionary<int, List<WbRd>> riede) {
var kgnr = json["kgnr"]!.AsValue().GetValue<int>();
var ried = json["ried"]?.AsValue().GetValue<string>();
WbRd? rd = null;
@ -556,6 +598,8 @@ namespace Elwig.Helpers.Export {
rde.Add(rd);
}
}
var createdAt = json["created_at"]?.AsValue().GetValue<string>();
var modifiedAt = json["modified_at"]?.AsValue().GetValue<string>();
return (new AreaCom {
FbNr = json["fbnr"]!.AsValue().GetValue<int>(),
MgNr = json["mgnr"]!.AsValue().GetValue<int>(),
@ -568,7 +612,11 @@ namespace Elwig.Helpers.Export {
YearFrom = json["year_from"]?.AsValue().GetValue<int>(),
YearTo = json["year_to"]?.AsValue().GetValue<int>(),
Comment = json["comment"]?.AsValue().GetValue<string>(),
}, newRd ? rd : null);
ImportedAt = DateTime.Now,
}, newRd ? rd : null,
createdAt == null || modifiedAt == null ? null :
(DateTime.ParseExact(createdAt, "yyyy-MM-ddTHH:mm:ssK", CultureInfo.InvariantCulture, DateTimeStyles.None),
DateTime.ParseExact(modifiedAt, "yyyy-MM-ddTHH:mm:ssK", CultureInfo.InvariantCulture, DateTimeStyles.None)));
}
public static JsonObject ToJson(this Delivery d) {
@ -596,6 +644,8 @@ namespace Elwig.Helpers.Export {
["manual_weighing"] = p.IsManualWeighing,
["modids"] = new JsonArray(p.Modifiers.Select(m => (JsonNode)m.ModId).ToArray()),
["comment"] = p.Comment,
["created_at"] = $"{p.CreatedAt:yyyy-MM-ddTHH:mm:ssK}",
["modified_at"] = $"{p.ModifiedAt:yyyy-MM-ddTHH:mm:ssK}",
};
if (p.IsSplCheck) obj["spl_check"] = p.IsSplCheck;
if (p.IsHandPicked != null) obj["hand_picked"] = p.IsHandPicked;
@ -609,10 +659,12 @@ namespace Elwig.Helpers.Export {
return obj;
}).ToArray()),
["comment"] = d.Comment,
["created_at"] = $"{d.CreatedAt:yyyy-MM-ddTHH:mm:ssK}",
["modified_at"] = $"{d.ModifiedAt:yyyy-MM-ddTHH:mm:ssK}",
};
}
public static (Delivery, List<DeliveryPart>, List<DeliveryPartModifier>) ToDelivery(this JsonNode json, Dictionary<string, int> currentLsNrs, Dictionary<int, int> currentDids) {
public static (Delivery, List<DeliveryPart>, List<DeliveryPartModifier>, (DateTime CreatedAt, DateTime ModifiedAt)?) ToDelivery(this JsonNode json, Dictionary<string, int> currentLsNrs, Dictionary<int, int> currentDids) {
var year = json["year"]!.AsValue().GetValue<int>();
var lsnr = json["lsnr"]!.AsValue().GetValue<string>();
var did = currentLsNrs.GetValueOrDefault(lsnr, -1);
@ -621,6 +673,8 @@ namespace Elwig.Helpers.Export {
did = ++currentDids[year];
}
currentLsNrs[lsnr] = did;
var createdAt = json["created_at"]?.AsValue().GetValue<string>();
var modifiedAt = json["modified_at"]?.AsValue().GetValue<string>();
return (new Delivery {
Year = year,
DId = did,
@ -631,6 +685,7 @@ namespace Elwig.Helpers.Export {
LsNr = lsnr,
MgNr = json["mgnr"]!.AsValue().GetValue<int>(),
Comment = json["comment"]?.AsValue().GetValue<string>(),
ImportedAt = DateTime.Now,
}, json["parts"]!.AsArray().Select(p => p!.AsObject()).Select(p => new DeliveryPart {
Year = year,
DId = did,
@ -661,7 +716,10 @@ namespace Elwig.Helpers.Export {
DId = did,
DPNr = p["dpnr"]!.AsValue().GetValue<int>(),
ModId = m!.AsValue().GetValue<string>(),
})).ToList());
})).ToList(),
createdAt == null || modifiedAt == null ? null :
(DateTime.ParseExact(createdAt, "yyyy-MM-ddTHH:mm:ssK", CultureInfo.InvariantCulture, DateTimeStyles.None),
DateTime.ParseExact(modifiedAt, "yyyy-MM-ddTHH:mm:ssK", CultureInfo.InvariantCulture, DateTimeStyles.None)));
}
}
}

@ -317,7 +317,7 @@ namespace Elwig.Helpers.Export {
c = $"<{ct} office:value-type=\"string\" calcext:value-type=\"string\"{add}><text:p>{SecurityElement.Escape(data.ToString())}</text:p></{ct}>";
}
return $" {c}\r\n" + (colSpan > 1 ? $" <table:covered-table-cell table:number-rows-repeated=\"{colSpan - 1}\"/>\r\n" : "");
return $" {c}\r\n" + (colSpan > 1 ? $" <table:covered-table-cell table:number-columns-repeated=\"{colSpan - 1}\"/>\r\n" : "");
}
}
}

@ -10,7 +10,7 @@ namespace Elwig.Helpers.Printing {
public static async Task Init(Action? evtHandler = null) {
var e = new RazorLightEngineBuilder()
.UseFileSystemProject(App.DataPath + "resources")
.UseFileSystemProject(App.DocumentsPath)
.UseMemoryCachingProvider()
.Build();

@ -14,7 +14,7 @@ using System.Drawing.Printing;
namespace Elwig.Helpers.Printing {
public static class Pdf {
private static readonly string WinziPrint = new string[] { App.ExePath }
private static readonly string WinziPrint = new string[] { App.InstallPath }
.Union(Environment.GetEnvironmentVariable("PATH")?.Split(';') ?? [])
.Select(x => Path.Combine(x, "WinziPrint.exe"))
.Where(File.Exists)

@ -434,8 +434,7 @@ namespace Elwig.Helpers {
if (accept != null)
client.DefaultRequestHeaders.Accept.Add(new(accept));
if (username != null || password != null)
client.DefaultRequestHeaders.Authorization = new("Basic", Convert.ToBase64String(
Utils.UTF8.GetBytes($"{username}:{password}")));
client.DefaultRequestHeaders.Authorization = new("Basic", Convert.ToBase64String(Utils.UTF8.GetBytes($"{username}:{password}")));
return client;
}
@ -454,6 +453,8 @@ namespace Elwig.Helpers {
}
public static async Task UploadExportData(string zip, string url, string username, string password) {
if (url.StartsWith("https://elwig.at/clients/"))
url = "https://sync.elwig.at/" + url[25..];
if (!url.EndsWith('/')) url += "/";
using var client = GetHttpClient(username, password, accept: "application/json");
var content = new StreamContent(new FileStream(zip, FileMode.Open, FileAccess.Read));
@ -463,6 +464,8 @@ namespace Elwig.Helpers {
}
public static async Task<JsonArray> GetExportMetaData(string url, string username, string password) {
if (url.StartsWith("https://elwig.at/clients/"))
url = "https://sync.elwig.at/" + url[25..];
using var client = GetHttpClient(username, password, accept: "application/json");
using var res = await client.GetAsync(url);
res.EnsureSuccessStatusCode();

@ -27,14 +27,14 @@ namespace Elwig.Models.Dtos {
MgNr = mgnr;
}
public static async Task<IDictionary<int, CreditNoteDeliveryData>> ForPaymentVariant(DbSet<CreditNoteDeliveryRowSingle> table, DbSet<Season> seasons, int year, int avnr) {
var variant = (await seasons.FindAsync(year))?.PaymentVariants.Where(v => v.AvNr == avnr).SingleOrDefault();
public static async Task<IDictionary<int, CreditNoteDeliveryData>> ForPaymentVariant(DbSet<CreditNoteDeliveryRowSingle> table, DbSet<PaymentVar> paymentVariants, int year, int avnr) {
var variant = await paymentVariants.FindAsync(year, avnr);
BillingData? varData = null;
try { varData = variant != null ? BillingData.FromJson(variant.Data) : null; } catch { }
return (await FromDbSet(table, year, avnr))
.GroupBy(
r => new { r.Year, r.AvNr, r.MgNr, r.TgNr, r.DId, r.DPNr },
(k, g) => new CreditNoteDeliveryRow(g, seasons, varData?.NetWeightModifier ?? 0.0, varData?.GrossWeightModifier ?? 0.0))
(k, g) => new CreditNoteDeliveryRow(g, variant, varData))
.GroupBy(
r => new { r.Year, r.AvNr, r.MgNr, r.TgNr },
(k, g) => new CreditNoteDeliveryData(g, k.Year, k.TgNr, mgnr: k.MgNr))
@ -86,13 +86,13 @@ namespace Elwig.Models.Dtos {
public decimal? Amount;
public double WeighingModifier;
public CreditNoteDeliveryRow(IEnumerable<CreditNoteDeliveryRowSingle> rows, DbSet<Season> seasons, double netWeightModifier, double grossWeightModifier) {
public CreditNoteDeliveryRow(IEnumerable<CreditNoteDeliveryRowSingle> rows, PaymentVar paymentVariant, BillingData? varData) {
var f = rows.First();
Year = f.Year;
TgNr = f.TgNr;
AvNr = f.AvNr;
MgNr = f.MgNr;
var season = seasons.Find(Year);
var season = paymentVariant.Season;
LsNr = f.LsNr;
DPNr = f.DPNr;
@ -115,7 +115,7 @@ namespace Elwig.Models.Dtos {
b.Price != null ? season?.DecFromDb((long)b.Price) : null,
b.Amount != null ? season?.DecFromDb((long)b.Amount) : null))
.ToArray();
WeighingModifier = f.NetWeight ? netWeightModifier : grossWeightModifier;
WeighingModifier = (varData == null || !varData.ConsiderDelieryModifiers) ? 0 : f.NetWeight ? varData.NetWeightModifier : varData.GrossWeightModifier;
Amount = f.TotalAmount != null ? season?.DecFromDb((long)f.TotalAmount) : null;
var netAmount = f.NetAmount != null ? season?.DecFromDb((long)f.NetAmount) : null;
var amt = netAmount * (decimal)(1.0 + WeighingModifier);

@ -17,9 +17,9 @@ namespace Elwig.Models.Dtos {
("Name2", "Vorname", null, 40),
("DefaultKg", "Ort", null, 40),
("SortId", "Sorte", null, 10),
("Weight", "Gewicht", "kg", 20),
("CreatedTimestamp", "Angemeldet", null, 35),
("ModifiedTimestamp", "Geändert", null, 35),
("Weight", "Menge", "kg", 20),
("CreatedAt", "Angemeldet", null, 35),
("ModifiedAt", "Geändert", null, 35),
("Status", "Status", null, 20),
];
@ -47,8 +47,8 @@ namespace Elwig.Models.Dtos {
public string? DefaultKg;
public string SortId;
public string Variety;
public DateTime CreatedTimestamp;
public DateTime? ModifiedTimestamp;
public DateTime CreatedAt;
public DateTime? ModifiedAt;
public int Weight;
public string? Status;
@ -65,10 +65,10 @@ namespace Elwig.Models.Dtos {
DefaultKg = m.DefaultKg?.Name;
SortId = a.SortId;
Variety = a.Variety.Name;
CreatedTimestamp = a.CreatedTimestamp;
ModifiedTimestamp = a.ModifiedTimestamp == a.CreatedTimestamp ? null : a.ModifiedTimestamp;
CreatedAt = a.CreatedAt;
ModifiedAt = a.ModifiedAt == a.CreatedAt ? null : a.ModifiedAt;
Weight = a.Weight;
Status = s.AncmtTo == null ? null : a.CreatedTimestamp >= s.AncmtTo ? "verspät." : "ok";
Status = s.AncmtTo == null ? null : a.CreatedAt >= s.AncmtTo ? "verspät." : "ok";
}
}
}

@ -16,7 +16,7 @@ namespace Elwig.Models.Dtos {
("QualityLevel", "Qualitätsstufe", null, 25),
("Gradation", "Gradation", "°Oe|°KMW", 32),
("Buckets", "Flächenbindung", "|kg", 36),
("Weight", "Gewicht", "kg", 16),
("Weight", "Menge", "kg", 16),
];
private readonly int MgNr;

@ -24,7 +24,7 @@ namespace Elwig.Models.Dtos {
("CultId", "Bewirt.", null, 15),
("QualId", "Qualität", null, 15),
("Gradation", "Gradation", "°Oe|°KMW", 40),
("Weight", "Gewicht", "kg", 20),
("Weight", "Menge", "kg", 20),
("IsNetWeight", "Gerebelt", null, 20),
("HkId", "Herkunft", null, 20),
("Modifiers", "Zu-/Abschläge", null, 40),

@ -18,8 +18,9 @@ namespace Elwig.Models.Dtos {
("Cultivation", "Bewirt.", null, 20),
("QualityLevel", "Qualitätsstufe", null, 30),
("Oe", "Gradation", "°Oe", 20),
("Ungeb", "ungebunden", "kg|€/kg", 40),
("Geb", "gebunden", "kg|€/kg", 40),
("Ungeb", "ungebunden", "kg|€/kg|€/kg", 60),
("LowGeb", "attributlos gebunden", "kg|€/kg|€/kg", 60),
("Geb", "gebunden", "kg|€/kg|€/kg", 60),
("Amount", "Gesamt", "€", 25),
];
@ -30,8 +31,9 @@ namespace Elwig.Models.Dtos {
string? Cultivation,
string QualityLevel,
double Oe,
(int Weight, decimal? Price) Ungeb,
(int Weight, decimal? Price) Geb,
(int Weight, decimal? MinPrice, decimal? MaxPrice) Ungeb,
(int Weight, decimal? MinPrice, decimal? MaxPrice) LowGeb,
(int Weight, decimal? MinPrice, decimal? MaxPrice) Geb,
decimal Amount
);
@ -42,8 +44,9 @@ namespace Elwig.Models.Dtos {
public static async Task<PaymentVariantSummaryData> ForPaymentVariant(PaymentVar v, DbSet<PaymentVariantSummaryRow> table) {
return new(v, (await FromDbSet(table, v.Year, v.AvNr))
.Select(r => new PaymentRow(r.Type, r.Variety, r.Attribute, r.Cultivation, r.QualityLevel, r.Oe,
(r.WeightUngeb, r.PriceUngeb != null ? Utils.DecFromDb(r.PriceUngeb.Value, v.Season.Precision) : null),
(r.WeightGeb, r.PriceGeb != null ? Utils.DecFromDb(r.PriceGeb.Value, v.Season.Precision) : null),
(r.WeightUngeb, r.MinPriceUngeb != null ? Utils.DecFromDb(r.MinPriceUngeb.Value, v.Season.Precision) : null, r.MaxPriceUngeb != null ? Utils.DecFromDb(r.MaxPriceUngeb.Value, v.Season.Precision) : null),
(r.WeightLowGeb, r.MinPriceLowGeb != null ? Utils.DecFromDb(r.MinPriceLowGeb.Value, v.Season.Precision) : null, r.MaxPriceLowGeb != null ? Utils.DecFromDb(r.MaxPriceLowGeb.Value, v.Season.Precision) : null),
(r.WeightGeb, r.MinPriceGeb != null ? Utils.DecFromDb(r.MinPriceGeb.Value, v.Season.Precision) : null, r.MaxPriceGeb != null ? Utils.DecFromDb(r.MaxPriceGeb.Value, v.Season.Precision) : null),
Utils.DecFromDb(r.Amount, v.Season.Precision)))
.ToArray());
}
@ -57,19 +60,23 @@ namespace Elwig.Models.Dtos {
q.name AS quality_level,
ROUND(kmw * (4.54 + 0.022 * kmw)) AS oe,
SUM(IIF(w.discr = '_', w.value, 0)) AS weight_ungeb,
MAX(IIF(w.discr = '_', b.price, NULL)) AS price_ungeb,
SUM(IIF(w.discr != '_', w.value, 0)) AS weight_geb,
MAX(IIF(w.discr != '_', b.price, NULL)) AS price_geb,
MIN(IIF(w.discr = '_', b.price, NULL)) AS min_price_ungeb,
MAX(IIF(w.discr = '_', b.price, NULL)) AS max_price_ungeb,
SUM(IIF(w.discr NOT IN (COALESCE(p.attrid, ''), '_'), w.value, 0)) AS weight_lowgeb,
MIN(IIF(w.discr NOT IN (COALESCE(p.attrid, ''), '_'), b.price, NULL)) AS min_price_lowgeb,
MAX(IIF(w.discr NOT IN (COALESCE(p.attrid, ''), '_'), b.price, NULL)) AS max_price_lowgeb,
SUM(IIF(w.discr = COALESCE(p.attrid, ''), w.value, 0)) AS weight_geb,
MIN(IIF(w.discr = COALESCE(p.attrid, ''), b.price, NULL)) AS min_price_geb,
MAX(IIF(w.discr = COALESCE(p.attrid, ''), b.price, NULL)) AS max_price_geb,
SUM(b.amount) AS amount
FROM payment_delivery_part_bucket b
LEFT JOIN delivery_part_bucket w ON (w.year, w.did, w.dpnr, w.bktnr) = (b.year, b.did, b.dpnr, b.bktnr)
LEFT JOIN delivery_part p ON (p.year, p.did, p.dpnr) = (b.year, b.did, b.dpnr)
LEFT JOIN delivery d ON (d.year, d.did) = (p.year, p.did)
LEFT JOIN wine_variety v ON v.sortid = p.sortid
LEFT JOIN wine_attribute a ON a.attrid = p.attrid
LEFT JOIN wine_cultivation c ON c.cultid = p.cultid
LEFT JOIN wine_quality_level q ON q.qualid = p.qualid
WHERE d.year = {year} AND b.avnr = {avnr}
WHERE p.year = {year} AND b.avnr = {avnr}
GROUP BY variety, attribute, cultivation, q.min_kmw, oe
ORDER BY variety, attribute, cultivation, q.min_kmw, oe
""").ToListAsync();
@ -92,12 +99,22 @@ namespace Elwig.Models.Dtos {
public double Oe { get; set; }
[Column("weight_ungeb")]
public int WeightUngeb { get; set; }
[Column("price_ungeb")]
public long? PriceUngeb { get; set; }
[Column("min_price_ungeb")]
public long? MinPriceUngeb { get; set; }
[Column("max_price_ungeb")]
public long? MaxPriceUngeb { get; set; }
[Column("weight_lowgeb")]
public int WeightLowGeb { get; set; }
[Column("min_price_lowgeb")]
public long? MinPriceLowGeb { get; set; }
[Column("max_price_lowgeb")]
public long? MaxPriceLowGeb { get; set; }
[Column("weight_geb")]
public int WeightGeb { get; set; }
[Column("price_geb")]
public long? PriceGeb { get; set; }
[Column("min_price_geb")]
public long? MinPriceGeb { get; set; }
[Column("max_price_geb")]
public long? MaxPriceGeb { get; set; }
[Column("amount")]
public long Amount { get; set; }
}

@ -14,7 +14,7 @@ namespace Elwig.Models.Dtos {
("CultId", "Bewirt.", null, 15),
("QualId", "Qual.", null, 15),
("Geb", "gebunden", null, 20),
("Weight", "Gewicht", "kg", 20),
("Weight", "Menge", "kg", 20),
];
public WeightBreakdownData(IEnumerable<WeightBreakdownRow> rows, int year, string name) :

@ -17,7 +17,7 @@ namespace Elwig.Models.Dtos {
("Members", "Mitgl.", "#", 15),
("Deliveries", "Lfrg.", "#", 15),
("Parts", "Teill.", "#", 15),
("Weight", "Gewicht", "kg", 20),
("Weight", "Menge", "kg", 20),
("Gradation", "Gradation", "°Oe|°KMW", 30),
];

@ -41,14 +41,36 @@ namespace Elwig.Models.Entities {
public string? Comment { get; set; }
[Column("ctime"), DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public long CTime { get; private set; }
public long CTime { get; set; }
[NotMapped]
public DateTime CreatedTimestamp => DateTimeOffset.FromUnixTimeSeconds(CTime).LocalDateTime;
public DateTime CreatedAt {
get => DateTimeOffset.FromUnixTimeSeconds(CTime).LocalDateTime;
set => CTime = ((DateTimeOffset)value.ToUniversalTime()).ToUnixTimeSeconds();
}
[Column("mtime"), DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public long MTime { get; private set; }
public long MTime { get; set; }
[NotMapped]
public DateTime ModifiedTimestamp => DateTimeOffset.FromUnixTimeSeconds(MTime).LocalDateTime;
public DateTime ModifiedAt {
get => DateTimeOffset.FromUnixTimeSeconds(MTime).LocalDateTime;
set => MTime = ((DateTimeOffset)value.ToUniversalTime()).ToUnixTimeSeconds();
}
[Column("xtime")]
public long? XTime { get; set; }
[NotMapped]
public DateTime? ExportedAt {
get => XTime == null ? null : DateTimeOffset.FromUnixTimeSeconds(XTime.Value).LocalDateTime;
set => XTime = value == null ? null : ((DateTimeOffset)value.Value.ToUniversalTime()).ToUnixTimeSeconds();
}
[Column("itime")]
public long? ITime { get; set; }
[NotMapped]
public DateTime? ImportedAt {
get => ITime == null ? null : DateTimeOffset.FromUnixTimeSeconds(ITime.Value).LocalDateTime;
set => ITime = value == null ? null : ((DateTimeOffset)value.Value.ToUniversalTime()).ToUnixTimeSeconds();
}
[ForeignKey("MgNr")]
public virtual Member Member { get; private set; } = null!;

@ -84,14 +84,36 @@ namespace Elwig.Models.Entities {
}
[Column("ctime"), DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public long CTime { get; private set; }
public long CTime { get; set; }
[NotMapped]
public DateTime CreatedTimestamp => DateTimeOffset.FromUnixTimeSeconds(CTime).LocalDateTime;
public DateTime CreatedAt {
get => DateTimeOffset.FromUnixTimeSeconds(CTime).LocalDateTime;
set => CTime = ((DateTimeOffset)value.ToUniversalTime()).ToUnixTimeSeconds();
}
[Column("mtime"), DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public long MTime { get; private set; }
public long MTime { get; set; }
[NotMapped]
public DateTime ModifiedTimestamp => DateTimeOffset.FromUnixTimeSeconds(MTime).LocalDateTime;
public DateTime ModifiedAt {
get => DateTimeOffset.FromUnixTimeSeconds(MTime).LocalDateTime;
set => MTime = ((DateTimeOffset)value.ToUniversalTime()).ToUnixTimeSeconds();
}
[Column("xtime")]
public long? XTime { get; set; }
[NotMapped]
public DateTime? ExportedAt {
get => XTime == null ? null : DateTimeOffset.FromUnixTimeSeconds(XTime.Value).LocalDateTime;
set => XTime = value == null ? null : ((DateTimeOffset)value.Value.ToUniversalTime()).ToUnixTimeSeconds();
}
[Column("itime")]
public long? ITime { get; set; }
[NotMapped]
public DateTime? ImportedAt {
get => ITime == null ? null : DateTimeOffset.FromUnixTimeSeconds(ITime.Value).LocalDateTime;
set => ITime = value == null ? null : ((DateTimeOffset)value.Value.ToUniversalTime()).ToUnixTimeSeconds();
}
[ForeignKey("Year, AvNr, MgNr")]
public virtual PaymentMember Payment { get; private set; } = null!;

@ -62,14 +62,36 @@ namespace Elwig.Models.Entities {
public string? Comment { get; set; }
[Column("ctime"), DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public long CTime { get; private set; }
public long CTime { get; set; }
[NotMapped]
public DateTime CreatedTimestamp => DateTimeOffset.FromUnixTimeSeconds(CTime).LocalDateTime;
public DateTime CreatedAt {
get => DateTimeOffset.FromUnixTimeSeconds(CTime).LocalDateTime;
set => CTime = ((DateTimeOffset)value.ToUniversalTime()).ToUnixTimeSeconds();
}
[Column("mtime"), DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public long MTime { get; private set; }
public long MTime { get; set; }
[NotMapped]
public DateTime ModifiedTimestamp => DateTimeOffset.FromUnixTimeSeconds(MTime).LocalDateTime;
public DateTime ModifiedAt {
get => DateTimeOffset.FromUnixTimeSeconds(MTime).LocalDateTime;
set => MTime = ((DateTimeOffset)value.ToUniversalTime()).ToUnixTimeSeconds();
}
[Column("xtime")]
public long? XTime { get; set; }
[NotMapped]
public DateTime? ExportedAt {
get => XTime == null ? null : DateTimeOffset.FromUnixTimeSeconds(XTime.Value).LocalDateTime;
set => XTime = value == null ? null : ((DateTimeOffset)value.Value.ToUniversalTime()).ToUnixTimeSeconds();
}
[Column("itime")]
public long? ITime { get; set; }
[NotMapped]
public DateTime? ImportedAt {
get => ITime == null ? null : DateTimeOffset.FromUnixTimeSeconds(ITime.Value).LocalDateTime;
set => ITime = value == null ? null : ((DateTimeOffset)value.Value.ToUniversalTime()).ToUnixTimeSeconds();
}
[ForeignKey("Year")]
public virtual Season Season { get; private set; } = null!;

@ -27,14 +27,36 @@ namespace Elwig.Models.Entities {
public required string Type { get; set; }
[Column("ctime"), DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public long CTime { get; private set; }
public long CTime { get; set; }
[NotMapped]
public DateTime CreatedTimestamp => DateTimeOffset.FromUnixTimeSeconds(CTime).LocalDateTime;
public DateTime CreatedAt {
get => DateTimeOffset.FromUnixTimeSeconds(CTime).LocalDateTime;
set => CTime = ((DateTimeOffset)value.ToUniversalTime()).ToUnixTimeSeconds();
}
[Column("mtime"), DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public long MTime { get; private set; }
public long MTime { get; set; }
[NotMapped]
public DateTime ModifiedTimestamp => DateTimeOffset.FromUnixTimeSeconds(MTime).LocalDateTime;
public DateTime ModifiedAt {
get => DateTimeOffset.FromUnixTimeSeconds(MTime).LocalDateTime;
set => MTime = ((DateTimeOffset)value.ToUniversalTime()).ToUnixTimeSeconds();
}
[Column("xtime")]
public long? XTime { get; set; }
[NotMapped]
public DateTime? ExportedAt {
get => XTime == null ? null : DateTimeOffset.FromUnixTimeSeconds(XTime.Value).LocalDateTime;
set => XTime = value == null ? null : ((DateTimeOffset)value.Value.ToUniversalTime()).ToUnixTimeSeconds();
}
[Column("itime")]
public long? ITime { get; set; }
[NotMapped]
public DateTime? ImportedAt {
get => ITime == null ? null : DateTimeOffset.FromUnixTimeSeconds(ITime.Value).LocalDateTime;
set => ITime = value == null ? null : ((DateTimeOffset)value.Value.ToUniversalTime()).ToUnixTimeSeconds();
}
[ForeignKey("Year, DsNr")]
public virtual DeliverySchedule Schedule { get; private set; } = null!;

@ -129,14 +129,36 @@ namespace Elwig.Models.Entities {
public string? Comment { get; set; }
[Column("ctime"), DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public long CTime { get; private set; }
public long CTime { get; set; }
[NotMapped]
public DateTime CreatedTimestamp => DateTimeOffset.FromUnixTimeSeconds(CTime).LocalDateTime;
public DateTime CreatedAt {
get => DateTimeOffset.FromUnixTimeSeconds(CTime).LocalDateTime;
set => CTime = ((DateTimeOffset)value.ToUniversalTime()).ToUnixTimeSeconds();
}
[Column("mtime"), DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public long MTime { get; private set; }
public long MTime { get; set; }
[NotMapped]
public DateTime ModifiedTimestamp => DateTimeOffset.FromUnixTimeSeconds(MTime).LocalDateTime;
public DateTime ModifiedAt {
get => DateTimeOffset.FromUnixTimeSeconds(MTime).LocalDateTime;
set => MTime = ((DateTimeOffset)value.ToUniversalTime()).ToUnixTimeSeconds();
}
[Column("xtime")]
public long? XTime { get; set; }
[NotMapped]
public DateTime? ExportedAt {
get => XTime == null ? null : DateTimeOffset.FromUnixTimeSeconds(XTime.Value).LocalDateTime;
set => XTime = value == null ? null : ((DateTimeOffset)value.Value.ToUniversalTime()).ToUnixTimeSeconds();
}
[Column("itime")]
public long? ITime { get; set; }
[NotMapped]
public DateTime? ImportedAt {
get => ITime == null ? null : DateTimeOffset.FromUnixTimeSeconds(ITime.Value).LocalDateTime;
set => ITime = value == null ? null : ((DateTimeOffset)value.Value.ToUniversalTime()).ToUnixTimeSeconds();
}
[InverseProperty(nameof(DeliveryPartModifier.Part))]
public virtual ICollection<DeliveryPartModifier> PartModifiers { get; private set; } = null!;

@ -145,14 +145,36 @@ namespace Elwig.Models.Entities {
public AT_Kg? DefaultKg => DefaultWbKg?.AtKg;
[Column("ctime"), DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public long CTime { get; private set; }
public long CTime { get; set; }
[NotMapped]
public DateTime CreatedTimestamp => DateTimeOffset.FromUnixTimeSeconds(CTime).LocalDateTime;
public DateTime CreatedAt {
get => DateTimeOffset.FromUnixTimeSeconds(CTime).LocalDateTime;
set => CTime = ((DateTimeOffset)value.ToUniversalTime()).ToUnixTimeSeconds();
}
[Column("mtime"), DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public long MTime { get; private set; }
public long MTime { get; set; }
[NotMapped]
public DateTime ModifiedTimestamp => DateTimeOffset.FromUnixTimeSeconds(MTime).LocalDateTime;
public DateTime ModifiedAt {
get => DateTimeOffset.FromUnixTimeSeconds(MTime).LocalDateTime;
set => MTime = ((DateTimeOffset)value.ToUniversalTime()).ToUnixTimeSeconds();
}
[Column("xtime")]
public long? XTime { get; set; }
[NotMapped]
public DateTime? ExportedAt {
get => XTime == null ? null : DateTimeOffset.FromUnixTimeSeconds(XTime.Value).LocalDateTime;
set => XTime = value == null ? null : ((DateTimeOffset)value.Value.ToUniversalTime()).ToUnixTimeSeconds();
}
[Column("itime")]
public long? ITime { get; set; }
[NotMapped]
public DateTime? ImportedAt {
get => ITime == null ? null : DateTimeOffset.FromUnixTimeSeconds(ITime.Value).LocalDateTime;
set => ITime = value == null ? null : ((DateTimeOffset)value.Value.ToUniversalTime()).ToUnixTimeSeconds();
}
[ForeignKey("ZwstId")]
public virtual Branch? Branch { get; private set; }

@ -46,14 +46,36 @@ namespace Elwig.Models.Entities {
public required string Data { get; set; }
[Column("ctime"), DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public long CTime { get; private set; }
public long CTime { get; set; }
[NotMapped]
public DateTime CreatedTimestamp => DateTimeOffset.FromUnixTimeSeconds(CTime).LocalDateTime;
public DateTime CreatedAt {
get => DateTimeOffset.FromUnixTimeSeconds(CTime).LocalDateTime;
set => CTime = ((DateTimeOffset)value.ToUniversalTime()).ToUnixTimeSeconds();
}
[Column("mtime"), DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public long MTime { get; private set; }
public long MTime { get; set; }
[NotMapped]
public DateTime ModifiedTimestamp => DateTimeOffset.FromUnixTimeSeconds(MTime).LocalDateTime;
public DateTime ModifiedAt {
get => DateTimeOffset.FromUnixTimeSeconds(MTime).LocalDateTime;
set => MTime = ((DateTimeOffset)value.ToUniversalTime()).ToUnixTimeSeconds();
}
[Column("xtime")]
public long? XTime { get; set; }
[NotMapped]
public DateTime? ExportedAt {
get => XTime == null ? null : DateTimeOffset.FromUnixTimeSeconds(XTime.Value).LocalDateTime;
set => XTime = value == null ? null : ((DateTimeOffset)value.Value.ToUniversalTime()).ToUnixTimeSeconds();
}
[Column("itime")]
public long? ITime { get; set; }
[NotMapped]
public DateTime? ImportedAt {
get => ITime == null ? null : DateTimeOffset.FromUnixTimeSeconds(ITime.Value).LocalDateTime;
set => ITime = value == null ? null : ((DateTimeOffset)value.Value.ToUniversalTime()).ToUnixTimeSeconds();
}
[ForeignKey("Year")]
public virtual Season Season { get; private set; } = null!;

@ -0,0 +1,33 @@
-- schema version 31 to 32
INSERT INTO client_parameter (param, value) VALUES ('ENABLE_TIME_TRIGGERS', '1');
ALTER TABLE member ADD COLUMN xtime INTEGER DEFAULT NULL;
ALTER TABLE member ADD COLUMN itime INTEGER DEFAULT NULL;
ALTER TABLE area_commitment ADD COLUMN xtime INTEGER DEFAULT NULL;
ALTER TABLE area_commitment ADD COLUMN itime INTEGER DEFAULT NULL;
ALTER TABLE delivery_announcement ADD COLUMN xtime INTEGER DEFAULT NULL;
ALTER TABLE delivery_announcement ADD COLUMN itime INTEGER DEFAULT NULL;
ALTER TABLE delivery ADD COLUMN xtime INTEGER DEFAULT NULL;
ALTER TABLE delivery ADD COLUMN itime INTEGER DEFAULT NULL;
ALTER TABLE delivery_part ADD COLUMN xtime INTEGER DEFAULT NULL;
ALTER TABLE delivery_part ADD COLUMN itime INTEGER DEFAULT NULL;
ALTER TABLE payment_variant ADD COLUMN xtime INTEGER DEFAULT NULL;
ALTER TABLE payment_variant ADD COLUMN itime INTEGER DEFAULT NULL;
ALTER TABLE credit ADD COLUMN xtime INTEGER DEFAULT NULL;
ALTER TABLE credit ADD COLUMN itime INTEGER DEFAULT NULL;
PRAGMA writable_schema = ON;
UPDATE sqlite_schema SET sql = REPLACE(REPLACE(sql,
' WHEN',
' WHEN (SELECT value FROM client_parameter WHERE param = ''ENABLE_TIME_TRIGGERS'') = 1 AND'),
'FOR EACH ROW' || char(10) ||
'BEGIN',
'FOR EACH ROW' || char(10) ||
' WHEN (SELECT value FROM client_parameter WHERE param = ''ENABLE_TIME_TRIGGERS'') = 1' || char(10) ||
'BEGIN')
WHERE type = 'trigger' AND name LIKE '%time%';
PRAGMA writable_schema = OFF;
PRAGMA schema_version = 3101;

@ -37,8 +37,8 @@ namespace Elwig.Services {
vm.DeliverySchedule = (DeliverySchedule?)ControlUtils.GetItemFromSourceWithPk(vm.DeliveryScheduleSource, a.Year, a.DsNr);
vm.SortId = a.SortId;
vm.Weight = a.Weight;
vm.StatusAncmtCreated = $"{a.CreatedTimestamp:dd.MM.yyyy, HH:mm} ({a.Type})";
vm.StatusAncmtModified = a.ModifiedTimestamp != a.CreatedTimestamp ? $"{a.ModifiedTimestamp:dd.MM.yyyy, HH:mm}" : "-";
vm.StatusAncmtCreated = $"{a.CreatedAt:dd.MM.yyyy, HH:mm} ({a.Type})";
vm.StatusAncmtModified = a.ModifiedAt != a.CreatedAt ? $"{a.ModifiedAt:dd.MM.yyyy, HH:mm}" : "-";
}
public static async Task<(List<string>, IQueryable<DeliveryAncmt>, List<string>)> GetFilters(this DeliveryAncmtAdminViewModel vm, AppDbContext ctx) {
@ -326,7 +326,7 @@ namespace Elwig.Services {
var weight = await deliveryAncmts.SumAsync(p => p.Weight);
text = $"{weight:N0} kg";
AddToolTipRow(grid, 0, "Gewicht", null, weight, null, weight);
AddToolTipRow(grid, 0, "Menge", null, weight, null, weight);
if (await deliveryAncmts.AnyAsync()) {
var attrGroups = await deliveryAncmts

@ -946,7 +946,7 @@ namespace Elwig.Services {
var weight = await deliveryParts.SumAsync(p => p.Weight);
wText = $"{weight:N0} kg";
wGrid.Add(("Gewicht", null, weight, null, weight));
wGrid.Add(("Menge", null, weight, null, weight));
if (await deliveryParts.AnyAsync()) {
var kmwMin = await deliveryParts.MinAsync(p => p.Kmw);

@ -1,20 +1,20 @@
using Elwig.Helpers;
using Elwig.Models.Entities;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System;
using Microsoft.EntityFrameworkCore;
using Elwig.Documents;
using System.Windows.Input;
using System.Windows;
using Elwig.Helpers;
using Elwig.Helpers.Billing;
using Elwig.Models.Dtos;
using Elwig.Helpers.Export;
using Microsoft.Win32;
using Elwig.Models.Dtos;
using Elwig.Models.Entities;
using Elwig.ViewModels;
using Microsoft.EntityFrameworkCore;
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
namespace Elwig.Services {
public static class MemberService {
@ -49,6 +49,11 @@ namespace Elwig.Services {
vm.StatusAreaCommitmentInfo = $"{Utils.CurrentLastSeason}";
vm.StatusAreaCommitmentToolTip = null;
vm.Age = "-";
vm.CreatedAt = "-";
vm.ModifiedAt = "-";
vm.ModifiedAtShort = "-";
vm.ExportedAt = "-";
vm.ImportedAt = "-";
}
public static void FillInputs(this MemberAdminViewModel vm, Member m) {
@ -137,6 +142,12 @@ namespace Elwig.Services {
vm.ContactViaPost = m.ContactViaPost;
vm.ContactViaEmail = m.ContactViaEmail;
vm.CreatedAt = $"{m.CreatedAt:dd.MM.yyyy, HH:mm:ss}";
vm.ModifiedAt = $"{m.ModifiedAt:dd.MM.yyyy, HH:mm:ss}";
vm.ModifiedAtShort = $"{m.ModifiedAt:dd.MM.yyyy, HH:mm}";
vm.ExportedAt = m.ExportedAt == null ? "-" : $"{m.ExportedAt:dd.MM.yyyy, HH:mm:ss}";
vm.ImportedAt = m.ImportedAt == null ? "-" : $"{m.ImportedAt:dd.MM.yyyy, HH:mm:ss}";
vm.StatusDeliveriesLastSeasonInfo = $"{Utils.CurrentLastSeason - 1}";
vm.StatusDeliveriesLastSeason = "...";
vm.StatusDeliveriesLastSeasonToolTip = null;
@ -401,7 +412,7 @@ namespace Elwig.Services {
try {
using var ctx = new AppDbContext();
var v = (await ctx.PaymentVariants.FindAsync(year, avnr))!;
var data = await CreditNoteDeliveryData.ForPaymentVariant(ctx.CreditNoteDeliveryRows, ctx.Seasons, year, avnr);
var data = await CreditNoteDeliveryData.ForPaymentVariant(ctx.CreditNoteDeliveryRows, ctx.PaymentVariants, year, avnr);
var p = (await ctx.MemberPayments.FindAsync(year, avnr, m.MgNr))!;
var b = BillingData.FromJson((await ctx.PaymentVariants.FindAsync(year, avnr))!.Data);

@ -153,6 +153,17 @@ namespace Elwig.ViewModels {
[ObservableProperty]
private bool _contactViaEmail;
[ObservableProperty]
private string _createdAt = "-";
[ObservableProperty]
private string _modifiedAt = "-";
[ObservableProperty]
private string _modifiedAtShort = "-";
[ObservableProperty]
private string _exportedAt = "-";
[ObservableProperty]
private string _importedAt = "-";
public ObservableCollection<string?> EmailAddresses { get; private set; } = [null, null, null, null, null, null, null, null, null];
public partial class PhoneNr(int? type = null, string? number = null, string? comment = null) : ObservableObject {

@ -217,7 +217,7 @@
<Bold>Zweigstelle</Bold>: z.B. musterort, ...<LineBreak/>
<Bold>Attribut</Bold>: z.B. kabinett, !kabinett (alle außer kabinett), ...<LineBreak/>
<Bold>Bewirtschaftung</Bold>: z.B. bio, !kip (alle außer KIP), ...<LineBreak/>
<Bold>Gewicht</Bold>: z.B. &lt;500kg, &gt;6000kg, ... (gilt für Gewicht der gesamten Lieferung)<LineBreak/>
<Bold>Gewicht</Bold>: z.B. &lt;500kg, &gt;6000kg, ... (gilt für Menge der gesamten Lieferung)<LineBreak/>
<Bold>Datum</Bold>: z.B. 1.9., 15.9.-10.10., -15.10.2020, ...<LineBreak/>
<Bold>Uhrzeit</Bold>: z.B. 06:00-08:00, 18:00-, ...<LineBreak/>
<Bold>Handwiegung</Bold>: handw[iegung], !Handw[iegung] (alle ohne Handwiegung)<LineBreak/>
@ -272,7 +272,7 @@
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
<DataGridTextColumn Header="Gewicht" Binding="{Binding FilteredWeight, StringFormat='{}{0:N0} kg '}" Width="75">
<DataGridTextColumn Header="Menge" Binding="{Binding FilteredWeight, StringFormat='{}{0:N0} kg '}" Width="75">
<DataGridTextColumn.CellStyle>
<Style>
<Setter Property="TextBlock.TextAlignment" Value="Right"/>
@ -516,7 +516,7 @@
</Grid>
</GroupBox>
<GroupBox Header="Gewicht" Grid.Column="1" Grid.Row="2" Margin="5,5,5,5">
<GroupBox Header="Menge" Grid.Column="1" Grid.Row="2" Margin="5,5,5,5">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="70"/>
@ -669,7 +669,7 @@
<Separator Grid.Column="5"/>
<StatusBarItem Grid.Column="6">
<TextBlock ToolTip="{Binding StatusWeightToolTip}">
Gewicht: <Run Text="{Binding StatusWeight}"/>
Menge: <Run Text="{Binding StatusWeight}"/>
</TextBlock>
</StatusBarItem>
<Separator Grid.Column="7"/>

@ -125,7 +125,7 @@
<Bold>Zweigstelle</Bold>: z.B. musterort, ...<LineBreak/>
<Bold>Attribut</Bold>: z.B. kabinett, !kabinett (alle außer kabinett), ...<LineBreak/>
<Bold>Bewirtschaftung</Bold>: z.B. bio, !kip (alle außer KIP), ...<LineBreak/>
<Bold>Gewicht</Bold>: z.B. &lt;500kg, &gt;6000kg, ...<LineBreak/>
<Bold>Menge</Bold>: z.B. &lt;500kg, &gt;6000kg, ...<LineBreak/>
<Bold>Datum</Bold>: z.B. 1.9., 15.9.-10.10., -15.10.2020, ...
</TextBlock>
</TextBox.ToolTip>
@ -191,7 +191,7 @@
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
<DataGridTextColumn Header="Gewicht" Binding="{Binding Weight, StringFormat='{}{0:N0} kg'}" Width="75">
<DataGridTextColumn Header="Menge" Binding="{Binding Weight, StringFormat='{}{0:N0} kg'}" Width="75">
<DataGridTextColumn.CellStyle>
<Style>
<Setter Property="TextBlock.TextAlignment" Value="Right"/>
@ -328,7 +328,7 @@
</ComboBox.ItemTemplateSelector>
</ComboBox>
<Label Content="Gewicht:" Margin="10,70,0,0" Grid.Column="0"/>
<Label Content="Menge:" Margin="10,70,0,0" Grid.Column="0"/>
<ctrl:UnitTextBox x:Name="WeightInput" Unit="kg" Text="{Binding WeightString, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Grid.Column="1" Margin="0,70,10,10" Width="61" HorizontalAlignment="Left"
TextChanged="WeightInput_TextChanged" KeyUp="Input_KeyUp"/>
@ -364,7 +364,7 @@
<Separator Grid.Column="1"/>
<StatusBarItem Grid.Column="2">
<TextBlock ToolTip="{Binding StatusWeightToolTip}">
Gewicht: <Run Text="{Binding StatusWeight}"/>
Menge: <Run Text="{Binding StatusWeight}"/>
</TextBlock>
</StatusBarItem>
<Separator Grid.Column="3"/>

@ -127,7 +127,7 @@
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
<DataGridTextColumn Header="Beschreibung" Binding="{Binding Description}" Width="200"/>
<DataGridTextColumn Header="Max. Gew." Binding="{Binding MaxWeight, StringFormat='{}{0:N0} kg'}" Width="80">
<DataGridTextColumn Header="Max. Mg." Binding="{Binding MaxWeight, StringFormat='{}{0:N0} kg'}" Width="80">
<DataGridTextColumn.CellStyle>
<Style>
<Setter Property="TextBlock.TextAlignment" Value="Right"/>
@ -211,7 +211,7 @@
Margin="0,40,10,10" Grid.Column="1" Grid.ColumnSpan="2"
TextChanged="TextBox_TextChanged"/>
<Label Content="Max. Gewicht:" Margin="10,70,0,10"/>
<Label Content="Max. Menge:" Margin="10,70,0,10"/>
<ctrl:UnitTextBox x:Name="MaxWeightInput" Unit="kg" Text="{Binding MaxWeightString, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Margin="0,70,10,10" Grid.Column="1" Grid.ColumnSpan="2" Width="68" HorizontalAlignment="Left"
TextChanged="MaxWeightInput_TextChanged"/>

@ -671,7 +671,7 @@ namespace Elwig.Windows {
var avnr = details.Item2;
try {
cnData[(year, avnr)] = (
await CreditNoteDeliveryData.ForPaymentVariant(ctx.CreditNoteDeliveryRows, ctx.Seasons, year, avnr),
await CreditNoteDeliveryData.ForPaymentVariant(ctx.CreditNoteDeliveryRows, ctx.PaymentVariants, year, avnr),
await ctx.MemberPayments.Where(p => p.Year == year && p.AvNr == avnr).ToDictionaryAsync(c => c.MgNr),
BillingData.FromJson((await ctx.PaymentVariants.FindAsync(year, avnr))!.Data)
);

@ -207,7 +207,7 @@
<Button x:Name="BreakdownButton"
Click="BreakdownButton_Click"
Margin="195,90,0,10" Width="190" Padding="3,5,5,5"
ToolTip="Aufschlüsselung des Gewichts nach Zweigstelle, Mitglied, Sorte, Attribut/Bewirt., Qualitätsstufe, gebunden/ungebunden">
ToolTip="Aufschlüsselung der Menge nach Zweigstelle, Mitglied, Sorte, Attribut/Bewirt., Qualitätsstufe, gebunden/ungebunden">
<Grid>
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xE9F9;"
TextAlignment="Left" HorizontalAlignment="Left" Padding="6.5,0.5,0,0"/>
@ -252,7 +252,7 @@
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="1" TextAlignment="Right"><Bold>Mitglieder</Bold></TextBlock>
<TextBlock Grid.Row="0" Grid.Column="2" TextAlignment="Right"><Bold>Gewicht</Bold></TextBlock>
<TextBlock Grid.Row="0" Grid.Column="2" TextAlignment="Right"><Bold>Menge</Bold></TextBlock>
<TextBlock Grid.Row="0" Grid.Column="3" TextAlignment="Right"><Bold>Fläche</Bold></TextBlock>
<TextBlock Grid.Row="1" Grid.Column="0">Gesamt:</TextBlock>

@ -212,7 +212,7 @@ namespace Elwig.Windows {
} catch (HttpRequestException exc) {
MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Lieferungen hochladen", MessageBoxButton.OK, MessageBoxImage.Error);
} catch (TaskCanceledException exc) {
MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Lieferungen hochladen", MessageBoxButton.OK, MessageBoxImage.Error);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Lieferungen hochladen", MessageBoxButton.OK, MessageBoxImage.Error);
}

@ -653,13 +653,40 @@
TextChanged="TextBox_TextChanged"
VerticalAlignment="Stretch" Height="auto" AcceptsReturn="True" TextWrapping="Wrap" VerticalScrollBarVisibility="Visible"/>
<Label Content="Kontaktart:" Margin="10,10,0,10" Grid.Column="0" VerticalAlignment="Bottom"/>
<Label Content="Kontaktart:" Margin="10,10,0,40" Grid.Column="0" VerticalAlignment="Bottom"/>
<CheckBox x:Name="ContactPostalInput" Content="Post" IsChecked="{Binding ContactViaPost, Mode=TwoWay}" IsEnabled="False"
Checked="CheckBox_Changed" Unchecked="CheckBox_Changed"
HorizontalAlignment="Left" Margin="0,0,0,15" VerticalAlignment="Bottom" Grid.Column="1" Grid.ColumnSpan="2"/>
HorizontalAlignment="Left" Margin="0,0,0,45" VerticalAlignment="Bottom" Grid.Column="1" Grid.ColumnSpan="2"/>
<CheckBox x:Name="ContactEmailInput" Content="E-Mail" IsChecked="{Binding ContactViaEmail, Mode=TwoWay}" IsEnabled="False"
Checked="ContactEmailInput_Changed" Unchecked="ContactEmailInput_Changed"
HorizontalAlignment="Left" Margin="60,0,0,15" VerticalAlignment="Bottom" Grid.Column="1" Grid.ColumnSpan="2"/>
HorizontalAlignment="Left" Margin="60,0,0,45" VerticalAlignment="Bottom" Grid.Column="1" Grid.ColumnSpan="2"/>
<Label Content="Zuletzt geändert:" Margin="10,10,0,10" Grid.Column="0" VerticalAlignment="Bottom"/>
<TextBlock x:Name="LastModified" Text="{Binding ModifiedAtShort}"
Margin="0,10,0,15" Grid.Column="1" TextWrapping="NoWrap" VerticalAlignment="Bottom">
<TextBlock.ToolTip>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/>
<ColumnDefinition Width="110"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="Erstellt:"/>
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding CreatedAt}"/>
<TextBlock Grid.Row="1" Grid.Column="0" Text="Zuletzt geändert:"/>
<TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding ModifiedAt}"/>
<TextBlock Grid.Row="2" Grid.Column="0" Text="Zuletzt von diesem Gerät exportiert:"/>
<TextBlock Grid.Row="2" Grid.Column="1" Text="{Binding ExportedAt}"/>
<TextBlock Grid.Row="3" Grid.Column="0" Text="Zuletzt auf dieses Gerät importiert:"/>
<TextBlock Grid.Row="3" Grid.Column="1" Text="{Binding ImportedAt}"/>
</Grid>
</TextBlock.ToolTip>
</TextBlock>
<Button x:Name="DeliveryButton" Content="Lieferungen" IsEnabled="{Binding IsMemberSelected}"
Click="DeliveryButton_Click"

@ -6,7 +6,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Elwig.Windows"
xmlns:ctrl="clr-namespace:Elwig.Controls"
Title="Auszahlungsvarianten - Elwig" Height="480" Width="850" MinHeight="400" MinWidth="830">
Title="Auszahlungsvarianten - Elwig" Height="480" Width="850" MinHeight="400" MinWidth="850">
<Window.Resources>
<Style TargetType="Label">
<Setter Property="HorizontalAlignment" Value="Left"/>
@ -160,7 +160,7 @@
</TextBlock>
<Label Content="Berücksichtigen:" Margin="90,70,10,10" Grid.Column="1"/>
<CheckBox x:Name="ConsiderModifiersInput" Content="Zu-/Abschläge bei Lieferungen"
<CheckBox x:Name="ConsiderModifiersInput" Content="Zu-/Abschläge bei Lieferungen (inkl. Rebelzuschl.)"
Margin="110,95,10,10" Grid.Column="1" HorizontalAlignment="Left" VerticalAlignment="Top"
Checked="ConsiderModifiersInput_Changed" Unchecked="ConsiderModifiersInput_Changed"/>
<CheckBox x:Name="ConsiderPenaltiesInput" Content="Pönalen bei Unterlieferungen (FB)"

@ -1,5 +0,0 @@
::mkdir "C:\Program Files\Elwig"
mkdir "C:\ProgramData\Elwig\resources"
copy /b /y Documents\*.css "C:\ProgramData\Elwig\resources"
copy /b /y Documents\*.cshtml "C:\ProgramData\Elwig\resources"
::copy /b /y ..\Installer\Files\*.exe "C:\Program Files\Elwig\"

@ -1,15 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
<Fragment>
<!-- C:\Program Files (x86)\Elwig oder C:\Program Files\Elwig -->
<StandardDirectory Id="ProgramFiles64Folder">
<Directory Id="InstallFolder" Name="!(bind.Property.ProductName)" />
<Directory Id="InstallFolder" Name="!(bind.Property.ProductName)">
<Directory Id="InstallFolderResources" Name="resources">
<Directory Id="InstallFolderDocuments" Name="Documents" />
</Directory>
</Directory>
</StandardDirectory>
<!-- C:\ProgramData\Elwig -->
<StandardDirectory Id="CommonAppDataFolder">
<Directory Id="ConfigFolder" Name="!(bind.Property.ProductName)">
<Directory Id="ConfigFolderResources" Name="resources" />
</Directory>
<Directory Id="ConfigFolder" Name="!(bind.Property.ProductName)" />
</StandardDirectory>
<!-- C:\ProgramData\Microsoft\Windows\Start Menu\Programs -->
@ -17,7 +20,7 @@
<Directory Id="StartMenuProgramsFolder" Name="!(bind.Property.ProductName)" />
</StandardDirectory>
<!-- C:\Users{USERNAME}\Desktop -->
<!-- C:\Users\{USERNAME}\Desktop -->
<StandardDirectory Id="DesktopFolder" />
</Fragment>
</Wix>

@ -6,7 +6,7 @@
<Exclude Files="..\Elwig\bin\Publish\**.exe" />
<Exclude Files="..\Elwig\bin\Publish\**.pdb" />
</Files>
<Files Directory="ConfigFolderResources" Include="..\Elwig\Documents\**">
<Files Directory="InstallFolderDocuments" Include="..\Elwig\Documents\**">
<Exclude Files="..\Elwig\Documents\**.cs" />
</Files>
</ComponentGroup>

@ -1,28 +0,0 @@
using Elwig.Documents;
using Elwig.Helpers;
using Elwig.Models.Dtos;
namespace Tests.DocumentTests {
[TestFixture]
public class PaymentVariantSummaryTest {
[Test]
public async Task Test_01_PaymentVariant2020() {
using var ctx = new AppDbContext();
var v = (await ctx.PaymentVariants.FindAsync(2020, 1))!;
var data = await PaymentVariantSummaryData.ForPaymentVariant(v, ctx.PaymentVariantSummaryRows);
using var doc = new PaymentVariantSummary(v, data);
var text = await Utils.GeneratePdfText(doc);
Assert.Multiple(() => {
Assert.That(text, Contains.Substring("Auszahlungsvariante"));
Assert.That(text, Contains.Substring(v.Name));
Assert.That(text, Contains.Substring("""
Gradation ungebunden gebunden Gesamt
[°Oe] [kg] [/kg] [kg] [/kg] []
Grüner Veltliner 3 219 0 1 609,50
Qualitätswein 73 3 219 0,5000 - - 1 609,50
"""));
});
}
}
}

@ -1,6 +1,6 @@
using OpenQA.Selenium;
using OpenQA.Selenium.Appium.Windows;
using Tests.WeighingTests;
using Tests.UnitTests.WeighingTests;
namespace Tests.E2ETests {
[TestFixture, Order(2)]

@ -3,7 +3,7 @@ using Elwig.Helpers;
using Elwig.Models.Dtos;
using Microsoft.EntityFrameworkCore;
namespace Tests.DocumentTests {
namespace Tests.UnitTests.DocumentTests {
[TestFixture]
public class CreditNoteTest {
@ -12,7 +12,7 @@ namespace Tests.DocumentTests {
using var ctx = new AppDbContext();
var m = await ctx.Members.FindAsync(101);
var p = await ctx.MemberPayments.Where(p => p.Year == 2020 && p.AvNr == 1).SingleAsync();
var data = await CreditNoteDeliveryData.ForPaymentVariant(ctx.CreditNoteDeliveryRows, ctx.Seasons, 2020, 1);
var data = await CreditNoteDeliveryData.ForPaymentVariant(ctx.CreditNoteDeliveryRows, ctx.PaymentVariants, 2020, 1);
using var doc = new CreditNote(ctx, p, data[m!.MgNr], false, false, false, false,
ctx.GetMemberUnderDelivery(2020, m!.MgNr).GetAwaiter().GetResult());
var text = await Utils.GeneratePdfText(doc);

@ -2,7 +2,7 @@ using Elwig.Documents;
using Elwig.Helpers;
using Elwig.Models.Dtos;
namespace Tests.DocumentTests {
namespace Tests.UnitTests.DocumentTests {
[TestFixture]
public class DeliveryAncmtListTest {

@ -2,7 +2,7 @@ using Elwig.Documents;
using Elwig.Helpers;
using Elwig.Models.Dtos;
namespace Tests.DocumentTests {
namespace Tests.UnitTests.DocumentTests {
[TestFixture]
public class DeliveryConfirmationTest {

@ -2,7 +2,7 @@ using Elwig.Documents;
using Elwig.Helpers;
using Elwig.Models.Dtos;
namespace Tests.DocumentTests {
namespace Tests.UnitTests.DocumentTests {
[TestFixture]
public class DeliveryDepreciationListTest {

@ -2,7 +2,7 @@ using Elwig.Documents;
using Elwig.Helpers;
using Elwig.Models.Dtos;
namespace Tests.DocumentTests {
namespace Tests.UnitTests.DocumentTests {
[TestFixture]
public class DeliveryJournalTest {

@ -1,7 +1,7 @@
using Elwig.Documents;
using Elwig.Helpers;
namespace Tests.DocumentTests {
namespace Tests.UnitTests.DocumentTests {
[TestFixture]
public class DeliveryNoteTest {

@ -1,7 +1,7 @@
using Elwig.Documents;
using Elwig.Helpers;
namespace Tests.DocumentTests {
namespace Tests.UnitTests.DocumentTests {
[TestFixture]
public class LetterheadTest {

@ -1,7 +1,7 @@
using Elwig.Documents;
using Elwig.Helpers;
namespace Tests.DocumentTests {
namespace Tests.UnitTests.DocumentTests {
[TestFixture]
public class MemberDataSheetTest {

@ -2,7 +2,7 @@ using Elwig.Documents;
using Elwig.Helpers;
using Elwig.Models.Dtos;
namespace Tests.DocumentTests {
namespace Tests.UnitTests.DocumentTests {
[TestFixture]
public class MemberListTest {

@ -0,0 +1,29 @@
using Elwig.Documents;
using Elwig.Helpers;
using Elwig.Models.Dtos;
namespace Tests.UnitTests.DocumentTests {
[TestFixture]
public class PaymentVariantSummaryTest {
[Test]
public async Task Test_01_PaymentVariant2020() {
using var ctx = new AppDbContext();
var v = (await ctx.PaymentVariants.FindAsync(2020, 1))!;
var data = await PaymentVariantSummaryData.ForPaymentVariant(v, ctx.PaymentVariantSummaryRows);
using var doc = new PaymentVariantSummary(v, data);
var text = await Utils.GeneratePdfText(doc, true);
var table = Utils.ExtractTable(text);
Assert.Multiple(() => {
Assert.That(text, Contains.Substring("Auszahlungsvariante"));
Assert.That(text, Contains.Substring(v.Name));
Assert.That(table.Skip(17).ToArray(), Is.EqualTo(new string[][] {
[ "Gradation", "ungebunden", "attributlos gebunden", "gebunden", "Gesamt" ],
[ "[°Oe]", "[kg]", "[/kg]", "[kg]", "[/kg]", "[kg]", "[/kg]", "[]" ],
["Grüner Veltliner", "3 219", "0", "0", "1 609,50"],
["Qualitätswein", "73", "3 219", "0,5000", "-", "-", "-", "-", "1 609,50"]
}));
});
}
}
}

@ -3,7 +3,7 @@ using System.Reflection;
using Microsoft.Data.Sqlite;
using Elwig.Helpers.Printing;
namespace Tests.DocumentTests {
namespace Tests.UnitTests.DocumentTests {
[SetUpFixture]
public class Setup {

@ -2,7 +2,7 @@ using Elwig.Documents;
using NReco.PdfRenderer;
using System.Text.RegularExpressions;
namespace Tests.DocumentTests {
namespace Tests.UnitTests.DocumentTests {
public static class Utils {
private static readonly string FileName = Path.Combine(Path.GetTempPath(), "test_document.pdf");

@ -2,7 +2,7 @@ using Elwig.Documents;
using Elwig.Helpers;
using Elwig.Models.Dtos;
namespace Tests.DocumentTests {
namespace Tests.UnitTests.DocumentTests {
[TestFixture]
public class WineQualityStatisticsTest {

@ -2,7 +2,7 @@ using Elwig.Helpers;
using Elwig.Helpers.Billing;
using System.Text.Json;
namespace Tests.HelperTests {
namespace Tests.UnitTests.HelperTests {
[TestFixture]
public class BillingDataTest {

@ -3,7 +3,7 @@ using Elwig.Helpers.Billing;
using Microsoft.Data.Sqlite;
using System.Reflection;
namespace Tests.HelperTests {
namespace Tests.UnitTests.HelperTests {
[TestFixture]
public class BillingTest {

@ -5,7 +5,7 @@ using Elwig.Models.Entities;
using System.Reflection;
using System.Xml;
namespace Tests.HelperTests {
namespace Tests.UnitTests.HelperTests {
// see https://www.iso20022.org/iso-20022-message-definitions
// and https://www.iso20022.org/catalogue-messages/iso-20022-messages-archive?search=pain
[TestFixture]

@ -1,6 +1,6 @@
using Elwig.Helpers;
namespace Tests.HelperTests {
namespace Tests.UnitTests.HelperTests {
[TestFixture]
public class UtilsTest {

@ -1,7 +1,7 @@
using Elwig.Helpers;
using System.Windows.Controls;
namespace Tests.HelperTests {
namespace Tests.UnitTests.HelperTests {
[TestFixture]
[Apartment(ApartmentState.STA)]
public class ValidatorTest {

@ -4,7 +4,7 @@ using Elwig.Services;
using Elwig.ViewModels;
using Microsoft.EntityFrameworkCore;
namespace Tests.ServiceTests {
namespace Tests.UnitTests.ServiceTests {
[TestFixture]
public class DeliveryServiceTest {

@ -4,7 +4,7 @@ using Elwig.Services;
using Elwig.ViewModels;
using Microsoft.EntityFrameworkCore;
namespace Tests.ServiceTests {
namespace Tests.UnitTests.ServiceTests {
[TestFixture]
public class MemberServiceTest {

@ -2,7 +2,7 @@ using Elwig.Helpers;
using System.Reflection;
using Microsoft.Data.Sqlite;
namespace Tests.ServiceTests {
namespace Tests.UnitTests.ServiceTests {
[SetUpFixture]
public class Setup {

@ -2,7 +2,7 @@ using System.Net.Sockets;
using System.Net;
using System.Text;
namespace Tests.WeighingTests {
namespace Tests.UnitTests.WeighingTests {
public abstract class MockScale : IDisposable {
protected readonly TcpListener Server;

@ -1,4 +1,4 @@
namespace Tests.WeighingTests {
namespace Tests.UnitTests.WeighingTests {
public static class ScaleHandlers {
public static (string, bool) Handle_IT3000A(string req, int weight, int tare, string? error, int identNr) {

@ -1,6 +1,6 @@
using Elwig.Helpers.Weighing;
namespace Tests.WeighingTests {
namespace Tests.UnitTests.WeighingTests {
[TestFixture]
public class ScaleTestBadenL320 {

@ -1,6 +1,6 @@
using Elwig.Helpers.Weighing;
namespace Tests.WeighingTests {
namespace Tests.UnitTests.WeighingTests {
[TestFixture]
class ScaleTestGrInzersdorfL246 {

@ -1,6 +1,6 @@
using Elwig.Helpers.Weighing;
namespace Tests.WeighingTests {
namespace Tests.UnitTests.WeighingTests {
[TestFixture]
public class ScaleTestHaugsdorfDMA02 {

@ -1,6 +1,6 @@
using Elwig.Helpers.Weighing;
namespace Tests.WeighingTests {
namespace Tests.UnitTests.WeighingTests {
[TestFixture]
class ScaleTestMatzenIT3000A {

@ -1,6 +1,6 @@
using Elwig.Helpers.Weighing;
namespace Tests.WeighingTests {
namespace Tests.UnitTests.WeighingTests {
[TestFixture]
public class ScaleTestSitzendorfDMA03Baby {

@ -1,6 +1,6 @@
using Elwig.Helpers.Weighing;
namespace Tests.WeighingTests {
namespace Tests.UnitTests.WeighingTests {
[TestFixture]
class ScaleTestWolkersdorfIT6000E {

@ -1,4 +1,4 @@
namespace Tests.WeighingTests {
namespace Tests.UnitTests.WeighingTests {
public static class Utils {
public static DateOnly Today => DateOnly.FromDateTime(DateTime.Now);
public static TimeOnly Time => new(DateTime.Now.Hour, DateTime.Now.Minute);

@ -1 +1 @@
curl --fail -s -L "https://elwig.at/files/create.sql?v=31" -u "elwig:ganzGeheim123!" -o "Resources\Sql\Create.sql"
curl --fail -s -L "https://elwig.at/files/create.sql?v=32" -u "elwig:ganzGeheim123!" -o "Resources\Sql\Create.sql"