Compare commits
	
		
			10 Commits
		
	
	
		
			v0.5.0
			...
			ed78c8facb
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| ed78c8facb | |||
| b096163ed3 | |||
| 486655d071 | |||
| d485f0fda1 | |||
| 8509f04d4d | |||
| 3ee7a6e75e | |||
| 8b96b65c8c | |||
| db8a449785 | |||
| 2cdde60644 | |||
| 32f229b0a5 | 
| @@ -1,5 +1,5 @@ | ||||
| using Elwig.Helpers; | ||||
| using Elwig.Models; | ||||
| using Elwig.Models.Entities; | ||||
|  | ||||
| namespace Elwig.Documents { | ||||
|     public abstract class BusinessDocument : Document { | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| using Elwig.Models; | ||||
| using Elwig.Models.Entities; | ||||
|  | ||||
| namespace Elwig.Documents { | ||||
|     public class BusinessLetter : BusinessDocument { | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| using Elwig.Helpers; | ||||
| using Elwig.Models; | ||||
| using Elwig.Models.Entities; | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using System.Collections.Generic; | ||||
| using System.Linq; | ||||
|   | ||||
| @@ -1,33 +1,25 @@ | ||||
| using Elwig.Helpers; | ||||
| using Elwig.Models; | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using Elwig.Models.Dtos; | ||||
| using Elwig.Models.Entities; | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Linq; | ||||
|  | ||||
| namespace Elwig.Documents { | ||||
|     public class DeliveryConfirmation : BusinessDocument { | ||||
|  | ||||
|         public Season Season; | ||||
|         public IEnumerable<DeliveryPart> Deliveries; | ||||
|         public DeliveryConfirmationData Data; | ||||
|         public string? Text = App.Client.TextDeliveryConfirmation; | ||||
|         public Dictionary<string, (string, int, int, int, int)> MemberBuckets; | ||||
|  | ||||
|         public DeliveryConfirmation(AppDbContext ctx, int year, Member m, IEnumerable<DeliveryPart>? deliveries = null) : | ||||
|         public DeliveryConfirmation(AppDbContext ctx, int year, Member m, DeliveryConfirmationData data) : | ||||
|             base($"Anlieferungsbestätigung {year}", m) { | ||||
|             Season = ctx.Seasons.Find(year) ?? throw new ArgumentException("invalid season"); | ||||
|             ShowDateAndLocation = true; | ||||
|             UseBillingAddress = true; | ||||
|             IncludeSender = true; | ||||
|             DocumentId = $"Anl.-Best. {Season.Year}/{m.MgNr}"; | ||||
|             Deliveries = deliveries ?? ctx.DeliveryParts.FromSqlRaw($""" | ||||
|                 SELECT p.* | ||||
|                 FROM v_delivery v | ||||
|                     JOIN delivery_part p ON (p.year, p.did, p.dpnr) = (v.year, v.did, v.dpnr) | ||||
|                 WHERE (v.year, v.mgnr) = ({Season.Year}, {m.MgNr}) | ||||
|                 ORDER BY v.sortid, v.abgewertet ASC, v.attribute_prio DESC, COALESCE(v.attrid, '~'), v.kmw DESC, v.lsnr, v.dpnr | ||||
|                 """) | ||||
|                 .ToList(); | ||||
|             Data = data; | ||||
|             MemberBuckets = ctx.GetMemberBuckets(Season.Year, m.MgNr).GetAwaiter().GetResult(); | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -41,39 +41,35 @@ | ||||
|         </thead> | ||||
|         <tbody> | ||||
|             @{ | ||||
|                 var lastSortId = ""; | ||||
|                 var lastVariant = ""; | ||||
|             } | ||||
|             @foreach (var p in Model.Deliveries) { | ||||
|                 var buckets = p.Buckets.Where(b => b.Value > 0).OrderByDescending(b => b.BktNr).ToArray(); | ||||
|                 var rowsBuckets = buckets.Length; | ||||
|                 var mods = p.Modifiers.Select(m => m.Name).ToArray(); | ||||
|                 var rowsMod = mods.Length + 1; | ||||
|                 var rows = Math.Max(rowsBuckets, rowsMod); | ||||
|             @foreach (var p in Model.Data.Rows) { | ||||
|                 var rows = Math.Max(p.Buckets.Length, p.Modifiers.Length + 1); | ||||
|                 var first = true; | ||||
|                 @for (int i = 0; i < rows; i++) { | ||||
|                     <tr class="@(first ? "first" : "") @(p.SortId != lastSortId && lastSortId != "" ? "new": "") @(rows > i + 1 ? "trailing" : "")"> | ||||
|                     <tr class="@(first ? "first" : "") @(p.Variant != lastVariant && lastVariant != "" ? "new": "") @(rows > i + 1 ? "trailing" : "")"> | ||||
|                         @if (first) { | ||||
|                             <td rowspan="@rows">@p.Delivery.LsNr</td> | ||||
|                             <td rowspan="@rows">@p.LsNr</td> | ||||
|                             <td rowspan="@rows">@p.DPNr</td> | ||||
|                             <td class="small">@p.Variant.Name</td> | ||||
|                             <td class="small">@p.Attribute?.Name</td> | ||||
|                             <td class="small">@p.Quality.Name</td> | ||||
|                             <td rowspan="@rows" class="grad">@($"{p.Oe:N0}")</td> | ||||
|                             <td rowspan="@rows" class="grad">@($"{p.Kmw:N1}")</td> | ||||
|                             <td class="small">@p.Variant</td> | ||||
|                             <td class="small">@p.Attribute</td> | ||||
|                             <td class="small">@p.QualityLevel</td> | ||||
|                             <td rowspan="@rows" class="grad">@($"{p.Gradation.Oe:N0}")</td> | ||||
|                             <td rowspan="@rows" class="grad">@($"{p.Gradation.Kmw:N1}")</td> | ||||
|                         } | ||||
|                         @if (i > 0 && i <= mods.Length) { | ||||
|                             <td colspan="3" class="mod">@(mods[i - 1])</td> | ||||
|                         @if (i > 0 && i <= p.Modifiers.Length) { | ||||
|                             <td colspan="3" class="mod">@(p.Modifiers[i - 1])</td> | ||||
|                         } else if (i > 0) { | ||||
|                             <td colspan="3"></td> | ||||
|                         } | ||||
|                         @if (i < buckets.Length) { | ||||
|                             var bucket = buckets[i]; | ||||
|                             <td class="geb">@(bucket.Discr == "_" ? "ungeb." : $"geb. {p.SortId}{bucket.Discr}"):</td> | ||||
|                         @if (i < p.Buckets.Length) { | ||||
|                             var bucket = p.Buckets[i]; | ||||
|                             <td class="geb">@bucket.Name:</td> | ||||
|                             <td class="weight">@($"{bucket.Value:N0}")</td> | ||||
|                         } else { | ||||
|                             <td colspan="2"></td> | ||||
|                         } | ||||
|                         @if (i == buckets.Length - 1) { | ||||
|                         @if (i == p.Buckets.Length - 1) { | ||||
|                             <td class="weight">@($"{p.Weight:N0}")</td> | ||||
|                         } else { | ||||
|                             <td></td> | ||||
| @@ -83,12 +79,12 @@ | ||||
|                             first = false; | ||||
|                         } | ||||
|                     </tr> | ||||
|                     lastSortId = p.SortId; | ||||
|                     lastVariant = p.Variant; | ||||
|                 } | ||||
|             } | ||||
|             <tr class="sum"> | ||||
|                 <td colspan="8">Gesamt:</td> | ||||
|                 <td colspan="2" class="weight">@($"{Model.Deliveries.Sum(p => p.Weight):N0}")</td> | ||||
|                 <td colspan="2" class="weight">@($"{Model.Data.Rows.Sum(p => p.Weight):N0}")</td> | ||||
|                 <td></td> | ||||
|             </tr> | ||||
|         </tbody> | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| using Elwig.Helpers; | ||||
| using Elwig.Models; | ||||
| using Elwig.Models.Entities; | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| using Elwig.Helpers; | ||||
| using Elwig.Models; | ||||
| using Elwig.Models.Entities; | ||||
| using System.Collections.Generic; | ||||
|  | ||||
| namespace Elwig.Documents { | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| using Elwig.Models; | ||||
| using Elwig.Models.Entities; | ||||
|  | ||||
| namespace Elwig.Documents { | ||||
|     public class Letterhead : BusinessDocument { | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using Elwig.Models; | ||||
| using Elwig.Models.Entities; | ||||
| using System.Linq; | ||||
| using System.Threading.Tasks; | ||||
| using System.IO; | ||||
| @@ -9,6 +9,7 @@ using Microsoft.Extensions.Logging; | ||||
| using Microsoft.Data.Sqlite; | ||||
| using System.Text.RegularExpressions; | ||||
| using System.Collections.Generic; | ||||
| using Elwig.Models.Dtos; | ||||
|  | ||||
| namespace Elwig.Helpers { | ||||
|     public class AppDbContext : DbContext { | ||||
| @@ -45,6 +46,10 @@ namespace Elwig.Helpers { | ||||
|         public DbSet<PaymentMember> MemberPayments { get; private set; } | ||||
|         public DbSet<Credit> Credits { get; private set; } | ||||
|  | ||||
|         public DbSet<OverUnderDeliveryRow> OverUnderDeliveryRows { get; private set; } | ||||
|         public DbSet<AreaComUnderDeliveryRowSingle> AreaComUnderDeliveryRows { get; private set; } | ||||
|         public DbSet<MemberDeliveryPerVariantRowSingle> MemberDeliveryPerVariantRows { get; private set; } | ||||
|  | ||||
|         private readonly StreamWriter? LogFile = null; | ||||
|         public static DateTime LastWriteTime => File.GetLastWriteTime(App.Config.DatabaseFile); | ||||
|         public DateTime SavedLastWriteTime { get; private set; } | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| using Elwig.Models; | ||||
| using Elwig.Models.Entities; | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Linq; | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| using Elwig.Models; | ||||
| using Elwig.Models.Entities; | ||||
| using Microsoft.Data.Sqlite; | ||||
| using System.Collections.Generic; | ||||
| using System.Linq; | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| using Elwig.Helpers.Billing; | ||||
| using Elwig.Models; | ||||
| using Elwig.Models.Entities; | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.IO; | ||||
|   | ||||
							
								
								
									
										268
									
								
								Elwig/Helpers/Export/Ods.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										268
									
								
								Elwig/Helpers/Export/Ods.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,268 @@ | ||||
| using Elwig.Models.Dtos; | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Data.Entity.Core.Common.CommandTrees.ExpressionBuilder; | ||||
| using System.IO; | ||||
| using System.IO.Compression; | ||||
| using System.Linq; | ||||
| using System.Threading.Tasks; | ||||
|  | ||||
| namespace Elwig.Helpers.Export { | ||||
|     public class OdsFile : IDisposable { | ||||
|  | ||||
|         protected readonly string FileName; | ||||
|         protected readonly ZipArchive ZipArchive; | ||||
|         protected StreamWriter? Content; | ||||
|         private readonly List<string> _tables; | ||||
|  | ||||
|         public OdsFile(string filename) { | ||||
|             FileName = filename; | ||||
|             File.Delete(filename); | ||||
|             ZipArchive = ZipFile.Open(FileName, ZipArchiveMode.Create); | ||||
|             _tables = new(); | ||||
|         } | ||||
|  | ||||
|         public void Dispose() { | ||||
|             AddTrailer().GetAwaiter().GetResult(); | ||||
|             ZipArchive?.Dispose(); | ||||
|             GC.SuppressFinalize(this); | ||||
|         } | ||||
|  | ||||
|         private async Task AddHeader() { | ||||
|             var mimetype = ZipArchive.CreateEntry("mimetype", CompressionLevel.NoCompression); | ||||
|             using (var writer = new StreamWriter(mimetype.Open(), Utils.UTF8)) { | ||||
|                 await writer.WriteAsync("application/vnd.oasis.opendocument.spreadsheet"); | ||||
|             } | ||||
|  | ||||
|             var manifest = ZipArchive.CreateEntry("META-INF/manifest.xml"); | ||||
|             using (var writer = new StreamWriter(manifest.Open(), Utils.UTF8)) { | ||||
|                 await writer.WriteAsync(""" | ||||
|                     <?xml version="1.0" encoding="UTF-8" standalone="yes"?> | ||||
|                     <manifest:manifest xmlns:manifest="urn:oasis:names:tc:opendocument:xmlns:manifest:1.0" manifest:version="1.3"> | ||||
|                      <manifest:file-entry manifest:full-path="/" manifest:version="1.3" manifest:media-type="application/vnd.oasis.opendocument.spreadsheet"/> | ||||
|                      <manifest:file-entry manifest:full-path="content.xml" manifest:media-type="text/xml"/> | ||||
|                      <manifest:file-entry manifest:full-path="styles.xml" manifest:media-type="text/xml"/> | ||||
|                      <manifest:file-entry manifest:full-path="meta.xml" manifest:media-type="text/xml"/> | ||||
|                      <manifest:file-entry manifest:full-path="settings.xml" manifest:media-type="text/xml"/> | ||||
|                     </manifest:manifest> | ||||
|  | ||||
|                     """); | ||||
|             } | ||||
|  | ||||
|             var styles = ZipArchive.CreateEntry("styles.xml"); | ||||
|             using (var writer = new StreamWriter(styles.Open(), Utils.UTF8)) { | ||||
|                 await writer.WriteAsync(""" | ||||
|                     <?xml version="1.0" encoding="UTF-8" standalone="yes"?> | ||||
|                     <office:document-styles xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" office:version="1.3"> | ||||
|                     </office:document-styles> | ||||
|  | ||||
|                     """); | ||||
|             } | ||||
|  | ||||
|             var meta = ZipArchive.CreateEntry("meta.xml"); | ||||
|             using (var writer = new StreamWriter(meta.Open(), Utils.UTF8)) { | ||||
|                 var now = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ"); | ||||
|                 await writer.WriteAsync($""" | ||||
|                     <?xml version="1.0" encoding="UTF-8" standalone="yes"?> | ||||
|                     <office:document-meta xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:dc="http://purl.org/dc/elements/1.1/" office:version="1.3"> | ||||
|                      <office:meta> | ||||
|                       <meta:generator>Elwig {App.Version}</meta:generator> | ||||
|                       <meta:initial-creator>Elwig</meta:initial-creator> | ||||
|                       <dc:creator>Elwig</dc:creator> | ||||
|                       <meta:creation-date>{now}</meta:creation-date> | ||||
|                       <dc:date>{now}</dc:date> | ||||
|                      </office:meta> | ||||
|                     </office:document-meta> | ||||
|  | ||||
|                     """); | ||||
|             } | ||||
|  | ||||
|             var content = ZipArchive.CreateEntry("content.xml"); | ||||
|             Content = new StreamWriter(content.Open(), Utils.UTF8); | ||||
|             await Content.WriteAsync(""" | ||||
|                 <?xml version="1.0" encoding="UTF-8" standalone="yes"?> | ||||
|                 <office:document-content xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:calcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" office:version="1.3"> | ||||
|                  <office:automatic-styles> | ||||
|                   <style:default-style style:family="table-cell"> | ||||
|                    <style:text-properties fo:language="de" fo:country="AT"/> | ||||
|                   </style:default-style> | ||||
|                   <style:style style:name="default" style:family="table-cell"> | ||||
|                    <style:table-cell-properties style:vertical-align="top"/> | ||||
|                   </style:style> | ||||
|  | ||||
|                 """); | ||||
|  | ||||
|             for (int i = 1; i <= 100; i++) { | ||||
|                 await Content.WriteAsync($"  <style:style style:name=\"colw{i}\" style:family=\"table-column\"><style:table-column-properties style:column-width=\"{i}mm\"/></style:style>\r\n"); | ||||
|             } | ||||
|  | ||||
|             await Content.WriteAsync(""" | ||||
|                   <style:style style:name="header" style:family="table-cell" style:parent-style-name="default"> | ||||
|                    <style:table-cell-properties style:text-align-source="fix" style:repeat-content="false"/> | ||||
|                    <style:paragraph-properties fo:text-align="center"/> | ||||
|                    <style:text-properties fo:font-weight="bold" style:font-weight-asian="bold" style:font-weight-complex="bold"  fo:font-size="16pt"/> | ||||
|                   </style:style> | ||||
|                   <style:style style:name="th" style:family="table-cell" style:parent-style-name="default"> | ||||
|                    <style:table-cell-properties style:text-align-source="fix" style:repeat-content="false" style:vertical-align="middle"/> | ||||
|                    <style:paragraph-properties fo:text-align="center"/> | ||||
|                    <style:text-properties fo:font-weight="bold" style:font-weight-asian="bold" style:font-weight-complex="bold"/> | ||||
|                   </style:style> | ||||
|                   <number:number-style style:name="nperc"> | ||||
|                    <number:number number:decimal-places="1" number:min-decimal-places="1" number:min-integer-digits="1"/> | ||||
|                   </number:number-style> | ||||
|                   <style:style style:name="perc" style:family="table-cell" style:parent-style-name="default" style:data-style-name="nperc"/> | ||||
|                  </office:automatic-styles> | ||||
|                  <office:body> | ||||
|                   <office:spreadsheet> | ||||
|                    <table:calculation-settings table:case-sensitive="false" table:search-criteria-must-apply-to-whole-cell="true" table:use-wildcards="true" table:use-regular-expressions="false" table:automatic-find-labels="false"/> | ||||
|  | ||||
|                 """); | ||||
|         } | ||||
|  | ||||
|         private async Task AddTrailer() { | ||||
|             if (Content == null) await AddHeader(); | ||||
|             if (Content == null) return; | ||||
|  | ||||
|             await Content.WriteAsync(""" | ||||
|                   </office:spreadsheet> | ||||
|                  </office:body> | ||||
|                 </office:document-content> | ||||
|  | ||||
|                 """); | ||||
|             Content.Close(); | ||||
|             Content.Dispose(); | ||||
|             Content = null; | ||||
|  | ||||
|             var settings = ZipArchive.CreateEntry("settings.xml"); | ||||
|             using (var writer = new StreamWriter(settings.Open(), Utils.UTF8)) { | ||||
|                 await writer.WriteAsync(""" | ||||
|                     <?xml version="1.0" encoding="UTF-8" standalone="yes"?> | ||||
|                     <office:document-settings xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ooo="http://openoffice.org/2004/office" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" office:version="1.3"> | ||||
|                      <office:settings> | ||||
|                       <config:config-item-set config:name="ooo:view-settings"> | ||||
|                        <config:config-item-map-indexed config:name="Views"> | ||||
|                         <config:config-item-map-entry> | ||||
|                          <config:config-item-map-named config:name="Tables"> | ||||
|  | ||||
|                     """); | ||||
|  | ||||
|                 foreach (var tbl in _tables) { | ||||
|                     await writer.WriteAsync($""" | ||||
|                               <config:config-item-map-entry config:name="{tbl}"> | ||||
|                                <config:config-item config:name="VerticalSplitMode" config:type="short">2</config:config-item> | ||||
|                                <config:config-item config:name="VerticalSplitPosition" config:type="int">4</config:config-item> | ||||
|                                <config:config-item config:name="PositionBottom" config:type="int">4</config:config-item> | ||||
|                               </config:config-item-map-entry> | ||||
|  | ||||
|                         """); | ||||
|                 } | ||||
|  | ||||
|                 await writer.WriteAsync(""" | ||||
|                          </config:config-item-map-named> | ||||
|                         </config:config-item-map-entry> | ||||
|                        </config:config-item-map-indexed> | ||||
|                       </config:config-item-set> | ||||
|                      </office:settings> | ||||
|                     </office:document-settings> | ||||
|  | ||||
|                     """); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         public async Task AddTable<T>(DataTable<T> table) { | ||||
|             if (Content == null) await AddHeader(); | ||||
|             if (Content == null) return; | ||||
|             var totalSpan = table.ColumnSpans.Sum(); | ||||
|  | ||||
|             _tables.Add(table.Name); | ||||
|             await Content.WriteAsync($"    <table:table table:name=\"{table.Name}\" table:default-cell-style-name=\"default\">\r\n"); | ||||
|             foreach (var (s, w) in table.ColumnSpans.Zip(table.ColumnWidths)) { | ||||
|                 for (int i = 0; i < s; i++) { | ||||
|                     await Content.WriteAsync("     <table:table-column" + (w != null ? $" table:style-name=\"colw{w / s}\"" : "") + "/>\r\n"); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             await Content.WriteAsync( | ||||
|                 $"     <table:table-row>\r\n" + | ||||
|                 FormatCell(table.FullName, colSpan: totalSpan, style: "header") + | ||||
|                 $"     </table:table-row>\r\n" + | ||||
|                 $"     <table:table-row>\r\n" + | ||||
|                 $"      <table:table-cell table:number-columns-repeated=\"{totalSpan}\"/>\r\n" + | ||||
|                 $"     </table:table-row>\r\n" + | ||||
|                 $"     <table:table-row>\r\n"); | ||||
|             foreach (var (name, span, units) in table.ColumnNames.Zip(table.ColumnSpans, table.ColumnUnits)) { | ||||
|                 var hasUnits = units.Length > 0; | ||||
|                 await Content.WriteAsync(FormatCell(name, colSpan: span, rowSpan: hasUnits ? 1 : 2, style: "th")); | ||||
|             } | ||||
|             await Content.WriteAsync("     </table:table-row>\r\n     <table:table-row>\r\n"); | ||||
|             foreach (var (span, units) in table.ColumnSpans.Zip(table.ColumnUnits)) { | ||||
|                 if (units.Length == 0) { | ||||
|                     await Content.WriteAsync($"      <table:covered-table-cell table:number-columns-repeated=\"{span}\"/>\r\n"); | ||||
|                     continue; | ||||
|                 } | ||||
|                 foreach (var u in units) { | ||||
|                     await Content.WriteAsync(FormatCell(u == null ? null : $"[{u}]", style: "th")); | ||||
|                 } | ||||
|             } | ||||
|             await Content.WriteAsync("     </table:table-row>\r\n"); | ||||
|  | ||||
|             foreach (var row in table.GetData()) { | ||||
|                 await FormatRow(row, table.ColumnUnits); | ||||
|             } | ||||
|  | ||||
|             await Content.WriteAsync("    </table:table>\r\n"); | ||||
|         } | ||||
|  | ||||
|         protected async Task FormatRow(IEnumerable<object?> row, IEnumerable<string?[]?> colUnits) { | ||||
|             if (Content == null) throw new InvalidOperationException(); | ||||
|             var arrays = row.Where(c => c is Array).Cast<Array>().Select(c => c.Length).ToArray(); | ||||
|             int rowNum = Math.Max(1, arrays.Length > 0 ? arrays.Max() : 0); | ||||
|             for (int i = 0; i < rowNum; i++) { | ||||
|                 await Content.WriteAsync("     <table:table-row>\r\n"); | ||||
|                 foreach (var (data, units) in row.Zip(colUnits)) { | ||||
|                     if (data is Array a) { | ||||
|                         await Content.WriteAsync(i < a.Length ? FormatCell(a.GetValue(i), units: units) : $"      <table:table-cell table:number-columns-repeated=\"{GetSubCols(a.GetType().GetElementType())}\"/>\r\n"); | ||||
|                     } else { | ||||
|                         await Content.WriteAsync(FormatCell(data, rowSpan: i == 0 ? rowNum : 1, isCovered: i > 0, units: units)); | ||||
|                     } | ||||
|                 } | ||||
|                 await Content.WriteAsync("     </table:table-row>\r\n"); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         private static int GetSubCols(Type? type) { | ||||
|             if (type != null && type.IsValueType == true && type.Name.StartsWith("ValueTuple")) | ||||
|                 return type.GetFields().Length; | ||||
|             return 1; | ||||
|         } | ||||
|  | ||||
|         protected static string FormatCell(object? data, int rowSpan = 1, int colSpan = 1, string? style = "default", bool isCovered = false, string?[]? units = null) { | ||||
|             if (data?.GetType().IsValueType == true && data.GetType().Name.StartsWith("ValueTuple")) | ||||
|                 return string.Join("", data.GetType().GetFields().Zip(units ?? Array.Empty<string?>()) | ||||
|                     .Select(p => FormatCell(p.First.GetValue(data), rowSpan, colSpan, style, isCovered, new[] { p.Second })) | ||||
|                 ); | ||||
|  | ||||
|             var add = (style != null ? $" table:style-name=\"{style}\"" : "") + (rowSpan > 1 || colSpan > 1 ? $" table:number-rows-spanned=\"{rowSpan}\" table:number-columns-spanned=\"{colSpan}\"" : ""); | ||||
|             string ct = isCovered ? "table:covered-table-cell" : "table:table-cell"; | ||||
|             var isPercent = units != null && units.Length > 0 && units[0] == "%"; | ||||
|  | ||||
|             string c; | ||||
|             if (data == null) { | ||||
|                 c = $"<{ct}{add}/>"; | ||||
|             } else if (data is float || data is double || data is byte || data is char || | ||||
|                        data is short || data is ushort || data is int || data is uint || data is long || data is ulong) { | ||||
|  | ||||
|                 double v = double.Parse(data?.ToString() ?? "0"); | ||||
|                 if (isPercent) { | ||||
|                     data = $"{v:N1}"; | ||||
|                     add = string.Join(" ", add.Split(" ").Select(p => p.StartsWith("table:style-name=") ? "table:style-name=\"perc\"" : p)); | ||||
|                 } | ||||
|                 c = $"<{ct} office:value-type=\"float\" calcext:value-type=\"float\" office:value=\"{v.ToString()?.Replace(",", ".")}\"{add}><text:p>{data}</text:p></{ct}>"; | ||||
|             } else { | ||||
|                 c = $"<{ct} office:value-type=\"string\" calcext:value-type=\"string\"{add}><text:p>{data}</text:p></{ct}>"; | ||||
|             } | ||||
|  | ||||
|             return $"      {c}\r\n" + (colSpan > 1 ? $"      <table:covered-table-cell table:number-rows-repeated=\"{colSpan - 1}\"/>\r\n" : ""); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,4 +1,4 @@ | ||||
| using Elwig.Models; | ||||
| using Elwig.Models.Entities; | ||||
|  | ||||
| namespace Elwig.Helpers { | ||||
|     public interface IAddress { | ||||
|   | ||||
| @@ -10,7 +10,7 @@ using System.Net.Sockets; | ||||
| using Elwig.Dialogs; | ||||
| using System.Text; | ||||
| using System.Numerics; | ||||
| using Elwig.Models; | ||||
| using Elwig.Models.Entities; | ||||
|  | ||||
| namespace Elwig.Helpers { | ||||
|     public static partial class Utils { | ||||
|   | ||||
| @@ -2,7 +2,7 @@ using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Linq; | ||||
| using System.Windows.Controls; | ||||
| using Elwig.Models; | ||||
| using Elwig.Models.Entities; | ||||
|  | ||||
| namespace Elwig.Helpers { | ||||
|     public static class Validator { | ||||
|   | ||||
							
								
								
									
										109
									
								
								Elwig/Models/Dtos/AreaComUnderDeliveyData.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								Elwig/Models/Dtos/AreaComUnderDeliveyData.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,109 @@ | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.ComponentModel.DataAnnotations.Schema; | ||||
| using System.Linq; | ||||
| using System.Threading.Tasks; | ||||
|  | ||||
| namespace Elwig.Models.Dtos { | ||||
|     public class AreaComUnderDeliveryData : DataTable<AreaComUnderDeliveryRow> { | ||||
|  | ||||
|         private static readonly (string, string, string?, int)[] FieldNames = new[] { | ||||
|             ("MgNr", "MgNr.", null, 12), | ||||
|             ("Name", "Name", null, 40), | ||||
|             ("GivenName", "Vorname", null, 40), | ||||
|             ("Address", "Adresse", null, 60), | ||||
|             ("Plz", "PLZ", null, 10), | ||||
|             ("Locality", "Ort", null, 60), | ||||
|             ("VtrgIds", "Vertrag", null, 14), | ||||
|             ("Areas", "Fläche", "m²", 16), | ||||
|             ("DeliveryObligations", "Lieferpflicht", "kg", 22), | ||||
|             ("Weights", "Geliefert", "kg", 22), | ||||
|             ("UnderDeliveries", "Unterliefert", "kg", 22), | ||||
|             ("Percents", "Prozent", "%", 16), | ||||
|         }; | ||||
|  | ||||
|         public AreaComUnderDeliveryData(IEnumerable<AreaComUnderDeliveryRow> rows, int year) : | ||||
|             base($"Unterlieferungen FB", $"Unterlieferungen laut Flächenbindungen {year}", rows, FieldNames) { | ||||
|         } | ||||
|  | ||||
|         public static async Task<AreaComUnderDeliveryData> ForSeason(DbSet<AreaComUnderDeliveryRowSingle> table, int year) { | ||||
|             return new AreaComUnderDeliveryData( | ||||
|                 (await FromDbSet(table, year)).GroupBy( | ||||
|                     r => r.MgNr, | ||||
|                     (k, g) => new AreaComUnderDeliveryRow(g) | ||||
|               ), year); | ||||
|         } | ||||
|  | ||||
|         private static async Task<IEnumerable<AreaComUnderDeliveryRowSingle>> FromDbSet(DbSet<AreaComUnderDeliveryRowSingle> table, int year) { | ||||
|             return await table.FromSqlRaw($""" | ||||
|                     SELECT m.mgnr, m.family_name, m.given_name, p.plz, o.name AS ort, m.address, | ||||
|                             c.bucket, c.area, u.min_kg, u.weight | ||||
|                     FROM member m | ||||
|                         LEFT JOIN AT_plz_dest p ON p.id = m.postal_dest | ||||
|                         LEFT JOIN AT_ort o ON o.okz = p.okz | ||||
|                         LEFT JOIN v_area_commitment_bucket_strict c ON c.mgnr = m.mgnr AND c.year = {year} | ||||
|                         JOIN v_under_delivery u ON (u.mgnr, u.bucket, u.year) = (m.mgnr, c.bucket, c.year) | ||||
|                     WHERE m.active = 1 | ||||
|                     ORDER BY m.mgnr, c.bucket | ||||
|                     """).ToListAsync(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public class AreaComUnderDeliveryRow { | ||||
|         public int MgNr; | ||||
|         public string Name; | ||||
|         public string GivenName; | ||||
|         public string Address; | ||||
|         public int Plz; | ||||
|         public string Locality; | ||||
|         public string[] VtrgIds; | ||||
|         public int[] Areas; | ||||
|         public int[] DeliveryObligations; | ||||
|         public int[] Weights; | ||||
|         public int?[] UnderDeliveries => Weights.Zip(DeliveryObligations) | ||||
|             .Select(v => v.First < v.Second ? (int?)v.First - v.Second : null) | ||||
|             .ToArray(); | ||||
|         public double?[] Percents => Weights.Zip(DeliveryObligations) | ||||
|             .Select(v => v.First < v.Second ? (double?)v.First * 100.0 / v.Second - 100.0 : null) | ||||
|             .ToArray(); | ||||
|  | ||||
|         public AreaComUnderDeliveryRow(IEnumerable<AreaComUnderDeliveryRowSingle> rows) { | ||||
|             var f = rows.First(); | ||||
|             MgNr = f.MgNr; | ||||
|             Name = f.Name; | ||||
|             GivenName = f.GivenName; | ||||
|             Address = f.Address; | ||||
|             Plz = f.Plz; | ||||
|             Locality = f.Locality.Split(",")[0]; | ||||
|             VtrgIds = rows.Select(r => r.VtrgId).ToArray(); | ||||
|             Areas = rows.Select(r => r.Area).ToArray(); | ||||
|             DeliveryObligations = rows.Select(r => r.DeliveryObligation).ToArray(); | ||||
|             Weights = rows.Select(r => r.Weight).ToArray(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     [Keyless] | ||||
|     public class AreaComUnderDeliveryRowSingle { | ||||
|         [Column("mgnr")] | ||||
|         public int MgNr { get; set; } | ||||
|         [Column("family_name")] | ||||
|         public string Name { get; set; } | ||||
|         [Column("given_name")] | ||||
|         public string GivenName { get; set; } | ||||
|         [Column("address")] | ||||
|         public string Address { get; set; } | ||||
|         [Column("plz")] | ||||
|         public int Plz { get; set; } | ||||
|         [Column("ort")] | ||||
|         public string Locality { get; set; } | ||||
|         [Column("bucket")] | ||||
|         public string VtrgId { get; set; } | ||||
|         [Column("area")] | ||||
|         public int Area { get; set; } | ||||
|         [Column("min_kg")] | ||||
|         public int DeliveryObligation { get; set; } | ||||
|         [Column("weight")] | ||||
|         public int Weight { get; set; } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										86
									
								
								Elwig/Models/Dtos/DataTable.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								Elwig/Models/Dtos/DataTable.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,86 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Linq; | ||||
| using System.Reflection; | ||||
|  | ||||
| namespace Elwig.Models.Dtos { | ||||
|     public class DataTable<T> { | ||||
|  | ||||
|         public string Name { get; set; } | ||||
|         public string FullName { get; set; } | ||||
|         public IEnumerable<T> Rows { get; private set; } | ||||
|         public int RowNum => Rows.Count(); | ||||
|         public int ColNum => ColumnNames.Count(); | ||||
|  | ||||
|         public IEnumerable<(string, Type?)> ColumnDefs => _map.Select(m => (m.Item1, m.Item2?.PropertyType ?? m.Item3?.FieldType)); | ||||
|         public IEnumerable<string> ColumnNames => ColumnDefs.Select(m => m.Item1); | ||||
|         public IEnumerable<Type?> ColumnTypes => ColumnDefs.Select(m => m.Item2); | ||||
|         public IEnumerable<Type?> ColumnFlatTypes { get; private set; } | ||||
|         public IEnumerable<int> ColumnSpans { get; private set; } | ||||
|         public IEnumerable<int?> ColumnWidths { get; private set; } | ||||
|         public IEnumerable<string?[]> ColumnUnits { get; private set; } | ||||
|  | ||||
|         private readonly PropertyInfo[] _properties; | ||||
|         private readonly FieldInfo[] _fields; | ||||
|         private readonly (string, PropertyInfo?, FieldInfo?)[] _map; | ||||
|  | ||||
|         public DataTable(string name, string fullName, IEnumerable<T> rows, IEnumerable<(string, string, string?, int?)>? colNames = null) { | ||||
|             _fields = typeof(T).GetFields(); | ||||
|             _properties = typeof(T).GetProperties(); | ||||
|             colNames ??= _properties.Select(p => p.Name).Union(_fields.Select(f => f.Name)).Select(i => (i, i, (string?)null, (int?)null)).ToList(); | ||||
|             _map = colNames.Select(n => (n.Item2, _properties.FirstOrDefault(p => p?.Name == n.Item1, null), _fields.FirstOrDefault(f => f?.Name == n.Item1, null))).ToArray(); | ||||
|             Name = name; | ||||
|             FullName = fullName; | ||||
|             Rows = rows; | ||||
|             ColumnFlatTypes = ColumnTypes.SelectMany(type => { | ||||
|                 var elType = type?.GetElementType(); | ||||
|                 return type != null && type.IsValueType && type.Name.StartsWith("ValueTuple") ? type.GetFields().Select(f => f.FieldType) : | ||||
|                        type != null && elType != null && type.IsArray && elType.IsValueType && elType.Name.StartsWith("ValueTuple") ? elType.GetFields().Select(f => f.FieldType) : | ||||
|                        new Type?[] { type }; | ||||
|             }).ToList(); | ||||
|             ColumnSpans = ColumnTypes.Select(type => { | ||||
|                 var elType = type?.GetElementType(); | ||||
|                 return type != null && type.IsValueType && type.Name.StartsWith("ValueTuple") ? type.GetFields().Length : | ||||
|                        type != null && elType != null && type.IsArray && elType.IsValueType && elType.Name.StartsWith("ValueTuple") ? elType.GetFields().Length : 1; | ||||
|             }).ToList(); | ||||
|             ColumnWidths = colNames.Select(c => c.Item4).ToList(); | ||||
|             ColumnUnits = colNames.Select(c => c.Item3?.Split("|").Select(p => p.Length == 0 ? null : p).ToArray() ?? Array.Empty<string?>()).ToList(); | ||||
|         } | ||||
|  | ||||
|         public DataTable(string name, string fullName, IEnumerable<T> rows, IEnumerable<(string, string, string?)>? colNames = null) : | ||||
|             this(name, fullName, rows, colNames?.Select(c => (c.Item1, c.Item2, c.Item3, (int?)null))) { | ||||
|         } | ||||
|  | ||||
|         public DataTable(string name, IEnumerable<T> rows, IEnumerable<(string, string, string?)>? colNames = null) : | ||||
|             this(name, name, rows, colNames) { | ||||
|         } | ||||
|  | ||||
|         public DataTable(string name, IEnumerable<T> rows, IEnumerable<(string, string, string?, int?)>? colNames = null) : | ||||
|             this(name, name, rows, colNames) { | ||||
|         } | ||||
|  | ||||
|         public DataTable(string name, IEnumerable<T> rows, IEnumerable<(string, string, string?, int)>? colNames = null) : | ||||
|             this(name, name, rows, colNames) { | ||||
|         } | ||||
|  | ||||
|         public DataTable(string name, string fullName, IEnumerable<T> rows, IEnumerable<(string, string, string?, int)>? colNames = null) : | ||||
|             this(name, fullName, rows, colNames?.Select(c => (c.Item1, c.Item2, c.Item3, (int?)c.Item4))) { | ||||
|         } | ||||
|  | ||||
|         protected IEnumerable<(string, object?)> GetNamedRowData(T row) { | ||||
|             return _map.Select(i => (i.Item1, i.Item2?.GetValue(row) ?? i.Item3?.GetValue(row))); | ||||
|         } | ||||
|  | ||||
|         protected IEnumerable<object?> GetRowData(T row) { | ||||
|             return GetNamedRowData(row).Select(i => i.Item2); | ||||
|         } | ||||
|  | ||||
|         public IEnumerable<IEnumerable<object?>> GetData() { | ||||
|             return Rows.Select(GetRowData); | ||||
|         } | ||||
|  | ||||
|         public IEnumerable<IEnumerable<(string, object?)>> GetNamedData() { | ||||
|             return Rows.Select(GetNamedRowData); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										97
									
								
								Elwig/Models/Dtos/DeliveryConfirmationData.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								Elwig/Models/Dtos/DeliveryConfirmationData.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,97 @@ | ||||
| using Elwig.Models.Entities; | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using System.Collections.Generic; | ||||
| using System.Linq; | ||||
| using System.Threading.Tasks; | ||||
|  | ||||
| namespace Elwig.Models.Dtos { | ||||
|     public class DeliveryConfirmationData : DataTable<DeliveryConfirmationRow> { | ||||
|  | ||||
|         private static readonly (string, string, string?, int)[] FieldNames = new[] { | ||||
|             ("LsNr", "LsNr.", null, 26), | ||||
|             ("DPNr", "Pos.", null, 8), | ||||
|             ("Variant", "Sorte", null, 40), | ||||
|             ("Attribute", "Attribut", null, 20), | ||||
|             ("Modifiers", "Zu-/Abschläge", null, 30), | ||||
|             ("QualityLevel", "Qualitätsstufe", null, 25), | ||||
|             ("Gradation", "Gradation", "°Oe|°KMW", 32), | ||||
|             ("Buckets", "Flächenbindung", "|kg", 36), | ||||
|             ("Weight", "Gewicht", "kg", 16), | ||||
|         }; | ||||
|  | ||||
|         private readonly int MgNr; | ||||
|  | ||||
|         private DeliveryConfirmationData(IEnumerable<DeliveryConfirmationRow> rows, int year, Member m) : | ||||
|             base($"Anlieferungsbestätigung", $"Anlieferungsbestätigung {year} – {m.AdministrativeName}", rows, FieldNames) { | ||||
|             MgNr = m.MgNr; | ||||
|         } | ||||
|  | ||||
|         public static async Task<IDictionary<int, DeliveryConfirmationData>> ForSeason(DbSet<DeliveryPart> table, int year) { | ||||
|             return (await FromDbSet(table, year)) | ||||
|                 .GroupBy( | ||||
|                     p => p.Delivery.Member, | ||||
|                     p => new DeliveryConfirmationRow(p), | ||||
|                     (k, g) => new DeliveryConfirmationData(g, year, k) | ||||
|                 ).ToDictionary(d => d.MgNr, d => d); | ||||
|         } | ||||
|  | ||||
|         public static async Task<DeliveryConfirmationData> ForMember(DbSet<DeliveryPart> table, int year, Member m) { | ||||
|             return new DeliveryConfirmationData((await FromDbSet(table, year, m.MgNr)).Select(p => new DeliveryConfirmationRow(p)), year, m); | ||||
|         } | ||||
|  | ||||
|         private static async Task<IEnumerable<DeliveryPart>> FromDbSet(DbSet<DeliveryPart> table, int? year = null, int? mgnr = null) { | ||||
|             var y = year?.ToString() ?? "NULL"; | ||||
|             var m = mgnr?.ToString() ?? "NULL"; | ||||
|             IQueryable<DeliveryPart> q = table; | ||||
|             if (year != null) q = q.Where(p => p.Year == year); | ||||
|             if (mgnr != null) q = q.Where(p => p.Delivery.MgNr == mgnr); | ||||
|             await q | ||||
|                  .Include(p => p.Delivery) | ||||
|                  .Include(p => p.Variant) | ||||
|                  .Include(p => p.Attribute) | ||||
|                  .Include(p => p.Quality) | ||||
|                  .Include(p => p.Buckets) | ||||
|                  .Include(p => p.PartModifiers) | ||||
|                  .ThenInclude(m => m.Modifier) | ||||
|                  .LoadAsync(); | ||||
|             return await table.FromSqlRaw($""" | ||||
|                     SELECT p.* | ||||
|                     FROM v_delivery v | ||||
|                         JOIN delivery_part p ON (p.year, p.did, p.dpnr) = (v.year, v.did, v.dpnr) | ||||
|                     WHERE (p.year = {y} OR {y} IS NULL) AND (v.mgnr = {m} OR {m} IS NULL) | ||||
|                     ORDER BY p.year, v.mgnr, v.sortid, v.abgewertet ASC, v.attribute_prio DESC, COALESCE(v.attrid, '~'), v.kmw DESC, v.lsnr, v.dpnr | ||||
|                     """).ToListAsync(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public class DeliveryConfirmationRow { | ||||
|         public string LsNr; | ||||
|         public int DPNr; | ||||
|         public string Variant; | ||||
|         public string? Attribute; | ||||
|         public string QualityLevel; | ||||
|         public (double Oe, double Kmw) Gradation; | ||||
|         public string[] Modifiers; | ||||
|         public int Weight; | ||||
|         public (string Name, int Value)[] Buckets; | ||||
|  | ||||
|         public DeliveryConfirmationRow(DeliveryPart p) { | ||||
|             var d = p.Delivery; | ||||
|             LsNr = d.LsNr; | ||||
|             DPNr = p.DPNr; | ||||
|             Variant = p.Variant.Name; | ||||
|             Attribute = p.Attribute?.Name; | ||||
|             QualityLevel = p.Quality.Name; | ||||
|             Gradation = (p.Oe, p.Kmw); | ||||
|             Modifiers = p.Modifiers | ||||
|                 .Select(m => m.Name) | ||||
|                 .ToArray(); | ||||
|             Weight = p.Weight; | ||||
|             Buckets = p.Buckets | ||||
|                 .Where(b => b.Value > 0) | ||||
|                 .OrderByDescending(b => b.BktNr) | ||||
|                 .Select(b => (b.Discr == "_" ? "ungeb." : $"geb. {p.SortId}{b.Discr}", b.Value)) | ||||
|                 .ToArray(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										118
									
								
								Elwig/Models/Dtos/MemberDeliveryPerVariantData.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								Elwig/Models/Dtos/MemberDeliveryPerVariantData.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,118 @@ | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.ComponentModel.DataAnnotations.Schema; | ||||
| using System.Linq; | ||||
| using System.Threading.Tasks; | ||||
|  | ||||
| namespace Elwig.Models.Dtos { | ||||
|     public class MemberDeliveryPerVariantData : DataTable<MemberDeliveryPerVariantRow> { | ||||
|  | ||||
|         private static readonly (string, string, string?, int)[] FieldNames = new[] { | ||||
|             ("MgNr", "MgNr.", null, 12), | ||||
|             ("Name", "Name", null, 40), | ||||
|             ("GivenName", "Vorname", null, 40), | ||||
|             ("Address", "Adresse", null, 60), | ||||
|             ("Plz", "PLZ", null, 10), | ||||
|             ("Locality", "Ort", null, 60), | ||||
|             ("SortIds", "Sorte", null, 12), | ||||
|             ("AttrIds", "Attribut", null, 16), | ||||
|             ("Weights", "Geliefert", "kg", 22), | ||||
|             ("Areas", "Fläche", "m²", 22), | ||||
|             ("Yields", "Ertrag", "kg/ha", 22), | ||||
|         }; | ||||
|  | ||||
|  | ||||
|         public MemberDeliveryPerVariantData(IEnumerable<MemberDeliveryPerVariantRow> rows, int year) : | ||||
|             base($"Liefermengen", $"Liefermengen pro Mitglied, Sorte und Attribut {year}", rows, FieldNames) { | ||||
|         } | ||||
|  | ||||
|         public static async Task<MemberDeliveryPerVariantData> ForSeason(DbSet<MemberDeliveryPerVariantRowSingle> table, int year) { | ||||
|             return new MemberDeliveryPerVariantData( | ||||
|                (await FromDbSet(table, year)).GroupBy( | ||||
|                    r => r.MgNr, | ||||
|                    (k, g) => new MemberDeliveryPerVariantRow(g) | ||||
|              ), year); | ||||
|         } | ||||
|  | ||||
|         private static async Task<IEnumerable<MemberDeliveryPerVariantRowSingle>> FromDbSet(DbSet<MemberDeliveryPerVariantRowSingle> table, int year) { | ||||
|             return await table.FromSqlRaw($""" | ||||
|                     SELECT m.mgnr, m.family_name, m.given_name, p.plz, o.name AS ort, m.address, | ||||
|                            v.bucket, v.weight, v.area | ||||
|                     FROM ( | ||||
|                             SELECT c.year AS year, | ||||
|                                    c.mgnr AS mgnr, | ||||
|                                    c.bucket AS bucket, | ||||
|                                    COALESCE(d.weight, 0) AS weight, | ||||
|                                    COALESCE(c.area, 0) AS area | ||||
|                             FROM v_area_commitment_bucket_strict c | ||||
|                                 LEFT JOIN v_delivery_bucket_strict d ON (d.year, d.mgnr, d.bucket) = (c.year, c.mgnr, c.bucket) | ||||
|                             WHERE c.year = {year} | ||||
|                             UNION | ||||
|                             SELECT d.year, | ||||
|                                    d.mgnr, | ||||
|                                    d.bucket, | ||||
|                                    COALESCE(d.weight, 0), | ||||
|                                    COALESCE(c.area, 0) | ||||
|                             FROM v_delivery_bucket_strict d | ||||
|                                 LEFT JOIN v_area_commitment_bucket_strict c ON (c.year, c.mgnr, c.bucket) = (d.year, d.mgnr, d.bucket) | ||||
|                             WHERE d.year = {year} | ||||
|                         ) v | ||||
|                         LEFT JOIN member m ON m.mgnr = v.mgnr | ||||
|                         LEFT JOIN AT_plz_dest p ON p.id = m.postal_dest | ||||
|                         LEFT JOIN AT_ort o ON o.okz = p.okz | ||||
|                     ORDER BY m.mgnr, v.bucket | ||||
|                     """).ToListAsync(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public class MemberDeliveryPerVariantRow { | ||||
|         public int MgNr; | ||||
|         public string Name; | ||||
|         public string GivenName; | ||||
|         public string Address; | ||||
|         public int Plz; | ||||
|         public string Locality; | ||||
|         public string[] SortIds; | ||||
|         public string[] AttrIds; | ||||
|         public int[] Areas; | ||||
|         public int[] Weights; | ||||
|         public int?[] Yields => Weights.Zip(Areas).Select(i => i.Second > 0 ? (int?)i.First * 10_000 / i.Second : null).ToArray(); | ||||
|  | ||||
|         public MemberDeliveryPerVariantRow(IEnumerable<MemberDeliveryPerVariantRowSingle> rows) { | ||||
|             var f = rows.First(); | ||||
|             MgNr = f.MgNr; | ||||
|             Name = f.Name; | ||||
|             GivenName = f.GivenName; | ||||
|             Address = f.Address; | ||||
|             Plz = f.Plz; | ||||
|             Locality = f.Locality.Split(",")[0]; | ||||
|             SortIds = rows.Select(r => r.VtrgId[..2]).ToArray(); | ||||
|             AttrIds = rows.Select(r => r.VtrgId[2..]).ToArray(); | ||||
|             Areas = rows.Select(r => r.Area).ToArray(); | ||||
|             Weights = rows.Select(r => r.Weight).ToArray(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     [Keyless] | ||||
|     public class MemberDeliveryPerVariantRowSingle { | ||||
|         [Column("mgnr")] | ||||
|         public int MgNr { get; set; } | ||||
|         [Column("family_name")] | ||||
|         public string Name { get; set; } | ||||
|         [Column("given_name")] | ||||
|         public string GivenName { get; set; } | ||||
|         [Column("address")] | ||||
|         public string Address { get; set; } | ||||
|         [Column("plz")] | ||||
|         public int Plz { get; set; } | ||||
|         [Column("ort")] | ||||
|         public string Locality { get; set; } | ||||
|         [Column("bucket")] | ||||
|         public string VtrgId { get; set; } | ||||
|         [Column("area")] | ||||
|         public int Area { get; set; } | ||||
|         [Column("weight")] | ||||
|         public int Weight { get; set; } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										81
									
								
								Elwig/Models/Dtos/OverUnderDeliveryData.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								Elwig/Models/Dtos/OverUnderDeliveryData.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,81 @@ | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.ComponentModel.DataAnnotations.Schema; | ||||
| using System.Threading.Tasks; | ||||
|  | ||||
| namespace Elwig.Models.Dtos { | ||||
|     public class OverUnderDeliveryData : DataTable<OverUnderDeliveryRow> { | ||||
|  | ||||
|         private static readonly (string, string, string?, int)[] FieldNames = new[] { | ||||
|             ("MgNr", "MgNr.", null, 12), | ||||
|             ("Name", "Name", null, 40), | ||||
|             ("GivenName", "Vorname", null, 40), | ||||
|             ("Address", "Adresse", null, 60), | ||||
|             ("Plz", "PLZ", null, 10), | ||||
|             ("Locality", "Ort", null, 60), | ||||
|             ("BusinessShares", "GA", null, 10), | ||||
|             ("DeliveryObligation", "Lieferpflicht", "kg", 22), | ||||
|             ("DeliveryRight", "Lieferrecht", "kg", 22), | ||||
|             ("Weight", "Geliefert", "kg", 22), | ||||
|             ("OverUnderDelivery", "Über-/Unterliefert", "kg", 35), | ||||
|             ("Percent", "Prozent", "%", 16), | ||||
|         }; | ||||
|  | ||||
|         public OverUnderDeliveryData(IEnumerable<OverUnderDeliveryRow> rows, int year) : | ||||
|             base($"Über-Unterlieferungen", $"Über- und Unterlieferungen laut gezeichneten Geschäftsanteilen {year}", rows, FieldNames) { | ||||
|         } | ||||
|  | ||||
|         public static async Task<OverUnderDeliveryData> ForSeason(DbSet<OverUnderDeliveryRow> table, int year) { | ||||
|             var rows = await table.FromSqlRaw($""" | ||||
|                 SELECT m.mgnr, m.family_name, m.given_name, p.plz, o.name AS ort, m.address, m.business_shares, | ||||
|                        m.business_shares * s.min_kg_per_bs AS min_kg, | ||||
|                        m.business_shares * s.max_kg_per_bs AS max_kg, | ||||
|                        COALESCE(SUM(d.weight), 0) AS sum | ||||
|                 FROM member m | ||||
|                     LEFT JOIN AT_plz_dest p ON p.id = m.postal_dest | ||||
|                     LEFT JOIN AT_ort o ON o.okz = p.okz | ||||
|                     LEFT JOIN season s ON s.year = {year} | ||||
|                     LEFT JOIN v_delivery d ON d.mgnr = m.mgnr AND d.year = s.year | ||||
|                 WHERE m.active = 1 | ||||
|                 GROUP BY d.year, m.mgnr | ||||
|                 ORDER BY 100.0 * sum / max_kg, m.mgnr; | ||||
|                 """).ToListAsync(); | ||||
|             return new OverUnderDeliveryData(rows, year); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     [Keyless] | ||||
|     public class OverUnderDeliveryRow { | ||||
|         [Column("mgnr")] | ||||
|         public int MgNr { get; set; } | ||||
|         [Column("family_name")] | ||||
|         public string Name { get; set; } | ||||
|         [Column("given_name")] | ||||
|         public string GivenName { get; set; } | ||||
|         [Column("address")] | ||||
|         public string Address { get; set; } | ||||
|         [Column("plz")] | ||||
|         public int Plz { get; set; } | ||||
|         [Column("ort")] | ||||
|         public string LocalityFull { get; set; } | ||||
|         [NotMapped] | ||||
|         public string Locality => LocalityFull.Split(",")[0]; | ||||
|         [Column("business_shares")] | ||||
|         public int BusinessShares { get; set; } | ||||
|         [Column("min_kg")] | ||||
|         public int DeliveryObligation { get; set; } | ||||
|         [Column("max_kg")] | ||||
|         public int DeliveryRight { get; set; } | ||||
|         [Column("sum")] | ||||
|         public int Weight { get; set; } | ||||
|         [NotMapped] | ||||
|         public int? OverUnderDelivery => | ||||
|             Weight < DeliveryObligation ? Weight - DeliveryObligation : | ||||
|             Weight > DeliveryRight ? Weight - DeliveryRight : null; | ||||
|         [NotMapped] | ||||
|         public double? Percent => | ||||
|             Weight < DeliveryObligation ? Weight * 100.0 / DeliveryObligation - 100.0 : | ||||
|             Weight > DeliveryRight ? Weight * 100.0 / DeliveryRight - 100 : null; | ||||
|     } | ||||
| } | ||||
| @@ -2,7 +2,7 @@ using Microsoft.EntityFrameworkCore; | ||||
| using System.Collections.Generic; | ||||
| using System.ComponentModel.DataAnnotations.Schema; | ||||
| 
 | ||||
| namespace Elwig.Models { | ||||
| namespace Elwig.Models.Entities { | ||||
|     [Table("AT_gem"), PrimaryKey("Gkz")] | ||||
|     public class AT_Gem { | ||||
|         [Column("gkz")] | ||||
| @@ -1,7 +1,7 @@ | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using System.ComponentModel.DataAnnotations.Schema; | ||||
| 
 | ||||
| namespace Elwig.Models { | ||||
| namespace Elwig.Models.Entities { | ||||
|     [Table("AT_kg"), PrimaryKey("KgNr")] | ||||
|     public class AT_Kg { | ||||
|         [Column("kgnr")] | ||||
| @@ -1,7 +1,7 @@ | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using System.ComponentModel.DataAnnotations.Schema; | ||||
| 
 | ||||
| namespace Elwig.Models { | ||||
| namespace Elwig.Models.Entities { | ||||
|     [Table("AT_ort"), PrimaryKey("Okz")] | ||||
|     public class AT_Ort { | ||||
|         [Column("okz")] | ||||
| @@ -2,7 +2,7 @@ using Microsoft.EntityFrameworkCore; | ||||
| using System.Collections.Generic; | ||||
| using System.ComponentModel.DataAnnotations.Schema; | ||||
| 
 | ||||
| namespace Elwig.Models { | ||||
| namespace Elwig.Models.Entities { | ||||
|     [Table("AT_plz"), PrimaryKey("Plz")] | ||||
|     public class AT_Plz { | ||||
|         [Column("plz")] | ||||
| @@ -2,7 +2,7 @@ using Microsoft.EntityFrameworkCore; | ||||
| using System.ComponentModel.DataAnnotations.Schema; | ||||
| using IndexAttribute = Microsoft.EntityFrameworkCore.IndexAttribute; | ||||
| 
 | ||||
| namespace Elwig.Models { | ||||
| namespace Elwig.Models.Entities { | ||||
|     [Table("AT_plz_dest"), PrimaryKey("Id"), Index("Plz", "Okz", IsUnique = true)] | ||||
|     public class AT_PlzDest { | ||||
|         [Column("plz")] | ||||
| @@ -1,11 +1,10 @@ | ||||
| using Elwig.Helpers; | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using System.Collections; | ||||
| using System.Collections.Generic; | ||||
| using System.ComponentModel.DataAnnotations.Schema; | ||||
| using System.Linq; | ||||
| 
 | ||||
| namespace Elwig.Models { | ||||
| namespace Elwig.Models.Entities { | ||||
|     [Table("area_commitment"), PrimaryKey("FbNr")] | ||||
|     public class AreaCom { | ||||
|         [Column("fbnr")] | ||||
| @@ -3,7 +3,7 @@ using Microsoft.EntityFrameworkCore; | ||||
| using System.ComponentModel.DataAnnotations.Schema; | ||||
| using IndexAttribute = Microsoft.EntityFrameworkCore.IndexAttribute; | ||||
| 
 | ||||
| namespace Elwig.Models { | ||||
| namespace Elwig.Models.Entities { | ||||
|     [Table("area_commitment_type"), PrimaryKey("VtrgId"), Index("SortId", "AttrId", "Discriminator", IsUnique = true)] | ||||
|     public class AreaComType { | ||||
|         [Column("vtrgid")] | ||||
| @@ -2,7 +2,7 @@ using Elwig.Helpers; | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using System.ComponentModel.DataAnnotations.Schema; | ||||
| 
 | ||||
| namespace Elwig.Models { | ||||
| namespace Elwig.Models.Entities { | ||||
|     [Table("member_billing_address"), PrimaryKey("MgNr")] | ||||
|     public class BillingAddr : IAddress { | ||||
|         [Column("mgnr")] | ||||
| @@ -3,7 +3,7 @@ using System.Collections.Generic; | ||||
| using System.ComponentModel.DataAnnotations.Schema; | ||||
| using IndexAttribute = Microsoft.EntityFrameworkCore.IndexAttribute; | ||||
| 
 | ||||
| namespace Elwig.Models { | ||||
| namespace Elwig.Models.Entities { | ||||
|     [Table("branch"), PrimaryKey("ZwstId"), Index("Name", IsUnique = true)] | ||||
|     public class Branch { | ||||
|         [Column("zwstid")] | ||||
| @@ -1,7 +1,7 @@ | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using System.ComponentModel.DataAnnotations.Schema; | ||||
| 
 | ||||
| namespace Elwig.Models { | ||||
| namespace Elwig.Models.Entities { | ||||
|     [Table("client_parameter"), PrimaryKey("Param")] | ||||
|     public class ClientParam { | ||||
|         [Column("param")] | ||||
| @@ -2,7 +2,7 @@ using Microsoft.EntityFrameworkCore; | ||||
| using System.ComponentModel.DataAnnotations.Schema; | ||||
| using IndexAttribute = Microsoft.EntityFrameworkCore.IndexAttribute; | ||||
| 
 | ||||
| namespace Elwig.Models { | ||||
| namespace Elwig.Models.Entities { | ||||
|     [Table("country"), PrimaryKey("Num"), Index("Alpha2", IsUnique = true), Index("Alpha3", IsUnique = true)] | ||||
|     public class Country { | ||||
|         [Column("num")] | ||||
| @@ -4,7 +4,7 @@ using System; | ||||
| using System.ComponentModel.DataAnnotations.Schema; | ||||
| using IndexAttribute = Microsoft.EntityFrameworkCore.IndexAttribute; | ||||
| 
 | ||||
| namespace Elwig.Models { | ||||
| namespace Elwig.Models.Entities { | ||||
|     [Table("credit"), PrimaryKey("Year", "TgNr"), Index("Year", "AvNr", "MgNr", IsUnique = true)] | ||||
|     public class Credit { | ||||
|         [Column("year")] | ||||
| @@ -2,7 +2,7 @@ using Elwig.Helpers; | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using System.ComponentModel.DataAnnotations.Schema; | ||||
| 
 | ||||
| namespace Elwig.Models { | ||||
| namespace Elwig.Models.Entities { | ||||
|     [Table("currency"), PrimaryKey("Code")] | ||||
|     public class Currency { | ||||
|         [Column("code")] | ||||
| @@ -6,7 +6,7 @@ using System.ComponentModel.DataAnnotations.Schema; | ||||
| using System.Linq; | ||||
| using IndexAttribute = Microsoft.EntityFrameworkCore.IndexAttribute; | ||||
| 
 | ||||
| namespace Elwig.Models { | ||||
| namespace Elwig.Models.Entities { | ||||
|     [Table("delivery"), PrimaryKey("Year", "DId"), Index("DateString", "ZwstId", "LNr", IsUnique = true), Index("LsNr", IsUnique = true)] | ||||
|     public class Delivery { | ||||
|         [Column("year")] | ||||
| @@ -4,7 +4,7 @@ using System.Collections.Generic; | ||||
| using System.ComponentModel.DataAnnotations.Schema; | ||||
| using System.Linq; | ||||
| 
 | ||||
| namespace Elwig.Models { | ||||
| namespace Elwig.Models.Entities { | ||||
|     [Table("delivery_part"), PrimaryKey("Year", "DId", "DPNr")] | ||||
|     public class DeliveryPart { | ||||
|         [Column("year")] | ||||
| @@ -1,7 +1,7 @@ | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using System.ComponentModel.DataAnnotations.Schema; | ||||
| 
 | ||||
| namespace Elwig.Models { | ||||
| namespace Elwig.Models.Entities { | ||||
|     [Table("delivery_part_bucket"), PrimaryKey("Year", "DId", "DPNr", "BktNr")] | ||||
|     public class DeliveryPartBucket { | ||||
|         [Column("year")] | ||||
| @@ -1,7 +1,7 @@ | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using System.ComponentModel.DataAnnotations.Schema; | ||||
| 
 | ||||
| namespace Elwig.Models { | ||||
| namespace Elwig.Models.Entities { | ||||
|     [Table("delivery_part_modifier"), PrimaryKey("Year", "DId", "DPNr", "ModId")] | ||||
|     public class DeliveryPartModifier { | ||||
|         [Column("year")] | ||||
| @@ -5,7 +5,7 @@ using System.Collections.Generic; | ||||
| using System.ComponentModel.DataAnnotations.Schema; | ||||
| using System.Linq; | ||||
| 
 | ||||
| namespace Elwig.Models { | ||||
| namespace Elwig.Models.Entities { | ||||
|     [Table("member"), PrimaryKey("MgNr")] | ||||
|     public class Member : IAddress { | ||||
|         [Column("mgnr")] | ||||
| @@ -1,7 +1,7 @@ | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using System.ComponentModel.DataAnnotations.Schema; | ||||
| 
 | ||||
| namespace Elwig.Models { | ||||
| namespace Elwig.Models.Entities { | ||||
|     [Table("member_email_address"), PrimaryKey("MgNr", "Nr")] | ||||
|     public class MemberEmailAddr { | ||||
|         [Column("mgnr")] | ||||
| @@ -1,7 +1,7 @@ | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using System.ComponentModel.DataAnnotations.Schema; | ||||
| 
 | ||||
| namespace Elwig.Models { | ||||
| namespace Elwig.Models.Entities { | ||||
|     [Table("member_telephone_number"), PrimaryKey("MgNr", "Nr")] | ||||
|     public class MemberTelNr { | ||||
|         [Column("mgnr")] | ||||
| @@ -4,7 +4,7 @@ using System; | ||||
| using System.ComponentModel.DataAnnotations.Schema; | ||||
| using System.Linq; | ||||
| 
 | ||||
| namespace Elwig.Models { | ||||
| namespace Elwig.Models.Entities { | ||||
|     [Table("modifier"), PrimaryKey("Year", "ModId")] | ||||
|     public class Modifier { | ||||
|         [Column("year")] | ||||
| @@ -1,7 +1,7 @@ | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using System.ComponentModel.DataAnnotations.Schema; | ||||
| 
 | ||||
| namespace Elwig.Models { | ||||
| namespace Elwig.Models.Entities { | ||||
|     [Table("payment_delivery_part"), PrimaryKey("Year", "DId", "DPNr", "AvNr")] | ||||
|     public class PaymentDeliveryPart { | ||||
|         [Column("year")] | ||||
| @@ -1,7 +1,7 @@ | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using System.ComponentModel.DataAnnotations.Schema; | ||||
| 
 | ||||
| namespace Elwig.Models { | ||||
| namespace Elwig.Models.Entities { | ||||
|     [Table("payment_delivery_part_bucket"), PrimaryKey("Year", "DId", "DPNr", "BktNr", "AvNr")] | ||||
|     public class PaymentDeliveryPartBucket { | ||||
|         [Column("year")] | ||||
| @@ -1,7 +1,7 @@ | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using System.ComponentModel.DataAnnotations.Schema; | ||||
| 
 | ||||
| namespace Elwig.Models { | ||||
| namespace Elwig.Models.Entities { | ||||
|     [Table("payment_member"), PrimaryKey("Year", "AvNr", "MgNr")] | ||||
|     public class PaymentMember { | ||||
|         [Column("year")] | ||||
| @@ -2,9 +2,8 @@ using Microsoft.EntityFrameworkCore; | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.ComponentModel.DataAnnotations.Schema; | ||||
| using System.Linq; | ||||
| 
 | ||||
| namespace Elwig.Models { | ||||
| namespace Elwig.Models.Entities { | ||||
|     [Table("payment_variant"), PrimaryKey("Year", "AvNr")] | ||||
|     public class PaymentVar { | ||||
|         [Column("year")] | ||||
| @@ -1,7 +1,7 @@ | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using System.ComponentModel.DataAnnotations.Schema; | ||||
| 
 | ||||
| namespace Elwig.Models { | ||||
| namespace Elwig.Models.Entities { | ||||
|     [Table("postal_dest"), PrimaryKey("CountryNum", "Id")] | ||||
|     public class PostalDest { | ||||
|         [Column("country")] | ||||
| @@ -4,7 +4,7 @@ using System; | ||||
| using System.Collections.Generic; | ||||
| using System.ComponentModel.DataAnnotations.Schema; | ||||
| 
 | ||||
| namespace Elwig.Models { | ||||
| namespace Elwig.Models.Entities { | ||||
|     [Table("season"), PrimaryKey("Year")] | ||||
|     public class Season { | ||||
|         [Column("year")] | ||||
| @@ -1,7 +1,7 @@ | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using System.ComponentModel.DataAnnotations.Schema; | ||||
| 
 | ||||
| namespace Elwig.Models { | ||||
| namespace Elwig.Models.Entities { | ||||
|     [Table("wb_gem"), PrimaryKey("Gkz")] | ||||
|     public class WbGem { | ||||
|         [Column("gkz")] | ||||
| @@ -2,7 +2,7 @@ using Microsoft.EntityFrameworkCore; | ||||
| using System.Collections.Generic; | ||||
| using System.ComponentModel.DataAnnotations.Schema; | ||||
| 
 | ||||
| namespace Elwig.Models { | ||||
| namespace Elwig.Models.Entities { | ||||
|     [Table("wb_gl"), PrimaryKey("GlNr")] | ||||
|     public class WbGl { | ||||
|         [Column("glnr")] | ||||
| @@ -2,7 +2,7 @@ using Microsoft.EntityFrameworkCore; | ||||
| using System.Collections.Generic; | ||||
| using System.ComponentModel.DataAnnotations.Schema; | ||||
| 
 | ||||
| namespace Elwig.Models { | ||||
| namespace Elwig.Models.Entities { | ||||
|     [Table("wb_kg"), PrimaryKey("KgNr")] | ||||
|     public class WbKg { | ||||
|         [Column("kgnr")] | ||||
| @@ -1,7 +1,7 @@ | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using System.ComponentModel.DataAnnotations.Schema; | ||||
| 
 | ||||
| namespace Elwig.Models { | ||||
| namespace Elwig.Models.Entities { | ||||
|     [Table("wb_rd"), PrimaryKey("KgNr", "RdNr")] | ||||
|     public class WbRd { | ||||
|         [Column("kgnr")] | ||||
| @@ -2,7 +2,7 @@ using Microsoft.EntityFrameworkCore; | ||||
| using System.ComponentModel.DataAnnotations.Schema; | ||||
| using IndexAttribute = Microsoft.EntityFrameworkCore.IndexAttribute; | ||||
| 
 | ||||
| namespace Elwig.Models { | ||||
| namespace Elwig.Models.Entities { | ||||
|     [Table("wine_attribute"), PrimaryKey("AttrId"), Index("Name", IsUnique = true)] | ||||
|     public class WineAttr { | ||||
|         [Column("attrid")] | ||||
| @@ -2,7 +2,7 @@ using Microsoft.EntityFrameworkCore; | ||||
| using System.ComponentModel.DataAnnotations.Schema; | ||||
| using IndexAttribute = Microsoft.EntityFrameworkCore.IndexAttribute; | ||||
| 
 | ||||
| namespace Elwig.Models { | ||||
| namespace Elwig.Models.Entities { | ||||
|     [Table("wine_cultivation"), PrimaryKey("CultId"), Index("Name", IsUnique = true)] | ||||
|     public class WineCult { | ||||
|         [Column("cultid")] | ||||
| @@ -5,7 +5,7 @@ using System.ComponentModel.DataAnnotations.Schema; | ||||
| using System.Linq; | ||||
| using IndexAttribute = Microsoft.EntityFrameworkCore.IndexAttribute; | ||||
| 
 | ||||
| namespace Elwig.Models { | ||||
| namespace Elwig.Models.Entities { | ||||
|     [Table("wine_origin"), PrimaryKey("HkId"), Index("Name", IsUnique = true)] | ||||
|     public class WineOrigin { | ||||
|         [Column("hkid")] | ||||
| @@ -2,10 +2,8 @@ using Elwig.Helpers; | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using System; | ||||
| using System.ComponentModel.DataAnnotations.Schema; | ||||
| using System.Configuration; | ||||
| using System.Security.Cryptography.Pkcs; | ||||
| 
 | ||||
| namespace Elwig.Models { | ||||
| namespace Elwig.Models.Entities { | ||||
|     [Table("wine_quality_level"), PrimaryKey("QualId")] | ||||
|     public class WineQualLevel : IEquatable<WineQualLevel> { | ||||
|         [Column("qualid")] | ||||
| @@ -1,7 +1,7 @@ | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using System.ComponentModel.DataAnnotations.Schema; | ||||
| 
 | ||||
| namespace Elwig.Models { | ||||
| namespace Elwig.Models.Entities { | ||||
|     [Table("wine_variety"), PrimaryKey("SortId")] | ||||
|     public class WineVar { | ||||
|         [Column("sortid")] | ||||
| @@ -1,6 +1,6 @@ | ||||
| using Elwig.Controls; | ||||
| using Elwig.Helpers; | ||||
| using Elwig.Models; | ||||
| using Elwig.Models.Entities; | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.ComponentModel; | ||||
|   | ||||
| @@ -3,13 +3,12 @@ using System.Linq; | ||||
| using System.Windows; | ||||
| using System.Windows.Controls; | ||||
| using Elwig.Helpers; | ||||
| using Elwig.Models; | ||||
| using Elwig.Models.Entities; | ||||
| using System; | ||||
| using System.Threading.Tasks; | ||||
| using System.Collections.Generic; | ||||
| using Microsoft.EntityFrameworkCore.ChangeTracking; | ||||
| using Xceed.Wpf.Toolkit.Primitives; | ||||
| using System.Runtime.Intrinsics.Arm; | ||||
|  | ||||
| namespace Elwig.Windows { | ||||
|     public partial class AreaComAdminWindow : AdministrationWindow { | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| using Elwig.Helpers; | ||||
| using Elwig.Models; | ||||
| using Elwig.Models.Entities; | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using System.Collections.Generic; | ||||
| using System.Collections.ObjectModel; | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| using Elwig.Models; | ||||
| using Elwig.Models.Entities; | ||||
| using System.Collections.Generic; | ||||
| using System.Collections.ObjectModel; | ||||
| using System.Windows.Controls; | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| using Elwig.Helpers; | ||||
| using Elwig.Models; | ||||
| using Elwig.Models.Entities; | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using System.Collections.Generic; | ||||
| using System.Collections.ObjectModel; | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| using Elwig.Helpers; | ||||
| using Elwig.Models; | ||||
| using Elwig.Models.Entities; | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using System.Linq; | ||||
| using System.Threading.Tasks; | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| using Elwig.Helpers; | ||||
| using Elwig.Models; | ||||
| using Elwig.Models.Entities; | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using System.Collections.Generic; | ||||
| using System.Collections.ObjectModel; | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| using Elwig.Helpers; | ||||
| using Elwig.Models; | ||||
| using Elwig.Models.Entities; | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using System.Collections.Generic; | ||||
| using System.Collections.ObjectModel; | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| using Elwig.Helpers; | ||||
| using Elwig.Models; | ||||
| using Elwig.Models.Entities; | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using System; | ||||
| using System.Linq; | ||||
|   | ||||
| @@ -9,7 +9,7 @@ using System.Windows.Controls; | ||||
| using System.Windows.Input; | ||||
| using Elwig.Helpers; | ||||
| using Elwig.Helpers.Billing; | ||||
| using Elwig.Models; | ||||
| using Elwig.Models.Entities; | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using Microsoft.EntityFrameworkCore.ChangeTracking; | ||||
| using ScottPlot; | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| using Elwig.Documents; | ||||
| using Elwig.Helpers; | ||||
| using Elwig.Helpers.Export; | ||||
| using Elwig.Models; | ||||
| using Elwig.Models.Entities; | ||||
| using LinqKit; | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using Microsoft.EntityFrameworkCore.ChangeTracking; | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| using Elwig.Documents; | ||||
| using Elwig.Models; | ||||
| using Elwig.Models.Dtos; | ||||
| using Elwig.Models.Entities; | ||||
| using Elwig.Windows; | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using System; | ||||
| @@ -84,17 +85,9 @@ namespace Elwig.Dialogs { | ||||
|                 list = list.Where((_, n) => n % 10 == r); | ||||
|             } | ||||
|  | ||||
|             var deliveries = await Context.DeliveryParts.FromSqlRaw($""" | ||||
|                 SELECT p.* | ||||
|                 FROM v_delivery v | ||||
|                     JOIN delivery_part p ON (p.year, p.did, p.dpnr) = (v.year, v.did, v.dpnr) | ||||
|                 WHERE v.year = {Year} | ||||
|                 ORDER BY v.sortid, v.abgewertet ASC, v.attribute_prio DESC, COALESCE(v.attrid, '~'), v.kmw DESC, v.lsnr, v.dpnr | ||||
|                 """) | ||||
|                 .ToListAsync(); | ||||
|  | ||||
|             var data = await DeliveryConfirmationData.ForSeason(Context.DeliveryParts, Year); | ||||
|             using var doc = Document.Merge(list.Select(m => | ||||
|                 new DeliveryConfirmation(Context, Year, m, deliveries.Where(d => d.Delivery.MgNr == m.MgNr).ToList()) { | ||||
|                 new DeliveryConfirmation(Context, Year, m, data[m.MgNr]) { | ||||
|                     //DoubleSided = true | ||||
|                 } | ||||
|             )); | ||||
|   | ||||
| @@ -6,7 +6,7 @@ using System.Windows; | ||||
| using System.Windows.Controls; | ||||
| using System.Windows.Input; | ||||
| using Elwig.Helpers; | ||||
| using Elwig.Models; | ||||
| using Elwig.Models.Entities; | ||||
| using System.Threading.Tasks; | ||||
| using Microsoft.EntityFrameworkCore.ChangeTracking; | ||||
| using System.Collections.ObjectModel; | ||||
|   | ||||
| @@ -1,10 +1,10 @@ | ||||
| using Elwig.Dialogs; | ||||
| using Elwig.Helpers; | ||||
| using Elwig.Helpers.Billing; | ||||
| using Elwig.Helpers.Export; | ||||
| using Elwig.Models.Dtos; | ||||
| using Microsoft.Win32; | ||||
| using System; | ||||
| using System.IO; | ||||
| using System.Text; | ||||
| using System.Threading.Tasks; | ||||
| using System.Windows; | ||||
| using System.Windows.Input; | ||||
| @@ -59,137 +59,23 @@ namespace Elwig.Windows { | ||||
|             if (SeasonInput.Value is not int year) | ||||
|                 return; | ||||
|             var d = new SaveFileDialog() { | ||||
|                 FileName = $"Über-Unterlieferungen-{year}.csv", | ||||
|                 DefaultExt = "csv", | ||||
|                 Filter = "CSV-Datei (*.csv)|*.csv", | ||||
|                 FileName = $"Über-Unterlieferungen-{year}.ods", | ||||
|                 DefaultExt = "ods", | ||||
|                 Filter = "OpenDocument Format Spreadsheet (*.ods)|*.ods", | ||||
|                 Title = $"Über-/Unterlieferungen {year} speichern unter - Elwig" | ||||
|             }; | ||||
|             if (d.ShowDialog() == false) | ||||
|                 return; | ||||
|  | ||||
|             Mouse.OverrideCursor = Cursors.AppStarting; | ||||
|  | ||||
|             try { | ||||
|                 using var file = new StreamWriter(d.FileName, false, Encoding.Latin1); | ||||
|                 using var cnx = await AppDbContext.ConnectAsync(); | ||||
|                 await file.WriteLineAsync($"Auswertungen {year};;;;;;;;;;;"); | ||||
|  | ||||
|                 await file.WriteLineAsync($";;;;;;;;;;;"); | ||||
|                 await file.WriteLineAsync($"Über-/Unterlieferungen lt. gez. GA;;;;;;;;;;;"); | ||||
|                 await file.WriteLineAsync($"MgNr;Name;Vorname;Adresse;PLZ;Ort;GA;Lieferpflicht;Lieferrecht;Geliefert;Über-/Unterliefert;Prozent"); | ||||
|                 using (var cmd = cnx.CreateCommand()) { | ||||
|                     cmd.CommandText = $""" | ||||
|                     SELECT m.mgnr, m.family_name, m.given_name, p.plz, o.name, m.address, m.business_shares, | ||||
|                            m.business_shares * s.min_kg_per_bs AS min_kg, | ||||
|                            m.business_shares * s.max_kg_per_bs AS max_kg, | ||||
|                            COALESCE(SUM(d.weight), 0) AS sum | ||||
|                     FROM member m | ||||
|                         LEFT JOIN AT_plz_dest p ON p.id = m.postal_dest | ||||
|                         LEFT JOIN AT_ort o ON o.okz = p.okz | ||||
|                         LEFT JOIN season s ON s.year = {year} | ||||
|                         LEFT JOIN v_delivery d ON d.mgnr = m.mgnr AND d.year = s.year | ||||
|                     WHERE m.active = 1 | ||||
|                     GROUP BY d.year, m.mgnr | ||||
|                     ORDER BY sum = 0 DESC, 100.0 * sum / max_kg, m.mgnr; | ||||
|                     """; | ||||
|                     using var reader = await cmd.ExecuteReaderAsync(); | ||||
|                     while (await reader.ReadAsync()) { | ||||
|                         var mgnr = reader.GetInt32(0); | ||||
|                         var familyName = reader.GetString(1); | ||||
|                         var givenName = reader.GetString(2); | ||||
|                         var plz = reader.GetInt32(3); | ||||
|                         var ort = reader.GetString(4).Split(',')[0]; | ||||
|                         var addr = reader.GetString(5); | ||||
|                         var ga = reader.GetInt32(6); | ||||
|                         var minKg = reader.GetInt32(7); | ||||
|                         var maxKg = reader.GetInt32(8); | ||||
|                         var sum = reader.GetInt32(9); | ||||
|                         var s1 = sum < minKg ? $"{sum - minKg}" : sum > maxKg ? $"{sum - maxKg}" : ""; | ||||
|                         var s2 = sum < minKg ? $"{sum * 100.0 / minKg - 100.0:0.0}" : sum > maxKg ? $"{sum * 100.0 / maxKg - 100:0.0}" : ""; | ||||
|                         await file.WriteLineAsync($"{mgnr};{familyName};{givenName};{addr};{plz};{ort};{ga};{minKg};{maxKg};{sum};{s1};{s2}"); | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 await file.WriteLineAsync($";;;;;;;;;;;"); | ||||
|                 await file.WriteLineAsync($"Unterlieferungen lt. Flächenbindungen;;;;;;;;;;;"); | ||||
|                 await file.WriteLineAsync($"MgNr;Name;Vorname;Adresse;PLZ;Ort;Vertrag;Fläche;Lieferpflicht;Geliefert;Unterliefert;Prozent"); | ||||
|                 using (var cmd = cnx.CreateCommand()) { | ||||
|                     cmd.CommandText = $""" | ||||
|                     SELECT m.mgnr, m.family_name, m.given_name, p.plz, o.name, m.address, | ||||
|                            c.bucket, c.area, u.min_kg, u.weight | ||||
|                     FROM member m | ||||
|                         LEFT JOIN AT_plz_dest p ON p.id = m.postal_dest | ||||
|                         LEFT JOIN AT_ort o ON o.okz = p.okz | ||||
|                         LEFT JOIN v_area_commitment_bucket_strict c ON c.mgnr = m.mgnr AND c.year = {year} | ||||
|                         JOIN v_under_delivery u ON (u.mgnr, u.bucket, u.year) = (m.mgnr, c.bucket, c.year) | ||||
|                     WHERE m.active = 1 | ||||
|                     ORDER BY m.mgnr, c.bucket | ||||
|                     """; | ||||
|                     using var reader = await cmd.ExecuteReaderAsync(); | ||||
|                     while (await reader.ReadAsync()) { | ||||
|                         var mgnr = reader.GetInt32(0); | ||||
|                         var familyName = reader.GetString(1); | ||||
|                         var givenName = reader.GetString(2); | ||||
|                         var plz = reader.GetInt32(3); | ||||
|                         var ort = reader.GetString(4).Split(',')[0]; | ||||
|                         var addr = reader.GetString(5); | ||||
|                         var id = reader.GetString(6); | ||||
|                         var area = reader.GetInt32(7); | ||||
|                         var minKg = reader.GetInt32(8); | ||||
|                         var sum = reader.GetInt32(9); | ||||
|                         await file.WriteLineAsync($"{mgnr};{familyName};{givenName};{addr};{plz};{ort};{id};{area};{minKg};{sum};{sum - minKg};{sum * 100.0 / minKg - 100.0:0.0}"); | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 await file.WriteLineAsync($";;;;;;;;;;;"); | ||||
|                 await file.WriteLineAsync($"Lieferungen pro Mitglied und Sorte;;;;;;;;;;;"); | ||||
|                 await file.WriteLineAsync($"MgNr;Name;Vorname;Adresse;PLZ;Ort;Sorte;Attribut;Geliefert;Fläche;Ertrag"); | ||||
|                 using (var cmd = cnx.CreateCommand()) { | ||||
|                     cmd.CommandText = $""" | ||||
|                     SELECT m.mgnr, m.family_name, m.given_name, p.plz, o.name, m.address, | ||||
|                            v.bucket, v.weight, v.area | ||||
|                     FROM ( | ||||
|                             SELECT c.year AS year, | ||||
|                                    c.mgnr AS mgnr, | ||||
|                                    c.bucket AS bucket, | ||||
|                                    COALESCE(d.weight, 0) AS weight, | ||||
|                                    COALESCE(c.area, 0) AS area | ||||
|                             FROM v_area_commitment_bucket_strict c | ||||
|                                 LEFT JOIN v_delivery_bucket_strict d ON (d.year, d.mgnr, d.bucket) = (c.year, c.mgnr, c.bucket) | ||||
|                             WHERE c.year = {year} | ||||
|                             UNION | ||||
|                             SELECT d.year, | ||||
|                                    d.mgnr, | ||||
|                                    d.bucket, | ||||
|                                    COALESCE(d.weight, 0), | ||||
|                                    COALESCE(c.area, 0) | ||||
|                             FROM v_delivery_bucket_strict d | ||||
|                                 LEFT JOIN v_area_commitment_bucket_strict c ON (c.year, c.mgnr, c.bucket) = (d.year, d.mgnr, d.bucket) | ||||
|                             WHERE d.year = {year} | ||||
|                         ) v | ||||
|                         LEFT JOIN member m ON m.mgnr = v.mgnr | ||||
|                         LEFT JOIN AT_plz_dest p ON p.id = m.postal_dest | ||||
|                         LEFT JOIN AT_ort o ON o.okz = p.okz | ||||
|                     ORDER BY m.mgnr, v.bucket | ||||
|                     """; | ||||
|                     using var reader = await cmd.ExecuteReaderAsync(); | ||||
|                     while (await reader.ReadAsync()) { | ||||
|                         var mgnr = reader.GetInt32(0); | ||||
|                         var familyName = reader.GetString(1); | ||||
|                         var givenName = reader.GetString(2); | ||||
|                         var plz = reader.GetInt32(3); | ||||
|                         var ort = reader.GetString(4).Split(',')[0]; | ||||
|                         var addr = reader.GetString(5); | ||||
|                         var id = reader.GetString(6); | ||||
|                         var sum = reader.GetInt32(7); | ||||
|                         var area = reader.GetInt32(8); | ||||
|                         await file.WriteLineAsync($"{mgnr};{familyName};{givenName};{addr};{plz};{ort};{id[..2]};{id[2..]};{sum};{area};{(area > 0 ? sum * 10000 / area : "")}"); | ||||
|                     } | ||||
|                 } | ||||
|                 using var ods = new OdsFile(d.FileName); | ||||
|                 await ods.AddTable(await OverUnderDeliveryData.ForSeason(Context.OverUnderDeliveryRows, year)); | ||||
|                 await ods.AddTable(await AreaComUnderDeliveryData.ForSeason(Context.AreaComUnderDeliveryRows, year)); | ||||
|                 await ods.AddTable(await MemberDeliveryPerVariantData.ForSeason(Context.MemberDeliveryPerVariantRows, year)); | ||||
|             } catch (Exception exc) { | ||||
|                 MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error); | ||||
|             } | ||||
|  | ||||
|             Mouse.OverrideCursor = null; | ||||
|         } | ||||
|  | ||||
|   | ||||
| @@ -18,6 +18,8 @@ | ||||
|         <TextBlock x:Name="Output" Height="20" Width="200" Margin="470,329,0,0"  HorizontalAlignment="Left" VerticalAlignment="Top"/> | ||||
|         <Button x:Name="ChartButton" Content="Chart" Click="ChartButton_Click" | ||||
|                 Margin="50,240,0,0" VerticalAlignment="Top" HorizontalAlignment="Left"/> | ||||
|         <Button x:Name="ZipButton" Content="ZIP-File" Click="ZipButton_Click" | ||||
|                 Margin="50,270,0,0" VerticalAlignment="Top" HorizontalAlignment="Left"/> | ||||
|  | ||||
|         <Button x:Name="PdfDeliveryButton" Content="Lieferschein Erzeugen" Click="PdfDeliveryButton_Click" Tag="Print" IsEnabled="False" | ||||
|                 Margin="260,190,0,0" VerticalAlignment="Top" HorizontalAlignment="Left"/> | ||||
|   | ||||
| @@ -1,8 +1,12 @@ | ||||
| using Elwig.Documents; | ||||
| using Elwig.Helpers; | ||||
| using Elwig.Helpers.Billing; | ||||
| using Elwig.Helpers.Export; | ||||
| using Elwig.Models.Dtos; | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using System; | ||||
| using System.Collections; | ||||
| using System.Collections.Generic; | ||||
| using System.Linq; | ||||
| using System.Windows; | ||||
| using System.Windows.Input; | ||||
| @@ -51,6 +55,12 @@ namespace Elwig.Windows { | ||||
|             w.Show(); | ||||
|         } | ||||
|  | ||||
|         private async void ZipButton_Click(object sender, RoutedEventArgs evt) { | ||||
|             using var ctx = new AppDbContext(); | ||||
|             using var ods = new OdsFile(@"C:\Users\Lorenz\Desktop\test.ods"); | ||||
|             await ods.AddTable(await DeliveryConfirmationData.ForMember(ctx.DeliveryParts, 2023, 2948)); | ||||
|         } | ||||
|  | ||||
|         private async void PdfDeliveryButton_Click(object sender, RoutedEventArgs evt) { | ||||
|             Mouse.OverrideCursor = Cursors.AppStarting; | ||||
|             using var ctx = new AppDbContext(); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user