Compare commits

..

10 Commits

11 changed files with 178 additions and 52 deletions

View File

@ -6,9 +6,10 @@
xmlns:local="clr-namespace:Elwig.Windows"
mc:Ignorable="d"
ResizeMode="NoResize"
Loaded="Window_Loaded"
Title="Anlieferungsbestätingungen - Elwig" Height="400" Width="600">
<Grid>
<GroupBox Header="Sortieren nach" Margin="10,10,10,10" Width="150" Height="80" VerticalAlignment="Top" HorizontalAlignment="Left">
<GroupBox Header="Sortieren nach" Margin="10,10,10,10" Width="180" Height="80" VerticalAlignment="Top" HorizontalAlignment="Left">
<StackPanel Margin="5,5,0,5">
<RadioButton GroupName="Order" x:Name="OrderMgNrInput" Content="Mitgliedsnummer" IsChecked="True"/>
<RadioButton GroupName="Order" x:Name="OrderNameInput" Content="Name"/>
@ -16,16 +17,20 @@
</StackPanel>
</GroupBox>
<TextBox x:Name="TextElement" TextWrapping="Wrap" VerticalScrollBarVisibility="Visible" AcceptsReturn="True"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="170,10,10,10" Height="Auto"/>
<CheckBox x:Name="AllMembersInput" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="10,100,10,10">
<TextBlock>Auch Mitglieder ohne<LineBreak/>Lieferungen miteinbeziehen</TextBlock>
</CheckBox>
<Button x:Name="TestButton" Content="Stichprobe" FontSize="14" Width="150" Margin="10,10,10,74" Height="27"
<TextBox x:Name="TextElement" TextWrapping="Wrap" VerticalScrollBarVisibility="Visible" AcceptsReturn="True"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="200,10,10,10" Height="Auto"/>
<Button x:Name="TestButton" Content="Stichprobe" FontSize="14" Width="180" Margin="10,10,10,74" Height="27" Tag="Print" IsEnabled="False"
Click="TestButton_Click"
VerticalAlignment="Bottom" HorizontalAlignment="Left"/>
<Button x:Name="ShowButton" Content="Vorschau" FontSize="14" Width="150" Margin="10,10,10,42" Height="27"
<Button x:Name="ShowButton" Content="Vorschau" FontSize="14" Width="180" Margin="10,10,10,42" Height="27" Tag="Print" IsEnabled="False"
Click="ShowButton_Click"
VerticalAlignment="Bottom" HorizontalAlignment="Left"/>
<Button x:Name="PrintButton" Content="Drucken" FontSize="14" Width="150" Margin="10,10,10,10" Height="27"
<Button x:Name="PrintButton" Content="Drucken" FontSize="14" Width="180" Margin="10,10,10,10" Height="27" Tag="Print" IsEnabled="False"
Click="PrintButton_Click"
VerticalAlignment="Bottom" HorizontalAlignment="Left"/>
</Grid>

View File

@ -24,6 +24,12 @@ namespace Elwig.Dialogs {
}
}
private void Window_Loaded(object sender, RoutedEventArgs evt) {
TestButton.IsEnabled = App.IsPrintingReady;
ShowButton.IsEnabled = App.IsPrintingReady;
PrintButton.IsEnabled = App.IsPrintingReady;
}
protected override async Task OnRenewContext() { }
private async Task UpdateTextParameter() {
@ -40,13 +46,19 @@ namespace Elwig.Dialogs {
Mouse.OverrideCursor = Cursors.AppStarting;
await UpdateTextParameter();
var members = Context.Members.FromSqlRaw($"""
SELECT m.*
FROM member m
INNER JOIN delivery d ON d.mgnr = m.mgnr
WHERE d.year = {Year}
GROUP BY m.mgnr
""");
IQueryable<Member> members;
if (AllMembersInput.IsChecked == true) {
members = Context.Members.Where(m => m.IsActive);
} else {
members = Context.Members.FromSqlRaw($"""
SELECT m.*
FROM member m
INNER JOIN delivery d ON d.mgnr = m.mgnr
WHERE d.year = {Year}
GROUP BY m.mgnr
""");
}
if (OrderMgNrInput.IsChecked == true) {
members = members
.OrderBy(m => m.MgNr);

View File

@ -118,32 +118,43 @@
</thead>
<tbody>
@{
string FormatRow(int obligation, int right, int sum, int? payment = null) {
var isGa = payment == null;
string FormatRow(int mode, int obligation, int right, int sum, int? payment = null) {
var isGa = mode == 0;
payment ??= sum;
return $"<td>{obligation:N0}</td>" +
$"<td>{right:N0}</td>" +
$"<td>{(payment < obligation ? $"<b>{obligation - payment:N0}\x3c/b>" : "-")}</td>" +
$"<td>{(sum >= obligation && sum <= right ? $"{right - sum:N0}" : "-")}</td>" +
$"<td>{(obligation == 0 && right == 0 ? "-" : (sum > right ? ((isGa ? "<b>" : "") + $"{sum - right:N0}" + (isGa ? "</b>" : "")) : "-"))}</td>" +
$"<td>{(obligation == 0 && right == 0 ? "-" : $"{payment:N0}")}</td>" +
return $"<td>{(mode == 1 ? "" : obligation == 0 ? "-" : $"{obligation:N0}")}</td>" +
$"<td>{(mode == 1 ? "" : right == 0 ? "-" : $"{right:N0}")}</td>" +
$"<td>{(mode == 1 ? "" : payment < obligation ? $"<b>{obligation - payment:N0}\x3c/b>" : "-")}</td>" +
$"<td>{(mode == 1 ? "" : payment >= obligation && sum <= right ? $"{right - sum:N0}" : "-")}</td>" +
$"<td>{(mode == 1 ? "" : obligation == 0 && right == 0 ? "-" : (sum > right ? ((isGa ? "<b>" : "") + $"{sum - right:N0}" + (isGa ? "</b>" : "")) : "-"))}</td>" +
$"<td>{(mode != 2 ? "" : obligation == 0 && right == 0 ? "-" : $"{payment:N0}")}</td>" +
$"<td>{sum:N0}</td>";
}
var mBins = Model.MemberBins.Where(b => b.Value.Item2 > 0 || b.Value.Item3 > 0 || b.Value.Item4 > 0).ToList();
var fbVars = mBins.Where(b => b.Value.Item2 > 0 || b.Value.Item3 > 0).Select(b => b.Key.Replace("_", "")).Order().ToArray();
var fbs = mBins.Where(b => fbVars.Contains(b.Key)).OrderBy(b => b.Value.Item1);
var rem = mBins.Where(b => !fbVars.Contains(b.Key)).OrderBy(b => b.Value.Item1);
}
<tr>
<th>Gesamtlieferung lt. gez. GA</th>
@Raw(FormatRow(Model.Member.DeliveryObligation, Model.Member.DeliveryRight, Model.Member.Deliveries.Where(d => d.Year == Model.Year).Sum(d => d.Weight)))
@Raw(FormatRow(0, Model.Member.DeliveryObligation, Model.Member.DeliveryRight, Model.Member.Deliveries.Where(d => d.Year == Model.Year).Sum(d => d.Weight)))
</tr>
<tr class="subheading">
<th>Flächenbindungen:</th>
</tr>
@foreach (var (id, (name, right, obligation, sum, payment)) in Model.MemberBins.OrderBy(b => b.Key)) {
if (right > 0 || obligation > 0 || sum > 0) {
<tr>
<th>@name</th>
@Raw(FormatRow(obligation, right, sum, payment))
</tr>
}
@if (rem.Any()) {
<tr class="subheading"><th colspan="8">Sortenaufteilung:</th></tr>
}
@foreach (var (id, (name, right, obligation, sum, payment)) in rem) {
<tr>
<th>@name</th>
@Raw(FormatRow(1, obligation, right, sum, payment))
</tr>
}
@if (fbs.Any()){
<tr class="subheading"><th colspan="8">Flächenbindungen:</th></tr>
}
@foreach (var (id, (name, right, obligation, sum, payment)) in fbs) {
<tr>
<th>@name</th>
@Raw(FormatRow(2, obligation, right, sum, payment))
</tr>
}
</tbody>
</table>

View File

@ -13,7 +13,7 @@ namespace Elwig.Documents {
public Dictionary<string, (string, int, int, int, int)> MemberBins;
public DeliveryConfirmation(AppDbContext ctx, int year, Member m) :
base($"Anlieferungsbestätigung {year} {((IAddress?)m.BillingAddress ?? m).Name}", m) {
base($"Anlieferungsbestätigung {year}", m) {
Year = year;
ShowDateAndLocation = true;
UseBillingAddress = true;

View File

@ -134,7 +134,7 @@ namespace Elwig.Documents {
public void Show() {
if (_pdfFile == null) throw new InvalidOperationException("Pdf file has not been generated yet");
Pdf.Show(_pdfFile.NewReference(), Title);
Pdf.Show(_pdfFile.NewReference(), Title + (this is BusinessDocument b ? $" - {b.Member.Name}" : ""));
}
private class InternalDocument : Document {

View File

@ -106,3 +106,8 @@ table.delivery-confirmation-stats tbody th {
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>elwig.ico</ApplicationIcon>
<Version>0.4.0</Version>
<Version>0.4.1</Version>
<SatelliteResourceLanguages>de-AT</SatelliteResourceLanguages>
</PropertyGroup>

View File

@ -215,17 +215,7 @@ namespace Elwig.Helpers {
cnx ??= await ConnectAsync();
var bins = new Dictionary<int, Dictionary<string, (int, int)>>();
using (var cmd = cnx.CreateCommand()) {
cmd.CommandText = $"""
SELECT mgnr, t.vtrgid,
ROUND(SUM(COALESCE(area * min_kg_per_ha, 0)) / 10000.0) AS min_kg,
ROUND(SUM(COALESCE(area * max_kg_per_ha, 0)) / 10000.0) AS max_kg
FROM area_commitment c
JOIN area_commitment_type t ON t.vtrgid = c.vtrgid
WHERE (year_from IS NULL OR year_from <= {year}) AND
(year_to IS NULL OR year_to >= {year})
GROUP BY mgnr, t.vtrgid
ORDER BY LENGTH(t.vtrgid) DESC, t.vtrgid
""";
cmd.CommandText = $"SELECT mgnr, bin, min_kg, max_kg FROM v_area_commitment_bin WHERE year = {year}";
using var reader = await cmd.ExecuteReaderAsync();
while (await reader.ReadAsync()) {
var mgnr = reader.GetInt32(0);
@ -305,7 +295,7 @@ namespace Elwig.Helpers {
var variety = await WineVarieties.FindAsync(id[..2]);
var attrIds = id[2..];
var attrs = await WineAttributes.Where(a => attrIds.Contains(a.AttrId)).ToListAsync();
var name = (variety?.Name ?? "") + (attrs.Count > 0 ? $" ({string.Join(" / ", attrs.Select(a => a.Name))})" : "");
var name = (variety?.Name ?? "") + (attrIds == "_" ? " (kein Qual.Wein)" : attrs.Count > 0 ? $" ({string.Join(" / ", attrs.Select(a => a.Name))})" : "");
bins[id] = (
name,
rightsAndObligations.GetValueOrDefault(id).Item1,

View File

@ -4,11 +4,11 @@ using System;
namespace Elwig.Helpers {
public static class AppDbUpdater {
public static readonly int RequiredSchemaVersion = 3;
public static readonly int RequiredSchemaVersion = 4;
private static int _versionOffset = 0;
private static readonly Action<SqliteConnection>[] _updaters = new[] {
UpdateDbSchema_1_To_2, UpdateDbSchema_2_To_3
UpdateDbSchema_1_To_2, UpdateDbSchema_2_To_3, UpdateDbSchema_3_To_4
};
private static void ExecuteNonQuery(SqliteConnection cnx, string sql) {
@ -184,5 +184,34 @@ namespace Elwig.Helpers {
ExecuteNonQuery(cnx, "ALTER TABLE wine_attribute ADD COLUMN fill_lower_bins INTEGER NOT NULL CHECK (fill_lower_bins IN (0, 1, 2)) DEFAULT 0");
}
private static void UpdateDbSchema_3_To_4(SqliteConnection cnx) {
ExecuteNonQuery(cnx, "DROP VIEW v_payment_bin");
ExecuteNonQuery(cnx, """
CREATE VIEW v_payment_bin AS
SELECT d.year, d.mgnr,
sortid || discr AS bin,
SUM(value) AS weight
FROM v_delivery d
JOIN delivery_part_bin b ON (b.year, b.did, b.dpnr) = (d.year, d.did, d.dpnr)
GROUP BY d.year, d.mgnr, bin
HAVING SUM(value) > 0
ORDER BY d.year, d.mgnr, LENGTH(bin) DESC, bin;
""");
ExecuteNonQuery(cnx, """
CREATE VIEW v_area_commitment_bin AS
SELECT s.year, c.mgnr,
c.vtrgid AS bin,
CAST(ROUND(SUM(COALESCE(area * min_kg_per_ha, 0)) / 10000.0, 0) AS INTEGER) AS min_kg,
CAST(ROUND(SUM(COALESCE(area * max_kg_per_ha, 0)) / 10000.0, 0) AS INTEGER) AS max_kg
FROM area_commitment c, season s
JOIN area_commitment_type t ON t.vtrgid = c.vtrgid
WHERE (year_from IS NULL OR year_from <= s.year) AND
(year_to IS NULL OR year_to >= s.year)
GROUP BY s.year, c.mgnr, c.vtrgid
ORDER BY s.year, c.mgnr, LENGTH(c.vtrgid) DESC, c.vtrgid;
""");
}
}
}

View File

@ -55,7 +55,7 @@ namespace Elwig.Helpers.Billing {
COALESCE(LENGTH(attributes), 0) ASC, attribute_prio DESC, COALESCE(attributes, '~'),
kmw DESC, lsnr, dpnr
""";
var reader = await cmd.ExecuteReaderAsync();
using var reader = await cmd.ExecuteReaderAsync();
while (await reader.ReadAsync()) {
deliveries.Add((
reader.GetInt32(0), reader.GetInt32(1), reader.GetInt32(2), reader.GetString(3), reader.GetInt32(4),
@ -125,7 +125,81 @@ namespace Elwig.Helpers.Billing {
await cmd.ExecuteNonQueryAsync();
}
// TODO add second round to avoid under deliveries
if (!avoidUnderDeliveries)
return;
var underDeliveries = new Dictionary<(int, string), int>();
using (var cmd = cnx.CreateCommand()) {
cmd.CommandText = $"""
SELECT c.mgnr, c.bin, COALESCE(p.weight, 0) - c.min_kg AS diff
FROM v_area_commitment_bin c
LEFT JOIN v_payment_bin p ON (p.year, p.mgnr, p.bin) = (c.year, c.mgnr, c.bin)
WHERE c.year = {Year} AND LENGTH(c.bin) = 2 AND diff < 0
""";
using var reader = await cmd.ExecuteReaderAsync();
while (await reader.ReadAsync()) {
underDeliveries[(reader.GetInt32(0), reader.GetString(1))] = reader.GetInt32(2);
}
}
var fittingBins = new Dictionary<(int, string), int>();
using (var cmd = cnx.CreateCommand()) {
cmd.CommandText = $"""
SELECT c.mgnr, c.bin, COALESCE(p.weight, 0) - c.min_kg AS diff
FROM v_area_commitment_bin c
LEFT JOIN v_payment_bin p ON (p.year, p.mgnr, p.bin) = (c.year, c.mgnr, c.bin)
WHERE c.year = {Year} AND LENGTH(c.bin) = 3 AND diff > 0
""";
using var reader = await cmd.ExecuteReaderAsync();
while (await reader.ReadAsync()) {
fittingBins[(reader.GetInt32(0), reader.GetString(1))] = reader.GetInt32(2);
}
}
foreach (var item in fittingBins) {
var mgnr = item.Key.Item1;
var id = item.Key.Item2[..2];
var attr = item.Key.Item2[2..];
var available = item.Value;
var needed = -underDeliveries.GetValueOrDefault((mgnr, id), 0);
if (needed <= 0) continue;
var fittingDeliveries = new List<(int, int, int, int)>();
using (var cmd = cnx.CreateCommand()) {
cmd.CommandText = $"""
SELECT d.did, d.dpnr, d.weight, b.value
FROM v_delivery d
JOIN delivery_part_bin b ON (b.year, b.did, b.dpnr) = (d.year, d.did, d.dpnr) AND b.discr = '{attr}'
WHERE d.year = {Year} AND mgnr = {mgnr} AND sortid = '{id}' AND attributes = '{attr}'
ORDER BY kmw ASC, lsnr DESC, d.dpnr DESC
""";
using var reader = await cmd.ExecuteReaderAsync();
while (await reader.ReadAsync()) {
fittingDeliveries.Add((
reader.GetInt32(0), reader.GetInt32(1), reader.GetInt32(2), reader.GetInt32(3)
));
}
}
var changes = new List<(int, int, int, int)>();
foreach (var (did, dpnr, _, w) in fittingDeliveries) {
int v = Math.Min(needed, w);
needed -= v;
changes.Add((did, dpnr, 1, v));
changes.Add((did, dpnr, 2, w - v));
if (needed == 0) break;
}
using (var cmd = cnx.CreateCommand()) {
cmd.CommandText = $"""
INSERT INTO delivery_part_bin (year, did, dpnr, binnr, discr, value)
VALUES {string.Join(",\n ", changes.Select(i => $"({Year}, {i.Item1}, {i.Item2}, {i.Item3}, '', {i.Item4})"))}
ON CONFLICT DO UPDATE
SET value = excluded.value
""";
await cmd.ExecuteNonQueryAsync();
}
}
}
}
}

View File

@ -29,7 +29,7 @@
Margin="50,80,0,0"/>
<CheckBox x:Name="AllowAttrIntoLowerBinsInput" Content="Erlauben Lieferungen auch auf (konfigurierte) &quot;schlechtere&quot; Flächenbindungen aufzuteilen" IsChecked="True"
VerticalAlignment="Top" HorizontalAlignment="Left" Margin="255,68,0,0"/>
<CheckBox x:Name="AvoidUnderDeliveriesInput" Content="Unterlieferungen durch Abzug bei &quot;besseren&quot; Flächenbindungen vermeiden" IsEnabled="False"
<CheckBox x:Name="AvoidUnderDeliveriesInput" Content="Unterlieferungen durch Abzug bei &quot;besseren&quot; Flächenbindungen vermeiden" IsChecked="True"
VerticalAlignment="Top" HorizontalAlignment="Left" Margin="255,88,0,0"/>
<CheckBox x:Name="HonorGebundenInput" Margin="255,108,0,0" VerticalAlignment="Top">
<TextBlock>Bei Lieferungen das Feld <Italic>Gebunden</Italic> berücksichtigen</TextBlock>