Compare commits

..

6 Commits

18 changed files with 559 additions and 27 deletions

View File

@@ -13,11 +13,11 @@
<col style="width: 18mm;"/>
<col style="width: 10mm;"/>
<col style="width: 10mm;"/>
<col style="width: 10mm;"/>
<col style="width: 10mm;"/>
<col style="width: 15mm;"/>
<col style="width: 12mm;"/>
<col style="width: 15mm;"/>
<col style="width: 10mm;"/>
<col style="width: 10mm;"/>
<col style="width: 15mm;"/>
</colgroup>
<thead>
@@ -27,21 +27,21 @@
<th rowspan="3" style="text-align: left;">Sorte</th>
<th rowspan="3" style="text-align: left;">Attribut</th>
<th rowspan="2" colspan="2">Gradation</th>
<th colspan="2">Zu-/Abschläge</th>
<th rowspan="2" colspan="2">Flächenbindung</th>
<th rowspan="2">Preis</th>
<th colspan="2">Zu-/Abschläge</th>
<th rowspan="2">Betrag</th>
</tr>
<tr>
<th>Abs.</th>
<th>Rel.</th>
<th>Abs.</th>
</tr>
<tr>
<th>[°Oe]</th>
<th>[°KMW]</th>
<th colspan="2">[kg]</th>
<th>[@Model.CurrencySymbol/kg]</th>
<th>[%]</th>
<th colspan="2">[kg]</th>
<th>[@Model.CurrencySymbol/kg]</th>
<th>[@Model.CurrencySymbol]</th>
</tr>
@@ -62,8 +62,6 @@
<td class="attribute small">@p.Attribute</td>
<td rowspan="@rows" class="oe">@($"{p.Gradation.Oe:N0}")</td>
<td rowspan="@rows" class="kmw">@($"{p.Gradation.Kmw:N1}")</td>
<td rowspan="@rows" class="abs">@abs</td>
<td rowspan="@rows" class="rel">@rel</td>
}
@if (i > 0 && i <= p.Modifiers.Length) {
<td colspan="2" class="mod">@(p.Modifiers[i - 1])</td>
@@ -79,7 +77,10 @@
<td colspan="3"></td>
}
@if (first) {
<td rowspan="@rows" class="amount">@($"{1000:N2}")</td>
<td rowspan="@rows" class="rel">@rel</td>
<td rowspan="@rows" class="abs">@abs</td>
<!-- FIXME rel/abs mods -->
<td rowspan="@rows" class="amount">@($"{p.Buckets.Sum(b => b.Amount):N2}")</td>
first = false;
}
</tr>

View File

@@ -32,7 +32,8 @@ table.credit .dpnr {
}
table.credit .amount,
table.credit .weight {
table.credit .weight,
table.credit .price {
text-align: right;
}

View File

@@ -67,6 +67,8 @@ namespace Elwig.Documents {
name = "Letterhead";
} else if (this is DeliveryConfirmation) {
name = "DeliveryConfirmation";
} else if (this is MemberDataSheet) {
name = "MemberDataSheet";
} else {
throw new InvalidOperationException("Invalid document object");
}

View File

@@ -0,0 +1,21 @@
using Elwig.Helpers;
using Elwig.Models.Entities;
using System;
using System.Collections.Generic;
namespace Elwig.Documents {
public class MemberDataSheet : BusinessDocument {
public Season Season;
public int Year = Utils.CurrentYear;
public Dictionary<string, (string, int, int, int, int)> MemberBuckets;
public Dictionary<string, int> BucketAreas;
public MemberDataSheet(Member m, AppDbContext ctx) : base($"Stammdatenblatt {m.AdministrativeName}", m) {
DocumentId = $"Stammdatenblatt {m.MgNr}";
Season = ctx.Seasons.Find(Year) ?? throw new ArgumentException("invalid season");
MemberBuckets = ctx.GetMemberBuckets(Year, m.MgNr).GetAwaiter().GetResult();
BucketAreas = ctx.GetMemberBucketAreas(Year, m.MgNr).GetAwaiter().GetResult();
}
}
}

View File

@@ -0,0 +1,324 @@
@using RazorLight
@inherits TemplatePage<Elwig.Documents.MemberDataSheet>
@model Elwig.Documents.MemberDataSheet
@{
Layout = "BusinessDocument";
}
<link rel="stylesheet" href="file:///@Raw(Model.DataPath)\resources\MemberDataSheet.css" />
<main>
<h1>@Model.Title</h1>
<table class="member">
<thead>
<tr><th colspan="4">Persönliche Daten</th></tr>
</thead>
<tbody>
<tr>
<th>Mitglieds-Nr.</th>
<td>@Model.Member.MgNr</td>
<th>Vorgänger MgNr.</th>
<td>@Model.Member.PredecessorMgNr</td>
</tr>
<tr>
<th>Titel (vorangestellt)</th>
<td>@Model.Member.Prefix</td>
<th>Titel (nachgestellt)</th>
<td>@Model.Member.Suffix</td>
</tr>
<tr>
<th>Vorname</th>
<td>@Model.Member.GivenName</td>
<th>Nachname</th>
<td>@Model.Member.FamilyName</td>
</tr>
<tr>
<th>Weitere Vornamen</th>
<td colspan="3">@Model.Member.MiddleName</td>
</tr>
<tr>
<th>Geburtsjahr/-tag</th>
<td colspan="3">@(string.Join('.', Model.Member.Birthday?.Split('-')?.Reverse()??Array.Empty<string>()))</td>
</tr>
</tbody>
<thead>
<tr><th colspan="4">Anschrift</th></tr>
</thead>
<tbody>
<tr>
<th>Adresse</th>
<td colspan="3">@Model.Member.Address</td>
</tr>
<tr>
<th>PLZ/Ort</th>
<td colspan="3">@Model.Member.PostalDest.AtPlz?.Plz @Model.Member.PostalDest.AtPlz?.Dest (@Model.Member.PostalDest.AtPlz?.Ort.Name)</td>
</tr>
</tbody>
<thead>
<tr><th colspan="4">Rechnungsadresse</th></tr>
</thead>
<tbody>
<tr>
<th>Name</th>
<td colspan="3">@Model.Member.BillingAddress?.Name</td>
</tr>
<tr>
<th>Adresse</th>
<td colspan="3">@Model.Member.BillingAddress?.Address</td>
</tr>
<tr>
<th>PLZ/Ort</th>
<td colspan="3">@Model.Member.BillingAddress?.PostalDest.AtPlz?.Plz @Model.Member.BillingAddress?.PostalDest.AtPlz?.Dest @((Model.Member.BillingAddress != null && Model.Member.BillingAddress.PostalDest.AtPlz != null) ? "(" + Model.Member.BillingAddress?.PostalDest.AtPlz?.Ort.Name + ")" : "")</td>
</tr>
</tbody>
<thead>
<tr><th colspan="4">Kontaktdaten</th></tr>
</thead>
<tbody>
@foreach (var e in Model.Member.EmailAddresses) {
var emailNumber = Model.Member.EmailAddresses.Count > 1 ? 1 : 0;
<tr>
<th>E-Mail-Adresse @(emailNumber != 0 ? $"({emailNumber})" : "")</th>
<td colspan="3">@e.Address</td>
</tr>
emailNumber++;
}
@foreach (var k in Model.Member.TelephoneNumbers) {
<tr>
@if (k.Type.Equals("landline")) {
<th>Tel.-Nr. (Festnetz)</th>
} else if (k.Type.Equals("mobile")) {
<th>Tel.-Nr. (mobil)</th>
} else if (k.Type.Equals("fax")) {
<th>Fax-Nr.</th>
}
<td colspan="3">@k.Number</td>
</tr>
}
</tbody>
<thead>
<tr><th colspan="4">Bankverbindung</th></tr>
</thead>
<tbody>
<tr>
<th>IBAN</th>
<td colspan="3">@(Model.Member.Iban != null ? Elwig.Helpers.Utils.FormatIban(Model.Member.Iban) : "")</td>
</tr>
<tr>
<th>BIC</th>
<td colspan="3">@Model.Member.Bic</td>
</tr>
</tbody>
<thead>
<tr><th colspan="4">Betrieb</th></tr>
</thead>
<tbody>
<tr>
<th>UID</th>
<td>@Model.Member.UstIdNr</td>
<th>Betriebs-Nr.</th>
<td>@Model.Member.LfbisNr</td>
</tr>
<tr>
<th>Buchführend</th>
<td>@(Model.Member.IsBuchführend ? "Ja" : "Nein")</td>
<th>Bio</th>
<td>@(Model.Member.IsOrganic ? "Ja" : "Nein")</td>
</tr>
</tbody>
<thead>
<tr><th colspan="4">Genossenschaft</th></tr>
</thead>
<tbody>
<tr>
<th>Eintritt</th>
<td>@Model.Member.EntryDate</td>
<th>Austritt</th>
<td>@Model.Member.ExitDate</td>
</tr>
<tr>
<th>Aktiv</th>
<td>@(Model.Member.IsActive ? "Ja" : "Nein")</td>
<th>Geschäftsanteile</th>
<td>@Model.Member.BusinessShares</td>
</tr>
<tr>
<th>Volllierferant</th>
<td>@(Model.Member.IsVollLieferant ? "Ja" : "Nein")</td>
<th>Funktionär</th>
<td>@(Model.Member.IsFunktionär ? "Ja" : "Nein")</td>
</tr>
<tr>
<th>Stamm-Zweigstelle</th>
<td>@Model.Member.Branch?.Name</td>
<th>Stammgemeinde</th>
<td>@Model.Member.DefaultKg?.Name</td>
</tr>
<tr>
<th>Kontakt via Post</th>
<td>@(Model.Member.ContactViaPost ? "Ja" : "Nein")</td>
<th>Kontakt via E-Mail</th>
<td>@(Model.Member.ContactViaEmail ? "Ja" : "Nein")</td>
</tr>
</tbody>
</table>
@{
var areaComs = Model.Member.ActiveAreaCommitments.GroupBy(a => a.AreaComType).Select(group => new {
AreaComType = group.Key,
AreaComs = group.OrderBy(c => c.Kg.AtKg.Name),
Size = group.Sum(c => c.Area)
}).OrderByDescending(a => a.Size).ToList();
var lastContract = "";
}
@if (areaComs.Count != 0) {
<h1>Flächenbindungen</h1>
<table class="area-commitements">
<colgroup>
<col style="width: 40mm;" />
<col style="width: 30mm;" />
<col style="width: 35mm;" />
<col style="width: 15mm;" />
<col style="width: 25mm;" />
<col style="width: 20mm;" />
</colgroup>
<thead>
<tr>
<th rowspan="2" style="text-align: left;">Katastralgemeinde</th>
<th rowspan="2" style="text-align: left;">Ried</th>
<th rowspan="2" style="text-align: left;">Parzelle(n)</th>
<th>Fläche</th>
<th rowspan="2" style="text-align: center;">Bewirt.</th>
<th rowspan="2" style="text-align: center;">Laufzeit</th>
</tr>
<tr>
<th>[m²]</th>
</tr>
</thead>
<tbody>
@foreach (var contractType in areaComs) {
<tr class="contract-type @(contractType.AreaComType.DisplayName != lastContract && lastContract != "" ? "new" : "")">
<th colspan="3" style="text-align: left">
@($"{contractType.AreaComType.WineVar.Name} {(contractType.AreaComType.WineAttr != null ? "(" + contractType.AreaComType.WineAttr + ")" : "")}")
</th>
<th style="text-align: right">@($"{contractType.Size:N0}")</th>
<th colspan="2"></th>
</tr>
@foreach (var areaCom in contractType.AreaComs) {
<tr class="area-commitment">
<td>@areaCom.Kg.AtKg.Name <span style="font-size: 8pt;">(@areaCom.Kg.AtKg.KgNr)</span></td>
<td>@areaCom.Rd?.Name</td>
<td>@areaCom.GstNr.Replace(",", ", ")</td>
<td style="text-align: right;">@($"{areaCom.Area:N0}")</td>
<td style="text-align: center;">@areaCom.WineCult.Name</td>
<td style="text-align: center;">Ab @areaCom.YearFrom</td>
</tr>
lastContract = contractType.AreaComType.DisplayName;
}
}
<tr class="new">
<th colspan="3" style="text-align: left">Gesamt</th>
<th style="text-align: right">@($"{Model.Member.ActiveAreaCommitments.Select(a => a.Area).Sum():N0}")</th>
<th colspan="2"></th>
</tr>
</tbody>
</table>
<table class="delivery-confirmation-stats">
<colgroup>
<col style="width: 102mm;" />
<col style="width: 21mm;" />
<col style="width: 21mm;" />
<col style="width: 21mm;" />
</colgroup>
<thead>
<tr>
<th rowspan="2"></th>
<th style="text-align: center;">Fläche</th>
<th style="text-align: center;">Lieferpflicht</th>
<th style="text-align: center;">Lieferrecht</th>
</tr>
<tr>
<th style="text-align: center;">[m²]</th>
<th style="text-align: center;">[kg]</th>
<th style="text-align: center;">[kg]</th>
</tr>
</thead>
<tbody>
@{
string FormatRow(int mode, int area, int obligation, int right) {
return $"<td>{(mode == 0 || mode == 1 ? "" : area == 0 ? "-" : $"{area:N0}")}</td>" +
$"<td>{(mode == 1 ? "" : obligation == 0 ? "-" : $"{obligation:N0}")}</td>" +
$"<td>{(mode == 1 ? "" : right == 0 ? "-" : $"{right:N0}")}</td>";
}
var mBuckets = Model.MemberBuckets.Where(b => b.Value.Item2 > 0 || b.Value.Item3 > 0 || b.Value.Item4 > 0).ToList();
var fbVars = mBuckets.Where(b => b.Value.Item2 > 0 || b.Value.Item3 > 0).Select(b => b.Key.Replace("_", "")).Order().ToArray();
var fbs = mBuckets.Where(b => fbVars.Contains(b.Key) && b.Key.Length == 2).OrderBy(b => b.Value.Item1);
var vtr = mBuckets.Where(b => fbVars.Contains(b.Key) && b.Key.Length > 2).OrderBy(b => b.Value.Item1);
}
<tr>
<th>Laut gezeichneten GA</th>
@Raw(FormatRow(
0,
0,
Model.Member.BusinessShares * Model.Season.MinKgPerBusinessShare,
Model.Member.BusinessShares * Model.Season.MaxKgPerBusinessShare
))
</tr>
@if (fbs.Any()) {
<tr class="subheading"><th colspan="8">Flächenbindungen:</th></tr>
}
@foreach (var (id, (name, right, obligation, _, _)) in fbs) {
<tr>
<th>@name</th>
@Raw(FormatRow(2, Model.BucketAreas[id], obligation, right))
</tr>
}
@if (vtr.Any()) {
<tr class="subheading"><th colspan="8">Verträge:</th></tr>
}
@foreach (var (id, (name, right, obligation, _, _)) in vtr) {
<tr>
<th>@name</th>
@Raw(FormatRow(2, Model.BucketAreas[id], obligation, right))
</tr>
}
</tbody>
</table>
} else {
<table class="delivery-confirmation-stats" style="margin-top: 5mm;">
<colgroup>
<col style="width: 123mm;" />
<col style="width: 21mm;" />
<col style="width: 21mm;" />
</colgroup>
<thead>
<tr>
<th rowspan="2"></th>
<th style="text-align: center;">Lieferpflicht</th>
<th style="text-align: center;">Lieferrecht</th>
</tr>
<tr>
<th style="text-align: center;">[kg]</th>
<th style="text-align: center;">[kg]</th>
</tr>
</thead>
<tbody>
<tr>
<th>Laut gezeichneten GA</th>
<td>@($"{Model.Member.BusinessShares * Model.Season.MinKgPerBusinessShare:N0}")</td>
<td>@($"{Model.Member.BusinessShares * Model.Season.MaxKgPerBusinessShare:N0}")</td>
</tr>
</tbody>
</table>
}
</main>

View File

@@ -0,0 +1,82 @@
table.member {
border: 0.5pt solid black;
}
table.member, table.area-commitements {
font-size: 10pt;
}
table.member tbody th {
font-weight: normal;
font-style: italic;
text-align: left;
}
table.member thead,
table.member tbody {
border: 0.5pt solid black;
}
table.area-commitements td {
overflow: hidden;
}
table.area-commitements thead tr th {
font-weight: normal;
font-style: italic;
}
table.area-commitements tbody tr.contract-type {
margin-bottom: 10px;
}
table.area-commitements tbody td {
vertical-align: top;
}
table.area-commitements tr.new th {
border-top: 0.5pt solid black;
}
table.delivery-confirmation-stats {
font-size: 10pt;
break-inside: avoid;
}
table.delivery-confirmation-stats th,
table.delivery-confirmation-stats td {
padding: 0.125mm 0;
overflow: hidden;
white-space: nowrap;
}
table.delivery-confirmation-stats tr.subheading th {
text-align: left;
}
table.delivery-confirmation-stats thead th {
font-weight: normal;
font-style: italic;
text-align: right;
font-size: 10pt;
}
table.delivery-confirmation-stats thead th:first-child {
text-align: left;
}
table.delivery-confirmation-stats td {
text-align: right;
}
table.delivery-confirmation-stats tbody th {
font-weight: normal;
font-style: italic;
text-align: left;
}
table.delivery-confirmation-stats tr.subheading th {
font-weight: bold;
border-top: 0.5pt solid black;
}

View File

@@ -7,7 +7,7 @@
<UseWPF>true</UseWPF>
<PreserveCompilationContext>true</PreserveCompilationContext>
<ApplicationIcon>Resources\Images\Elwig.ico</ApplicationIcon>
<Version>0.5.0</Version>
<Version>0.5.1</Version>
<SatelliteResourceLanguages>de-AT</SatelliteResourceLanguages>
</PropertyGroup>

View File

@@ -63,6 +63,7 @@ namespace Elwig.Helpers {
private readonly Dictionary<int, Dictionary<int, Dictionary<string, (int, int)>>> _memberRightsAndObligations = new();
private readonly Dictionary<int, Dictionary<int, Dictionary<string, int>>> _memberDeliveryBuckets = new();
private readonly Dictionary<int, Dictionary<int, Dictionary<string, int>>> _memberPaymentBuckets = new();
private readonly Dictionary<int, Dictionary<int, Dictionary<string, int>>> _memberBucketAreas = new();
public AppDbContext() {
if (App.Config.DatabaseLog != null) {
@@ -256,6 +257,28 @@ namespace Elwig.Helpers {
_memberPaymentBuckets[year] = buckets;
}
private async Task FetchMemberBucketAreas(int year, SqliteConnection? cnx = null) {
var ownCnx = cnx == null;
cnx ??= await ConnectAsync();
var buckets = new Dictionary<int, Dictionary<string, int>>();
using (var cmd = cnx.CreateCommand()) {
cmd.CommandText = $"SELECT mgnr, bucket, area FROM v_area_commitment_bucket_strict WHERE year = {year}";
using var reader = await cmd.ExecuteReaderAsync();
while (await reader.ReadAsync()) {
var mgnr = reader.GetInt32(0);
var bucket = reader.GetString(1);
var v = reader.GetInt32(2);
if (!buckets.ContainsKey(mgnr)) buckets[mgnr] = new();
buckets[mgnr][bucket] = v;
if (bucket.Length > 2) {
buckets[mgnr][bucket[..2]] = buckets[mgnr].GetValueOrDefault(bucket[..2], 0) + v;
}
}
}
if (ownCnx) await cnx.DisposeAsync();
_memberBucketAreas[year] = buckets;
}
public async Task<Dictionary<string, (int, int)>> GetMemberRightsAndObligations(int year, int mgnr, SqliteConnection? cnx = null) {
if (!_memberRightsAndObligations.ContainsKey(year))
await FetchMemberRightsAndObligations(year, cnx);
@@ -274,6 +297,12 @@ namespace Elwig.Helpers {
return _memberPaymentBuckets[year].GetValueOrDefault(mgnr, new());
}
public async Task<Dictionary<string, int>> GetMemberBucketAreas(int year, int mgnr, SqliteConnection? cnx = null) {
if (!_memberBucketAreas.ContainsKey(year))
await FetchMemberBucketAreas(year, cnx);
return _memberBucketAreas[year].GetValueOrDefault(mgnr, new());
}
public async Task<Dictionary<string, (string, int, int, int, int)>> GetMemberBuckets(int year, int mgnr, SqliteConnection? cnx = null) {
var ownCnx = cnx == null;
cnx ??= await ConnectAsync();

View File

@@ -689,7 +689,7 @@ namespace Elwig.Helpers {
BEGIN
INSERT INTO payment_member (year, avnr, mgnr, net_amount)
VALUES (NEW.year, NEW.avnr, (SELECT mgnr FROM delivery WHERE (year, did) = (NEW.year, NEW.did)), NEW.amount)
ON CONFLICT DO UPDATE SET amount = amount + excluded.amount;
ON CONFLICT DO UPDATE SET net_amount = net_amount + excluded.net_amount;
END;
""");
ExecuteNonQuery(cnx, """

View File

@@ -1,4 +1,3 @@
using Microsoft.Data.Sqlite;
using System;
using System.Collections.Generic;
using System.Linq;

View File

@@ -1,7 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Elwig.Helpers.Billing {
@@ -15,6 +13,10 @@ namespace Elwig.Helpers.Billing {
protected async Task DeleteInDb() {
using var cnx = await AppDbContext.ConnectAsync();
using (var cmd = cnx.CreateCommand()) {
cmd.CommandText = $"DELETE FROM payment_delivery_part_bucket WHERE (year, avnr) = ({Year}, {AvNr})";
await cmd.ExecuteNonQueryAsync();
}
using (var cmd = cnx.CreateCommand()) {
cmd.CommandText = $"DELETE FROM payment_delivery_part WHERE (year, avnr) = ({Year}, {AvNr})";
await cmd.ExecuteNonQueryAsync();
@@ -27,15 +29,41 @@ namespace Elwig.Helpers.Billing {
public async Task CalculatePrices() {
await DeleteInDb();
var tasks = new List<Task>();
foreach (var mgnr in Context.Members.Select(m => m.MgNr)) {
tasks.Add(Task.Run(() => CalculateMemberPrices(mgnr)));
using var cnx = await AppDbContext.ConnectAsync();
var parts = new List<(int Year, int DId, int DPNr, int BktNr, string SortId, string Discr, int Value, bool MinQuw, double Oe, double Kmw)>();
using (var cmd = cnx.CreateCommand()) {
cmd.CommandText = $"""
SELECT d.year, d.did, d.dpnr, b.bktnr, d.sortid, b.discr, b.value, d.min_quw, d.oe, d.kmw
FROM delivery_part_bucket b
JOIN v_delivery d ON (d.year, d.did, d.dpnr) = (b.year, b.did, b.dpnr)
WHERE b.year = {Year}
""";
using var reader = await cmd.ExecuteReaderAsync();
while (await reader.ReadAsync()) {
parts.Add((
reader.GetInt32(0), reader.GetInt32(1), reader.GetInt32(2), reader.GetInt32(3),
reader.GetString(4), reader.GetString(5), reader.GetInt32(6),
reader.GetBoolean(7), reader.GetDouble(8), reader.GetDouble(9)
));
}
}
await Task.WhenAll(tasks);
}
protected async Task CalculateMemberPrices(int mgnr) {
var inserts = new List<(int Year, int DId, int DPNr, int BktNr, long Price, long Amount)>();
foreach (var part in parts) {
var price = !part.MinQuw ? 0.5m : ((part.BktNr == 2 ? 0.8m : (part.BktNr == 1 ? 0.7m : 0.6m)) + ((decimal)(part.Oe - 73) * 0.005m)); // TODO
var priceL = Utils.DecToDb(price, 4);
var amount = Utils.DecToDb(price * part.Value, 4);
inserts.Add((part.Year, part.DId, part.DPNr, part.BktNr, priceL, amount));
}
using (var cmd = cnx.CreateCommand()) {
cmd.CommandText = $"""
INSERT INTO payment_delivery_part_bucket (year, did, dpnr, bktnr, avnr, price, amount)
VALUES {string.Join(",\n ", inserts.Select(i => $"({i.Year}, {i.DId}, {i.DPNr}, {i.BktNr}, {AvNr}, {i.Price}, {i.Amount})"))}
""";
await cmd.ExecuteNonQueryAsync();
}
}
}
}

View File

@@ -146,6 +146,10 @@ namespace Elwig.Helpers {
return CalcCrc16Modbus(Encoding.ASCII.GetBytes(data));
}
public static string FormatIban(string iban) {
return Regex.Replace(iban, ".{4}", "$0 ");
}
public static void RunBackground(string title, Func<Task> a) {
Task.Run(async () => {
try {

View File

@@ -90,9 +90,10 @@ namespace Elwig.Models.Dtos {
Buckets = rows
.Where(b => b.Value > 0)
.OrderByDescending(b => b.BktNr)
// FIXME precision
.Select(b => (b.Discr == "_" ? "ungeb." : $"geb. {f.SortId}{b.Discr}", b.Value,
b.Price != null ? (decimal?)Utils.DecFromDb((long)b.Price, 0) : null,
b.Amount != null ? (decimal?)Utils.DecFromDb((long)b.Amount, 0) : null))
b.Price != null ? (decimal?)Utils.DecFromDb((long)b.Price, 4) : null,
b.Amount != null ? (decimal?)Utils.DecFromDb((long)b.Amount, 4) : null))
.ToArray();
}
}

View File

@@ -7,7 +7,7 @@
xmlns:ctrl="clr-namespace:Elwig.Controls"
mc:Ignorable="d"
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
Title="Flächenbindungen - Elwig" Height="500" Width="920" MinWidth="860"
Title="Flächenbindungen - Elwig" Height="500" MinHeight="440" Width="920" MinWidth="860"
Loaded="Window_Loaded">
<Window.Resources>
<Style TargetType="Label">

View File

@@ -69,8 +69,9 @@ namespace Elwig.Windows {
StatusAreaCommitments.Text = $"Flächenbindungen: {areaComs.Count}";
StatusArea.Text = $"Fläche: {areaComs.Select(a => a.Area).Sum():N0} m²";
}
var groups = areaComs.GroupBy(a => a.AreaComType.DisplayName).Select(a => (a.Key, a.Sum(b => b.Area))).OrderByDescending(a => a.Item2).ToList();
var groups = areaComs.GroupBy(a => $"{a.AreaComType.SortId}{a.AreaComType.AttrId}").Select(a => (a.Key, a.Sum(b => b.Area))).OrderByDescending(a => a.Item2).ToList();
StatusContracts.Text = $"Vertragsarten: {groups.Count} (" + string.Join(", ", groups.Select(g => $"{g.Key}: {g.Item2:N0} m²")) + ")";
groups = areaComs.GroupBy(a => a.AreaComType.DisplayName).Select(a => (a.Key, a.Sum(b => b.Area))).OrderByDescending(a => a.Item2).ToList();
StatusContracts.ToolTip = $"Vertragsarten: {groups.Count}\n" + string.Join($"\n", groups.Select(g => $"{g.Key}: {g.Item2:N0} m²"));
}

View File

@@ -53,7 +53,8 @@
<MenuItem Header="Drucken">
<MenuItem x:Name="Menu_Print_Letterhead" Header="Briefkopf drucken"
Click="Menu_Print_Letterhead_Click"/>
<MenuItem Header="Stammdatenblatt drucken"/>
<MenuItem x:Name="Menu_Show_Memberdata" Header="Stammdatenblatt anzeigen" Click="Menu_Show_Memberdata_Click"/>
<MenuItem x:Name="Menu_Print_Memberdata" Header="Stammdatenblatt drucken" Click="Menu_Print_Memberdata_Click"/>
<MenuItem Header="Briefköpfe drucken">
<MenuItem x:Name="Menu_Print_Letterheads_MgNr" Header="nach MgNr. sortiert" IsEnabled="False" Tag="Print"
Click="Menu_Print_Letterheads_MgNr_Click"/>

View File

@@ -57,6 +57,8 @@ namespace Elwig.Windows {
private void Window_Loaded(object sender, RoutedEventArgs evt) {
Menu_Print_Letterheads_MgNr.IsEnabled = App.IsPrintingReady;
Menu_Print_Letterheads_Name.IsEnabled = App.IsPrintingReady;
Menu_Show_Memberdata.IsEnabled = App.IsPrintingReady;
Menu_Print_Memberdata.IsEnabled = App.IsPrintingReady;
ActiveMemberInput.IsChecked = true;
UpdatePhoneNrInputVisibility();
@@ -337,6 +339,30 @@ namespace Elwig.Windows {
await PrintLetterheads(2);
}
private async void Menu_Print_Memberdata_Click(object sender, RoutedEventArgs evt) {
if (MemberList.SelectedItem is not Member m)
return;
Mouse.OverrideCursor = Cursors.AppStarting;
using var doc = new MemberDataSheet(m, Context);
await doc.Generate();
Mouse.OverrideCursor = null;
if (App.Config.Debug) {
doc.Show();
} else {
await doc.Print();
}
}
private async void Menu_Show_Memberdata_Click(object sender, RoutedEventArgs evt) {
if (MemberList.SelectedItem is not Member m)
return;
Mouse.OverrideCursor = Cursors.AppStarting;
using var doc = new MemberDataSheet(m, Context);
await doc.Generate();
Mouse.OverrideCursor = null;
doc.Show();
}
private void FocusSearchInput(object sender, RoutedEventArgs evt) {
if (!IsEditing && !IsCreating) {
SearchInput.Focus();
@@ -623,6 +649,8 @@ namespace Elwig.Windows {
Menu_Member_SendEmail.IsEnabled = m.EmailAddresses.Count > 0;
Menu_Print_Letterhead.IsEnabled = true;
Menu_Show_Memberdata.IsEnabled = true;
Menu_Print_Memberdata.IsEnabled = true;
FinishInputFilling();
}
@@ -630,6 +658,8 @@ namespace Elwig.Windows {
new protected void ClearInputs(bool validate = false) {
Menu_Member_SendEmail.IsEnabled = false;
Menu_Print_Letterhead.IsEnabled = false;
Menu_Show_Memberdata.IsEnabled = false;
Menu_Print_Memberdata.IsEnabled = false;
StatusDeliveriesLastSeason.Text = $"Lieferungen ({Utils.CurrentLastSeason - 1}): -";
StatusDeliveriesThisSeason.Text = $"Lieferungen ({Utils.CurrentLastSeason}): -";
StatusAreaCommitment.Text = "Gebundene Fläche: -";

View File

@@ -10,6 +10,7 @@ using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using Elwig.Helpers.Billing;
namespace Elwig.Windows {
public partial class PaymentVariantsWindow : ContextWindow {
@@ -68,8 +69,15 @@ namespace Elwig.Windows {
}
private void CalculateButton_Click(object sender, RoutedEventArgs evt) {
private async void CalculateButton_Click(object sender, RoutedEventArgs evt) {
if (PaymentVariantList.SelectedValue is not PaymentVar v)
return;
CalculateButton.IsEnabled = false;
Mouse.OverrideCursor = Cursors.AppStarting;
var b = new BillingVariant(v.Year, v.AvNr);
await b.CalculatePrices();
Mouse.OverrideCursor = null;
CalculateButton.IsEnabled = true;
}
private void EditButton_Click(object sender, RoutedEventArgs evt) {