Compare commits

...

125 Commits

Author SHA1 Message Date
lorenz.stechauner b063b201e3 Elwig: Bump version to 0.6.4 2024-01-31 13:03:08 +01:00
lorenz.stechauner 60b624b009 PaymentVariantsWindow: Make buttons more user friendly 2024-01-31 13:01:43 +01:00
lorenz.stechauner 71a234ca60 CreditNote: Make credit sum table bigger and other small fixes 2024-01-31 12:57:48 +01:00
lorenz.stechauner 38abfb0edd CreditNote: Add switches to control which deductions are shown 2024-01-31 12:36:28 +01:00
lorenz.stechauner 05a037db70 BillingTest: Fix TearDown 2024-01-31 12:08:45 +01:00
lorenz.stechauner b9287f8260 Billing: Always call CalculateBuckets() when Calculate() is called to avoid user confusion 2024-01-31 12:07:06 +01:00
lorenz.stechauner 50ac757067 BillingTest: Add names for more test methods 2024-01-29 19:40:23 +01:00
lorenz.stechauner c6cd9d7c73 Elwig: Bump version to 0.6.3 2024-01-29 14:52:31 +01:00
lorenz.stechauner 1b28752f4c PaymentVariantsWindow: Add button to export Buchungsliste 2024-01-29 14:51:03 +01:00
lorenz.stechauner e0bdbee2ae AppDbUpdater: Add new VIEWs for penalties 2024-01-29 14:49:07 +01:00
lorenz.stechauner ff3bd5cea5 Dtos: Use collection initializer 2024-01-29 12:13:15 +01:00
lorenz.stechauner 116d88d3d6 Ods: Add support for decimals and add number grouping 2024-01-29 12:12:02 +01:00
lorenz.stechauner 6bcb2fb406 Dtos: Rename CreditNote and DeliveryConfirmation DTOs 2024-01-28 22:52:26 +01:00
lorenz.stechauner 8665c93702 CreditNote: Add footer text 2024-01-28 22:15:30 +01:00
lorenz.stechauner 62496a0770 BillingTest: Small refactoring 2024-01-28 20:32:10 +01:00
lorenz.stechauner 8678a02318 BillingTest: Use Assert.That(...) more often 2024-01-28 20:22:38 +01:00
lorenz.stechauner 9de7fad139 Tests: Add BillingTest.Test_01_NoActiveAreaCommitments() 2024-01-28 20:12:37 +01:00
lorenz.stechauner 85c8783f7e [#4] MemberAdminWindow: Add more input fields for email addresses 2024-01-28 16:22:54 +01:00
lorenz.stechauner 75e02751f0 [#12] DeliveryAdminWindow: Add abgewertet filter keyword 2024-01-28 11:01:58 +01:00
lorenz.stechauner ef1c3b25cf MemberListWindow: Remove window 2024-01-27 23:03:01 +01:00
lorenz.stechauner 255953a658 ChartWindow: Set limits to 120 Oe and 1.5 euro 2024-01-27 22:53:40 +01:00
lorenz.stechauner 9470b26aec ChartWindow: Define legend items as static 2024-01-27 22:47:49 +01:00
lorenz.stechauner 3a2bf81bd9 ChartWindow: Rework varibute selection 2024-01-27 22:41:31 +01:00
lorenz.stechauner d3aca196dd Elwig: Rename (wine) variant to variety 2024-01-27 11:38:08 +01:00
lorenz.stechauner 519e903d1c ChartWindow: Rename graph to curve for UI 2024-01-27 11:38:08 +01:00
lorenz.stechauner dd568b81e8 Billing: Rename attributeVariants and contracts to vaributes 2024-01-27 11:37:51 +01:00
lorenz.stechauner 31b0ae245d curl: Follow all redirects 2024-01-25 19:41:40 +01:00
lorenz.stechauner 46498ce337 Printing: Show message box when error in printing process occurs 2024-01-25 19:40:56 +01:00
lorenz.stechauner 0fff698a5d ChartWindow: Only display contracts delivered in current season 2024-01-25 12:42:46 +01:00
lorenz.stechauner a71c6685f0 BillingVariant: Calculate member modifiers only if delivery modifiers are considered 2024-01-25 12:27:53 +01:00
lorenz.stechauner ab41702f6c ChartWindow: Round PriceInput text 2024-01-25 01:26:55 +01:00
lorenz.stechauner 2bbf4dd1fd Billing: Prefer Attribute over gebunden status for price 2024-01-25 01:25:20 +01:00
lorenz.stechauner 8909b4a3a8 PaymentVariantsWindow: Disable calcualte button when save button is enabled 2024-01-25 01:00:29 +01:00
lorenz.stechauner f8d776c028 Bump version to 0.6.1 2024-01-25 00:42:02 +01:00
lorenz.stechauner 2154e253ad EditBillingData: Only show graphs with countract count > 0 2024-01-25 00:37:16 +01:00
thomas.hilscher df83430c35 ChartWindow: Add MessageBox when removing contract from other graph 2024-01-25 00:36:36 +01:00
lorenz.stechauner d59a713a8c ChartWindow: Margin fixes 2024-01-25 00:18:02 +01:00
lorenz.stechauner 5e48d8e8d1 PaymentVariantsWindow: Button IsEnabled fixes 2024-01-25 00:16:21 +01:00
lorenz.stechauner 4f95d3fe16 PaymentVariantsWindow: Initialize Quality object for newly addes payment variants 2024-01-24 23:51:25 +01:00
lorenz.stechauner ce3185842a BillingData: Implement GetQualtyGraphEntries 2024-01-24 23:41:53 +01:00
thomas.hilscher e1d19fd9e5 ChartWindow: Fix AbgewertetInput Unchecked 2024-01-23 01:28:24 +01:00
lorenz.stechauner 3931a4084c Billing: Fixes 2024-01-23 01:16:53 +01:00
lorenz.stechauner 1a492e4eff PaymentVariantsWindow: Fix locked json field 2024-01-23 00:56:23 +01:00
lorenz.stechauner 58a13eb3cc BillingData: Fix typo 2024-01-23 00:56:08 +01:00
lorenz.stechauner d5124829de EditBillingData: Fix conversion error 2024-01-23 00:47:33 +01:00
lorenz.stechauner 37658869e4 ChartWindow: Small fixes 2024-01-23 00:37:18 +01:00
lorenz.stechauner 24a43ff37d ChartWindow: Make GraphList bigger 2024-01-23 00:10:32 +01:00
lorenz.stechauner 16cf055834 GraphEntry: Update StringSimple 2024-01-23 00:02:47 +01:00
lorenz.stechauner ef0b913063 EditBillingData: Fix ids for virtual curves 2024-01-22 23:27:55 +01:00
lorenz.stechauner 05909919e2 Billing: Add functionality to collapse curves 2024-01-22 23:09:48 +01:00
thomas.hilscher 3642c5ac07 ChartWindow: Upgrade to Scottplot 5 2024-01-22 21:41:01 +01:00
lorenz.stechauner 6cee604448 Tests: Rename Helpers/ to HelperTests/ 2024-01-22 20:59:25 +01:00
lorenz.stechauner 89d20f4c42 ChartWindow: Make gebunden type fixed more user friendly 2024-01-21 12:48:40 +01:00
lorenz.stechauner 182b367811 ChartWindow: Fix saving bug 2024-01-21 01:20:50 +01:00
lorenz.stechauner a2bb09cfbd Billing: Build BillingData-Json in BillingData instead of anywhere else 2024-01-21 00:31:20 +01:00
thomas.hilscher b981b5f895 ChartWindow/Billing: Misc improvements 2024-01-20 19:24:26 +01:00
lorenz.stechauner 9dc2e8a59a Windows: Add Ctrl+P and Ctrl+Shift+P for delivery and member 2024-01-20 15:47:20 +01:00
lorenz.stechauner 1dc05e47cf DeliveryAdminWindow: Use Saison instead of Season in GUI string 2024-01-20 15:32:44 +01:00
lorenz.stechauner 21cc20ee63 Billing/GraphEntry: Use 73 Oe as MinX for gebunden graph 2024-01-20 12:27:01 +01:00
lorenz.stechauner 491c41b239 ChartWindow: Minor bugfixes and polishing 2024-01-20 12:02:50 +01:00
lorenz.stechauner 47658a72ae ChartWindow: Enhance ComboCheckBox 2024-01-20 02:57:22 +01:00
lorenz.stechauner 8b0a4d7979 EditBillingData: Use 140 as upper boundary 2024-01-20 02:43:15 +01:00
lorenz.stechauner 9ee7f6baf1 Billing/Graph: Remove ParseGraphData() 2024-01-20 02:35:59 +01:00
lorenz.stechauner ecbc9c2d82 BillingData: Add GetCurveValueAt(), extracted from PaymentBillingData 2024-01-20 02:33:05 +01:00
lorenz.stechauner bf90543ad8 ChartWindow: Change gebunden color to yellow 2024-01-20 02:09:03 +01:00
lorenz.stechauner 6a5676f916 ChartWindow: Load GraphEntries correctly from EditBillingData 2024-01-20 01:53:27 +01:00
lorenz.stechauner 75e9d756d2 BillingData: Upgrade GetSelection() 2024-01-20 00:48:23 +01:00
lorenz.stechauner ee161b149b BillingData: Extract GetData() from PaymentBillingData into GetSelection() 2024-01-20 00:31:50 +01:00
thomas.hilscher 0cb7b4bfc8 ChartWindow: Added second graph for gebunden 2024-01-19 23:54:16 +01:00
lorenz.stechauner 4a49a17b6a Tests: Update FetchResource target 2024-01-19 16:57:35 +01:00
lorenz.stechauner 741ccaacae Test: Update target once again 2024-01-19 15:50:50 +01:00
lorenz.stechauner 19f4300440 Tests: Add DependsOnTargets 2024-01-19 15:42:32 +01:00
lorenz.stechauner 954c7a8bdb Tests: Change Target to be executed before CoreBuild 2024-01-19 15:38:02 +01:00
lorenz.stechauner 626724fe87 [#29] DeliveryAdminWindow: Only show sums of filtered parts when filtering 2024-01-19 13:35:53 +01:00
lorenz.stechauner 42bf01656e Some Bugfixes 2024-01-19 00:22:49 +01:00
lorenz.stechauner 51293baaae App: Fix GroupSeparator bug 2024-01-18 23:48:42 +01:00
lorenz.stechauner 1d1398a9cd Config: Use Path.Combine() instead of GetAbsolutePath() 2024-01-18 22:07:31 +01:00
lorenz.stechauner 7d199282d0 Elwig: Upgrade publish profile to .Net8 2024-01-18 21:48:11 +01:00
lorenz.stechauner b56a5ed5c6 Setup: Update dependencies 2024-01-18 21:44:28 +01:00
lorenz.stechauner 201b63c2f1 Installer: Update dependencies 2024-01-18 21:42:34 +01:00
lorenz.stechauner b2bd0c9a21 Tests: Update dependencies 2024-01-18 21:42:07 +01:00
lorenz.stechauner 8502afdc9a Elwig: Update dependencies (except ScottPlot) 2024-01-18 21:38:50 +01:00
lorenz.stechauner cb541cb6e6 Billing: Add EditBillingData class 2024-01-18 21:30:42 +01:00
lorenz.stechauner 403e7723d2 Bump version to 0.6.0 2024-01-18 21:26:33 +01:00
lorenz.stechauner 8fbce03031 Tests: Adapt to new PaymentBillingData class usage 2024-01-18 21:23:10 +01:00
lorenz.stechauner b32a935150 DeliveryAdminWindow: Add filter for red/white 2024-01-18 20:02:16 +01:00
lorenz.stechauner 337bfa89d9 DeliveryAdminWindow: Fix filters for quality and attributes 2024-01-18 19:49:43 +01:00
lorenz.stechauner f886888ccc Billing: Split BillingData into BillingData and PaymentBillingData 2024-01-18 01:22:12 +01:00
thomas.hilscher 4dd036babd ChartWindow: wip 2024-01-17 22:33:20 +01:00
thomas.hilscher b6fd62f8ca ChartWindow: Use BillingData and Curve 2024-01-17 22:08:36 +01:00
lorenz.stechauner b52c09a176 Billing: Add possibility to automatically add business shares 2024-01-17 18:59:25 +01:00
lorenz.stechauner 668eb9a2d0 BillingData: Fix setter for ConsiderAutoBusinessShares 2024-01-17 17:37:08 +01:00
lorenz.stechauner 9eb013ce11 PaymentVariantsWindow: Add possibility to switch options on/off 2024-01-17 14:57:45 +01:00
lorenz.stechauner 38ad433b4e Fix ods export with doubles 2024-01-17 12:10:11 +01:00
lorenz.stechauner 0a60f01979 MemberDataSheet: Fix constructor 2024-01-14 21:41:53 +01:00
lorenz.stechauner a1ddef4666 MemberAdminWindow: Fix ToolTip for search bar 2024-01-14 21:40:23 +01:00
lorenz.stechauner 788d0efa4a CreditNote: Fix double-border for sum without Treuebonus 2024-01-14 10:15:29 +01:00
lorenz.stechauner 0f06d98d39 [#9] MemberAdminWindow: Add "Bio-Knopf" 2024-01-14 01:26:35 +01:00
lorenz.stechauner 228d17f8cb MemberAdminWindow: Enlarge comment field 2024-01-14 00:31:45 +01:00
lorenz.stechauner 1664024e64 [#2] MemberAdminWindow: Add search ToolTip 2024-01-13 20:38:51 +01:00
lorenz.stechauner 8fbfd33e8f DeliveryAdminWindow: Update search ToolTip 2024-01-13 20:38:49 +01:00
lorenz.stechauner 95853099bb [#2] MemberAdminWindow: Update search filters 2024-01-13 20:38:36 +01:00
lorenz.stechauner 8072febd5b DeliveryAdminWindow: Add branch name in title for übernahme mode 2024-01-13 19:40:00 +01:00
lorenz.stechauner 62d9641b28 Tests/DatabaseSetup: Add Insert.sql 2024-01-09 13:10:06 +01:00
lorenz.stechauner 3aabfbc603 PaymentVariantsWindow: Implement SeasonLock 2024-01-08 20:08:07 +01:00
lorenz.stechauner 09e55264bb BillingData: Implement WG Master parsing 2024-01-08 19:31:13 +01:00
lorenz.stechauner f894c3b212 PaymentVariantsWindow: Rework Buttons 2024-01-08 14:20:20 +01:00
lorenz.stechauner 09a7889044 AppDbContext: Add NextAvNr() 2024-01-08 14:18:15 +01:00
lorenz.stechauner d77aac43ec App: Add FocusBaseDataSeason() 2024-01-08 14:17:56 +01:00
lorenz.stechauner 062c7bed5e AppDbUpdater: Fix migration 10 to 11 by creating a new table 2024-01-08 11:21:01 +01:00
lorenz.stechauner b723161fa5 BillingVariant: Fix CalculateModifiers() by restricting modifier year 2024-01-08 02:40:32 +01:00
lorenz.stechauner debee3b4bf PaymentVariantsWindow: Fix once wrong, every time wrong error 2024-01-08 02:24:19 +01:00
lorenz.stechauner f15733f827 CreditNote: Fix styling 2024-01-07 23:43:56 +01:00
lorenz.stechauner 05b6e8ddd6 CreditNote: Add hint regarding calculations 2024-01-07 22:24:06 +01:00
lorenz.stechauner 5ae326074f Document: Fix GenerationProportion 2024-01-07 22:08:21 +01:00
lorenz.stechauner 60b99bf95b CreditNote: Split table into two tables 2024-01-07 21:47:25 +01:00
lorenz.stechauner 3562d304de Document: Remove break-before: avoid for tr.sum 2024-01-07 21:39:42 +01:00
lorenz.stechauner 9f73d13dbf Document: Add GenerationProportion and set to 1/8 2024-01-07 15:26:51 +01:00
lorenz.stechauner 5e665ffb50 PaymentVariantsWindow: Impelment BillingVariant 2024-01-07 15:21:32 +01:00
lorenz.stechauner 1e751c473a BillingData: Implement elwig json format parsing 2024-01-06 14:45:18 +01:00
lorenz.stechauner d67e434fed Use InvariantCulture instead of replacing ',' with '.' 2024-01-06 14:25:42 +01:00
lorenz.stechauner 3a89e16db3 Tests: Move tests into package 2024-01-05 18:03:28 +01:00
lorenz.stechauner 2556033a07 Tests: Add in-memory database for testing 2024-01-05 18:03:21 +01:00
lorenz.stechauner 3f6a94e773 AppDbContext: Add ExecuteEmbeddedScript() 2024-01-05 14:49:28 +01:00
lorenz.stechauner e75e2ddbda BillingData: Move schema to Resources/ 2024-01-05 13:45:37 +01:00
86 changed files with 4205 additions and 1355 deletions
+1
View File
@@ -3,3 +3,4 @@ bin/
*.user
.vs
.idea
Tests/Resources/Create.sql
+19 -6
View File
@@ -17,6 +17,7 @@ using Elwig.Windows;
using Elwig.Dialogs;
using System.Threading.Tasks;
using Elwig.Helpers.Billing;
using Elwig.Models.Entities;
namespace Elwig {
public partial class App : Application {
@@ -64,22 +65,27 @@ namespace Elwig {
MainDispatcher = Dispatcher;
Scales = Array.Empty<IScale>();
CurrentApp = this;
OverrideCulture();
}
protected override async void OnStartup(StartupEventArgs evt) {
var locale = new CultureInfo("de-AT");
locale.NumberFormat.CurrencyGroupSeparator = "\u202f";
locale.NumberFormat.NumberGroupSeparator = "\u202f";
locale.NumberFormat.PercentGroupSeparator = "\u202f";
private static void OverrideCulture() {
var locale = new CultureInfo("de-AT", false);
locale.NumberFormat.CurrencyGroupSeparator = Utils.GroupSeparator;
locale.NumberFormat.NumberGroupSeparator = Utils.GroupSeparator;
locale.NumberFormat.PercentGroupSeparator = Utils.GroupSeparator;
CultureInfo.CurrentCulture = locale;
CultureInfo.CurrentUICulture = locale;
Thread.CurrentThread.CurrentCulture = locale;
Thread.CurrentThread.CurrentUICulture = locale;
CultureInfo.DefaultThreadCurrentCulture = locale;
CultureInfo.DefaultThreadCurrentUICulture = locale;
FrameworkElement.LanguageProperty.OverrideMetadata(
typeof(FrameworkElement),
new FrameworkPropertyMetadata(XmlLanguage.GetLanguage(CultureInfo.CurrentCulture.IetfLanguageTag))
new FrameworkPropertyMetadata(XmlLanguage.GetLanguage(CultureInfo.CurrentCulture.Name))
);
}
protected override async void OnStartup(StartupEventArgs evt) {
Version = typeof(App).GetTypeInfo().Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion.Split("+")[0] ?? "0.0.0";
try {
@@ -221,6 +227,13 @@ namespace Elwig {
return w;
}
public static BaseDataWindow FocusBaseDataSeason(int year) {
var w = FocusBaseData();
w.Seasons.Focus();
ControlUtils.SelectListBoxItem(w.SeasonList, s => (s as Season)?.Year, year);
return w;
}
public static SeasonFinishWindow FocusSeasonFinish() {
return FocusWindow<SeasonFinishWindow>(() => new());
}
+1 -1
View File
@@ -12,7 +12,7 @@ namespace Elwig.Dialogs {
InitializeComponent();
TextLsNr.Text = lsnr;
TextMember.Text = name;
TextWeight.Text = $"{weight:N0}\u202fkg";
TextWeight.Text = $"{weight:N0}{Utils.UnitSeparator}kg";
}
private void ConfirmButton_Click(object sender, RoutedEventArgs evt) {
@@ -1,5 +1,4 @@
using Elwig.Helpers;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Controls;
@@ -14,7 +13,7 @@ namespace Elwig.Dialogs {
private void ConfirmButton_Click(object sender, RoutedEventArgs evt) {
DialogResult = true;
Price = double.Parse(PriceInput.Text.Replace("\u202f", ""));
Price = double.Parse(PriceInput.Text.Replace(Utils.GroupSeparator, ""));
Close();
}
+2 -1
View File
@@ -1,6 +1,7 @@
using Elwig.Helpers;
using Elwig.Models.Entities;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
namespace Elwig.Documents {
@@ -36,7 +37,7 @@ namespace Elwig.Documents {
}
private static string GetColGroup(IEnumerable<double> cols) {
return "<colgroup>\n" + string.Join("\n", cols.Select(g => $"<col style=\"width: {g.ToString().Replace(',', '.')}mm;\"/>")) + "\n</colgroup>\n";
return "<colgroup>\n" + string.Join("\n", cols.Select(g => $"<col style=\"width: {g.ToString(CultureInfo.InvariantCulture)}mm;\"/>")) + "\n</colgroup>\n";
}
public static string PrintSortenaufteilung(Dictionary<string, MemberBucket> buckets) {
+5
View File
@@ -119,6 +119,11 @@ main h1 {
font-size: 10pt;
}
.main-wrapper p.custom {
white-space: pre-wrap;
break-inside: avoid;
}
.main-wrapper .hidden {
break-before: avoid;
}
+43 -16
View File
@@ -11,14 +11,24 @@ namespace Elwig.Documents {
public PaymentMember? Payment;
public Credit? Credit;
public CreditNoteData Data;
public CreditNoteDeliveryData Data;
public string? Text;
public string CurrencySymbol;
public int Precision;
public string MemberModifier;
public IEnumerable<(string Name, int Kg, decimal Amount)>? MemberUnderDeliveries;
public decimal MemberTotalUnderDelivery;
public decimal MemberAutoBusinessShares;
public CreditNote(AppDbContext ctx, PaymentMember p, CreditNoteData data, Dictionary<string, UnderDelivery>? underDeliveries = null) :
public CreditNote(
AppDbContext ctx,
PaymentMember p,
CreditNoteDeliveryData data,
bool considerContractPenalties,
bool considerTotalPenalty,
bool considerAutoBusinessShares,
Dictionary<string, UnderDelivery>? underDeliveries = null
) :
base($"{Name} {(p.Credit != null ? $"Nr. {p.Credit.Year}/{p.Credit.TgNr:000}" : p.Member.Name)} {p.Variant.Name}", p.Member) {
UseBillingAddress = true;
ShowDateAndLocation = true;
@@ -38,23 +48,40 @@ namespace Elwig.Documents {
$"<tr><th>Überw. am</th><td>{p.Variant.TransferDate:dd.MM.yyyy}</td></tr>" +
$"<tr><th>Datum/Zeit</th><td>{p.Credit?.ModifiedTimestamp:dd.MM.yyyy} / {p.Credit?.ModifiedTimestamp:HH:mm}</td></tr>" +
$"</tbody></table>";
Text = App.Client.TextDeliveryNote;
Text = App.Client.TextCreditNote;
DocumentId = $"Tr.-Gutschr. " + (p.Credit != null ? $"{p.Credit.Year}/{p.Credit.TgNr:000}" : p.MgNr);
CurrencySymbol = season.Currency.Symbol ?? season.Currency.Code;
Precision = season.Precision;
var variants = ctx.WineVarieties.ToDictionary(v => v.SortId, v => v);
var attributes = ctx.WineAttributes.ToDictionary(a => a.AttrId, a => a);
var comTypes = ctx.AreaCommitmentTypes.ToDictionary(t => t.VtrgId, t => t);
MemberUnderDeliveries = underDeliveries?
.OrderBy(u => u.Key)
.Select(u => (
variants[u.Key[..2]].Name + (u.Key.Length > 2 ? " " + attributes[u.Key[2..]].Name : ""),
u.Value.Diff,
u.Value.Diff * (comTypes[u.Key].PenaltyPerKg ?? 0)
- (comTypes[u.Key].PenaltyAmount ?? 0)
- ((u.Value.Weight == 0 ? comTypes[u.Key].PenaltyNone : null) ?? 0)))
.Where(u => u.Item3 != 0)
.ToList();
if (considerTotalPenalty) {
var total = data.Rows.SelectMany(r => r.Buckets).Sum(b => b.Value);
var totalUnderDelivery = total - p.Member.BusinessShares * season.MinKgPerBusinessShare;
MemberTotalUnderDelivery = totalUnderDelivery < 0 ? totalUnderDelivery * (season.PenaltyPerKg ?? 0) - (season.PenaltyAmount ?? 0) : 0;
if (total == 0)
MemberTotalUnderDelivery -= (season.PenaltyNone ?? 0);
}
if (considerAutoBusinessShares) {
var fromDate = $"{season.Year}-01-01";
var toDate = $"{season.Year}-12-31";
MemberAutoBusinessShares = ctx.MemberHistory
.Where(h => h.MgNr == p.Member.MgNr && h.Type == "auto")
.Where(h => h.DateString.CompareTo(fromDate) >= 0 && h.DateString.CompareTo(toDate) <= 0)
.Sum(h => h.BusinessShares) * (-season.BusinessShareValue ?? 0);
}
if (considerContractPenalties) {
var varieties = ctx.WineVarieties.ToDictionary(v => v.SortId, v => v);
var attributes = ctx.WineAttributes.ToDictionary(a => a.AttrId, a => a);
var comTypes = ctx.AreaCommitmentTypes.ToDictionary(t => t.VtrgId, t => t);
MemberUnderDeliveries = underDeliveries?
.OrderBy(u => u.Key)
.Select(u => (
varieties[u.Key[..2]].Name + (u.Key.Length > 2 ? " " + attributes[u.Key[2..]].Name : ""),
u.Value.Diff,
u.Value.Diff * (comTypes[u.Key].PenaltyPerKg ?? 0)
- (comTypes[u.Key].PenaltyAmount ?? 0)
- ((u.Value.Weight == 0 ? comTypes[u.Key].PenaltyNone : null) ?? 0)))
.Where(u => u.Item3 != 0)
.ToList();
}
}
}}
+47 -23
View File
@@ -41,7 +41,7 @@
<th class="unit">[@Model.CurrencySymbol]</th>
</tr>
</thead>
<tbody>
<tbody class="sum">
@foreach (var p in Model.Data.Rows) {
var rows = Math.Max(p.Buckets.Length, p.Modifiers.Length + 1);
@for (int i = 0; i < rows; i++) {
@@ -49,7 +49,7 @@
@if (i == 0) {
<td rowspan="@rows">@p.LsNr</td>
<td rowspan="@rows">@p.DPNr</td>
<td class="small">@p.Variant</td>
<td class="small">@p.Variety</td>
<td class="small">@p.Attribute</td>
<td rowspan="@rows" class="center">@($"{p.Gradation.Oe:N0}")</td>
<td rowspan="@rows" class="center">@($"{p.Gradation.Kmw:N1}")</td>
@@ -78,36 +78,46 @@
}
}
</tbody>
</table>
<div class="hint">
Hinweis:<br/>
Die Summe der Lieferungen und die Summe der anfal&shy;lenden Pönalen werden mit
@Model.Payment?.Variant.Season.Precision Nach&shy;komma&shy;stellen berechnent,
erst das Ergebnis wird kauf&shy;männisch auf 2 Nach&shy;komma&shy;stellen gerundet.
</div>
<table class="credit-sum">
<colgroup>
<col style="width: auto;"/>
<col style="width: 5mm;"/>
<col style="width: 30mm;"/>
</colgroup>
@{
string FormatRow(string name, decimal? value, bool add = false, bool halfLine = true, bool bold = false, bool subCat = false) {
return $"<tr class=\"{(halfLine || add ? "" : "sum")} {(bold ? "large bold" : "")}\">"
+ $"<td colspan=\"4\"></td>"
+ $"<td class=\"{(halfLine && !add ? "sum" : "")} {(subCat ? "small" : "")}\" colspan=\"4\" style=\"overflow: visible;\">{name}:</td>"
+ $"<td class=\"number {(subCat ? "small" : "large")} {(halfLine && !add ? "sum" : "")}\">{(value < 0 ? "" : (add ? "+" : ""))}</td>"
+ $"<td colspan=\"2\" class=\"number {(subCat ? "small" : "large")} {(halfLine && !add ? "sum" : "")}\">"
string FormatRow(string name, decimal? value, bool add = false, bool bold = false, bool subCat = false, bool noTopBorder = false) {
return $"<tr class=\"{(!add && !noTopBorder ? "sum" : !add ? "large" : "")} {(bold ? "large bold" : "")}\">"
+ $"<td class=\"{(subCat ? "small" : "")}\">{name}:</td>"
+ $"<td class=\"number {(subCat ? "small" : "large")}\">{(value < 0 ? "" : (add ? "+" : ""))}</td>"
+ $"<td class=\"number {(subCat ? "small" : "large")}\">"
+ $"<span class=\"fleft\">{Model.CurrencySymbol}</span>{Math.Abs(value ?? 0):N2}</td>"
+ $"</tr>\n";
}
}
<tbody style="break-inside: avoid; break-before: avoid;">
<tbody style="break-inside: avoid;">
@{ var sum = Model.Data.Rows.Sum(p => p.Amount); }
@if (Model.Payment == null) {
@Raw(FormatRow("Gesamt", sum, halfLine: false, bold: true))
@Raw(FormatRow("Gesamt", sum, bold: true, noTopBorder: true))
} else {
var totalLine = false;
var noBorder = true;
if (Model.Payment.NetAmount != Model.Payment.Amount) {
@Raw(FormatRow("Zwischensumme", Model.Payment.NetAmount, halfLine: totalLine))
totalLine = true;
@Raw(FormatRow(Model.MemberModifier, Model.Payment.Amount - Model.Payment.NetAmount, add: true, halfLine: totalLine))
@Raw(FormatRow("Zwischensumme", Model.Payment.NetAmount, noTopBorder: noBorder))
noBorder = false;
@Raw(FormatRow(Model.MemberModifier, Model.Payment.Amount - Model.Payment.NetAmount, add: true))
}
if (Model.Credit == null) {
@Raw(FormatRow("Gesamtbetrag", Model.Payment.Amount, bold: true, halfLine: totalLine))
totalLine = true;
@Raw(FormatRow("Gesamtbetrag", Model.Payment.Amount, bold: true, noTopBorder: noBorder))
// TODO Mock VAT
} else {
var hasPrev = Model.Credit.PrevNetAmount != null;
@Raw(FormatRow(hasPrev ? "Gesamtbetrag" : "Nettobetrag", Model.Credit.NetAmount, bold: true, halfLine: totalLine))
totalLine = true;
@Raw(FormatRow(hasPrev ? "Gesamtbetrag" : "Nettobetrag", Model.Credit.NetAmount, bold: true, noTopBorder: noBorder))
if (hasPrev) {
@Raw(FormatRow("Bisher berücksichtigt", -Model.Credit.PrevNetAmount, add: true))
@Raw(FormatRow("Nettobetrag", Model.Credit.NetAmount - (Model.Credit.PrevNetAmount ?? 0)))
@@ -122,9 +132,8 @@
@if (Model.MemberUnderDeliveries != null && Model.MemberUnderDeliveries.Count() > 0) {
<tr class="small">
<td colspan="4"></td>
<td colspan="5" style="padding-top: 5mm;">Anfallende Pönalen durch Unterlieferungen:</td>
<td colspan="2"></td>
<td colspan="2" style="padding-top: 5mm;">Anfallende Pönalen durch Unterlieferungen:</td>
<td></td>
</tr>
foreach (var u in Model.MemberUnderDeliveries) {
@Raw(FormatRow($"{u.Name} ({u.Kg:N0} kg)", u.Amount, add: true, subCat: true))
@@ -132,12 +141,21 @@
}
penalty = Math.Round(penalty, 2, MidpointRounding.AwayFromZero);
}
@if (Model.MemberTotalUnderDelivery != 0) {
@Raw(FormatRow("Unterlieferung (GA)", Model.MemberTotalUnderDelivery, add: true));
penalty += Model.MemberTotalUnderDelivery;
}
@if (Model.MemberAutoBusinessShares != 0) {
@Raw(FormatRow("Autom. Nachz. von GA", Model.MemberAutoBusinessShares, add: true));
penalty += Model.MemberAutoBusinessShares;
}
@if (Model.Credit == null) {
@Raw(FormatRow("Auszahlungsbetrag", (Model.Payment?.Amount + penalty) ?? (sum + penalty), bold: true))
} else {
if (Model.Credit.Modifiers - penalty != 0) {
@Raw(FormatRow("Weitere Abzüge", Model.Credit.Modifiers - penalty, add: true))
var diff = Model.Credit.Modifiers - penalty;
if (diff != 0) {
@Raw(FormatRow(diff < 0 ? "Weitere Abzüge" : "Weitere Zuschläge", diff, add: true))
}
if (Model.Credit.PrevModifiers != null && Model.Credit.PrevModifiers != 0) {
@Raw(FormatRow("Bereits berücksichtigte Abzüge", -Model.Credit.PrevModifiers, add: true))
@@ -146,4 +164,10 @@
}
</tbody>
</table>
<p>Überweisung erfolgt auf Konto @(Elwig.Helpers.Utils.FormatIban(Model.Member.Iban ?? "-")).</p>
<div style="margin-top: 1em;">
@if (Model.Text != null) {
<p class="custom">@Model.Text</p>
}
</div>
</main>
+22 -3
View File
@@ -1,4 +1,8 @@
table.credit {
margin-bottom: 0;
}
table.credit .mod {
padding-left: 5mm;
}
@@ -19,11 +23,26 @@ table.credit tr.last td {
padding-bottom: 0;
}
table.credit .sum {
table.credit-sum {
width: 60%;
margin-left: 40%;
}
table.credit-sum tr.sum,
table.credit-sum tr .sum {
font-size: 12pt;
}
table.credit tr.sum td,
table.credit td.sum {
table.credit-sum tr.sum td,
table.credit-sum td.sum {
padding-top: 1mm !important;
}
.hint {
font-style: italic;
font-size: 8pt;
width: 56mm;
position: absolute;
left: 0;
margin: 2mm 4mm;
}
+2 -2
View File
@@ -10,11 +10,11 @@ namespace Elwig.Documents {
public new static string Name => "Anlieferungsbestätigung";
public Season Season;
public DeliveryConfirmationData Data;
public DeliveryConfirmationDeliveryData Data;
public string? Text = App.Client.TextDeliveryConfirmation;
public Dictionary<string, MemberBucket> MemberBuckets;
public DeliveryConfirmation(AppDbContext ctx, int year, Member m, DeliveryConfirmationData data) :
public DeliveryConfirmation(AppDbContext ctx, int year, Member m, DeliveryConfirmationDeliveryData data) :
base($"{Name} {year}", m) {
Season = ctx.Seasons.Find(year) ?? throw new ArgumentException("invalid season");
ShowDateAndLocation = true;
+6 -6
View File
@@ -42,17 +42,17 @@
</thead>
<tbody>
@{
var lastVariant = "";
var lastVariety = "";
}
@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.Variant != lastVariant && lastVariant != "" ? "new": "") @(rows > i + 1 ? "last" : "")">
<tr class="@(first ? "first" : "") @(p.Variety != lastVariety && lastVariety != "" ? "new": "") @(rows > i + 1 ? "last" : "")">
@if (first) {
<td rowspan="@rows">@p.LsNr</td>
<td rowspan="@rows">@p.DPNr</td>
<td class="small">@p.Variant</td>
<td class="small">@p.Variety</td>
<td class="small">@p.Attribute</td>
<td class="small">@p.QualityLevel</td>
<td rowspan="@rows" class="center">@($"{p.Gradation.Oe:N0}")</td>
@@ -80,7 +80,7 @@
first = false;
}
</tr>
lastVariant = p.Variant;
lastVariety = p.Variety;
}
}
<tr class="sum bold">
@@ -92,9 +92,9 @@
</table>
@Raw(BusinessDocument.PrintSortenaufteilung(Model.MemberBuckets))
@Raw(Model.PrintBucketTable(Model.Season, Model.MemberBuckets, includePayment: true))
<div class="text" style="margin-top: 2em;">
<div style="margin-top: 2em;">
@if (Model.Text != null) {
<p class="comment" style="white-space: pre-wrap; break-inside: avoid;">@Model.Text</p>
<p class="custom comment">@Model.Text</p>
}
</div>
</main>
+1 -1
View File
@@ -19,7 +19,7 @@ namespace Elwig.Documents {
public DeliveryJournal(string filter, IQueryable<DeliveryPart> deliveries) :
this(filter, deliveries
.Include(p => p.Delivery).ThenInclude(d => d.Member)
.Include(p => p.Variant)
.Include(p => p.Variety)
.ToList()) { }
public DeliveryJournal(AppDbContext ctx, DateOnly date) :
+1 -1
View File
@@ -45,7 +45,7 @@
<td class="small">@($"{p.Delivery.Time:HH:mm}")</td>
<td class="number">@p.Delivery.Member.MgNr</td>
<td class="small">@p.Delivery.Member.AdministrativeName</td>
<td class="small">@p.Variant.Name</td>
<td class="small">@p.Variety.Name</td>
<td class="center">@($"{p.Oe:N0}")</td>
<td class="center">@($"{p.Kmw:N1}")</td>
<td class="number">@($"{p.Weight:N0}")</td>
+1 -1
View File
@@ -36,7 +36,7 @@
@foreach (var part in Model.Delivery.Parts.OrderBy(p => p.DPNr)) {
<tr class="main">
<td class="center">@part.DPNr</td>
<td colspan="2">@part.Variant.Name</td>
<td colspan="2">@part.Variety.Name</td>
<td colspan="2">@part.Attribute?.Name</td>
<td>@part.Quality.Name</td>
<td class="center">@($"{part.Oe:N0}")</td>
+4 -5
View File
@@ -128,11 +128,6 @@ main table tr.border {
border-top: 0.5pt solid black;
}
main table tr.sum,
main table td.sum {
break-before: avoid;
}
main table th.unit {
font-size: 8pt;
}
@@ -152,3 +147,7 @@ main table .rborder {border-right: 0.5pt solid black;}
main table .fleft {
float: left;
}
main tbody.sum tr:last-child {
border-bottom: 0.5pt solid black;
}
+5 -3
View File
@@ -11,6 +11,8 @@ namespace Elwig.Documents {
public static string Name => "Dokument";
private static readonly double GenerationProportion = 0.125;
private TempFile? _pdfFile = null;
public bool ShowFoldMarks = App.Config.Debug;
@@ -95,10 +97,10 @@ namespace Elwig.Documents {
await File.WriteAllTextAsync(tmpHtml.FilePath, await doc.Render(), Utils.UTF8);
tmpHtmls.Add(tmpHtml);
i++;
progress?.Report(25.0 * i / n);
progress?.Report(GenerationProportion * 100 * i / n);
}
progress?.Report(25.0);
await Pdf.Convert(tmpHtmls.Select(f => f.FileName), pdf.FileName, DoubleSided, new Progress<double>(v => progress?.Report(25.0 + v * 0.75)));
progress?.Report(GenerationProportion * 100);
await Pdf.Convert(tmpHtmls.Select(f => f.FileName), pdf.FileName, DoubleSided, new Progress<double>(v => progress?.Report(GenerationProportion * 100 + v * (1 - GenerationProportion))));
foreach (var tmp in tmpHtmls) {
tmp.Dispose();
}
+4 -4
View File
@@ -2,6 +2,7 @@
using Elwig.Models.Entities;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Elwig.Documents {
public class MemberDataSheet : BusinessDocument {
@@ -9,13 +10,12 @@ namespace Elwig.Documents {
public new static string Name => "Stammdatenblatt";
public Season Season;
public int Year = Utils.CurrentYear;
public Dictionary<string, MemberBucket> MemberBuckets;
public MemberDataSheet(Member m, AppDbContext ctx) : base($"{Name} {m.AdministrativeName}", m) {
DocumentId = $"{Name} {m.MgNr}";
Season = ctx.Seasons.Find(Year) ?? throw new ArgumentException("invalid season");
MemberBuckets = ctx.GetMemberBuckets(Year, m.MgNr).GetAwaiter().GetResult();
Season = ctx.Seasons.ToList().MaxBy(s => s.Year) ?? throw new ArgumentException("invalid season");
MemberBuckets = ctx.GetMemberBuckets(Season.Year, m.MgNr).GetAwaiter().GetResult();
}
}
}
}
+7 -7
View File
@@ -7,14 +7,14 @@
<UseWPF>true</UseWPF>
<PreserveCompilationContext>true</PreserveCompilationContext>
<ApplicationIcon>Resources\Images\Elwig.ico</ApplicationIcon>
<Version>0.5.1</Version>
<Version>0.6.4</Version>
<SatelliteResourceLanguages>de-AT</SatelliteResourceLanguages>
</PropertyGroup>
<ItemGroup>
<Resource Include="Resources\Images\Elwig.png" />
<Content Include="Resources\Images\Elwig.ico" />
<EmbeddedResource Include="Schemas\payment_variant.json" />
<EmbeddedResource Include="Resources\Schemas\PaymentVariantData.json" />
<EmbeddedResource Include="Resources\Sql\*" />
</ItemGroup>
@@ -25,14 +25,14 @@
<ItemGroup>
<PackageReference Include="Extended.Wpf.Toolkit" Version="4.5.1" />
<PackageReference Include="LinqKit" Version="1.2.5" />
<PackageReference Include="Microsoft.AspNetCore.Razor.Language" Version="6.0.25" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="8.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Razor.Language" Version="6.0.26" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="8.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Ini" Version="8.0.0" />
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.2151.40" />
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.2210.55" />
<PackageReference Include="NJsonSchema" Version="11.0.0" />
<PackageReference Include="RazorLight" Version="2.3.1" />
<PackageReference Include="ScottPlot.WPF" Version="4.1.68" />
<PackageReference Include="ScottPlot.WPF" Version="5.0.19" />
<PackageReference Include="System.IO.Ports" Version="8.0.0" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="8.0.0" />
</ItemGroup>
+18 -1
View File
@@ -10,6 +10,7 @@ using Microsoft.Data.Sqlite;
using System.Text.RegularExpressions;
using System.Collections.Generic;
using Elwig.Models.Dtos;
using System.Reflection;
namespace Elwig.Helpers {
@@ -43,6 +44,7 @@ namespace Elwig.Helpers {
public DbSet<Member> Members { get; private set; }
public DbSet<BillingAddr> BillingAddresses { get; private set; }
public DbSet<MemberTelNr> MemberTelephoneNrs { get; private set; }
public DbSet<MemberHistory> MemberHistory { get; private set; }
public DbSet<AreaCom> AreaCommitments { get; private set; }
public DbSet<Season> Seasons { get; private set; }
public DbSet<Modifier> Modifiers { get; private set; }
@@ -56,6 +58,7 @@ namespace Elwig.Helpers {
public DbSet<OverUnderDeliveryRow> OverUnderDeliveryRows { get; private set; }
public DbSet<AreaComUnderDeliveryRowSingle> AreaComUnderDeliveryRows { get; private set; }
public DbSet<MemberDeliveryPerVariantRowSingle> MemberDeliveryPerVariantRows { get; private set; }
public DbSet<CreditNoteDeliveryRowSingle> CreditNoteDeliveryRows { get; private set; }
public DbSet<CreditNoteRowSingle> CreditNoteRows { get; private set; }
private readonly StreamWriter? LogFile = null;
@@ -63,7 +66,8 @@ namespace Elwig.Helpers {
public DateTime SavedLastWriteTime { get; private set; }
public bool HasBackendChanged => SavedLastWriteTime != LastWriteTime;
public static string ConnectionString => $"Data Source=\"{App.Config.DatabaseFile}\"; Foreign Keys=True; Mode=ReadWrite; Cache=Default";
public static string? ConnectionStringOverride { get; set; } = null;
public static string ConnectionString => ConnectionStringOverride ?? $"Data Source=\"{App.Config.DatabaseFile}\"; Mode=ReadWrite; Foreign Keys=True; Cache=Default";
private readonly Dictionary<int, Dictionary<int, Dictionary<string, AreaComBucket>>> _memberAreaCommitmentBuckets = [];
private readonly Dictionary<int, Dictionary<int, Dictionary<string, int>>> _memberDeliveryBuckets = [];
@@ -106,6 +110,12 @@ namespace Elwig.Helpers {
await (await cmd.ExecuteReaderAsync()).CloseAsync();
}
public static async Task ExecuteEmbeddedScript(SqliteConnection cnx, Assembly asm, string name) {
using var stream = asm.GetManifestResourceStream(name) ?? throw new FileNotFoundException("Unable to load embedded resource");
using var reader = new StreamReader(stream);
await ExecuteBatch(cnx, await reader.ReadToEndAsync());
}
public static async Task<object?> ExecuteScalar(SqliteConnection cnx, string sql) {
using var cmd = cnx.CreateCommand();
cmd.CommandText = sql;
@@ -192,6 +202,13 @@ namespace Elwig.Helpers {
return c + 1;
}
public async Task<int> NextAvNr(int year) {
int c = 0;
(await PaymentVariants.Where(v => v.Year == year).Select(v => v.AvNr).ToListAsync())
.ForEach(a => { if (a <= c + 100) c = a; });
return c + 1;
}
public async Task<WineQualLevel> GetWineQualityLevel(double kmw) {
return await WineQualityLevels
.Where(q => !q.IsPredicate && (q.MinKmw == null || q.MinKmw <= kmw))
+4 -6
View File
@@ -1,7 +1,6 @@
using Microsoft.Data.Sqlite;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
@@ -9,7 +8,8 @@ using System.Threading.Tasks;
namespace Elwig.Helpers {
public static class AppDbUpdater {
public static readonly int RequiredSchemaVersion = 12;
// Don't forget to update value in Tests/fetch-resources.bat!
public static readonly int RequiredSchemaVersion = 15;
private static int VersionOffset = 0;
@@ -44,7 +44,7 @@ namespace Elwig.Helpers {
private static async Task UpdateDbSchema(SqliteConnection cnx, int fromVersion, int toVersion) {
if (fromVersion == toVersion) {
//return;
return;
} else if (fromVersion > toVersion) {
throw new Exception("schema_version of database is too new");
} else if (fromVersion <= 0) {
@@ -77,9 +77,7 @@ namespace Elwig.Helpers {
BEGIN EXCLUSIVE;
""");
foreach (var script in toExecute) {
using var stream = asm.GetManifestResourceStream(script) ?? throw new Exception("Unable to load embedded resource");
using var reader = new StreamReader(stream);
await AppDbContext.ExecuteBatch(cnx, await reader.ReadToEndAsync());
await AppDbContext.ExecuteEmbeddedScript(cnx, asm, script);
}
await AppDbContext.ExecuteBatch(cnx, $"""
PRAGMA foreign_key_check;
+32 -12
View File
@@ -1,3 +1,5 @@
using Elwig.Models.Entities;
using Microsoft.Data.Sqlite;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -8,6 +10,7 @@ namespace Elwig.Helpers.Billing {
protected readonly int Year;
protected readonly AppDbContext Context;
protected readonly Season Season;
protected readonly Dictionary<string, string> Attributes;
protected readonly Dictionary<string, (decimal?, decimal?)> Modifiers;
protected readonly Dictionary<string, (string, string?, string?, int?, decimal?)> AreaComTypes;
@@ -15,6 +18,7 @@ namespace Elwig.Helpers.Billing {
public Billing(int year) {
Year = year;
Context = new AppDbContext();
Season = Context.Seasons.Find(Year)!;
Attributes = Context.WineAttributes.ToDictionary(a => a.AttrId, a => a.Name);
Modifiers = Context.Modifiers.Where(m => m.Year == Year).ToDictionary(m => m.ModId, m => (m.Abs, m.Rel));
AreaComTypes = Context.AreaCommitmentTypes.ToDictionary(v => v.VtrgId, v => (v.SortId, v.AttrId, v.Discriminator, v.MinKgPerHa, v.PenaltyAmount));
@@ -26,15 +30,26 @@ namespace Elwig.Helpers.Billing {
UPDATE season
SET (start_date, end_date) = (SELECT MIN(date), MAX(date) FROM delivery WHERE year = {Year})
WHERE year = {Year};
DELETE FROM delivery_part_bucket WHERE year = {Year};
""");
}
public async Task CalculateBuckets(bool allowAttrsIntoLower, bool avoidUnderDeliveries, bool honorGebunden) {
public async Task AutoAdjustBusinessShare() {
using var cnx = await AppDbContext.ConnectAsync();
await AppDbContext.ExecuteBatch(cnx, $"""
INSERT INTO member_history (mgnr, date, business_shares, type)
SELECT u.mgnr, '{Utils.Today:yyyy-MM-dd}', u.diff / s.max_kg_per_bs AS bs, 'auto'
FROM v_total_under_delivery u
JOIN season s ON s.year = u.year
WHERE s.year = {Year} AND bs > 0
ON CONFLICT DO NOTHING
""");
}
public async Task CalculateBuckets(bool allowAttrsIntoLower, bool avoidUnderDeliveries, bool honorGebunden, SqliteConnection? cnx = null) {
var attrVals = Context.WineAttributes.ToDictionary(a => a.AttrId, a => (a.IsStrict, a.FillLower));
var attrForced = attrVals.Where(a => a.Value.IsStrict && a.Value.FillLower == 0).Select(a => a.Key).ToArray();
using var cnx = await AppDbContext.ConnectAsync();
var ownCnx = cnx == null;
cnx ??= await AppDbContext.ConnectAsync();
await Context.GetMemberAreaCommitmentBuckets(Year, 0, cnx);
var inserts = new List<(int, int, int, string, int)>();
@@ -53,7 +68,7 @@ namespace Elwig.Helpers.Billing {
reader.GetInt32(0), reader.GetInt32(1), reader.GetInt32(2), reader.GetString(3), reader.GetInt32(4),
reader.GetDouble(5), reader.GetString(6),
reader.IsDBNull(7) ? null : reader.GetString(7),
reader.IsDBNull(8) ? Array.Empty<string>() : reader.GetString(8).Split(",").Order().ToArray(),
reader.IsDBNull(8) ? [] : reader.GetString(8).Split(",").Order().ToArray(),
reader.IsDBNull(9) ? null : reader.GetBoolean(9)
));
}
@@ -61,11 +76,11 @@ namespace Elwig.Helpers.Billing {
int lastMgNr = 0;
Dictionary<string, AreaComBucket>? rightsAndObligations = null;
Dictionary<string, int> used = new();
Dictionary<string, int> used = [];
foreach (var (mgnr, did, dpnr, sortid, weight, kmw, qualid, attrid, modifiers, gebunden) in deliveries) {
if (lastMgNr != mgnr) {
rightsAndObligations = await Context.GetMemberAreaCommitmentBuckets(Year, mgnr);
used = new();
used = [];
}
if ((honorGebunden && gebunden == false) ||
rightsAndObligations == null || rightsAndObligations.Count == 0 ||
@@ -80,16 +95,16 @@ namespace Elwig.Helpers.Billing {
}
int w = weight;
var attributes = attrid == null ? Array.Empty<string>() : new string[] { attrid };
var attributes = attrid == null ? [] : new string[] { attrid };
var isStrict = attrid != null && attrVals[attrid].IsStrict;
foreach (var p in Utils.Permutate(attributes, attributes.Intersect(attrForced))) {
var c = p.Count();
var key = sortid + string.Join("", p);
if (rightsAndObligations.ContainsKey(key)) {
if (rightsAndObligations.TryGetValue(key, out AreaComBucket value)) {
int i = (c == 0) ? 1 : 2;
var u = used.GetValueOrDefault(key, 0);
var vr = Math.Max(0, Math.Min(rightsAndObligations[key].Right - u, w));
var vo = Math.Max(0, Math.Min(rightsAndObligations[key].Obligation - u, w));
var vr = Math.Max(0, Math.Min(value.Right - u, w));
var vo = Math.Max(0, Math.Min(value.Obligation - u, w));
var v = (attributes.Length == c || attributes.Select(a => !attrVals[a].IsStrict ? 2 : attrVals[a].FillLower).Min() == 2) ? vr : vo;
used[key] = u + v;
if (key.Length > 2 && !isStrict) used[key[..2]] = used.GetValueOrDefault(key[..2], 0) + v;
@@ -103,14 +118,17 @@ namespace Elwig.Helpers.Billing {
}
await AppDbContext.ExecuteBatch(cnx, $"""
UPDATE delivery_part_bucket SET value = 0 WHERE year = {Year};
INSERT INTO delivery_part_bucket (year, did, dpnr, bktnr, discr, value)
VALUES {string.Join(",\n ", inserts.Select(i => $"({Year}, {i.Item1}, {i.Item2}, {i.Item3}, '{i.Item4}', {i.Item5})"))}
ON CONFLICT DO UPDATE
SET discr = excluded.discr, value = value + excluded.value;
""");
if (!avoidUnderDeliveries)
if (!avoidUnderDeliveries) {
if (ownCnx) await cnx.DisposeAsync();
return;
}
// FIXME avoidUnderDelivery-calculations not always right!
@@ -188,6 +206,8 @@ namespace Elwig.Helpers.Billing {
ON CONFLICT DO UPDATE
SET value = excluded.value;
""");
if (ownCnx) await cnx.DisposeAsync();
}
}
}
+349 -6
View File
@@ -1,6 +1,9 @@
using Newtonsoft.Json;
using NJsonSchema;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Text.Json.Nodes;
using System.Threading.Tasks;
@@ -8,21 +11,58 @@ using System.Threading.Tasks;
namespace Elwig.Helpers.Billing {
public class BillingData {
public enum CalculationMode { Elwig, WgMaster }
public enum CurveMode { Oe, Kmw }
public record struct Curve(CurveMode Mode, Dictionary<double, decimal> Normal, Dictionary<double, decimal>? Gebunden);
public static JsonSchema? Schema { get; private set; }
public static async Task Init() {
var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Elwig.Schemas.payment_variant.json");
var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Elwig.Resources.Schemas.PaymentVariantData.json");
Schema = await JsonSchema.FromJsonAsync(stream ?? throw new ArgumentException("JSON schema not found"));
}
private JsonObject Data;
public readonly JsonObject Data;
public readonly CalculationMode Mode;
public bool ConsiderDelieryModifiers {
get => GetConsider("consider_delivery_modifiers");
set => SetConsider(value, "consider_delivery_modifiers");
}
public bool ConsiderContractPenalties {
get => GetConsider("consider_contract_penalties");
set => SetConsider(value, "consider_contract_penalties");
}
public bool ConsiderTotalPenalty {
get => GetConsider("consider_total_penalty");
set => SetConsider(value, "consider_total_penalty");
}
public bool ConsiderAutoBusinessShares {
get => GetConsider("consider_auto_business_shares");
set => SetConsider(value, "consider_auto_business_shares");
}
private bool GetConsider(string name, string? wgMasterName = null) {
return ((Mode == CalculationMode.Elwig) ? Data[name] : Data[wgMasterName ?? ""])?.AsValue().GetValue<bool>() ?? false;
}
private void SetConsider(bool value, string name, string? wgMasterName = null) {
if (Mode == CalculationMode.WgMaster && wgMasterName == null) {
return;
} else if (value) {
Data[(Mode == CalculationMode.Elwig) ? name : wgMasterName ?? ""] = value;
} else {
Data.Remove((Mode == CalculationMode.Elwig) ? name : wgMasterName ?? "");
}
}
public BillingData(JsonObject data) {
Data = data;
var mode = Data["mode"]?.GetValue<string>();
Mode = (mode == "elwig") ? CalculationMode.Elwig : CalculationMode.WgMaster;
}
public static JsonObject ParseJson(string json) {
protected static JsonObject ParseJson(string json) {
if (Schema == null) throw new InvalidOperationException("Schema has to be initialized first");
try {
var errors = Schema.Validate(json);
@@ -37,8 +77,311 @@ namespace Elwig.Helpers.Billing {
return new(ParseJson(json));
}
public decimal CalculatePrice(string sortId, string? attrid, string qualId, bool gebunden, double oe, double kmw, bool minQuw) {
return 0m;
protected JsonArray GetCurvesEntry() {
return Data[Mode == CalculationMode.Elwig ? "curves" : "Kurven"]?.AsArray() ?? throw new InvalidOperationException();
}
protected JsonNode GetPaymentEntry() {
return Data[Mode == CalculationMode.Elwig ? "payment" : "AuszahlungSorten"] ?? throw new InvalidOperationException();
}
protected JsonObject? GetQualityEntry() {
return Data[Mode == CalculationMode.Elwig ? "quality" : "AuszahlungSortenQualitätsstufe"]?.AsObject();
}
private static Dictionary<double, decimal> GetCurveData(JsonObject data, CurveMode mode) {
var dict = new Dictionary<double, decimal>();
foreach (var (index, price) in data) {
double idx;
bool? gtlt = index.StartsWith('>') ? true : index.StartsWith('<') ? false : null;
if (index.EndsWith("kmw")) {
idx = double.Parse(index[(gtlt != null ? 1 : 0)..^3], CultureInfo.InvariantCulture);
if (mode == CurveMode.Oe) {
idx = Utils.KmwToOe(idx);
}
} else if (index.EndsWith("oe")) {
idx = double.Parse(index[(gtlt != null ? 1 : 0)..^2], CultureInfo.InvariantCulture);
if (mode == CurveMode.Kmw) {
idx = Utils.OeToKmw(idx);
}
} else {
throw new InvalidOperationException();
}
if (gtlt == true) {
idx = Math.BitIncrement(idx);
} else if (gtlt == false) {
idx = Math.BitDecrement(idx);
}
dict[idx] = price?.AsValue().GetValue<decimal>() ?? throw new InvalidOperationException();
}
return dict;
}
protected Dictionary<int, Curve> GetCurves() {
var dict = new Dictionary<int, Curve>();
var curves = GetCurvesEntry();
foreach (var c in curves) {
var obj = c?.AsObject() ?? throw new InvalidOperationException();
var id = obj["id"]?.GetValue<int>() ?? throw new InvalidOperationException();
var cMode = (obj["mode"]?.GetValue<string>() == "kmw") ? CurveMode.Kmw : CurveMode.Oe;
double quw = cMode == CurveMode.Oe ? 73 : 15;
Dictionary<double, decimal> c1;
Dictionary<double, decimal>? c2 = null;
var norm = obj["data"];
if (norm is JsonObject) {
c1 = GetCurveData(norm.AsObject(), cMode);
} else if (norm?.AsValue().TryGetValue(out decimal v) == true) {
c1 = new() { { quw, v } };
} else {
throw new InvalidOperationException();
}
var geb = obj["geb"];
if (geb is JsonObject) {
c2 = GetCurveData(geb.AsObject(), cMode);
} else if (geb?.AsValue().TryGetValue(out decimal v) == true) {
var splitVal = GetCurveValueAt(c1, quw);
c2 = c1.ToDictionary(e => e.Key, e => e.Value + (e.Key >= quw ? v : 0));
c2[quw] = splitVal + v;
c2[Math.BitDecrement(quw)] = splitVal;
}
dict.Add(id, new(cMode, c1, c2));
}
return dict;
}
protected static Dictionary<string, JsonValue> GetSelection(JsonNode value, IEnumerable<string> vaributes) {
if (value is JsonValue flatRate) {
return vaributes.ToDictionary(e => e, _ => flatRate);
} if (value is not JsonObject data) {
throw new InvalidOperationException();
}
Dictionary<string, JsonValue> dict;
if (data["default"] is JsonValue def) {
dict = vaributes.ToDictionary(e => e, _ => def);
} else {
dict = [];
}
var varieties = data.Where(p => !p.Key.StartsWith('/') && p.Key.Length == 2);
var attributes = data.Where(p => p.Key.StartsWith('/'));
var others = data.Where(p => !p.Key.StartsWith('/') && p.Key.Length > 2 && p.Key != "default");
foreach (var (idx, v) in varieties) {
var curve = v?.AsValue() ?? throw new InvalidOperationException();
foreach (var i in vaributes.Where(e => e.StartsWith(idx[..^1]))) {
dict[i] = curve;
}
}
foreach (var (idx, v) in attributes) {
var curve = v?.AsValue() ?? throw new InvalidOperationException();
foreach (var i in vaributes.Where(e => e[2..] == idx[1..])) {
dict[i] = curve;
}
}
foreach (var (idx, v) in others) {
var curve = v?.AsValue() ?? throw new InvalidOperationException();
dict[idx.Replace("/", "")] = curve;
}
return dict;
}
public static decimal GetCurveValueAt(Dictionary<double, decimal> curve, double key) {
if (curve.Count == 1) return curve.First().Value;
var lt = curve.Keys.Where(v => v <= key);
var gt = curve.Keys.Where(v => v >= key);
if (!lt.Any()) {
return curve[gt.Min()];
} else if (!gt.Any()) {
return curve[lt.Max()];
}
var max = lt.Max();
var min = gt.Min();
if (max == min) return curve[key];
var p1 = ((decimal)key - (decimal)min) / ((decimal)max - (decimal)min);
var p2 = 1 - p1;
return curve[min] * p2 + curve[max] * p1;
}
protected static JsonObject GraphToJson(Graph graph, string mode) {
var x = graph.DataX;
var y = graph.DataY;
var prec = graph.Precision;
try {
return new JsonObject() {
["15kmw"] = Math.Round(y.Distinct().Single(), prec)
};
} catch { }
var data = new JsonObject();
if (y[0] != y[1]) {
data[$"{x[0]}{mode}"] = Math.Round(y[0], prec);
}
for (int i = 1; i < x.Length - 1; i++) {
var d1 = Math.Round(y[i] - y[i - 1], prec);
var d2 = Math.Round(y[i + 1] - y[i], prec);
if (d1 != d2) {
data[$"{x[i]}{mode}"] = Math.Round(y[i], prec);
}
}
if (y[^1] != y[^2]) {
data[$"{x[^1]}{mode}"] = Math.Round(y[^1], prec);
}
return data;
}
protected static JsonNode GraphEntryToJson(GraphEntry entry) {
try {
if (entry.GebundenFlatBonus == null) {
return JsonValue.Create((decimal)entry.DataGraph.DataY.Distinct().Single());
}
} catch { }
var curve = new JsonObject {
["id"] = entry.Id,
["mode"] = entry.Mode.ToString().ToLower(),
};
curve["data"] = GraphToJson(entry.DataGraph, entry.Mode.ToString().ToLower());
if (entry.GebundenFlatBonus != null) {
curve["geb"] = (decimal)entry.GebundenFlatBonus;
} else if (entry.GebundenGraph != null) {
curve["geb"] = GraphToJson(entry.GebundenGraph, entry.Mode.ToString().ToLower());
}
return curve;
}
protected static void CollapsePaymentData(JsonObject data, IEnumerable<string> vaributes, bool useDefault = true) {
Dictionary<string, List<string>> rev1 = [];
Dictionary<decimal, List<string>> rev2 = [];
foreach (var (k, v) in data) {
if (k == "default" || k.StartsWith('/') || !k.Contains('/') || v is not JsonValue val) {
continue;
} else if (val.TryGetValue<decimal>(out var dec)) {
rev2[dec] = rev2.GetValueOrDefault(dec) ?? [];
rev2[dec].Add(k);
} else if (val.TryGetValue<string>(out var cur)) {
rev1[cur] = rev1.GetValueOrDefault(cur) ?? [];
rev1[cur].Add(k);
}
}
if (!data.ContainsKey("default")) {
foreach (var (v, ks) in rev1) {
if ((ks.Count >= vaributes.Count() * 0.5 && useDefault) || ks.Count == vaributes.Count()) {
foreach (var k in ks) data.Remove(k);
data["default"] = v;
CollapsePaymentData(data, vaributes, useDefault);
return;
}
}
foreach (var (v, ks) in rev2) {
if ((ks.Count >= vaributes.Count() * 0.5 && useDefault) || ks.Count == vaributes.Count()) {
foreach (var k in ks) data.Remove(k);
data["default"] = v;
CollapsePaymentData(data, vaributes, useDefault);
return;
}
}
}
var attributes = data
.Select(e => e.Key)
.Where(k => k.Length > 3 && k.Contains('/'))
.Select(k => "/" + k.Split('/')[1])
.Distinct()
.ToList();
foreach (var idx in attributes) {
var len = vaributes.Count(e => e.EndsWith(idx));
foreach (var (v, ks) in rev1) {
var myKs = ks.Where(k => k.EndsWith(idx)).ToList();
if (myKs.Count > 1 && ((myKs.Count >= len * 0.5 && useDefault) || myKs.Count == len)) {
foreach (var k in myKs) data.Remove(k);
data[idx] = v;
}
}
foreach (var (v, ks) in rev2) {
var myKs = ks.Where(k => k.EndsWith(idx)).ToList();
if (myKs.Count > 1 && ((myKs.Count >= len * 0.5 && useDefault) || myKs.Count == len)) {
foreach (var k in myKs) data.Remove(k);
data[idx] = v;
}
}
}
}
public static JsonObject FromGraphEntries(
IEnumerable<GraphEntry> graphEntries,
BillingData? origData = null,
IEnumerable<string>? vaributes = null,
bool useDefaultPayment = true,
bool useDefaultQuality = true
) {
var payment = new JsonObject();
var qualityWei = new JsonObject();
var curves = new JsonArray();
int curveId = 0;
foreach (var entry in graphEntries) {
var curve = GraphEntryToJson(entry);
JsonValue node;
if (curve is JsonObject obj) {
obj["id"] = ++curveId;
node = JsonValue.Create($"curve:{curveId}");
curves.Add(obj);
} else if (curve is JsonValue val && val.TryGetValue<decimal>(out var flat)) {
node = JsonValue.Create(flat);
} else {
continue;
}
foreach (var c in entry.Vaributes) {
if (entry.Abgewertet) {
qualityWei[$"{c.Variety?.SortId}/{c.Attribute?.AttrId}"] = node.DeepClone();
} else {
payment[$"{c.Variety?.SortId}/{c.Attribute?.AttrId}"] = node.DeepClone();
}
}
}
CollapsePaymentData(payment, vaributes ?? payment.Select(e => e.Key).ToList(), useDefaultPayment);
CollapsePaymentData(qualityWei, vaributes ?? qualityWei.Select(e => e.Key).ToList(), useDefaultQuality);
var data = new JsonObject {
["mode"] = "elwig",
["version"] = 1,
};
if (origData?.ConsiderDelieryModifiers == true)
data["consider_delivery_modifiers"] = true;
if (origData?.ConsiderContractPenalties == true)
data["consider_contract_penalties"] = true;
if (origData?.ConsiderTotalPenalty == true)
data["consider_total_penalty"] = true;
if (origData?.ConsiderAutoBusinessShares == true)
data["consider_auto_business_shares"] = true;
if (payment.Count == 0) {
data["payment"] = 0;
} else if (payment.Count == 1 && payment.First().Key == "default") {
data["payment"] = payment.Single().Value?.DeepClone();
} else {
data["payment"] = payment;
}
if (qualityWei.Count == 1 && qualityWei.First().Key == "default") {
data["quality"] = new JsonObject() {
["WEI"] = qualityWei.Single().Value?.DeepClone()
};
} else if (qualityWei.Count >= 1) {
data["quality"] = new JsonObject() {
["WEI"] = qualityWei
};
}
data["curves"] = curves;
return data;
}
}
}
+46 -35
View File
@@ -1,3 +1,6 @@
using Elwig.Models.Entities;
using Microsoft.Data.Sqlite;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
@@ -5,18 +8,32 @@ using System.Threading.Tasks;
namespace Elwig.Helpers.Billing {
public class BillingVariant : Billing {
private readonly int AvNr;
protected readonly int AvNr;
protected readonly PaymentVar PaymentVariant;
protected readonly PaymentBillingData Data;
public BillingVariant(int year, int avnr) : base(year) {
AvNr = avnr;
PaymentVariant = Context.PaymentVariants.Find(Year, AvNr) ?? throw new ArgumentException("PaymentVar not found");
Data = PaymentBillingData.FromJson(PaymentVariant.Data, Utils.GetVaributes(Context, Year, onlyDelivered: false));
}
public async Task Calculate() {
await DeleteInDb();
await SetCalcTime();
await CalculatePrices();
await CalculateModifiers();
await CalculateMemberModifiers();
using var cnx = await AppDbContext.ConnectAsync();
using var tx = await cnx.BeginTransactionAsync();
await CalculateBuckets(
Season.Billing_AllowAttrsIntoLower,
Season.Billing_AvoidUnderDeliveries,
Season.Billing_HonorGebunden,
cnx);
await DeleteInDb(cnx);
await SetCalcTime(cnx);
await CalculatePrices(cnx);
if (Data.ConsiderDelieryModifiers) {
await CalculateDeliveryModifiers(cnx);
await CalculateMemberModifiers(cnx);
}
await tx.CommitAsync();
}
public async Task Commit() {
@@ -31,7 +48,10 @@ namespace Elwig.Helpers.Billing {
ROUND(p.amount / POW(10, s.precision - 2)) AS net_amount,
ROUND(lp.amount / POW(10, s.precision - 2)) AS prev_amount,
IIF(m.buchführend, s.vat_normal, s.vat_flatrate) AS vat,
ROUND(COALESCE(u.total_penalty, 0) / POW(10, 4 - 2)) AS modifiers,
ROUND(IIF({Data.ConsiderContractPenalties}, COALESCE(u.total_penalty, 0), 0) / POW(10, 4 - 2)) +
ROUND(IIF({Data.ConsiderTotalPenalty}, COALESCE(b.total_penalty, 0), 0) / POW(10, s.precision - 2)) +
ROUND(IIF({Data.ConsiderAutoBusinessShares}, -COALESCE(a.total_amount, 0), 0) / POW(10, s.precision - 2))
AS modifiers,
lc.modifiers AS prev_modifiers
FROM season s
JOIN payment_variant v ON v.year = s.year
@@ -47,13 +67,9 @@ namespace Elwig.Helpers.Billing {
LEFT JOIN payment_member lp ON (lp.year, lp.avnr, lp.mgnr) = (l.year, l.avnr, m.mgnr)
LEFT JOIN payment_member p ON (p.year, p.avnr, p.mgnr) = (v.year, v.avnr, m.mgnr)
LEFT JOIN credit lc ON (lc.year, lc.avnr, lc.mgnr) = (l.year, l.avnr, m.mgnr)
LEFT JOIN (SELECT year, mgnr,
SUM(COALESCE(IIF(u.weight = 0, -t.penalty_none, 0), 0) +
COALESCE(IIF(u.diff < 0, -t.penalty_amount, 0), 0) +
COALESCE(u.diff * t.penalty_per_kg, 0)) AS total_penalty
FROM v_under_delivery u
JOIN area_commitment_type t ON t.vtrgid = u.bucket
GROUP BY year, mgnr) u ON (u.year, u.mgnr) = (s.year, m.mgnr)
LEFT JOIN v_penalty_area_commitments u ON (u.year, u.mgnr) = (s.year, m.mgnr)
LEFT JOIN v_penalty_business_shares b ON (b.year, b.mgnr) = (s.year, m.mgnr)
LEFT JOIN v_auto_business_shares a ON (a.year, a.mgnr) = (s.year, m.mgnr)
WHERE s.year = {Year} AND v.avnr = {AvNr};
UPDATE payment_variant SET test_variant = FALSE WHERE (year, avnr) = ({Year}, {AvNr});
@@ -68,15 +84,13 @@ namespace Elwig.Helpers.Billing {
""");
}
protected async Task SetCalcTime() {
using var cnx = await AppDbContext.ConnectAsync();
protected async Task SetCalcTime(SqliteConnection cnx) {
await AppDbContext.ExecuteBatch(cnx, $"""
UPDATE payment_variant SET calc_time = UNIXEPOCH() WHERE (year, avnr) = ({Year}, {AvNr})
""");
}
protected async Task DeleteInDb() {
using var cnx = await AppDbContext.ConnectAsync();
protected async Task DeleteInDb(SqliteConnection cnx) {
await AppDbContext.ExecuteBatch(cnx, $"""
DELETE FROM payment_delivery_part_bucket WHERE (year, avnr) = ({Year}, {AvNr});
DELETE FROM payment_delivery_part WHERE (year, avnr) = ({Year}, {AvNr});
@@ -85,8 +99,7 @@ namespace Elwig.Helpers.Billing {
""");
}
protected async Task CalculateMemberModifiers() {
using var cnx = await AppDbContext.ConnectAsync();
protected async Task CalculateMemberModifiers(SqliteConnection cnx) {
if (App.Client.IsMatzen) {
var lastYears = 3;
var multiplier = 0.50;
@@ -113,13 +126,11 @@ namespace Elwig.Helpers.Billing {
}
}
protected async Task CalculatePrices() {
using var cnx = await AppDbContext.ConnectAsync();
var parts = new List<(int Year, int DId, int DPNr, int BktNr, string SortId, string Discr, int Value, bool MinQuw, double Oe, double Kmw)>();
protected async Task CalculatePrices(SqliteConnection cnx) {
var parts = new List<(int Year, int DId, int DPNr, int BktNr, string SortId, string? AttrId, string Discr, int Value, double Oe, double Kmw, string QualId)>();
using (var cmd = cnx.CreateCommand()) {
cmd.CommandText = $"""
SELECT d.year, d.did, d.dpnr, b.bktnr, d.sortid, b.discr, b.value, d.min_quw, d.oe, d.kmw
SELECT d.year, d.did, d.dpnr, b.bktnr, d.sortid, d.attrid, b.discr, b.value, d.oe, d.kmw, d.qualid
FROM delivery_part_bucket b
JOIN v_delivery d ON (d.year, d.did, d.dpnr) = (b.year, b.did, b.dpnr)
WHERE b.year = {Year}
@@ -128,19 +139,20 @@ namespace Elwig.Helpers.Billing {
while (await reader.ReadAsync()) {
parts.Add((
reader.GetInt32(0), reader.GetInt32(1), reader.GetInt32(2), reader.GetInt32(3),
reader.GetString(4), reader.GetString(5), reader.GetInt32(6),
reader.GetBoolean(7), reader.GetDouble(8), reader.GetDouble(9)
reader.GetString(4), reader.IsDBNull(5) ? null : reader.GetString(5), reader.GetString(6),
reader.GetInt32(7), reader.GetDouble(8), reader.GetDouble(9), reader.GetString(10)
));
}
}
var inserts = new List<(int Year, int DId, int DPNr, int BktNr, long Price, long Amount)>();
foreach (var part in parts) {
var price = !part.MinQuw ? 0.5m : ((part.BktNr == 2 ? 0.8m : (part.BktNr == 1 ? 0.7m : 0.6m)) + ((decimal)(part.Oe - 73) * 0.005m)); // TODO
price += (AvNr - 1) * 0.2m;
var priceL = Utils.DecToDb(price, 4);
var amount = Utils.DecToDb(price * part.Value, 4);
inserts.Add((part.Year, part.DId, part.DPNr, part.BktNr, priceL, amount));
var ungeb = part.Discr == "_";
var payAttrId = (part.Discr is "" or "_") ? null : part.Discr;
var geb = !ungeb && payAttrId == part.AttrId;
var price = Data.CalculatePrice(part.SortId, part.AttrId, part.QualId, geb, part.Oe, part.Kmw);
var priceL = PaymentVariant.Season.DecToDb(price);
inserts.Add((part.Year, part.DId, part.DPNr, part.BktNr, priceL, priceL * part.Value));
}
await AppDbContext.ExecuteBatch(cnx, $"""
@@ -149,14 +161,13 @@ namespace Elwig.Helpers.Billing {
""");
}
protected async Task CalculateModifiers() {
using var cnx = await AppDbContext.ConnectAsync();
protected async Task CalculateDeliveryModifiers(SqliteConnection cnx) {
await AppDbContext.ExecuteBatch(cnx, $"""
INSERT INTO payment_delivery_part (year, did, dpnr, avnr, net_amount, mod_abs, mod_rel)
SELECT d.year, d.did, d.dpnr, {AvNr}, 0, COALESCE(m.abs, 0), COALESCE(m.rel, 0)
FROM delivery_part d
LEFT JOIN delivery_part_modifier p ON (p.year, p.did, p.dpnr) = (d.year, d.did, d.dpnr)
LEFT JOIN modifier m ON m.modid = p.modid
LEFT JOIN modifier m ON (m.year, m.modid) = (d.year, p.modid)
WHERE d.year = {Year}
ON CONFLICT DO UPDATE
SET mod_abs = mod_abs + excluded.mod_abs,
+106
View File
@@ -0,0 +1,106 @@
using Elwig.Models.Entities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json.Nodes;
namespace Elwig.Helpers.Billing {
public class EditBillingData : BillingData {
protected readonly IEnumerable<string> Vaributes;
public EditBillingData(JsonObject data, IEnumerable<string> vaributes) :
base(data) {
Vaributes = vaributes;
}
public static EditBillingData FromJson(string json, IEnumerable<string> vaributes) {
return new(ParseJson(json), vaributes);
}
private (Dictionary<int, Curve>, Dictionary<int, List<string>>) GetGraphEntries(JsonNode root) {
Dictionary<int, List<string>> dict1 = [];
Dictionary<decimal, List<string>> dict2 = [];
if (root is JsonObject paymentObj) {
foreach (var (selector, node) in paymentObj) {
var val = node?.AsValue();
if (val == null) {
continue;
} else if (val.TryGetValue<decimal>(out var price)) {
if (!dict2.ContainsKey(price)) dict2[price] = [];
dict2[price].Add(selector);
} else if (val.TryGetValue<string>(out var curve)) {
var idx = int.Parse(curve.Split(":")[1] ?? "0");
if (!dict1.ContainsKey(idx)) dict1[idx] = [];
dict1[idx].Add(selector);
}
}
} else if (root is JsonValue paymentVal) {
if (paymentVal.TryGetValue<decimal>(out var price)) {
if (!dict2.ContainsKey(price)) dict2[price] = [];
dict2[price].Add("default");
} else if (paymentVal.TryGetValue<string>(out var curve)) {
var idx = int.Parse(curve.Split(":")[1] ?? "0");
if (!dict1.ContainsKey(idx)) dict1[idx] = [];
dict1[idx].Add("default");
}
}
var virtOffset = dict1.Count > 0 ? dict1.Max(e => e.Key) + 1 : 1;
Dictionary<int, Curve> curves = GetCurves();
decimal[] virtCurves = [.. dict2.Keys.Order()];
for (int i = 0; i < virtCurves.Length; i++) {
var idx = virtCurves[i];
dict1[i + virtOffset] = dict2[idx];
curves[i + virtOffset] = new Curve(CurveMode.Oe, new() { { 73, idx } }, null);
}
Dictionary<int, List<string>> dict3 = curves.ToDictionary(c => c.Key, _ => new List<string>());
foreach (var (selector, value) in GetSelection(root, Vaributes)) {
int? idx = null;
if (value.TryGetValue<decimal>(out var val)) {
idx = Array.IndexOf(virtCurves, val) + virtOffset;
} else if (value.TryGetValue<string>(out var str)) {
idx = int.Parse(str.Split(":")[1]);
}
if (idx != null)
dict3[(int)idx].Add(selector);
}
return (curves, dict3);
}
private static List<GraphEntry> CreateGraphEntries(
AppDbContext ctx, int precision,
Dictionary<int, Curve> curves,
Dictionary<int, List<string>> entries
) {
var vars = ctx.WineVarieties.ToDictionary(v => v.SortId, v => v);
var attrs = ctx.WineAttributes.ToDictionary(a => a.AttrId, a => a);
return entries
.Select(e => new GraphEntry(e.Key, precision, curves[e.Key], e.Value
.Select(s => new Varibute(vars[s[..2]], s.Length > 2 ? attrs[s[2..]] : null))
.ToList()))
.ToList();
}
public IEnumerable<GraphEntry> GetPaymentGraphEntries(AppDbContext ctx, Season season) {
var root = GetPaymentEntry();
var (curves, entries) = GetGraphEntries(root);
return CreateGraphEntries(ctx, season.Precision, curves, entries).Where(e => e.Vaributes.Count > 0);
}
public IEnumerable<GraphEntry> GetQualityGraphEntries(AppDbContext ctx, Season season, int idOffset = 0) {
var root = GetQualityEntry();
if (root == null || root["WEI"] is not JsonNode qualityWei)
return [];
var (curves, entries) = GetGraphEntries(qualityWei);
var list = CreateGraphEntries(ctx, season.Precision, curves, entries).Where(e => e.Vaributes.Count > 0);
foreach (var e in list) {
e.Id += idOffset;
e.Abgewertet = true;
}
return list;
}
}
}
+65 -77
View File
@@ -1,119 +1,107 @@
using ScottPlot;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json.Nodes;
namespace Elwig.Helpers.Billing {
public class Graph : ICloneable {
public string Type { get; set; }
public int Num { get; set; }
private int MinX { get; set; }
private int MaxX { get; set; }
public string Contracts { get; set; }
public readonly int Precision;
public double[] DataX { get; set; }
public double[] DataY { get; set; }
public int MinX { get; set; }
public int MaxX { get; set; }
public Graph(int num, int minX, int maxX) {
Type = "oe";
Num = num;
Contracts = "";
public Graph(int precision, int minX, int maxX) {
Precision = precision;
MinX = minX;
MaxX = maxX;
DataX = DataGen.Range(MinX, MaxX + 1);
DataY = DataGen.Zeros(MaxX - MinX + 1);
DataX = Enumerable.Range(minX, maxX - minX + 1).Select(n => (double)n).ToArray();
DataY = new double[DataX.Length];
}
public Graph(string type, int num, JsonObject graphData, string contracts, int minX, int maxX) {
Type = type;
Num = num;
Contracts = contracts;
public Graph(Dictionary<double, decimal> data, int precision, int minX, int maxX) {
Precision = precision;
MinX = minX;
MaxX = maxX;
DataX = DataGen.Range(MinX, MaxX + 1);
DataY = DataGen.Zeros(MaxX - MinX + 1);
ParseGraphData(graphData);
DataX = Enumerable.Range(minX, maxX - minX + 1).Select(n => (double)n).ToArray();
DataY = DataX.Select(i => (double)BillingData.GetCurveValueAt(data, i)).ToArray();
}
public Graph(string type, int num, int minX, int maxX, string contracts, double[] dataX, double[] dataY) {
Type = type;
Num = num;
public Graph(double[] values, int precision, int minX, int maxX) {
Precision = precision;
MinX = minX;
MaxX = maxX;
DataX = Enumerable.Range(MinX, MaxX - MinX + 1).Select(i => (double)i).ToArray();
DataY = values;
}
private Graph(double[] dataX, double[] dataY, int precision, int minX, int maxX) {
Precision = precision;
MinX = minX;
MaxX = maxX;
Contracts = contracts;
DataX = dataX;
DataY = dataY;
}
private void ParseGraphData(JsonObject graphData) {
var GraphPoints = graphData.ToDictionary(p => int.Parse(p.Key[..^2]), p => (double)p.Value?.AsValue());
public double GetOechsleAt(int index) {
return DataX[index];
}
if (GraphPoints.Keys.Count < 1) {
return;
}
public void SetOechsleAt(int index, double oechsle) {
DataX[index] = oechsle;
}
var minKey = GraphPoints.Keys.Order().First();
var maxKey = GraphPoints.Keys.OrderDescending().First();
public void SetPriceAt(int index, double price) {
DataY[index] = price;
}
if (!GraphPoints.ContainsKey(MinX)) {
GraphPoints.Add(MinX, GraphPoints.GetValueOrDefault(minKey));
}
if (!GraphPoints.ContainsKey(MaxX)) {
GraphPoints.Add(MaxX, GraphPoints.GetValueOrDefault(maxKey));
}
public double GetPriceAt(int index) {
return DataY[index];
}
var keys = GraphPoints.Keys.Order().ToArray();
public double GetPriceAtOe(double oe) {
return DataY[Array.IndexOf(DataX, oe)];
}
for (int i = 0; i < keys.Length; i++) {
double point1Value = GraphPoints[keys[i]];
if (i + 1 < keys.Length) {
double point2Value = GraphPoints[keys[i + 1]];
if (point1Value == point2Value) {
for (int j = keys[i] - MinX; j < keys[i + 1] - MinX; j++) {
DataY[j] = point1Value;
}
} else {
int steps = Math.Abs(keys[i + 1] - keys[i]);
double step = (point2Value - point1Value) / steps;
DataY[keys[i] - MinX] = point1Value;
DataY[keys[i + 1] - MinX] = point2Value;
for (int j = keys[i] - MinX; j < keys[i + 1] - MinX - 1; j++) {
DataY[j + 1] = Math.Round(DataY[j] + step, 4); // TODO richtig runden
}
}
}
else {
for (int j = keys[i] - MinX; j < DataX.Length; j++) {
DataY[j] = point1Value;
}
}
private void FlattenGraph(int begin, int end, double value) {
for (int i = begin; i <= end; i++) {
DataY[i] = value;
}
}
public JsonObject ToJson() {
JsonObject graph = new();
public void FlattenGraphLeft(int pointIndex) {
FlattenGraph(0, pointIndex, DataY[pointIndex]);
}
if (DataY[0] != DataY[1]) {
graph.Add(new KeyValuePair<string, JsonNode?>(DataX[0] + Type.ToLower(), Math.Round(DataY[0], 4)));
public void FlattenGraphRight(int pointIndex) {
FlattenGraph(pointIndex, DataY.Length - 1, DataY[pointIndex]);
}
private void LinearIncreaseGraph(int begin, int end, double inc) {
for (int i = begin; i < end; i++) {
DataY[i + 1] = Math.Round(DataY[i] + inc, Precision);
}
for (int i = 1; i < DataX.Length - 1; i++) {
if (Math.Round(DataY[i] - DataY[i - 1], 4) != Math.Round(DataY[i + 1] - DataY[i], 4)) {
graph.Add(new KeyValuePair<string, JsonNode?>(DataX[i] + Type.ToLower(), Math.Round(DataY[i], 4)));
}
}
public void LinearIncreaseGraphToEnd(int begin, double inc) {
LinearIncreaseGraph(begin, DataY.Length - 1, inc);
}
public void InterpolateGraph(int firstPoint, int secondPoint) {
int steps = Math.Abs(firstPoint - secondPoint);
if (firstPoint == -1 || secondPoint == -1 || steps < 2) {
return;
}
if (DataY[^1] != DataY[^2]) {
graph.Add(new KeyValuePair<string, JsonNode?>(DataX[^1] + Type.ToLower(), Math.Round(DataY[^1], 4)));
var (lowIndex, highIndex) = firstPoint < secondPoint ? (firstPoint, secondPoint) : (secondPoint, firstPoint);
double step = (DataY[highIndex] - DataY[lowIndex]) / steps;
for (int i = lowIndex; i < highIndex - 1; i++) {
DataY[i + 1] = Math.Round(DataY[i] + step, Precision);
}
return graph;
}
public object Clone() {
return new Graph(Type, Num, MinX, MaxX, Contracts, (double[])DataX.Clone(), (double[])DataY.Clone());
return new Graph((double[])DataX.Clone(), (double[])DataY.Clone(), Precision, MinX, MaxX);
}
}
}
+88
View File
@@ -0,0 +1,88 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Elwig.Helpers.Billing {
public class GraphEntry {
public const int MinX = 50;
public const int MinXGeb = 73;
public const int MaxX = 120;
public int Id { get; set; }
public BillingData.CurveMode Mode { get; set; }
public bool Abgewertet { get; set; }
public Graph DataGraph { get; set; }
public Graph? GebundenGraph { get; set; }
public double? GebundenFlatBonus {
get {
try {
var val = GebundenGraph?.DataX.Zip(GebundenGraph.DataY)
.Select(e => Math.Round(e.Second - DataGraph.GetPriceAtOe(e.First), Precision))
.Distinct()
.Single();
return (val == 0) ? null : val;
} catch {
return null;
}
}
set {
if (value is not double v) return;
var values = Enumerable.Range(MinXGeb, MaxX - MinXGeb + 1)
.Select(i => Math.Round(DataGraph.GetPriceAtOe(i) + v, Precision))
.ToArray();
GebundenGraph = new Graph(values, Precision, MinXGeb, MaxX);
}
}
public List<Varibute> Vaributes { get; set; }
public string VaributeStringSimple => (Abgewertet ? "Abgew.: " : "") + (Vaributes.Count != 0 ? (Vaributes.Count >= 25 ? "Restliche Sorten" : string.Join(", ", Vaributes.Select(c => c.Listing))) : "-");
public string VaributeString => Vaributes.Count != 0 ? string.Join("\n", Vaributes.Select(c => c.FullName)) : "-";
public string VaributeStringChange => (Abgewertet ? "A." : "") + string.Join(",", Vaributes.Select(c => c.Listing));
private readonly int Precision;
public GraphEntry(int id, int precision, BillingData.CurveMode mode) {
Id = id;
Precision = precision;
Mode = mode;
DataGraph = new Graph(precision, MinX, MaxX); ;
Vaributes = [];
}
public GraphEntry(int id, int precision, BillingData.CurveMode mode, Dictionary<double, decimal> data, Dictionary<double, decimal>? gebunden) :
this(id, precision, mode) {
DataGraph = new Graph(data, precision, MinX, MaxX);
if (gebunden != null) GebundenGraph = new Graph(gebunden, precision, MinXGeb, MaxX);
}
public GraphEntry(int id, int precision, BillingData.Curve curve, List<Varibute> vaributes) :
this(id, precision, curve.Mode) {
DataGraph = new Graph(curve.Normal, precision, MinX, MaxX);
if (curve.Gebunden != null)
GebundenGraph = new Graph(curve.Gebunden, precision, MinXGeb, MaxX);
Vaributes = vaributes;
}
private GraphEntry(int id, int precision, BillingData.CurveMode mode, Graph dataGraph, Graph? gebundenGraph, List<Varibute> vaributes) {
Id = id;
Precision = precision;
Mode = mode;
DataGraph = dataGraph;
GebundenGraph = gebundenGraph;
Vaributes = vaributes;
}
public void AddGebundenGraph() {
GebundenGraph ??= new Graph(Precision, MinXGeb, MaxX);
}
public void RemoveGebundenGraph() {
GebundenGraph = null;
}
public GraphEntry Copy(int id) {
return new GraphEntry(id, Precision, Mode, (Graph)DataGraph.Clone(), (Graph?)GebundenGraph?.Clone(), []);
}
}
}
@@ -0,0 +1,73 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json.Nodes;
namespace Elwig.Helpers.Billing {
public class PaymentBillingData : BillingData {
protected readonly Dictionary<int, Curve> Curves;
protected readonly Dictionary<string, Curve> PaymentData;
protected readonly Dictionary<string, Curve> QualityData;
protected readonly IEnumerable<string> Vaributes;
public PaymentBillingData(JsonObject data, IEnumerable<string> vaributes) :
base(data) {
if (vaributes.Any(e => e.Any(c => c < 'A' || c > 'Z')))
throw new ArgumentException("Invalid vaributes");
Vaributes = vaributes;
Curves = GetCurves();
PaymentData = GetPaymentData();
QualityData = GetQualityData();
}
public static PaymentBillingData FromJson(string json, IEnumerable<string> vaributes) {
return new(ParseJson(json), vaributes);
}
private Dictionary<string, Curve> GetData(JsonNode data) {
return GetSelection(data, Vaributes).ToDictionary(e => e.Key, e => LookupCurve(e.Value));
}
protected Dictionary<string, Curve> GetPaymentData() {
return GetData(GetPaymentEntry());
}
protected Dictionary<string, Curve> GetQualityData() {
Dictionary<string, Curve> dict = [];
var q = GetQualityEntry();
if (q == null) return dict;
foreach (var (qualid, data) in q) {
foreach (var (idx, d) in GetData(data ?? throw new InvalidOperationException())) {
dict[$"{qualid}/{idx}"] = d;
}
}
return dict;
}
public decimal CalculatePrice(string sortid, string? attrid, string qualid, bool gebunden, double oe, double kmw) {
var curve = GetQualityCurve(qualid, sortid, attrid) ?? GetCurve(sortid, attrid);
return GetCurveValueAt((gebunden ? curve.Gebunden : null) ?? curve.Normal, curve.Mode == CurveMode.Oe ? oe : kmw);
}
private Curve LookupCurve(JsonValue val) {
if (val.TryGetValue(out string? curve)) {
var curveId = int.Parse(curve.Split(":")[1]);
return Curves[curveId];
} else if (val.TryGetValue(out decimal value)) {
return new(CurveMode.Oe, new() { { 73, value } }, null);
}
throw new InvalidOperationException();
}
protected Curve GetCurve(string sortid, string? attrid) {
return PaymentData[$"{sortid}{attrid}"];
}
protected Curve? GetQualityCurve(string qualid, string sortid, string? attrid) {
return QualityData.TryGetValue($"{qualid}/{sortid}{attrid}", out var curve) ? curve : null;
}
}
}
+28
View File
@@ -0,0 +1,28 @@
using Elwig.Models.Entities;
using System;
namespace Elwig.Helpers.Billing {
public class Varibute : IComparable<Varibute> {
public WineVar? Variety { get; }
public WineAttr? Attribute { get; }
public int? AssignedGraphId { get; set; }
public int? AssignedAbgewGraphId { get; set; }
public string Listing => $"{Variety?.SortId}{Attribute?.AttrId}";
public string FullName => $"{Variety?.Name}" + (Variety != null && Attribute != null ? " " : "") + $"{Attribute?.Name}";
public Varibute(WineVar? var, WineAttr? attr) {
Variety = var;
Attribute = attr;
}
public override string ToString() {
return Listing;
}
public int CompareTo(Varibute? other) {
return Listing.CompareTo(other?.Listing);
}
}
}
+4
View File
@@ -60,6 +60,7 @@ namespace Elwig.Helpers {
public string? TextDeliveryNote;
public string? TextDeliveryConfirmation;
public string? TextCreditNote;
public ClientParameters(AppDbContext ctx) : this(ctx.ClientParameters.ToDictionary(e => e.Param, e => e.Value)) { }
@@ -99,6 +100,8 @@ namespace Elwig.Helpers {
if (TextDeliveryNote == "") TextDeliveryNote = null;
TextDeliveryConfirmation = parameters.GetValueOrDefault("TEXT_DELIVERYCONFIRMATION");
if (TextDeliveryConfirmation == "") TextDeliveryConfirmation = null;
TextCreditNote = parameters.GetValueOrDefault("TEXT_CREDITNOTE");
if (TextCreditNote == "") TextCreditNote = null;
} catch {
throw new KeyNotFoundException();
}
@@ -133,6 +136,7 @@ namespace Elwig.Helpers {
("DOCUMENT_SENDER", Sender2),
("TEXT_DELIVERYNOTE", TextDeliveryNote),
("TEXT_DELIVERYCONFIRMATION", TextDeliveryConfirmation),
("TEXT_CREDITNOTE", TextCreditNote),
};
}
+3 -3
View File
@@ -24,9 +24,9 @@ namespace Elwig.Helpers {
public void Read() {
var config = new ConfigurationBuilder().AddIniFile(FileName).Build();
DatabaseFile = Utils.GetAbsolutePath(config["database:file"] ?? "database.sqlite3", App.DataPath);
DatabaseFile = Path.Combine(App.DataPath, config["database:file"] ?? "database.sqlite3");
var log = config["database:log"];
DatabaseLog = log != null ? Utils.GetAbsolutePath(log, App.DataPath) : null;
DatabaseLog = log != null ? Path.Combine(App.DataPath, log) : null;
Branch = config["general:branch"];
Debug = trueValues.Contains(config["general:debug"]?.ToLower());
@@ -35,7 +35,7 @@ namespace Elwig.Helpers {
Scales = ScaleList;
foreach (var s in scales) {
string? scaleLog = config[$"scale.{s}:log"];
if (scaleLog != null) scaleLog = Utils.GetAbsolutePath(scaleLog, App.DataPath);
if (scaleLog != null) scaleLog = Path.Combine(App.DataPath, scaleLog);
ScaleList.Add([
s, config[$"scale.{s}:type"], config[$"scale.{s}:model"], config[$"scale.{s}:connection"],
config[$"scale.{s}:empty"], config[$"scale.{s}:filling"], config[$"scale.{s}:limit"], scaleLog
+12 -11
View File
@@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
using System.Data.Entity.Core.Common.CommandTrees.ExpressionBuilder;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
@@ -107,19 +108,19 @@ namespace Elwig.Helpers.Export {
<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="NN0"><number:number number:decimal-places="0" number:min-decimal-places="0" number:min-integer-digits="1"/></number:number-style>
<number:number-style style:name="NN0"><number:number number:decimal-places="0" number:min-decimal-places="0" number:min-integer-digits="1" number:grouping="true"/></number:number-style>
<style:style style:name="N0" style:family="table-cell" style:parent-style-name="default" style:data-style-name="NN0"/>
<number:number-style style:name="NN1"><number:number number:decimal-places="1" number:min-decimal-places="1" number:min-integer-digits="1"/></number:number-style>
<number:number-style style:name="NN1"><number:number number:decimal-places="1" number:min-decimal-places="1" number:min-integer-digits="1" number:grouping="true"/></number:number-style>
<style:style style:name="N1" style:family="table-cell" style:parent-style-name="default" style:data-style-name="NN1"/>
<number:number-style style:name="NN2"><number:number number:decimal-places="2" number:min-decimal-places="2" number:min-integer-digits="1"/></number:number-style>
<number:number-style style:name="NN2"><number:number number:decimal-places="2" number:min-decimal-places="2" number:min-integer-digits="1" number:grouping="true"/></number:number-style>
<style:style style:name="N2" style:family="table-cell" style:parent-style-name="default" style:data-style-name="NN2"/>
<number:number-style style:name="NN3"><number:number number:decimal-places="3" number:min-decimal-places="3" number:min-integer-digits="1"/></number:number-style>
<number:number-style style:name="NN3"><number:number number:decimal-places="3" number:min-decimal-places="3" number:min-integer-digits="1" number:grouping="true"/></number:number-style>
<style:style style:name="N3" style:family="table-cell" style:parent-style-name="default" style:data-style-name="NN3"/>
<number:number-style style:name="NN4"><number:number number:decimal-places="4" number:min-decimal-places="4" number:min-integer-digits="1"/></number:number-style>
<number:number-style style:name="NN4"><number:number number:decimal-places="4" number:min-decimal-places="4" number:min-integer-digits="1" number:grouping="true"/></number:number-style>
<style:style style:name="N4" style:family="table-cell" style:parent-style-name="default" style:data-style-name="NN4"/>
<number:number-style style:name="NN5"><number:number number:decimal-places="5" number:min-decimal-places="5" number:min-integer-digits="1"/></number:number-style>
<number:number-style style:name="NN5"><number:number number:decimal-places="5" number:min-decimal-places="5" number:min-integer-digits="1" number:grouping="true"/></number:number-style>
<style:style style:name="N5" style:family="table-cell" style:parent-style-name="default" style:data-style-name="NN5"/>
<number:number-style style:name="NN6"><number:number number:decimal-places="6" number:min-decimal-places="6" number:min-integer-digits="1"/></number:number-style>
<number:number-style style:name="NN6"><number:number number:decimal-places="6" number:min-decimal-places="6" number:min-integer-digits="1" number:grouping="true"/></number:number-style>
<style:style style:name="N6" style:family="table-cell" style:parent-style-name="default" style:data-style-name="NN6"/>
</office:automatic-styles>
<office:body>
@@ -261,20 +262,20 @@ namespace Elwig.Helpers.Export {
string c;
if (data == null) {
c = $"<{ct}{add}/>";
} else if (data is float || data is double || data is byte || data is char ||
} else if (data is decimal || 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");
double v = double.Parse(data?.ToString() ?? "0"); // use default culture for ToString and Parse()!
if (units != null && units.Length > 0) {
int n = -1;
switch (units[0]) {
case "%": n = 1; data = $"{v:N1}"; break;
case "€": n = 2; data = $"{v:N2}"; break;
case "°KMW": n = 1; data = $"{v:N1}"; break;
case "°Oe": n = 0; data = $"{v:N0}"; break;
}
if (n >= 0) add = string.Join(" ", add.Split(" ").Select(p => p.StartsWith("table:style-name=") ? $"table:style-name=\"N{n}\"" : p));
}
c = $"<{ct} office:value-type=\"float\" calcext:value-type=\"float\" office:value=\"{v.ToString()?.Replace(",", ".")}\"{add}><text:p>{data}</text:p></{ct}>";
c = $"<{ct} office:value-type=\"float\" calcext:value-type=\"float\" office:value=\"{v.ToString(CultureInfo.InvariantCulture)}\"{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}>";
}
+10 -6
View File
@@ -74,12 +74,16 @@ namespace Elwig.Helpers.Printing {
}
public static async Task Print(string path, int copies = 1) {
var p = new Process() { StartInfo = new() { FileName = PdfToPrinter } };
p.StartInfo.ArgumentList.Add(path);
p.StartInfo.ArgumentList.Add("/s");
p.StartInfo.ArgumentList.Add($"copies={copies}");
p.Start();
await p.WaitForExitAsync();
try {
var p = new Process() { StartInfo = new() { FileName = PdfToPrinter } };
p.StartInfo.ArgumentList.Add(path);
p.StartInfo.ArgumentList.Add("/s");
p.StartInfo.ArgumentList.Add($"copies={copies}");
p.Start();
await p.WaitForExitAsync();
} catch (Exception e) {
MessageBox.Show("Beim Drucken ist ein Fehler aufgetreten:\n\n" + e.Message, "Fehler beim Drucken");
}
}
}
}
+22 -3
View File
@@ -12,6 +12,8 @@ using System.Text;
using System.Numerics;
using Elwig.Models.Entities;
using System.IO;
using ScottPlot.TickGenerators.TimeUnits;
using Elwig.Helpers.Billing;
namespace Elwig.Helpers {
public static partial class Utils {
@@ -48,6 +50,9 @@ namespace Elwig.Helpers {
[GeneratedRegex(@"^(.*?) +([0-9].*)$", RegexOptions.Compiled)]
private static partial Regex GeneratedAddressRegex();
public static readonly string GroupSeparator = "\u202F";
public static readonly string UnitSeparator = "\u00A0";
public static readonly KeyValuePair<string, string>[] PhoneNrTypes = [
new("landline", "Tel.-Nr. (Festnetz)"),
new("mobile", "Tel.-Nr. (mobil)"),
@@ -158,7 +163,7 @@ namespace Elwig.Helpers {
}
public static string FormatIban(string iban) {
return Regex.Replace(iban, ".{4}", "$0 ");
return Regex.Replace(iban.Trim(), ".{4}", "$0 ").Trim();
}
public static void RunBackground(string title, Func<Task> a) {
@@ -357,8 +362,22 @@ namespace Elwig.Helpers {
return output.OrderByDescending(l => l.Count());
}
public static string GetAbsolutePath(string path, string basePath) {
return (path.Length > 1 && (path[1] == ':' || path[0] == '/' || path[0] == '\\')) ? Path.Combine(basePath, path) : path;
public static List<string> GetVaributes(AppDbContext ctx, int year, bool withSlash = false, bool onlyDelivered = true) {
var varieties = ctx.WineVarieties.Select(v => v.SortId).ToList();
var delivered = ctx.DeliveryParts
.Where(d => d.Year == year)
.Select(d => $"{d.SortId}{(withSlash ? "/" : "")}{d.AttrId}")
.Distinct()
.ToList();
return [.. (onlyDelivered ? delivered : delivered.Union(varieties)).Order()];
}
public static List<Varibute> GetVaributeList(AppDbContext ctx, int year, bool onlyDelivered = true) {
var varieties = ctx.WineVarieties.ToDictionary(v => v.SortId, v => v);
var attributes = ctx.WineAttributes.ToDictionary(a => a.AttrId, a => a);
return GetVaributes(ctx, year, false, onlyDelivered)
.Select(s => new Varibute(varieties[s[..2]], s.Length > 2 ? attributes[s[2..]] : null))
.ToList();
}
}
}
+39 -19
View File
@@ -9,14 +9,14 @@ namespace Elwig.Helpers {
private static readonly Dictionary<string, string[][]> PHONE_NRS = new() {
{ "43", new string[][] {
Array.Empty<string>(),
new string[] { "57", "59" },
new string[] {
[],
["57", "59"],
[
"50", "517", "718", "804", "720", "780", "800", "802", "810",
"820", "821", "828", "900", "901", "930", "931", "939",
"650", "651", "652", "653", "655", "657", "659", "660", "661",
"663", "664", "665", "666", "667", "668", "669", "67", "68", "69"
}
]
} },
{ "49", Array.Empty<string[]>() },
{ "48", Array.Empty<string[]>() },
@@ -162,7 +162,7 @@ namespace Elwig.Helpers {
if (text.StartsWith("+43 ")) {
var nr = text[4..];
var vws = PHONE_NRS["43"];
if (!text.EndsWith(" ") && v >= 4 && v - 4 < vws.Length && vws[v - 4].Any(vw => nr.StartsWith(vw))) {
if (!text.EndsWith(' ') && v >= 4 && v - 4 < vws.Length && vws[v - 4].Any(vw => nr.StartsWith(vw))) {
text += ' ';
} else if (nr == "1") {
text += ' ';
@@ -320,18 +320,24 @@ namespace Elwig.Helpers {
return new(true, null);
} else if (input.Text.Length != 7) {
return new(false, "Betriebsnummer zu kurz");
} else if (!CheckLfbisNr(input.Text)) {
return new(false, "Prüfsumme der Betriebsnummer ist falsch");
} else {
return new(true, null);
}
}
public static bool CheckLfbisNr(string nr) {
if (nr.Length != 7 || !nr.All(char.IsAsciiDigit))
return false;
// https://statistik.at/fileadmin/shared/QM/Standarddokumentationen/RW/std_r_land-forstw_register.pdf#page=41
int s = 0, v;
for (int i = 0; i < 6; i++)
s += (input.Text[i] - '0') * (7 - i);
s += (nr[i] - '0') * (7 - i);
v = (11 - (s % 11)) % 10;
if (v != (input.Text[6] - '0'))
return new(false, "Prüfsumme der Betriebsnummer ist falsch");
return new(true, null);
return v == (nr[6] - '0');
}
public static ValidationResult CheckUstIdNr(TextBox input, bool required) {
@@ -373,17 +379,11 @@ namespace Elwig.Helpers {
return required ? new(false, "UID ist nicht optional") : new(true, null);
if (text.StartsWith("AT")) {
if (text.Length != 11 || text[2] != 'U')
if (text.Length != 11 || text[2] != 'U') {
return new(false, "UID ist ungültig");
// http://www.pruefziffernberechnung.de/U/USt-IdNr.shtml
int s = 0, v = 0;
for (int i = 0; i < 7; i++)
s += ((text[i + 3] - '0') * (i % 2 + 1)).ToString().Select(ch => ch - '0').Sum();
v = (96 - s) % 10;
if (v != (text[10] - '0'))
} else if (!CheckUstIdNr(text)) {
return new(false, "Prüfsumme der UID ist falsch");
}
} else {
return new(false, "Not implemented yet");
}
@@ -391,6 +391,26 @@ namespace Elwig.Helpers {
return new(true, null);
}
public static bool CheckUstIdNr(string nr) {
if (nr.Length < 4 || nr.Length > 14 || !nr.All(char.IsAsciiLetterOrDigit))
return false;
if (nr.StartsWith("AT")) {
if (nr.Length != 11 || nr[2] != 'U')
return false;
// http://www.pruefziffernberechnung.de/U/USt-IdNr.shtml
int s = 0, v = 0;
for (int i = 0; i < 7; i++)
s += ((nr[i + 3] - '0') * (i % 2 + 1)).ToString().Select(ch => ch - '0').Sum();
v = (96 - s) % 10;
return v == (nr[10] - '0');
} else {
return false;
}
}
public static ValidationResult CheckMgNr(TextBox input, bool required, AppDbContext ctx) {
var res = CheckInteger(input, required);
if (!res.IsValid) {
+2 -3
View File
@@ -1,5 +1,4 @@
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
@@ -8,7 +7,7 @@ using System.Threading.Tasks;
namespace Elwig.Models.Dtos {
public class AreaComUnderDeliveryData : DataTable<AreaComUnderDeliveryRow> {
private static readonly (string, string, string?, int)[] FieldNames = new[] {
private static readonly (string, string, string?, int)[] FieldNames = [
("MgNr", "MgNr.", null, 12),
("Name", "Name", null, 40),
("GivenName", "Vorname", null, 40),
@@ -20,7 +19,7 @@ namespace Elwig.Models.Dtos {
("DeliveryObligations", "Lieferpflicht", "kg", 22),
("Weights", "Geliefert", "kg", 22),
("UnderDeliveries", "Unterliefert", "kg|%", 34),
};
];
public AreaComUnderDeliveryData(IEnumerable<AreaComUnderDeliveryRow> rows, int year) :
base($"Unterlieferungen FB", $"Unterlieferungen laut Flächenbindungen {year}", rows, FieldNames) {
+147 -129
View File
@@ -1,162 +1,180 @@
using Elwig.Helpers;
using Elwig.Helpers.Billing;
using Elwig.Models.Entities;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity.Core.Common.CommandTrees.ExpressionBuilder;
using System.Linq;
using System.Threading.Tasks;
namespace Elwig.Models.Dtos {
public class CreditNoteData : DataTable<CreditNoteRow> {
private static readonly (string, string, string?)[] FieldNames = new[] {
("", "", (string?)null), // TODO
};
private static readonly (string, string, string?, int)[] FieldNames = [
("MgNr", "MgNr.", null, 12),
("Name", "Name", null, 40),
("GivenName", "Vorname", null, 40),
("Address", "Adresse", null, 60),
("Plz", "PLZ", null, 10),
("Locality", "Ort", null, 60),
("Iban", "IBAN", null, 45),
("TgNr", "TG-Nr.", null, 20),
("Sum", "Zwischens.", "€", 20),
("Surcharge", "Zuschlag", "€", 20),
("Total", "Gesamt", "€", 20),
("ConsideredSum", "Berückstgt.", "€", 20),
("Net", "Netto", "€", 20),
("Vat1", "10% MwSt.", "€", 20),
("Vat2", "13% MwSt.", "€", 20),
("Gross", "Brutto", "€", 20),
("Penalties", "Pönalen FB", "€", 20),
("Penalty", "Unterl. GA", "€", 20),
("AutoBs", "GA Nachz.", "€", 20),
("Others", "Sonstige", "€", 20),
("Considered", "Berückstgt.", "€", 20),
("Amount", "Betrag", "€", 20),
];
private readonly int Year;
private readonly int? TgNr;
private readonly int? AvNr;
private readonly int? MgNr;
private CreditNoteData(IEnumerable<CreditNoteRow> rows, int year, int? tgnr, int? avnr = null, int? mgnr = null) :
base($"Traubengutschrift {year}/{tgnr}", rows, FieldNames) {
Year = year;
TgNr = tgnr;
AvNr = avnr;
MgNr = mgnr;
public CreditNoteData(IEnumerable<CreditNoteRow> rows, int year, string name) :
base($"Buchungsliste", $"Buchungsliste {name} {year}", rows, FieldNames) {
}
public static async Task<IDictionary<int, CreditNoteData>> ForPaymentVariant(DbSet<CreditNoteRowSingle> table, DbSet<Season> seasons, int year, int avnr) {
return (await FromDbSet(table, year, avnr))
.GroupBy(
r => new { r.Year, r.AvNr, r.MgNr, r.TgNr, r.DId, r.DPNr },
(k, g) => new CreditNoteRow(g, seasons))
.GroupBy(
r => new { r.Year, r.AvNr, r.MgNr, r.TgNr },
(k, g) => new CreditNoteData(g, k.Year, k.TgNr, mgnr: k.MgNr))
.ToDictionary(d => d.MgNr ?? 0);
public static async Task<CreditNoteData> ForPaymentVariant(AppDbContext ctx, int year, int avnr) {
var variant = await ctx.PaymentVariants.FindAsync(year, avnr);
var name = variant!.Name;
var data = BillingData.FromJson(variant!.Data);
var rows = (await FromDbSet(ctx.CreditNoteRows, year, avnr)).Select(r => new CreditNoteRow(r, data)).ToList();
return new CreditNoteData(rows, year, name);
}
private static async Task<IEnumerable<CreditNoteRowSingle>> FromDbSet(DbSet<CreditNoteRowSingle> table, int? year = null, int? avnr = null, int? mgnr = null) {
var y = year?.ToString() ?? "NULL";
var v = avnr?.ToString() ?? "NULL";
var m = mgnr?.ToString() ?? "NULL";
return await table.FromSqlRaw($"""
SELECT d.year, c.tgnr, v.avnr, d.mgnr, d.did, d.lsnr, d.dpnr, d.weight, d.modifiers,
b.bktnr, d.sortid, b.discr, b.value, pb.price, pb.amount, p.net_amount, p.amount AS total_amount,
s.name AS variant, a.name AS attribute, q.name AS quality_level, d.oe, d.kmw
FROM v_delivery d
JOIN wine_variety s ON s.sortid = d.sortid
LEFT JOIN wine_attribute a ON a.attrid = d.attrid
JOIN wine_quality_level q ON q.qualid = d.qualid
LEFT JOIN delivery_part_bucket b ON (b.year, b.did, b.dpnr) = (d.year, d.did, d.dpnr)
LEFT JOIN payment_variant v ON v.year = d.year
LEFT JOIN payment_delivery_part p ON (p.year, p.did, p.dpnr, p.avnr) = (d.year, d.did, d.dpnr, v.avnr)
LEFT JOIN payment_delivery_part_bucket pb ON (pb.year, pb.did, pb.dpnr, pb.bktnr, pb.avnr) = (b.year, b.did, b.dpnr, b.bktnr, v.avnr)
LEFT JOIN credit c ON (c.year, c.avnr, c.mgnr) = (d.year, v.avnr, d.mgnr)
WHERE b.value > 0 AND (d.year = {y} OR {y} IS NULL) AND (v.avnr = {v} OR {v} IS NULL) AND (d.mgnr = {m} OR {m} IS NULL)
ORDER BY d.year, v.avnr, d.mgnr, d.lsnr, d.dpnr
private static async Task<IEnumerable<CreditNoteRowSingle>> FromDbSet(DbSet<CreditNoteRowSingle> table, int year, int avnr) {
return await table.FromSql($"""
SELECT m.mgnr, m.family_name, m.given_name, p.plz, o.name AS ort, m.address, m.iban, c.tgnr, s.year, s.precision,
p.amount - p.net_amount AS surcharge,
c.net_amount, c.prev_net_amount, c.vat, c.vat_amount, c.gross_amount, c.modifiers, c.prev_modifiers, c.amount,
ROUND(COALESCE(u.total_penalty, 0) / POW(10, 4 - 2)) AS fb_penalty,
ROUND(COALESCE(b.total_penalty, 0) / POW(10, s.precision - 2)) AS bs_penalty,
ROUND(COALESCE(a.total_amount, 0) / POW(10, s.precision - 2)) AS auto_bs
FROM credit c
LEFT JOIN member m ON m.mgnr = c.mgnr
LEFT JOIN payment_member p ON (p.year, p.avnr, p.mgnr) = (c.year, c.avnr, c.mgnr)
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 = c.year
LEFT JOIN v_penalty_area_commitments u ON (u.year, u.mgnr) = (s.year, m.mgnr)
LEFT JOIN v_penalty_business_shares b ON (b.year, b.mgnr) = (s.year, m.mgnr)
LEFT JOIN v_auto_business_shares a ON (a.year, a.mgnr) = (s.year, m.mgnr)
WHERE c.year = {year} AND c.avnr = {avnr}
ORDER BY m.mgnr
""").ToListAsync();
}
}
public class CreditNoteRow {
public int Year;
public int? TgNr;
public int AvNr;
public int MgNr;
public string Name;
public string GivenName;
public string Address;
public int Plz;
public string Locality;
public string Iban;
public string TgNr;
public decimal Sum;
public decimal? Surcharge;
public decimal Total;
public decimal? ConsideredSum;
public decimal Net;
public decimal? Vat1, Vat2;
public decimal Gross;
public decimal? Penalties;
public decimal? Penalty;
public decimal? AutoBs;
public decimal? Others;
public decimal? Considered;
public decimal Amount;
public string LsNr;
public int DPNr;
public string Variant;
public string? Attribute;
public string[] Modifiers;
public string QualityLevel;
public (double Oe, double Kmw) Gradation;
public (string Name, int Value, decimal? Price, decimal? Amount)[] Buckets;
public decimal? TotalModifiers;
public decimal? Amount;
public CreditNoteRow(IEnumerable<CreditNoteRowSingle> rows, DbSet<Season> seasons) {
var f = rows.First();
Year = f.Year;
TgNr = f.TgNr;
MgNr = f.MgNr;
var season = seasons.Find(Year);
LsNr = f.LsNr;
DPNr = f.DPNr;
Variant = f.Variant;
Attribute = f.Attribute;
var modifiers = (IEnumerable<Modifier>)(f.Modifiers ?? "").Split(',')
.Select(m => season?.Modifiers.FirstOrDefault(s => s.ModId == m))
.Where(m => m != null)
.OrderBy(m => m.Ordering)
.ToList();
Modifiers = modifiers.Select(m => m.Name).ToArray();
QualityLevel = f.QualityLevel;
Gradation = (f.Oe, f.Kmw);
Buckets = rows
.Where(b => b.Value > 0)
.OrderByDescending(b => b.BktNr)
.Select(b => (b.Discr == "_" ? "ungeb." : $"geb. {f.SortId}{b.Discr}", b.Value,
b.Price != null ? season?.DecFromDb((long)b.Price) : null,
b.Amount != null ? season?.DecFromDb((long)b.Amount) : null))
.ToArray();
Amount = f.TotalAmount != null ? season?.DecFromDb((long)f.TotalAmount) : null;
var netAmount = f.NetAmount != null ? season?.DecFromDb((long)f.NetAmount) : null;
TotalModifiers = Amount - netAmount;
public CreditNoteRow(CreditNoteRowSingle row, BillingData data) {
byte prec1 = 2, prec2 = row.Precision;
MgNr = row.MgNr;
Name = row.Name;
GivenName = row.GivenName;
Address = row.Address;
Plz = row.Plz;
Locality = row.Locality;
Iban = Utils.FormatIban(row.Iban);
TgNr = $"{row.Year}/{row.TgNr}";
Total = Utils.DecFromDb(row.NetAmount, prec1);
Surcharge = (row.Surcharge == null || row.Surcharge == 0) ? null : Utils.DecFromDb((long)row.Surcharge, prec2);
Sum = Total - (Surcharge ?? 0);
ConsideredSum = (row.PrevNetAmount == null ||row.PrevNetAmount == 0) ? null : -Utils.DecFromDb((long)row.PrevNetAmount, prec1);
Net = Total + (ConsideredSum ?? 0);
if (row.Vat == 0.10) {
Vat1 = Utils.DecFromDb(row.VatAmount, prec1);
} else if (row.Vat == 0.13) {
Vat2 = Utils.DecFromDb(row.VatAmount, prec1);
}
decimal mod = (row.Modifiers == null) ? 0 : Utils.DecFromDb((long)row.Modifiers, prec1);
if (data.ConsiderContractPenalties)
Penalties = (row.FbPenalty == null || row.FbPenalty == 0) ? null : Utils.DecFromDb((long)row.FbPenalty, prec1);
if (data.ConsiderTotalPenalty)
Penalty = (row.BsPealty == null || row.BsPealty == 0) ? null : Utils.DecFromDb((long)row.BsPealty, prec1);
if (data.ConsiderAutoBusinessShares)
AutoBs = (row.AutoBs == null || row.AutoBs == 0) ? null : -Utils.DecFromDb((long)row.AutoBs, prec1);
mod -= (Penalties ?? 0) + (Penalty ?? 0) + (AutoBs ?? 0);
Others = (mod == 0) ? null : mod;
Gross = Utils.DecFromDb(row.GrossAmount, prec1);
Considered = (row.PrevModifiers == null || row.PrevModifiers == 0) ? null : -Utils.DecFromDb((long)row.PrevModifiers, prec1);
Amount = Utils.DecFromDb(row.Amount, prec1);
}
}
[Keyless]
public class CreditNoteRowSingle {
[Column("year")]
public int Year { get; set; }
[Column("tgnr")]
public int? TgNr { get; set; }
[Column("avnr")]
public int? AvNr { get; set; }
[Column("mgnr")]
public int MgNr { get; set; }
[Column("did")]
public int DId { get; set; }
[Column("lsnr")]
public string LsNr { get; set; }
[Column("dpnr")]
public int DPNr { get; set; }
[Column("weight")]
public int Weight { get; set; }
[Column("modifiers")]
public string? Modifiers { get; set; }
[Column("bktnr")]
public int BktNr { get; set; }
[Column("sortid")]
public string SortId { get; set; }
[Column("discr")]
public string Discr { get; set; }
[Column("value")]
public int Value { get; set; }
[Column("price")]
public long? Price { get; set; }
[Column("amount")]
public long? Amount { 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("iban")]
public string Iban { get; set; }
[Column("year")]
public int Year { get; set; }
[Column("precision")]
public byte Precision { get; set; }
[Column("tgnr")]
public string TgNr { get; set; }
[Column("surcharge")]
public long? Surcharge { get; set; }
[Column("net_amount")]
public long? NetAmount { get; set; }
[Column("total_amount")]
public long? TotalAmount { get; set; }
[Column("variant")]
public string Variant { get; set; }
[Column("attribute")]
public string? Attribute { get; set; }
[Column("quality_level")]
public string QualityLevel { get; set; }
[Column("oe")]
public double Oe { get; set; }
[Column("kmw")]
public double Kmw { get; set; }
public long NetAmount { get; set; }
[Column("prev_net_amount")]
public long? PrevNetAmount { get; set; }
[Column("vat")]
public double Vat { get; set; }
[Column("vat_amount")]
public long VatAmount { get; set; }
[Column("gross_amount")]
public long GrossAmount { get; set; }
[Column("modifiers")]
public long? Modifiers { get; set; }
[Column("prev_modifiers")]
public long? PrevModifiers { get; set; }
[Column("amount")]
public long Amount { get; set; }
[Column("fb_penalty")]
public long? FbPenalty { get; set; }
[Column("bs_penalty")]
public long? BsPealty { get; set; }
[Column("auto_bs")]
public long? AutoBs { get; set; }
}
}
+159
View File
@@ -0,0 +1,159 @@
using Elwig.Models.Entities;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Threading.Tasks;
namespace Elwig.Models.Dtos {
public class CreditNoteDeliveryData : DataTable<CreditNoteDeliveryRow> {
private static readonly (string, string, string?)[] FieldNames = [
("", "", null), // TODO
];
private readonly int Year;
private readonly int? TgNr;
private readonly int? AvNr;
private readonly int? MgNr;
private CreditNoteDeliveryData(IEnumerable<CreditNoteDeliveryRow> rows, int year, int? tgnr, int? avnr = null, int? mgnr = null) :
base($"Traubengutschrift {year}/{tgnr}", rows, FieldNames) {
Year = year;
TgNr = tgnr;
AvNr = avnr;
MgNr = mgnr;
}
public static async Task<IDictionary<int, CreditNoteDeliveryData>> ForPaymentVariant(DbSet<CreditNoteDeliveryRowSingle> table, DbSet<Season> seasons, int year, int avnr) {
return (await FromDbSet(table, year, avnr))
.GroupBy(
r => new { r.Year, r.AvNr, r.MgNr, r.TgNr, r.DId, r.DPNr },
(k, g) => new CreditNoteDeliveryRow(g, seasons))
.GroupBy(
r => new { r.Year, r.AvNr, r.MgNr, r.TgNr },
(k, g) => new CreditNoteDeliveryData(g, k.Year, k.TgNr, mgnr: k.MgNr))
.ToDictionary(d => d.MgNr ?? 0);
}
private static async Task<IEnumerable<CreditNoteDeliveryRowSingle>> FromDbSet(DbSet<CreditNoteDeliveryRowSingle> table, int? year = null, int? avnr = null, int? mgnr = null) {
var y = year?.ToString() ?? "NULL";
var v = avnr?.ToString() ?? "NULL";
var m = mgnr?.ToString() ?? "NULL";
return await table.FromSqlRaw($"""
SELECT d.year, c.tgnr, v.avnr, d.mgnr, d.did, d.lsnr, d.dpnr, d.weight, d.modifiers,
b.bktnr, d.sortid, b.discr, b.value, pb.price, pb.amount, p.net_amount, p.amount AS total_amount,
s.name AS variety, a.name AS attribute, q.name AS quality_level, d.oe, d.kmw
FROM v_delivery d
JOIN wine_variety s ON s.sortid = d.sortid
LEFT JOIN wine_attribute a ON a.attrid = d.attrid
JOIN wine_quality_level q ON q.qualid = d.qualid
LEFT JOIN delivery_part_bucket b ON (b.year, b.did, b.dpnr) = (d.year, d.did, d.dpnr)
LEFT JOIN payment_variant v ON v.year = d.year
LEFT JOIN payment_delivery_part p ON (p.year, p.did, p.dpnr, p.avnr) = (d.year, d.did, d.dpnr, v.avnr)
LEFT JOIN payment_delivery_part_bucket pb ON (pb.year, pb.did, pb.dpnr, pb.bktnr, pb.avnr) = (b.year, b.did, b.dpnr, b.bktnr, v.avnr)
LEFT JOIN credit c ON (c.year, c.avnr, c.mgnr) = (d.year, v.avnr, d.mgnr)
WHERE b.value > 0 AND (d.year = {y} OR {y} IS NULL) AND (v.avnr = {v} OR {v} IS NULL) AND (d.mgnr = {m} OR {m} IS NULL)
ORDER BY d.year, v.avnr, d.mgnr, d.lsnr, d.dpnr
""").ToListAsync();
}
}
public class CreditNoteDeliveryRow {
public int Year;
public int? TgNr;
public int AvNr;
public int MgNr;
public string LsNr;
public int DPNr;
public string Variety;
public string? Attribute;
public string[] Modifiers;
public string QualityLevel;
public (double Oe, double Kmw) Gradation;
public (string Name, int Value, decimal? Price, decimal? Amount)[] Buckets;
public decimal? TotalModifiers;
public decimal? Amount;
public CreditNoteDeliveryRow(IEnumerable<CreditNoteDeliveryRowSingle> rows, DbSet<Season> seasons) {
var f = rows.First();
Year = f.Year;
TgNr = f.TgNr;
MgNr = f.MgNr;
var season = seasons.Find(Year);
LsNr = f.LsNr;
DPNr = f.DPNr;
Variety = f.Variety;
Attribute = f.Attribute;
var modifiers = (IEnumerable<Modifier>)(f.Modifiers ?? "").Split(',')
.Select(m => season?.Modifiers.FirstOrDefault(s => s.ModId == m))
.Where(m => m != null)
.OrderBy(m => m.Ordering)
.ToList();
Modifiers = modifiers.Select(m => m.Name).ToArray();
QualityLevel = f.QualityLevel;
Gradation = (f.Oe, f.Kmw);
Buckets = rows
.Where(b => b.Value > 0)
.OrderByDescending(b => b.BktNr)
.Select(b => (b.Discr == "_" ? "ungeb." : $"geb. {f.SortId}{b.Discr}", b.Value,
b.Price != null ? season?.DecFromDb((long)b.Price) : null,
b.Amount != null ? season?.DecFromDb((long)b.Amount) : null))
.ToArray();
Amount = f.TotalAmount != null ? season?.DecFromDb((long)f.TotalAmount) : null;
var netAmount = f.NetAmount != null ? season?.DecFromDb((long)f.NetAmount) : null;
TotalModifiers = Amount - netAmount;
}
}
[Keyless]
public class CreditNoteDeliveryRowSingle {
[Column("year")]
public int Year { get; set; }
[Column("tgnr")]
public int? TgNr { get; set; }
[Column("avnr")]
public int? AvNr { get; set; }
[Column("mgnr")]
public int MgNr { get; set; }
[Column("did")]
public int DId { get; set; }
[Column("lsnr")]
public string LsNr { get; set; }
[Column("dpnr")]
public int DPNr { get; set; }
[Column("weight")]
public int Weight { get; set; }
[Column("modifiers")]
public string? Modifiers { get; set; }
[Column("bktnr")]
public int BktNr { get; set; }
[Column("sortid")]
public string SortId { get; set; }
[Column("discr")]
public string Discr { get; set; }
[Column("value")]
public int Value { get; set; }
[Column("price")]
public long? Price { get; set; }
[Column("amount")]
public long? Amount { get; set; }
[Column("net_amount")]
public long? NetAmount { get; set; }
[Column("total_amount")]
public long? TotalAmount { get; set; }
[Column("variety")]
public string Variety { get; set; }
[Column("attribute")]
public string? Attribute { get; set; }
[Column("quality_level")]
public string QualityLevel { get; set; }
[Column("oe")]
public double Oe { get; set; }
[Column("kmw")]
public double Kmw { get; set; }
}
}
@@ -5,38 +5,42 @@ using System.Linq;
using System.Threading.Tasks;
namespace Elwig.Models.Dtos {
public class DeliveryConfirmationData : DataTable<DeliveryConfirmationRow> {
public class DeliveryConfirmationDeliveryData : DataTable<DeliveryConfirmationDeliveryRow> {
private static readonly (string, string, string?, int)[] FieldNames = new[] {
private static readonly (string, string, string?, int)[] FieldNames = [
("LsNr", "LsNr.", null, 26),
("DPNr", "Pos.", null, 8),
("Variant", "Sorte", null, 40),
("Variety", "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) :
private DeliveryConfirmationDeliveryData(IEnumerable<DeliveryConfirmationDeliveryRow> 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) {
public static DeliveryConfirmationDeliveryData CreateEmpty(int year, Member m) {
return new([], year, m);
}
public static async Task<IDictionary<int, DeliveryConfirmationDeliveryData>> 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)
p => new DeliveryConfirmationDeliveryRow(p),
(k, g) => new DeliveryConfirmationDeliveryData(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);
public static async Task<DeliveryConfirmationDeliveryData> ForMember(DbSet<DeliveryPart> table, int year, Member m) {
return new DeliveryConfirmationDeliveryData((await FromDbSet(table, year, m.MgNr)).Select(p => new DeliveryConfirmationDeliveryRow(p)), year, m);
}
private static async Task<IEnumerable<DeliveryPart>> FromDbSet(DbSet<DeliveryPart> table, int? year = null, int? mgnr = null) {
@@ -47,7 +51,7 @@ namespace Elwig.Models.Dtos {
if (mgnr != null) q = q.Where(p => p.Delivery.MgNr == mgnr);
await q
.Include(p => p.Delivery)
.Include(p => p.Variant)
.Include(p => p.Variety)
.Include(p => p.Attribute)
.Include(p => p.Quality)
.Include(p => p.Buckets)
@@ -64,10 +68,10 @@ namespace Elwig.Models.Dtos {
}
}
public class DeliveryConfirmationRow {
public class DeliveryConfirmationDeliveryRow {
public string LsNr;
public int DPNr;
public string Variant;
public string Variety;
public string? Attribute;
public string QualityLevel;
public (double Oe, double Kmw) Gradation;
@@ -75,11 +79,11 @@ namespace Elwig.Models.Dtos {
public int Weight;
public (string Name, int Value)[] Buckets;
public DeliveryConfirmationRow(DeliveryPart p) {
public DeliveryConfirmationDeliveryRow(DeliveryPart p) {
var d = p.Delivery;
LsNr = d.LsNr;
DPNr = p.DPNr;
Variant = p.Variant.Name;
Variety = p.Variety.Name;
Attribute = p.Attribute?.Name;
QualityLevel = p.Quality.Name;
Gradation = (p.Oe, p.Kmw);
@@ -8,7 +8,7 @@ using System.Threading.Tasks;
namespace Elwig.Models.Dtos {
public class MemberDeliveryPerVariantData : DataTable<MemberDeliveryPerVariantRow> {
private static readonly (string, string, string?, int)[] FieldNames = new[] {
private static readonly (string, string, string?, int)[] FieldNames = [
("MgNr", "MgNr.", null, 12),
("Name", "Name", null, 40),
("GivenName", "Vorname", null, 40),
@@ -20,7 +20,7 @@ namespace Elwig.Models.Dtos {
("Weights", "Geliefert", "kg", 22),
("Areas", "Fläche", "m²", 22),
("Yields", "Ertrag", "kg/ha", 22),
};
];
public MemberDeliveryPerVariantData(IEnumerable<MemberDeliveryPerVariantRow> rows, int year) :
+3 -3
View File
@@ -7,7 +7,7 @@ using System.Threading.Tasks;
namespace Elwig.Models.Dtos {
public class OverUnderDeliveryData : DataTable<OverUnderDeliveryRow> {
private static readonly (string, string, string?, int)[] FieldNames = new[] {
private static readonly (string, string, string?, int)[] FieldNames = [
("MgNr", "MgNr.", null, 12),
("Name", "Name", null, 40),
("GivenName", "Vorname", null, 40),
@@ -19,7 +19,7 @@ namespace Elwig.Models.Dtos {
("DeliveryRight", "Lieferrecht", "kg", 22),
("Weight", "Geliefert", "kg", 22),
("OverUnderDelivery", "Über-/Unterliefert", "kg|%", 34),
};
];
public OverUnderDeliveryData(IEnumerable<OverUnderDeliveryRow> rows, int year) :
base($"Über-Unterlieferungen", $"Über- und Unterlieferungen laut gezeichneten Geschäftsanteilen {year}", rows, FieldNames) {
@@ -71,6 +71,6 @@ namespace Elwig.Models.Dtos {
[NotMapped]
public (int? Kg, double? Percent) OverUnderDelivery =>
Weight < DeliveryObligation ? (Weight - DeliveryObligation, Weight * 100.0 / DeliveryObligation - 100.0) :
Weight > DeliveryRight ? (Weight - DeliveryRight, Weight * 100.0 / DeliveryRight - 100) : (null, null);
Weight > DeliveryRight ? (Weight - DeliveryRight, Weight * 100.0 / DeliveryRight - 100.0) : (null, null);
}
}
+14 -1
View File
@@ -68,19 +68,32 @@ namespace Elwig.Models.Entities {
[InverseProperty("Delivery")]
public virtual ISet<DeliveryPart> Parts { get; private set; }
[NotMapped]
public IEnumerable<DeliveryPart> FilteredParts => PartFilter == null ? Parts : Parts.Where(p => PartFilter(p));
[NotMapped]
public Predicate<DeliveryPart>? PartFilter { get; set; }
public int Weight => Parts.Select(p => p.Weight).Sum();
public int FilteredWeight => FilteredParts.Select(p => p.Weight).Sum();
public IEnumerable<string> SortIds => Parts
.GroupBy(p => p.SortId)
.OrderByDescending(g => g.Select(p => p.Weight).Sum())
.Select(g => g.Select(p => p.SortId).First());
.Select(g => g.Key);
public IEnumerable<string> FilteredSortIds => FilteredParts
.GroupBy(p => p.SortId)
.OrderByDescending(g => g.Select(p => p.Weight).Sum())
.Select(g => g.Key);
public string SortIdString => string.Join(", ", SortIds);
public string FilteredSortIdString => string.Join(", ", FilteredSortIds);
public double Kmw => Utils.AggregateDeliveryPartsKmw(Parts);
public double FilteredKmw => Utils.AggregateDeliveryPartsKmw(FilteredParts);
public double Oe => Utils.KmwToOe(Kmw);
public double FilteredOe => Utils.KmwToOe(FilteredKmw);
public int SearchScore(IEnumerable<string> keywords) {
var list = new string?[] {
+1 -1
View File
@@ -23,7 +23,7 @@ namespace Elwig.Models.Entities {
public string SortId { get; set; }
[ForeignKey("SortId")]
public virtual WineVar Variant { get; private set; }
public virtual WineVar Variety { get; private set; }
[Column("attrid")]
public string? AttrId { get; set; }
+4 -13
View File
@@ -62,12 +62,8 @@ namespace Elwig.Models.Entities {
[NotMapped]
public DateOnly? EntryDate {
get {
return EntryDateString != null ? DateOnly.ParseExact(EntryDateString, "yyyy-MM-dd") : null;
}
set {
EntryDateString = value?.ToString("yyyy-MM-dd");
}
get => EntryDateString != null ? DateOnly.ParseExact(EntryDateString, "yyyy-MM-dd") : null;
set => EntryDateString = value?.ToString("yyyy-MM-dd");
}
[Column("exit_date")]
@@ -75,12 +71,8 @@ namespace Elwig.Models.Entities {
[NotMapped]
public DateOnly? ExitDate {
get {
return ExitDateString != null ? DateOnly.ParseExact(ExitDateString, "yyyy-MM-dd") : null;
}
set {
ExitDateString = value?.ToString("yyyy-MM-dd");
}
get => ExitDateString != null ? DateOnly.ParseExact(ExitDateString, "yyyy-MM-dd") : null;
set => ExitDateString = value?.ToString("yyyy-MM-dd");
}
[Column("business_shares")]
@@ -181,7 +173,6 @@ namespace Elwig.Models.Entities {
public int SearchScore(IEnumerable<string> keywords) {
return Utils.GetSearchScore(new string?[] {
MgNr.ToString(),
FamilyName, MiddleName, GivenName,
BillingAddress?.Name,
Comment,
+31
View File
@@ -0,0 +1,31 @@
using Microsoft.EntityFrameworkCore;
using System;
using System.ComponentModel.DataAnnotations.Schema;
namespace Elwig.Models.Entities {
[Table("member_history"), PrimaryKey("MgNr", "DateString")]
public class MemberHistory {
[Column("mgnr")]
public int MgNr { get; set; }
[Column("date")]
public string DateString { get; set; }
[NotMapped]
public DateOnly Date {
get => DateOnly.ParseExact(DateString, "yyyy-MM-dd");
set => value.ToString("yyyy-MM-dd");
}
[Column("business_shares")]
public int BusinessShares { get; set; }
[Column("type")]
public string Type { get; set; }
[Column("comment")]
public string? Comment { get; set; }
[ForeignKey("MgNr")]
public virtual Member Member { get; private set; }
}
}
+26 -2
View File
@@ -55,9 +55,16 @@ namespace Elwig.Models.Entities {
set => PenaltyNoneValue = value != null ? DecToDb(value.Value) : null;
}
[Column("bs_value")]
public long? BusinessShareValueValue { get; set; }
[NotMapped]
public decimal? BusinessShareValue {
get => BusinessShareValueValue != null ? DecFromDb(BusinessShareValueValue.Value) : null;
set => BusinessShareValueValue = value != null ? DecToDb(value.Value) : null;
}
[Column("start_date")]
public string? StartDateString { get; set; }
[NotMapped]
public DateOnly? StartDate {
get => StartDateString != null ? DateOnly.ParseExact(StartDateString, "yyyy-MM-dd") : null;
@@ -66,13 +73,30 @@ namespace Elwig.Models.Entities {
[Column("end_date")]
public string? EndDateString { get; set; }
[NotMapped]
public DateOnly? EndDate {
get => EndDateString != null ? DateOnly.ParseExact(EndDateString, "yyyy-MM-dd") : null;
set => EndDateString = value?.ToString("yyyy-MM-dd");
}
[Column("calc_mode")]
public int CalcMode { get; set; }
[NotMapped]
public bool Billing_HonorGebunden {
get => (CalcMode & 0x1) != 0;
set => CalcMode = value ? CalcMode | 0x1 : CalcMode & ~0x1;
}
[NotMapped]
public bool Billing_AllowAttrsIntoLower {
get => (CalcMode & 0x4) != 0;
set => CalcMode = value ? CalcMode | 0x4 : CalcMode & ~0x4;
}
[NotMapped]
public bool Billing_AvoidUnderDeliveries {
get => (CalcMode & 0x2) != 0;
set => CalcMode = value ? CalcMode | 0x2 : CalcMode & ~0x2;
}
[ForeignKey("CurrencyCode")]
public virtual Currency Currency { get; private set; }
+7
View File
@@ -23,6 +23,13 @@ namespace Elwig.Models.Entities {
[Column("fill_lower")]
public int FillLower { get; set; }
public WineAttr() { }
public WineAttr(string attrId, string name) {
AttrId = attrId;
Name = name;
}
public override string ToString() {
return Name;
}
+7
View File
@@ -21,6 +21,13 @@ namespace Elwig.Models.Entities {
public bool IsRed => Type == "R";
public bool IsWhite => Type == "W";
public WineVar() { }
public WineVar(string sortId, string name) {
SortId = sortId;
Name = name;
}
public override string ToString() {
return Name;
}
@@ -9,7 +9,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<PublishDir>bin\Publish</PublishDir>
<PublishProtocol>FileSystem</PublishProtocol>
<_TargetId>Folder</_TargetId>
<TargetFramework>net7.0-windows</TargetFramework>
<TargetFramework>net8.0-windows</TargetFramework>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<SelfContained>true</SelfContained>
<PublishSingleFile>false</PublishSingleFile>
@@ -8,25 +8,49 @@
"properties": {
"mode": {"enum": ["elwig"]},
"version": {"enum": [1]},
"payment": {
"type": ["number", "string", "object"],
"pattern": "^curve:[0-9]+$",
"additionalProperties": false,
"properties": {
"default": {
"type": ["number", "string"],
"pattern": "^curve:[0-9]+$"
}
},
"patternProperties": {
"^[A-Z]+$": {
"type": ["number", "string"],
"pattern": "^curve:[0-9]+$"
}
"consider_delivery_modifiers": {"type": "boolean"},
"consider_contract_penalties": {"type": "boolean"},
"consider_total_penalty": {"type": "boolean"},
"consider_auto_business_shares": {"type": "boolean"},
"payment": {"$ref": "#/definitions/payment_1"},
"quality": {"$ref": "#/definitions/quality_1"},
"curves": {
"type": "array",
"items": {"$ref": "#/definitions/curve"}
}
}
}, {
"required": ["AuszahlungSorten", "Kurven"],
"properties": {
"mode": {"enum": ["wgmaster"]},
"AuszahlungSorten": {"$ref": "#/definitions/payment_1"},
"AuszahlungSortenQualitätsstufe": {"$ref": "#/definitions/quality_1"},
"Kurven": {
"type": "array",
"items": {"$ref": "#/definitions/curve"}
}
}
}],
"definitions": {
"payment_1": {
"type": ["number", "string", "object"],
"pattern": "^curve:[0-9]+$",
"additionalProperties": false,
"properties": {
"default": {
"type": ["number", "string"],
"pattern": "^curve:[0-9]+$"
}
},
"quality": {
"type": "object",
"patternProperties": {
"^([A-Z]{2})?(\/[A-Z]*)?$": {
"type": ["number", "string"],
"pattern": "^curve:[0-9]+$"
}
}
},
"quality_1": {
"type": "object",
"additionalProperties": false,
"patternProperties": {
"^[A-Z]{3}$": {
@@ -40,65 +64,14 @@
}
},
"patternProperties": {
"^[A-Z]+$": {
"^([A-Z]{2})?(\/[A-Z]*)?$": {
"type": ["number", "string"],
"pattern": "^curve:[0-9]+$"
}
}
}
}
},
"curves": {
"type": "array",
"items": {"$ref": "#/definitions/curve"}
}
}
}, {
"properties": {
"mode": {"enum": ["wgmaster"]},
"AuszahlungSorten": {
"type": "object",
"additionalProperties": false,
"required": ["Kurven"],
"properties": {
"Kurven": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"minProperties": 1,
"patternProperties": {
"^[0-9]+(\\.[0-9]+)?oe$": {"type": "number"}
}
}
}
},
"patternProperties": {
"^[A-Z]{2}$": {
"type": "object",
"additionalProperties": false,
"patternProperties": {
"^[A-Z]*$": {
"type": "object",
"additionalProperties": false,
"properties": {
"Gebunden": {
"type": "integer",
"minimum": 0
},
"NichtGebunden": {
"type": "integer",
"minimum": 0
}
}
}
}
}
}
}
}
}],
"definitions": {
},
"curve": {
"type": "object",
"required": ["id", "mode", "data"],
@@ -111,15 +84,15 @@
"mode": {"enum": ["oe", "kmw"]},
"data": {
"anyOf": [
{"type": "number" },
{"type": "number"},
{"$ref": "#/definitions/curve_data"}
]
]
},
"geb": {
"anyOf": [
{"type": "number"},
{"$ref": "#/definitions/curve_data"}
]
]
}
}
},
@@ -128,7 +101,7 @@
"additionalProperties": false,
"minProperties": 1,
"patternProperties": {
"^([0-9]+(\\.[0-9]+)?)(oe|kmw)$": {"type": "number"}
"^[<>]?([0-9]+(\\.[0-9]+)?)(oe|kmw)$": {"type": "number"}
}
}
}
+30 -14
View File
@@ -1,17 +1,33 @@
-- schema version 10 to 11
-- Drop columns, if they exist...
ALTER TABLE payment_delivery_part DROP COLUMN price_1;
ALTER TABLE payment_delivery_part DROP COLUMN price_2;
ALTER TABLE payment_delivery_part DROP COLUMN price_3;
ALTER TABLE payment_delivery_part DROP COLUMN price_4;
ALTER TABLE payment_delivery_part DROP COLUMN price_5;
ALTER TABLE payment_delivery_part DROP COLUMN price_6;
ALTER TABLE payment_delivery_part DROP COLUMN price_7;
ALTER TABLE payment_delivery_part DROP COLUMN price_8;
ALTER TABLE payment_delivery_part DROP COLUMN price_9;
CREATE TABLE payment_delivery_part_new (
year INTEGER NOT NULL,
did INTEGER NOT NULL,
dpnr INTEGER NOT NULL,
avnr INTEGER NOT NULL,
DROP TRIGGER t_payment_delivery_part_i;
net_amount INTEGER NOT NULL,
mod_abs INTEGER NOT NULL DEFAULT 0,
mod_rel REAL NOT NULL DEFAULT 0,
amount INTEGER NOT NULL GENERATED ALWAYS AS (ROUND(net_amount * (1 + mod_rel) + mod_abs)) STORED,
CONSTRAINT pk_payment_delivery_part PRIMARY KEY (year, did, dpnr, avnr),
CONSTRAINT fk_payment_delivery_part_delivery_part FOREIGN KEY (year, did, dpnr) REFERENCES delivery_part (year, did, dpnr)
ON UPDATE CASCADE
ON DELETE CASCADE,
CONSTRAINT fk_payment_delivery_part_payment_variant FOREIGN KEY (year, avnr) REFERENCES payment_variant (year, avnr)
ON UPDATE CASCADE
ON DELETE CASCADE
) STRICT;
INSERT INTO payment_delivery_part_new (year, did, dpnr, avnr, net_amount, mod_abs, mod_rel)
SELECT year, did, dpnr, avnr, net_amount, mod_abs, mod_rel
FROM payment_delivery_part;
PRAGMA writable_schema = ON;
DROP TABLE payment_delivery_part;
ALTER TABLE payment_delivery_part_new RENAME TO payment_delivery_part;
PRAGMA writable_schema = OFF;
DROP TRIGGER IF EXISTS t_payment_delivery_part_i;
CREATE TRIGGER t_payment_delivery_part_i
AFTER INSERT ON payment_delivery_part FOR EACH ROW
BEGIN
@@ -20,7 +36,7 @@ BEGIN
ON CONFLICT DO UPDATE SET net_amount = net_amount + excluded.net_amount;
END;
DROP TRIGGER t_payment_delivery_part_u;
DROP TRIGGER IF EXISTS t_payment_delivery_part_u;
CREATE TRIGGER t_payment_delivery_part_u
AFTER UPDATE ON payment_delivery_part FOR EACH ROW
BEGIN
@@ -32,7 +48,7 @@ BEGIN
ON CONFLICT DO UPDATE SET net_amount = net_amount + excluded.net_amount;
END;
DROP TRIGGER t_payment_delivery_part_d;
DROP TRIGGER IF EXISTS t_payment_delivery_part_d;
CREATE TRIGGER t_payment_delivery_part_d
AFTER DELETE ON payment_delivery_part FOR EACH ROW
BEGIN
@@ -41,7 +57,7 @@ BEGIN
WHERE (year, avnr, mgnr) IN (SELECT year, OLD.avnr, mgnr FROM delivery WHERE (year, did) = (OLD.year, OLD.did));
END;
DROP TRIGGER t_payment_delivery_part_bucket_u;
DROP TRIGGER IF EXISTS t_payment_delivery_part_bucket_u;
CREATE TRIGGER t_payment_delivery_part_bucket_u
AFTER UPDATE ON payment_delivery_part_bucket FOR EACH ROW
BEGIN
+31
View File
@@ -0,0 +1,31 @@
-- schema version 12 to 13
ALTER TABLE season ADD COLUMN bs_value INTEGER;
CREATE TABLE member_history (
mgnr INTEGER NOT NULL,
date TEXT NOT NULL CHECK (date REGEXP '^[1-9][0-9]{3}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])$') DEFAULT CURRENT_DATE,
business_shares INTEGER NOT NULL,
type TEXT NOT NULL CHECK (type REGEXP '^[a-z_]+$'),
comment TEXT DEFAULT NULL,
CONSTRAINT pk_member_history PRIMARY KEY (mgnr, date),
CONSTRAINT fk_member_history_member FOREIGN KEY (mgnr) REFERENCES member (mgnr)
ON UPDATE CASCADE
ON DELETE CASCADE
) STRICT;
CREATE VIEW v_total_under_delivery AS
SELECT s.year, m.mgnr, 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(d.sum, 0) AS weight,
IIF(COALESCE(d.sum, 0) < m.business_shares * s.min_kg_per_bs,
COALESCE(d.sum, 0) - m.business_shares * s.min_kg_per_bs,
IIF(COALESCE(d.sum, 0) > m.business_shares * s.max_kg_per_bs,
COALESCE(d.sum, 0) - m.business_shares * s.max_kg_per_bs,
0)) AS diff
FROM member m, season s
LEFT JOIN v_stat_member d ON (d.year, d.mgnr) = (s.year, m.mgnr)
ORDER BY s.year, m.mgnr;
+33
View File
@@ -0,0 +1,33 @@
-- schema version 13 to 14
CREATE VIEW v_penalty_area_commitments AS
SELECT year, mgnr,
SUM(COALESCE(IIF(u.weight = 0, -t.penalty_none, 0), 0) +
COALESCE(IIF(u.diff < 0, -t.penalty_amount, 0), 0) +
COALESCE(u.diff * t.penalty_per_kg, 0)
) AS total_penalty
FROM v_under_delivery u
JOIN area_commitment_type t ON t.vtrgid = u.bucket
GROUP BY year, mgnr
HAVING total_penalty < 0
ORDER BY year, mgnr;
CREATE VIEW v_penalty_business_shares AS
SELECT s.year, u.mgnr,
(COALESCE(IIF(u.weight = 0, -s.penalty_none, 0), 0) +
COALESCE(IIF(u.diff < 0, -s.penalty_amount, 0), 0) +
COALESCE(u.diff * s.penalty_per_kg, 0)
) AS total_penalty
FROM v_total_under_delivery u
JOIN season s ON s.year = u.year
WHERE u.diff < 0 AND total_penalty < 0
ORDER BY s.year, u.mgnr;
CREATE VIEW v_auto_business_shares AS
SELECT s.year, h.mgnr,
SUM(h.business_shares) AS business_shares,
SUM(h.business_shares) * s.bs_value AS total_amount
FROM member_history h, season s
WHERE h.type = 'auto' AND h.date >= s.year || '-01-01' AND h.date <= s.year || '-12-31'
GROUP BY s.year, h.mgnr
ORDER BY s.year, h.mgnr;
+18
View File
@@ -0,0 +1,18 @@
-- schema version 14 to 15
ALTER TABLE season ADD COLUMN calc_mode INTEGER NOT NULL DEFAULT 0;
DROP TRIGGER t_payment_delivery_part_u;
CREATE TRIGGER t_payment_delivery_part_u
AFTER UPDATE ON payment_delivery_part FOR EACH ROW
BEGIN
UPDATE payment_member
SET net_amount = net_amount - OLD.amount
WHERE (year, avnr, mgnr) IN (SELECT year, OLD.avnr, mgnr FROM delivery WHERE (year, did) = (OLD.year, OLD.did));
INSERT INTO payment_member (year, avnr, mgnr, net_amount)
SELECT d.year, v.avnr, d.mgnr, NEW.amount
FROM delivery d, payment_variant v
WHERE (d.year, d.did) = (NEW.year, NEW.did) AND (v.year, v.avnr) = (NEW.year, NEW.avnr)
ON CONFLICT DO UPDATE SET net_amount = net_amount + excluded.net_amount;
END;
+13 -6
View File
@@ -375,14 +375,14 @@
</Grid>
</TabItem>
<TabItem Header="Saisons">
<TabItem Header="Saisons" x:Name="Seasons">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="180"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="180"/>
<RowDefinition Height="205"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
@@ -446,6 +446,10 @@
<Label Content="Strafe (Nicht-Lieferung):" Margin="10,130,0,10" Grid.Column="2"/>
<ctrl:UnitTextBox x:Name="SeasonPenaltyNoneInput" Unit="€" TextChanged="SeasonPenaltyInput_TextChanged"
Grid.Column="3" Width="68" Margin="0,130,10,10" HorizontalAlignment="Left" VerticalAlignment="Top"/>
<Label Content="GA-Wert:" Margin="10,160,0,10" Grid.Column="2"/>
<ctrl:UnitTextBox x:Name="SeasonBsValueInput" Unit="€/GA" TextChanged="SeasonPenaltyInput_TextChanged"
Grid.Column="3" Width="85" Margin="0,160,10,10" HorizontalAlignment="Left" VerticalAlignment="Top"/>
</Grid>
<GroupBox Grid.Column="1" Grid.Row="1" Header="Zu-/Abschläge" Margin="0,0,10,10" VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
@@ -531,15 +535,18 @@
</GroupBox>
<GroupBox Header="Anlieferungsbestätigung" Margin="10,10,10,10" Height="250">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBox x:Name="TextElementDeliveryConfirmation" TextWrapping="Wrap" VerticalScrollBarVisibility="Visible" AcceptsReturn="True"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="10,10,10,10" Height="Auto"
TextChanged="TextBox_TextChanged"/>
</Grid>
</GroupBox>
<GroupBox Header="Traubengutschrift" Margin="10,10,10,10" Height="250">
<Grid>
<TextBox x:Name="TextElementCreditNote" TextWrapping="Wrap" VerticalScrollBarVisibility="Visible" AcceptsReturn="True"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="10,10,10,10" Height="Auto"
TextChanged="TextBox_TextChanged"/>
</Grid>
</GroupBox>
</StackPanel>
</ScrollViewer>
</TabItem>
@@ -125,6 +125,7 @@ namespace Elwig.Windows {
if (old != null) _branches[old] = id;
branch.ZwstId = id;
branch.Name = BranchNameInput.Text;
branch.CountryNum = 40;
branch.PostalDestId = (BranchOrtInput.SelectedItem as AT_PlzDest)?.Id;
branch.Address = BranchAddressInput.Text;
branch.PhoneNr = BranchPhoneNrInput.Text;
+5 -4
View File
@@ -52,13 +52,13 @@ namespace Elwig.Windows {
var year = (SeasonList.SelectedItem as Season)?.Year;
foreach (var (modid, _) in _mods.Where(m => m.Value == null)) {
Context.Remove(Context.Modifiers.Find(new object?[] { year, modid }));
Context.Remove(Context.Modifiers.Find(year, modid));
}
foreach (var (mod, old) in _modIds) {
mod.ModId = old;
}
foreach (var (old, modid) in _mods.Where(m => m.Value != null)) {
Context.Update(Context.Modifiers.Find(new object?[] { year, old }));
Context.Update(Context.Modifiers.Find(year, old));
}
await Context.SaveChangesAsync();
@@ -102,8 +102,9 @@ namespace Elwig.Windows {
if (_modList == null || SeasonList.SelectedItem is not Season s) return;
_modChanged = true;
var idx = (SeasonModifierList.SelectedIndex != -1) ? SeasonModifierList.SelectedIndex + 1 : _modList.Count;
var item = Context.CreateProxy<Modifier>();
item.Year = s.Year;
var item = new Modifier {
Year = s.Year
};
_modList.Insert(idx, item);
SeasonModifierList.SelectedIndex = idx;
UpdateButtons();
@@ -43,12 +43,14 @@ namespace Elwig.Windows {
SeasonPenaltyPerKgInput.Text = s.PenaltyPerKg?.ToString() ?? "";
SeasonPenaltyInput.Text = s.PenaltyAmount?.ToString() ?? "";
SeasonPenaltyNoneInput.Text = s.PenaltyNone?.ToString() ?? "";
SeasonBsValueInput.Text = s.BusinessShareValue?.ToString() ?? "";
var sym = s.Currency.Symbol ?? "";
SeasonModifierAbsInput.Unit = $"{sym}/kg";
SeasonPenaltyPerKgInput.Unit = $"{sym}/kg";
SeasonPenaltyInput.Unit = sym;
SeasonPenaltyNoneInput.Unit = sym;
SeasonBsValueInput.Unit = $"{sym}/GA";
AreaCommitmentTypePenaltyPerKgInput.Unit = $"{sym}/kg";
AreaCommitmentTypePenaltyInput.Unit = sym;
AreaCommitmentTypePenaltyNoneInput.Unit = sym;
@@ -64,6 +66,7 @@ namespace Elwig.Windows {
SeasonPenaltyPerKgInput.Text = "";
SeasonPenaltyInput.Text = "";
SeasonPenaltyNoneInput.Text = "";
SeasonBsValueInput.Text = "";
}
_seasonUpdate = false;
}
@@ -85,6 +88,7 @@ namespace Elwig.Windows {
s.PenaltyPerKg = (SeasonPenaltyPerKgInput.Text.Length > 0) ? decimal.Parse(SeasonPenaltyPerKgInput.Text) : null;
s.PenaltyAmount = (SeasonPenaltyInput.Text.Length > 0) ? decimal.Parse(SeasonPenaltyInput.Text) : null;
s.PenaltyNone = (SeasonPenaltyNoneInput.Text.Length > 0) ? decimal.Parse(SeasonPenaltyNoneInput.Text) : null;
s.BusinessShareValue = (SeasonBsValueInput.Text.Length > 0) ? decimal.Parse(SeasonBsValueInput.Text) : null;
UpdateButtons();
}
+12 -5
View File
@@ -12,11 +12,11 @@ namespace Elwig.Windows {
public BaseDataWindow() {
InitializeComponent();
RequiredInputs = new Control[] {
RequiredInputs = [
ClientNameInput, ClientNameTypeInput, ClientNameTokenInput, ClientNameShortInput,
ClientAddressInput, ClientPlzInput, ClientOrtInput,
};
ExemptInputs = new Control[] {
];
ExemptInputs = [
ClientNameFull,
BranchIdInput, BranchNameInput, BranchPlzInput, BranchOrtInput,
BranchAddressInput, BranchPhoneNrInput, BranchFaxNrInput, BranchMobileNrInput,
@@ -27,9 +27,10 @@ namespace Elwig.Windows {
AreaCommitmentTypeMinKgPerHaInput.TextBox, AreaCommitmentTypePenaltyPerKgInput.TextBox,
AreaCommitmentTypePenaltyInput.TextBox, AreaCommitmentTypePenaltyNoneInput.TextBox,
SeasonMaxKgPerHaInput.TextBox, SeasonVatNormalInput.TextBox, SeasonVatFlatrateInput.TextBox, SeasonStartInput, SeasonEndInput,
SeasonMinKgPerBsInput.TextBox, SeasonMaxKgPerBsInput.TextBox, SeasonPenaltyPerKgInput.TextBox, SeasonPenaltyInput.TextBox, SeasonPenaltyNoneInput.TextBox,
SeasonMinKgPerBsInput.TextBox, SeasonMaxKgPerBsInput.TextBox, SeasonBsValueInput.TextBox,
SeasonPenaltyPerKgInput.TextBox, SeasonPenaltyInput.TextBox, SeasonPenaltyNoneInput.TextBox,
SeasonModifierIdInput, SeasonModifierNameInput, SeasonModifierRelInput.TextBox, SeasonModifierAbsInput.TextBox,
};
];
WineAttributeFillLowerInput.Visibility = Visibility.Hidden;
WineAttributeFillLowerLabel.Visibility = Visibility.Hidden;
}
@@ -72,6 +73,7 @@ namespace Elwig.Windows {
SeasonPenaltyPerKgInput.TextBox.IsReadOnly = true;
SeasonPenaltyInput.TextBox.IsReadOnly = true;
SeasonPenaltyNoneInput.TextBox.IsReadOnly = true;
SeasonBsValueInput.TextBox.IsReadOnly = true;
SeasonModifierIdInput.IsReadOnly = true;
SeasonModifierNameInput.IsReadOnly = true;
@@ -117,6 +119,7 @@ namespace Elwig.Windows {
SeasonPenaltyPerKgInput.TextBox.IsReadOnly = false;
SeasonPenaltyInput.TextBox.IsReadOnly = false;
SeasonPenaltyNoneInput.TextBox.IsReadOnly = false;
SeasonBsValueInput.TextBox.IsReadOnly = false;
SeasonModifierIdInput.IsReadOnly = false;
SeasonModifierNameInput.IsReadOnly = false;
@@ -261,6 +264,8 @@ namespace Elwig.Windows {
ClearInputStates();
FillInputs(App.Client);
LockInputs();
await HintContextChange();
}
private void FillInputs(ClientParameters p) {
@@ -292,6 +297,7 @@ namespace Elwig.Windows {
case 3: ModeDeliveryNoteFull.IsChecked = true; break;
}
TextElementDeliveryConfirmation.Text = p.TextDeliveryConfirmation;
TextElementCreditNote.Text = p.TextCreditNote;
FinishInputFilling();
}
@@ -317,6 +323,7 @@ namespace Elwig.Windows {
p.TextDeliveryNote = TextElementDeliveryNote.Text.Length > 0 ? TextElementDeliveryNote.Text : null;
p.ModeDeliveryNoteStats = (ModeDeliveryNoteNone.IsChecked == true) ? 0 : (ModeDeliveryNoteGaOnly.IsChecked == true) ? 1 : (ModeDeliveryNoteShort.IsChecked == true) ? 2 : (ModeDeliveryNoteFull.IsChecked == true) ? 3 : 2;
p.TextDeliveryConfirmation = TextElementDeliveryConfirmation.Text.Length > 0 ? TextElementDeliveryConfirmation.Text : null;
p.TextCreditNote = TextElementCreditNote.Text.Length > 0 ? TextElementCreditNote.Text : null;
await p.UpdateValues();
}
+85 -86
View File
@@ -1,15 +1,17 @@
<local:AdministrationWindow
<local:ContextWindow
x:Class="Elwig.Windows.ChartWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Elwig.Windows"
xmlns:ctrl="clr-namespace:Elwig.Controls"
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
xmlns:ScottPlot="clr-namespace:ScottPlot;assembly=ScottPlot.WPF"
xmlns:ScottPlot="clr-namespace:ScottPlot.WPF;assembly=ScottPlot.WPF"
mc:Ignorable="d"
Title="Auszahlung - Elwig" Height="700" Width="1500"
Loaded="Window_Loaded">
Title="Auszahlung - Elwig" Height="700" Width="1500" MinWidth="1000" MinHeight="500"
Loaded="Window_Loaded"
Closing="Window_Closing">
<Window.Resources>
<Style TargetType="Label">
@@ -43,94 +45,82 @@
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="19"/>
<RowDefinition Height="40"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="330"/>
<ColumnDefinition Width="300"/>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="200"/>
</Grid.ColumnDefinitions>
<Grid Grid.Row="1" Margin="5,0,0,0">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="42"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0" Grid.Column="1" Grid.ColumnSpan="3">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="560"/>
<ColumnDefinition Width="100"/>
</Grid.ColumnDefinitions>
<DataGrid x:Name="GraphList" AutoGenerateColumns="False" HeadersVisibility="Column" IsReadOnly="True" GridLinesVisibility="None" SelectionMode="Single"
CanUserDeleteRows="False" CanUserResizeRows="False" CanUserAddRows="False"
SelectionChanged="GraphList_SelectionChanged"
Margin="5,15,5,0" Grid.Row="0" FontSize="14" Grid.ColumnSpan="3">
<DataGrid.Columns>
<DataGridTextColumn Header="Nr." Binding="{Binding Num}" Width="40">
<DataGridTextColumn.ElementStyle>
<Style>
<Setter Property="TextBlock.TextWrapping" Value="Wrap" />
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
<DataGridTextColumn Header="Typ" Binding="{Binding Type}" Width="40"/>
<DataGridTextColumn Header="Angewandte Verträge" Binding="{Binding Contracts}" Width="4*"/>
</DataGrid.Columns>
</DataGrid>
<Button x:Name="NewButton" Content="Neu"
HorizontalAlignment="Stretch" VerticalAlignment="Bottom" Margin="5,5,2.5,10" Grid.Column="0" Grid.Row="2"
Click="NewButton_Click"/>
<Button x:Name="EditButton" Content="Bearbeiten" IsEnabled="False"
HorizontalAlignment="Stretch" VerticalAlignment="Bottom" Margin="2.5,5,2.5,10" Grid.Column="1" Grid.Row="2"
Click="EditButton_Click"/>
<Button x:Name="DeleteButton" Content="Löschen" IsEnabled="False"
HorizontalAlignment="Stretch" VerticalAlignment="Bottom" Margin="2.5,5,5,10" Grid.Column="2" Grid.Row="2"
Click="DeleteButton_Click"/>
<Label Content="Für:" Margin="10,-2,0,0" FontSize="14" Grid.Column="0" VerticalAlignment="Center"/>
<xctk:CheckComboBox x:Name="VaributeInput" Margin="50,0,0,0" Grid.Column="0" Width="500" Height="25" HorizontalAlignment="Left"
IsSelectAllActive="True" SelectAllContent="Alle Sorten" Delimiter=", " AllItemsSelectedContent="Alle Sorten"
IsEnabled="False" ItemSelectionChanged="VaributeInput_Changed">
<xctk:CheckComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Variety.Name}" Width="150"/>
<TextBlock Text="{Binding Variety.Type}" Width="30"/>
<TextBlock Text="{Binding Attribute.Name}" Width="120"/>
<TextBlock Text="{Binding AssignedGraphId}" Width="30"/>
<TextBlock Text="{Binding AssignedAbgewGraphId}" Width="30"/>
</StackPanel>
</DataTemplate>
</xctk:CheckComboBox.ItemTemplate>
</xctk:CheckComboBox>
<Button x:Name="SaveButton" Content="Speichern" IsEnabled="False" Visibility="Hidden"
HorizontalAlignment="Stretch" VerticalAlignment="Bottom" Margin="5,5,2.5,10" Grid.Column="0" Grid.Row="2"
Click="SaveButton_Click"/>
<Button x:Name="ResetButton" Content="Zurücksetzen" IsEnabled="False" Visibility="Hidden"
HorizontalAlignment="Stretch" VerticalAlignment="Bottom" Margin="2.5,5,2.5,10" Grid.Column="1" Grid.Row="2"
Click="ResetButton_Click"/>
<Button x:Name="CancelButton" Content="Abbrechen" IsEnabled="False" Visibility="Hidden" IsCancel="True"
HorizontalAlignment="Stretch" VerticalAlignment="Bottom" Margin="2.5,5,5,10" Grid.Column="2" Grid.Row="2"
Click="CancelButton_Click"/>
<CheckBox x:Name="AbgewertetInput" Content="Abgewertet" IsEnabled="False" Checked="AbgewertetInput_Changed" Unchecked="AbgewertetInput_Changed"
VerticalAlignment="Center" HorizontalAlignment="Left" Margin="0,0,0,0" Grid.Column="1"/>
</Grid>
<Grid Grid.Row="1" Grid.Column="1">
<ScottPlot:WpfPlot x:Name="OechslePricePlot" MouseMove="OechslePricePlot_MouseMove" MouseDown="OechslePricePlot_MouseDown" IsEnabled="False"/>
<ListBox x:Name="GraphList" Margin="10,10,35,42" Grid.Column="0" Grid.Row="0" Grid.RowSpan="2" SelectionChanged="GraphList_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Id}" Width="30"/>
<TextBlock Text="{Binding VaributeStringSimple}" ToolTip="{Binding VaributeString}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button x:Name="SaveButton" Content="Speichern" IsEnabled="False"
HorizontalAlignment="Stretch" VerticalAlignment="Bottom" Margin="10,5,35,10" Grid.Column="0" Grid.Row="2"
Click="SaveButton_Click"/>
<Button x:Name="AddButton" Content="&#xF8AA;" FontFamily="Segoe MDL2 Assets" FontSize="11" Padding="0,1.5,0,0" ToolTip="Neue Auszahlungsvariante hinzufügen"
VerticalAlignment="Center" HorizontalAlignment="Right" Width="25" Height="25" Margin="5,0,5,60" Grid.Column="0" Grid.RowSpan="2" Grid.Row="0"
Click="AddButton_Click"/>
<Button x:Name="CopyButton" Content="&#xE8C8;" FontFamily="Segoe MDL2 Assets" FontSize="12" Padding="0,0,0,0" IsEnabled="False" ToolTip="Ausgewählte Auszahlungsvariante duplizieren"
VerticalAlignment="Center" HorizontalAlignment="Right" Width="25" Height="25" Margin="0,0,5,0" Grid.Column="0" Grid.RowSpan="2" Grid.Row="0"
Click="CopyButton_Click"/>
<Button x:Name="DeleteButton" Content="&#xF8AB;" FontFamily="Segoe MDL2 Assets" FontSize="11" Padding="0,1.5,0,0" IsEnabled="False" ToolTip="Ausgewählte Auszahlungsvariante löschen"
VerticalAlignment="Center" HorizontalAlignment="Right" Width="25" Height="25" Margin="5,60,5,0" Grid.Column="0" Grid.RowSpan="2" Grid.Row="0"
Click="DeleteButton_Click"/>
<Grid Grid.Row="1" Grid.Column="1" Margin="0,0,10,10">
<ScottPlot:WpfPlot x:Name="OechslePricePlot" IsEnabled="False"
MouseWheel="OechslePricePlot_MouseWheel" MouseMove="OechslePricePlot_MouseMove" MouseDown="OechslePricePlot_MouseDown"/>
</Grid>
<Grid Grid.Row="1" Grid.Column="2" Margin="0,0,5,0">
<Grid Grid.Row="1" Grid.Column="2" Margin="0,0,5,36">
<Grid.RowDefinitions>
<RowDefinition Height="120"/>
<RowDefinition Height="120"/>
<RowDefinition Height="90"/>
<RowDefinition Height="210"/>
<RowDefinition Height="1*"/>
<RowDefinition Height="110"/>
<RowDefinition Height="42"/>
</Grid.RowDefinitions>
<GroupBox Header="Graph" Grid.Row="0" Margin="0,5,5,5">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="85"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Content="Nummer:" Margin="10,10,0,0" Grid.Column="0"/>
<TextBox x:Name="GraphNumberInput" Grid.Column="1" HorizontalAlignment="Left" Margin="0,10,0,0" Text="" Width="90" TextChanged="GraphNumberInput_TextChanged" LostFocus="GraphNumberInput_LostFocus"/>
<Label Content="Typ:" Margin="10,45,0,0" Grid.Column="0"/>
<RadioButton x:Name="OechsleGraphType_Input" GroupName="GraphType" Grid.Column="1" Margin="0,45,0,0">Oechsle</RadioButton>
<RadioButton x:Name="KmwGraphType_Input" GroupName="GraphType" Grid.Column="1" Margin="0,60,0,0">KMW</RadioButton>
</Grid>
</GroupBox>
<GroupBox Header="Datenpunkt" Grid.Row="1" Margin="0,5,5,5">
<GroupBox Header="Datenpunkt" Grid.Row="0" Margin="0,5,5,5">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="85"/>
@@ -138,35 +128,44 @@
</Grid.ColumnDefinitions>
<Label Content="Oechsle:" Margin="10,10,0,0" Grid.Column="0"/>
<TextBox x:Name="OechsleInput" Grid.Column="1" HorizontalAlignment="Left" Margin="0,10,0,0" Text="" Width="90" TextChanged="OechsleInput_TextChanged" LostFocus="OechsleInput_LostFocus"/>
<ctrl:UnitTextBox x:Name="OechsleInput" Unit="°Oe" TextChanged="OechsleInput_TextChanged" IsEnabled="False" LostFocus="OechsleInput_LostFocus"
Grid.Column="1" Width="90" Margin="0,10,0,0" HorizontalAlignment="Left" VerticalAlignment="Top"/>
<Label Content="Preis pro kg:" Margin="10,40,0,0" Grid.Column="0"/>
<TextBox x:Name="PriceInput" Grid.Column="1" HorizontalAlignment="Left" Margin="0,40,0,0" Text="" Width="90" TextChanged="PriceInput_TextChanged" LostFocus="PriceInput_LostFocus"/>
<Label Content="Preis:" Margin="10,40,0,0" Grid.Column="0"/>
<ctrl:UnitTextBox x:Name="PriceInput" Unit="€/kg" TextChanged="PriceInput_TextChanged" IsEnabled="False" LostFocus="PriceInput_LostFocus"
Grid.Column="1" Width="90" Margin="0,40,0,0" HorizontalAlignment="Left" VerticalAlignment="Top"/>
</Grid>
</GroupBox>
<GroupBox Header="Gebunden Aufschlag" Grid.Row="1" Margin="0,5,5,5">
<Grid>
<StackPanel Margin="10,10,0,0">
<RadioButton x:Name="GebundenTypeFixed" GroupName="GebundenType" Checked="GebundenType_Checked" IsEnabled="False">Fix</RadioButton>
<RadioButton x:Name="GebundenTypeGraph" GroupName="GebundenType" Checked="GebundenType_Checked" IsEnabled="False">Frei</RadioButton>
<RadioButton x:Name="GebundenTypeNone" GroupName="GebundenType" Checked="GebundenType_Checked" IsEnabled="False">Nein</RadioButton>
</StackPanel>
<ctrl:UnitTextBox x:Name="GebundenFlatBonus" Unit="€/kg" TextChanged="GebundenFlatBonus_TextChanged" IsEnabled="False"
Width="90" Margin="5,5,5,5" HorizontalAlignment="Right" VerticalAlignment="Top" Grid.Column="1"/>
</Grid>
</GroupBox>
<GroupBox Header="Aktionen" Grid.Row="2" Margin="0,5,5,5">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Button x:Name="LeftFlatButton" Content="Links flach" Click="LeftFlatButton_Click" IsEnabled="False"
HorizontalAlignment="Stretch" VerticalAlignment="Top" Margin="10,10,10,10"/>
<Button x:Name="RightFlatButton" Content="Rechts flach" Click="RightFlatButton_Click" IsEnabled="False"
HorizontalAlignment="Stretch" VerticalAlignment="Top" Margin="10,50,10,10"/>
HorizontalAlignment="Stretch" VerticalAlignment="Top" Margin="10,45,10,10"/>
<Button x:Name="InterpolateButton" Content="Interpolieren" Click="InterpolateButton_Click" IsEnabled="False"
HorizontalAlignment="Stretch" VerticalAlignment="Top" Margin="10,90,10,10"/>
HorizontalAlignment="Stretch" VerticalAlignment="Top" Margin="10,80,10,10"/>
<Button x:Name="LinearIncreaseButton" Content="Linear wachsen" Click="LinearIncreaseButton_Click" IsEnabled="False"
HorizontalAlignment="Stretch" VerticalAlignment="Top" Margin="10,130,10,10"/>
HorizontalAlignment="Stretch" VerticalAlignment="Top" Margin="10,115,10,10"/>
</Grid>
</GroupBox>
<GroupBox Header="Optionen" Grid.Row="3" Margin="0,5,5,5">
<GroupBox Header="Optionen" Grid.Row="4" Margin="0,5,5,5">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
@@ -186,4 +185,4 @@
</GroupBox>
</Grid>
</Grid>
</local:AdministrationWindow>
</local:ContextWindow>
File diff suppressed because it is too large Load Diff
+24 -5
View File
@@ -92,8 +92,27 @@
</Grid.ColumnDefinitions>
<TextBox x:Name="SearchInput" Grid.ColumnSpan="3" Margin="5,10,161,0" IsReadOnly="False"
TextChanged="SearchInput_TextChanged"
ToolTip="Lieferungen filtern und durchsuchen. Die Filter sind beliebig kombinierbar.&#xA;&#xA;Filtern nach:&#xA;Sorte: z.B. GV, ZW, rr, sa, !gv (ausgenommen GV), ...&#xA;Qualitätsstufe: z.B. QUW, kab, ldw, ...&#xA;Gradation: z.B. &gt;73, &lt;15, 17-18, 15-, &gt;17,5, 62-75, ...&#xA;Mitglied: z.B. 1234, 987, ...&#xA;Saison: z.B. 2020, &gt;2015, 2017-2019, &lt;2005, 2019-, ...&#xA;Zweigstelle: z.B. musterort, ...&#xA;Attribute: z.B. kabinett, !kabinett (alle außer kabinett), ...&#xA;Datum: z.B. 1.9., 15.9.-10.10., -15.10.2020, ...&#xA;Uhrzeit: z.B. 06:00-08:00, 18:00-, ...&#xA;Freitext: z.B. Lieferscheinnummern, &quot;quw&quot; (sucht nach dem Text &quot;quw&quot;)"/>
TextChanged="SearchInput_TextChanged">
<TextBox.ToolTip>
<TextBlock>
Lieferungen filtern und durchsuchen. Die Filter sind beliebig kombinierbar.<LineBreak/>
Groß- und Kleinschreibung ist in den meisten Fällen egal.<LineBreak/>
<LineBreak/>
Filtern nach:<LineBreak/>
<Bold>Sorte</Bold>: z.B. GV, ZW, rr, sa, !gv (ausgenommen GV), ...<LineBreak/>
<Bold>Rot/Weiß</Bold>: z.B. r, Rot, w, weiß, ...<LineBreak/>
<Bold>Qualitätsstufe</Bold>: z.B. QUW, kab, !ldw (ausgenommen LDW), abgew[ertet], ...<LineBreak/>
<Bold>Gradation</Bold>: z.B. &gt;73, &lt;15, 17-18, 15-, &gt;17,5, 62-75, ...<LineBreak/>
<Bold>Mitglied</Bold>: z.B. 1234, 987, ...<LineBreak/>
<Bold>Saison</Bold>: z.B. 2020, &gt;2015, 2017-2019, &lt;2005, 2019-, ...<LineBreak/>
<Bold>Zweigstelle</Bold>: z.B. musterort, ...<LineBreak/>
<Bold>Attribut</Bold>: z.B. kabinett, !kabinett (alle außer kabinett), ...<LineBreak/>
<Bold>Datum</Bold>: z.B. 1.9., 15.9.-10.10., -15.10.2020, ...<LineBreak/>
<Bold>Uhrzeit</Bold>: z.B. 06:00-08:00, 18:00-, ...<LineBreak/>
<Bold>Freitext</Bold>: z.B. Lieferscheinnummern, Anmerkung, "quw" (sucht nach dem Text "quw")
</TextBlock>
</TextBox.ToolTip>
</TextBox>
<xctk:IntegerUpDown x:Name="SeasonInput" Grid.ColumnSpan="3" Height="25" Width="56" FontSize="14" Minimum="1000" Maximum="9999"
Margin="0,10,100,0" VerticalAlignment="Top" HorizontalAlignment="Right"
ValueChanged="SeasonInput_ValueChanged"/>
@@ -129,21 +148,21 @@
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
<DataGridTextColumn Header="Sorte" Binding="{Binding SortIdString}" Width="50">
<DataGridTextColumn Header="Sorte" Binding="{Binding FilteredSortIdString}" Width="50">
<DataGridTextColumn.CellStyle>
<Style>
<Setter Property="TextBlock.TextAlignment" Value="Center"/>
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
<DataGridTextColumn Header="Gewicht" Binding="{Binding Weight, StringFormat='{}{0:N0} kg '}" Width="75">
<DataGridTextColumn Header="Gewicht" Binding="{Binding FilteredWeight, StringFormat='{}{0:N0} kg '}" Width="75">
<DataGridTextColumn.CellStyle>
<Style>
<Setter Property="TextBlock.TextAlignment" Value="Right"/>
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
<DataGridTextColumn Header="Gradation" Binding="{Binding Kmw, StringFormat='{}{0:N1}° '}" Width="50">
<DataGridTextColumn Header="Gradation" Binding="{Binding FilteredKmw, StringFormat='{}{0:N1}° '}" Width="50">
<DataGridTextColumn.CellStyle>
<Style>
<Setter Property="TextBlock.TextAlignment" Value="Right"/>
+88 -52
View File
@@ -8,7 +8,9 @@ using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
@@ -25,8 +27,11 @@ namespace Elwig.Windows {
private bool IsUpdatingGradation = false;
private Member? Member = null;
private readonly DispatcherTimer Timer;
private List<string> TextFilter = new();
private readonly RoutedCommand CtrlF = new();
private List<string> TextFilter = [];
private readonly RoutedCommand CtrlF = new("CtrlF", typeof(DeliveryAdminWindow), [new KeyGesture(Key.F, ModifierKeys.Control)]);
private readonly RoutedCommand CtrlP = new("CtrlP", typeof(DeliveryAdminWindow), [new KeyGesture(Key.P, ModifierKeys.Control)]);
private readonly RoutedCommand CtrlShiftP = new("CtrlShiftP", typeof(DeliveryAdminWindow), [new KeyGesture(Key.P, ModifierKeys.Control | ModifierKeys.Shift)]);
private string? LastScaleError = null;
private string? ManualWeighingReason = null;
@@ -36,24 +41,25 @@ namespace Elwig.Windows {
public DeliveryAdminWindow(bool receipt = false) {
InitializeComponent();
CtrlF.InputGestures.Add(new KeyGesture(Key.F, ModifierKeys.Control));
CommandBindings.Add(new CommandBinding(CtrlF, FocusSearchInput));
RequiredInputs = new Control[] {
CommandBindings.Add(new CommandBinding(CtrlP, Menu_Print_ShowDeliveryNote_Click));
CommandBindings.Add(new CommandBinding(CtrlShiftP, Menu_Print_PrintDeliveryNote_Click));
RequiredInputs = [
MgNrInput, MemberInput,
LsNrInput, DateInput, BranchInput,
SortIdInput, WineVarietyInput,
GradationOeInput.TextBox, GradationKmwInput.TextBox, WineQualityLevelInput,
WineOriginInput, WineKgInput,
WeightInput.TextBox
};
ExemptInputs = new Control[] {
];
ExemptInputs = [
SearchInput, SeasonInput, TodayOnlyInput, AllSeasonsInput,
DeliveryList, DeliveryPartList,
MemberAddressField,
};
WeighingButtons = new Button[] {
];
WeighingButtons = [
WeighingAButton, WeighingBButton, WeighingCButton, WeighingDButton,
};
];
IsReceipt = receipt;
Timer = new DispatcherTimer();
@@ -67,7 +73,7 @@ namespace Elwig.Windows {
DoShowWarningWindows = false;
if (IsReceipt) {
Title = "Übernahme - Elwig";
Title = $"Übernahme - {App.BranchName} - Elwig";
TodayOnlyInput.IsChecked = true;
var n = App.Scales.Count;
if (n < 1) WeighingAButton.Visibility = Visibility.Hidden;
@@ -167,7 +173,7 @@ namespace Elwig.Windows {
private async void Menu_Print_DeliveryJournal_ShowFilter_Click(object sender, RoutedEventArgs evt) {
Mouse.OverrideCursor = Cursors.AppStarting;
var (f, _, d, _) = await GetFilters();
var (f, _, d, _, _) = await GetFilters();
var doc = new DeliveryJournal(string.Join(" / ", f), d);
await doc.Generate();
Mouse.OverrideCursor = null;
@@ -176,7 +182,7 @@ namespace Elwig.Windows {
private async void Menu_Print_DeliveryJournal_PrintFilter_Click(object sender, RoutedEventArgs evt) {
Mouse.OverrideCursor = Cursors.AppStarting;
var (f, _, d, _) = await GetFilters();
var (f, _, d, _, _) = await GetFilters();
var doc = new DeliveryJournal(string.Join(" / ", f), d);
await doc.Generate();
Mouse.OverrideCursor = null;
@@ -295,8 +301,8 @@ namespace Elwig.Windows {
await RefreshDeliveryListQuery();
}
private async Task<(List<string>, IQueryable<Delivery>, IQueryable<DeliveryPart>, List<string>)> GetFilters() {
List<string> filterNames = new();
private async Task<(List<string>, IQueryable<Delivery>, IQueryable<DeliveryPart>, Predicate<DeliveryPart>, List<string>)> GetFilters() {
List<string> filterNames = [];
IQueryable<Delivery> deliveryQuery = Context.Deliveries;
if (IsReceipt && App.BranchNum > 1) {
deliveryQuery = deliveryQuery.Where(d => d.ZwstId == App.ZwstId);
@@ -315,16 +321,13 @@ namespace Elwig.Windows {
deliveryQuery = deliveryQuery.Where(d => d.Year == SeasonInput.Value);
filterNames.Add(SeasonInput.Value.ToString() ?? "");
}
IQueryable<DeliveryPart> dpq = deliveryQuery
.SelectMany(d => d.Parts)
.OrderBy(p => p.Delivery.DateString)
.ThenBy(p => p.Delivery.TimeString)
.ThenBy(p => p.Delivery.LsNr)
.ThenBy(p => p.DPNr);
Expression<Func<DeliveryPart, bool>> prd = p => true;
var filterVar = new List<string>();
var filterNotVar = new List<string>();
var filterQual = new List<string>();
var filterNotQual = new List<string>();
var filterMgNr = new List<int>();
var filterZwst = new List<string>();
var filterAttr = new List<string>();
@@ -345,7 +348,15 @@ namespace Elwig.Windows {
for (int i = 0; i < filter.Count; i++) {
var e = filter[i];
if (e.Length == 2 && var.ContainsKey(e.ToUpper())) {
if (e.ToLower() is "r" or "rot") {
filterVar.AddRange(var.Values.Where(v => v.IsRed).Select(v => v.SortId));
filter.RemoveAt(i--);
filterNames.Add("Rotweinsorten");
} else if (e.ToLower() is "w" or "weiß" or "weiss") {
filterVar.AddRange(var.Values.Where(v => v.IsWhite).Select(v => v.SortId));
filter.RemoveAt(i--);
filterNames.Add("Weißweinsorten");
} else if (e.Length == 2 && var.ContainsKey(e.ToUpper())) {
filterVar.Add(e.ToUpper());
filter.RemoveAt(i--);
filterNames.Add(var[e.ToUpper()].Name);
@@ -354,13 +365,27 @@ namespace Elwig.Windows {
filter.RemoveAt(i--);
filterNames.Add("außer " + var[e[1..].ToUpper()].Name);
} else if (e.Length == 3 && qual.ContainsKey(e.ToUpper())) {
filterQual.Add(e.ToUpper());
var qualId = e.ToUpper();
filterQual.Add(qualId);
filter.RemoveAt(i--);
filterNames.Add(qual[e.ToUpper()].Name);
} else if (e.All(char.IsAsciiDigit) && mgnr.ContainsKey(e)) {
filterNames.Add(qualId == "WEI" ? "abgewertet" : qual[e.ToUpper()].Name);
} else if (e[0] == '!' && qual.ContainsKey(e[1..].ToUpper())) {
var qualId = e[1..].ToUpper();
filterNotQual.Add(qualId);
filter.RemoveAt(i--);
filterNames.Add(qualId == "WEI" ? "nicht abgewertet" : "außer " + qual[e[1..].ToUpper()].Name);
} else if (e.Length >= 5 && e.Length <= 10 && "abgewertet".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) {
filterQual.Add("WEI");
filter.RemoveAt(i--);
filterNames.Add("abgewertet");
} else if (e.Length >= 6 && e.Length <= 11 && "!abgewertet".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) {
filterNotQual.Add("WEI");
filter.RemoveAt(i--);
filterNames.Add("nicht abgewertet");
} else if (e.All(char.IsAsciiDigit) && mgnr.TryGetValue(e, out var member)) {
filterMgNr.Add(int.Parse(e));
filter.RemoveAt(i--);
filterNames.Add(mgnr[e].AdministrativeName);
filterNames.Add(member.AdministrativeName);
} else if (attr.ContainsKey(e.ToLower())) {
var a = attr[e.ToLower()];
filterAttr.Add(a.AttrId);
@@ -376,7 +401,7 @@ namespace Elwig.Windows {
filterZwst.Add(b.ZwstId);
filter.RemoveAt(i--);
filterNames.Add($"Zweigstelle {b.Name}");
} else if (e.StartsWith(">") || e.StartsWith("<")) {
} else if (e.StartsWith('>') || e.StartsWith('<')) {
if (double.TryParse(e[1..], out var num)) {
switch ((e[0], num)) {
case ('>', <= 30): filterKmwGt = num; break;
@@ -391,8 +416,8 @@ namespace Elwig.Windows {
if (e.Length == 1) filter.RemoveAt(i--);
} else if (e.Length > 1 && Utils.FromToRegex.IsMatch(e)) {
var parts = e.Split("-");
double? from = (parts[0].Length > 0) ? double.Parse(parts[0].Replace(".", ",")) : null;
double? to = (parts[1].Length > 0) ? double.Parse(parts[1].Replace(".", ",")) : null;
double? from = (parts[0].Length > 0) ? double.Parse(parts[0], CultureInfo.InvariantCulture) : null;
double? to = (parts[1].Length > 0) ? double.Parse(parts[1], CultureInfo.InvariantCulture) : null;
switch ((from, to)) {
case ( <= 30, <= 30):
case ( <= 30, null):
@@ -466,38 +491,39 @@ namespace Elwig.Windows {
filterNames.Add($"bis zum {n2}");
}
}
} else if (e.Length > 2 && e.StartsWith("\"") && e.EndsWith("\"")) {
} else if (e.Length > 2 && e.StartsWith('"') && e.EndsWith('"')) {
filter[i] = e[1..^1];
} else if (e.Length <= 2) {
filter.RemoveAt(i--);
}
}
if (filterYearGt > 0) dpq = dpq.Where(p => p.Year >= filterYearGt);
if (filterYearLt > 0) dpq = dpq.Where(p => p.Year < filterYearLt);
if (filterMgNr.Count > 0) dpq = dpq.Where(p => filterMgNr.Contains(p.Delivery.MgNr));
if (filterYearGt > 0) prd = prd.And(p => p.Year >= filterYearGt);
if (filterYearLt > 0) prd = prd.And(p => p.Year < filterYearLt);
if (filterMgNr.Count > 0) prd = prd.And(p => filterMgNr.Contains(p.Delivery.MgNr));
if (filterDate.Count > 0) {
var pr = PredicateBuilder.New<DeliveryPart>(false);
foreach (var (d1, d2) in filterDate)
pr.Or(p => (d1 == null || d1.CompareTo(p.Delivery.DateString.Substring(10 - d1.Length)) <= 0) && (d2 == null || d2.CompareTo(p.Delivery.DateString.Substring(10 - d2.Length)) >= 0));
dpq = dpq.Where(pr);
prd = prd.And(pr);
}
if (filterTime.Count > 0) {
var pr = PredicateBuilder.New<DeliveryPart>(false);
foreach (var (t1, t2) in filterTime)
pr.Or(p => (t1 == null || t1.CompareTo(p.Delivery.TimeString) <= 0) && (t2 == null || t2.CompareTo(p.Delivery.TimeString) > 0));
dpq = dpq.Where(p => p.Delivery.TimeString != null).Where(pr);
prd = prd.And(p => p.Delivery.TimeString != null).And(pr);
}
if (filterVar.Count > 0) dpq = dpq.Where(p => filterVar.Contains(p.SortId));
if (filterNotVar.Count > 0) dpq = dpq.Where(p => !filterNotVar.Contains(p.SortId));
if (filterQual.Count > 0) dpq = dpq.Where(p => filterQual.Contains(p.QualId));
if (filterZwst.Count > 0) dpq = dpq.Where(p => filterZwst.Contains(p.Delivery.ZwstId));
if (filterAttr.Count > 0) dpq = dpq.Where(p => p.AttrId != null && filterAttr.Contains(p.AttrId));
if (filterNotAttr.Count > 0) dpq = dpq.Where(p => p.AttrId == null || !filterAttr.Contains(p.AttrId));
if (filterKmwGt > 0) dpq = dpq.Where(p => p.Kmw >= filterKmwGt);
if (filterKmwLt > 0) dpq = dpq.Where(p => p.Kmw < filterKmwLt);
if (filterOeGt > 0) dpq = dpq.Where(p => p.Kmw * (4.54 + 0.022 * p.Kmw) >= filterOeGt);
if (filterOeLt > 0) dpq = dpq.Where(p => p.Kmw * (4.54 + 0.022 * p.Kmw) < filterOeLt);
if (filterVar.Count > 0) prd = prd.And(p => filterVar.Contains(p.SortId));
if (filterNotVar.Count > 0) prd = prd.And(p => !filterNotVar.Contains(p.SortId));
if (filterQual.Count > 0) prd = prd.And(p => filterQual.Contains(p.QualId));
if (filterNotQual.Count > 0) prd = prd.And(p => !filterNotQual.Contains(p.QualId));
if (filterZwst.Count > 0) prd = prd.And(p => filterZwst.Contains(p.Delivery.ZwstId));
if (filterAttr.Count > 0) prd = prd.And(p => p.AttrId != null && filterAttr.Contains(p.AttrId));
if (filterNotAttr.Count > 0) prd = prd.And(p => p.AttrId == null || !filterNotAttr.Contains(p.AttrId));
if (filterKmwGt > 0) prd = prd.And(p => p.Kmw >= filterKmwGt);
if (filterKmwLt > 0) prd = prd.And(p => p.Kmw < filterKmwLt);
if (filterOeGt > 0) prd = prd.And(p => p.Kmw * (4.54 + 0.022 * p.Kmw) >= filterOeGt);
if (filterOeLt > 0) prd = prd.And(p => p.Kmw * (4.54 + 0.022 * p.Kmw) < filterOeLt);
if (filterYearGt > 0 && filterYearLt > 0) {
filterNames.Insert(0, $"{filterYearGt}{filterYearLt - 1}");
@@ -522,7 +548,15 @@ namespace Elwig.Windows {
}
}
return (filterNames, dpq.Select(p => p.Delivery).Distinct().OrderBy(d => d.DateString).ThenBy(d => d.TimeString), dpq, filter);
IQueryable<DeliveryPart> dpq = deliveryQuery
.SelectMany(d => d.Parts)
.Where(prd)
.OrderBy(p => p.Delivery.DateString)
.ThenBy(p => p.Delivery.TimeString)
.ThenBy(p => p.Delivery.LsNr)
.ThenBy(p => p.DPNr);
return (filterNames, dpq.Select(p => p.Delivery).Distinct().OrderBy(d => d.DateString).ThenBy(d => d.TimeString), dpq, prd.Invoke, filter);
}
private static void AddToolTipCell(Grid grid, string text, int row, int col, int colSpan = 1, bool bold = false, bool alignRight = false, bool alignCenter = false) {
@@ -558,7 +592,7 @@ namespace Elwig.Windows {
}
private async Task RefreshDeliveryListQuery(bool updateSort = false) {
var (_, deliveryQuery, deliveryPartsQuery, filter) = await GetFilters();
var (_, deliveryQuery, deliveryPartsQuery, predicate, filter) = await GetFilters();
var deliveries = await deliveryQuery.ToListAsync();
deliveries.Reverse();
@@ -574,8 +608,10 @@ namespace Elwig.Windows {
.ToList();
}
deliveries.ForEach(d => { d.PartFilter = predicate; });
ControlUtils.RenewItemsSource(DeliveryList, deliveries, d => ((d as Delivery)?.Year, (d as Delivery)?.DId),
DeliveryList_SelectionChanged, filter.Count > 0 ? ControlUtils.RenewSourceDefault.IfOnly : ControlUtils.RenewSourceDefault.None, !updateSort);
await RefreshDeliveryParts();
var members = deliveries.Select(d => d.Member).DistinctBy(m => m.MgNr).ToList();
StatusMembers.Text = $"Mitglieder: {members.Count}" + (members.Count > 0 && members.Count <= 4 ? $" ({string.Join(", ", members.Select(m => m.AdministrativeName))})" : "");
@@ -707,7 +743,7 @@ namespace Elwig.Windows {
Menu_Export_Bki.Items.Clear();
foreach (var s in await Context.Seasons.OrderByDescending(s => s.Year).ToListAsync()) {
var i = new MenuItem {
Header = $"Season {s.Year}",
Header = $"Saison {s.Year}",
};
i.Click += Menu_Export_Bki_Click;
Menu_Export_Bki.Items.Add(i);
@@ -745,7 +781,7 @@ namespace Elwig.Windows {
private async Task RefreshDeliveryParts() {
if (DeliveryList.SelectedItem is Delivery d) {
ControlUtils.RenewItemsSource(ModifiersInput, await Context.Modifiers.Where(m => m.Year == d.Year).OrderBy(m => m.Ordering).ToListAsync(), i => (i as Modifier)?.ModId);
ControlUtils.RenewItemsSource(DeliveryPartList, d.Parts.OrderBy(p => p.DPNr).ToList(), i => ((i as DeliveryPart)?.Year, (i as DeliveryPart)?.DId, (i as DeliveryPart)?.DPNr), DeliveryPartList_SelectionChanged, ControlUtils.RenewSourceDefault.First);
ControlUtils.RenewItemsSource(DeliveryPartList, d.FilteredParts.OrderBy(p => p.DPNr).ToList(), i => ((i as DeliveryPart)?.Year, (i as DeliveryPart)?.DId, (i as DeliveryPart)?.DPNr), DeliveryPartList_SelectionChanged, ControlUtils.RenewSourceDefault.First);
} else {
ControlUtils.RenewItemsSource(ModifiersInput, await Context.Modifiers.Where(m => m.Year == Utils.CurrentLastSeason).OrderBy(m => m.Ordering).ToListAsync(), i => (i as Modifier)?.ModId);
DeliveryPartList.ItemsSource = null;
@@ -874,7 +910,7 @@ namespace Elwig.Windows {
p.Acid = (AcidInput.Text == "") ? null : double.Parse(AcidInput.Text);
p.Comment = (PartCommentInput.Text == "") ? null : PartCommentInput.Text;
p.Weight = int.Parse(WeightInput.Text.Replace("\u202f", ""));
p.Weight = int.Parse(WeightInput.Text.Replace(Utils.GroupSeparator, ""));
p.ManualWeighing = ManualWeighingInput.IsChecked ?? false;
p.ScaleId = ScaleId;
p.WeighingId = WeighingId;
@@ -1119,7 +1155,7 @@ namespace Elwig.Windows {
} else {
// switch to last delivery part
DeliveryPartList.IsEnabled = true;
DeliveryPartList.SelectedItem = d.Parts.Last();
DeliveryPartList.SelectedItem = d.FilteredParts.Last();
}
}
@@ -1589,7 +1625,7 @@ namespace Elwig.Windows {
}
private void GradationKmwInput_LostFocus(object sender, RoutedEventArgs evt) {
if (GradationKmwInput.Text.EndsWith(",")) GradationKmwInput.Text += "0";
if (GradationKmwInput.Text.EndsWith(',')) GradationKmwInput.Text += "0";
InputLostFocus((TextBox)sender, Validator.CheckGradationKmw);
if (GradationKmwInput.Text.Length != 0 && !GradationKmwInput.Text.Contains(','))
GradationKmwInput.Text += ",0";
@@ -1687,7 +1723,7 @@ namespace Elwig.Windows {
if (sender is not TextBox tb) return;
if (tb.Text.Length > 0) {
if (!tb.Text.Contains(',')) tb.Text += ",0";
if (tb.Text.EndsWith(",")) tb.Text += "0";
if (tb.Text.EndsWith(',')) tb.Text += "0";
}
InputLostFocus(tb, Validator.CheckDecimal(tb, false, 2, 1));
}
@@ -74,9 +74,9 @@ namespace Elwig.Dialogs {
}
IEnumerable<Member> list = await members.ToListAsync();
var data = await DeliveryConfirmationData.ForSeason(Context.DeliveryParts, Year);
var data = await DeliveryConfirmationDeliveryData.ForSeason(Context.DeliveryParts, Year);
using var doc = Document.Merge(list.Select(m =>
new DeliveryConfirmation(Context, Year, m, data[m.MgNr]) {
new DeliveryConfirmation(Context, Year, m, data.TryGetValue(m.MgNr, out var d) ? d : DeliveryConfirmationDeliveryData.CreateEmpty(Year, m)) {
//DoubleSided = true
}
));
-5
View File
@@ -27,11 +27,6 @@ namespace Elwig.Windows {
w.Show();
}
private void MemberListButton_Click(object sender, RoutedEventArgs evt) {
var w = new MemberListWindow();
w.Show();
}
private void ReceiptButton_Click(object sender, RoutedEventArgs evt) {
App.FocusReceipt();
}
+69 -17
View File
@@ -2,8 +2,6 @@
x:Class="Elwig.Windows.MemberAdminWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Elwig.Windows"
Title="Mitglieder - Elwig" Height="700" Width="1250" MinHeight="650" MinWidth="1150"
Loaded="Window_Loaded">
@@ -89,7 +87,29 @@
</Grid.ColumnDefinitions>
<TextBox x:Name="SearchInput" Grid.ColumnSpan="3" Margin="5,7,145,0" IsReadOnly="False"
TextChanged="SearchInput_TextChanged"/>
TextChanged="SearchInput_TextChanged">
<TextBox.ToolTip>
<TextBlock>
Mitglieder filtern und durchsuchen. Die Filter sind beliebig kombinierbar.<LineBreak/>
Groß- und Kleinschreibung ist in den meisten Fällen egal.<LineBreak/>
<LineBreak/>
Filtern nach:<LineBreak/>
<Bold>MgNr.</Bold>: z.B. 1234, 89, ...<LineBreak/>
<Bold>Vor-/Nachname</Bold>: z.B. Max Mustermann... <LineBreak/>
<Bold>Stamm-Zweigstelle</Bold>: z.B. zwst:Matzen, zwst:wolkersdorf, ...<LineBreak/>
<Bold>Stammgemeinde</Bold>: z.B. matzen, Wolkersdorf, ...<LineBreak/>
<Bold>UID</Bold>: z.B. ATU12345678, ...<LineBreak/>
<Bold>Betriebs-Nr.</Bold>: z.B. 0123456, ...<LineBreak/>
<Bold>Bio-Betrieb</Bold>: BIO, !bio (ausgenommen Bio)<LineBreak/>
<Bold>Buchführend</Bold>: buchf[ührend], Pauschal[iert], !buchf[ührend]<LineBreak/>
<Bold>Volllieferant</Bold>: voll[lieferant], !Voll[lieferant] (nicht-Volllieferant)<LineBreak/>
<Bold>Funktionär</Bold>: Funkt[ionär], !funkt[ionär] (nicht-Funktionär)<LineBreak/>
<Bold>Telefon-Nr.</Bold>: z.B. +436641234, ....<LineBreak/>
<Bold>Flächenbindungen</Bold>: z.B. zw, GVK, WRB, ... (Mitglieder mit aktiven Flächenbindungen)<LineBreak/>
<Bold>Freitext</Bold>: z.B. Rechnungsaddresse, Anmerkung, "matzen" (sucht nach dem Text "matzen")
</TextBlock>
</TextBox.ToolTip>
</TextBox>
<CheckBox x:Name="ActiveMemberInput" Content="Nur aktive anzeigen"
Checked="ActiveMemberInput_Changed" Unchecked="ActiveMemberInput_Changed"
HorizontalAlignment="Right" Margin="0,12,10,0" VerticalAlignment="Top" Grid.Column="1" Grid.ColumnSpan="2"/>
@@ -143,11 +163,11 @@
<Grid Grid.Column="2" Grid.Row="1">
<Grid.RowDefinitions>
<RowDefinition Height="0.8*"/>
<RowDefinition Height="0.8*"/>
<RowDefinition Height="0.2*"/>
<RowDefinition Height="1.3*"/>
<RowDefinition Height="0.8*"/>
<RowDefinition Height="108"/>
<RowDefinition Height="120"/>
<RowDefinition Height="18"/>
<RowDefinition Height="*"/>
<RowDefinition Height="113"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
@@ -217,14 +237,42 @@
<ColumnDefinition Width="2*"/>
</Grid.ColumnDefinitions>
<Label Content="E-Mail-Adresse (1):" Margin="10,10,0,0" Grid.Column="0"/>
<Label x:Name="EmailAddress1Label" Content="E-Mail-Adresse:" Margin="10,10,0,0" Grid.Column="0"/>
<TextBox x:Name="EmailAddress1Input" Margin="0,10,10,0" Grid.Column="1" Grid.ColumnSpan="2"
TextChanged="EmailAddressInput_TextChanged" LostFocus="EmailAddressInput_LostFocus"/>
<Label Content="E-Mail-Adresse (2):" Margin="10,40,0,0" Grid.Column="0"/>
<Label x:Name="EmailAddress2Label" Content="E-Mail-Adresse:" Margin="10,40,0,0" Grid.Column="0"/>
<TextBox x:Name="EmailAddress2Input" Margin="0,40,10,0" Grid.Column="1" Grid.ColumnSpan="2"
TextChanged="EmailAddressInput_TextChanged" LostFocus="EmailAddressInput_LostFocus"/>
<Label x:Name="EmailAddress3Label" Content="E-Mail-Adresse:" Margin="10,70,0,0" Grid.Column="0"/>
<TextBox x:Name="EmailAddress3Input" Margin="0,70,10,0" Grid.Column="1" Grid.ColumnSpan="2"
TextChanged="EmailAddressInput_TextChanged" LostFocus="EmailAddressInput_LostFocus"/>
<Label x:Name="EmailAddress4Label" Content="E-Mail-Adresse:" Margin="10,100,0,0" Grid.Column="0"/>
<TextBox x:Name="EmailAddress4Input" Margin="0,100,10,0" Grid.Column="1" Grid.ColumnSpan="2"
TextChanged="EmailAddressInput_TextChanged" LostFocus="EmailAddressInput_LostFocus"/>
<Label x:Name="EmailAddress5Label" Content="E-Mail-Adresse:" Margin="10,130,0,0" Grid.Column="0"/>
<TextBox x:Name="EmailAddress5Input" Margin="0,130,10,0" Grid.Column="1" Grid.ColumnSpan="2"
TextChanged="EmailAddressInput_TextChanged" LostFocus="EmailAddressInput_LostFocus"/>
<Label x:Name="EmailAddress6Label" Content="E-Mail-Adresse:" Margin="10,160,0,0" Grid.Column="0"/>
<TextBox x:Name="EmailAddress6Input" Margin="0,160,10,0" Grid.Column="1" Grid.ColumnSpan="2"
TextChanged="EmailAddressInput_TextChanged" LostFocus="EmailAddressInput_LostFocus"/>
<Label x:Name="EmailAddress7Label" Content="E-Mail-Adresse:" Margin="10,190,0,0" Grid.Column="0"/>
<TextBox x:Name="EmailAddress7Input" Margin="0,190,10,0" Grid.Column="1" Grid.ColumnSpan="2"
TextChanged="EmailAddressInput_TextChanged" LostFocus="EmailAddressInput_LostFocus"/>
<Label x:Name="EmailAddress8Label" Content="E-Mail-Adresse:" Margin="10,210,0,0" Grid.Column="0"/>
<TextBox x:Name="EmailAddress8Input" Margin="0,210,10,0" Grid.Column="1" Grid.ColumnSpan="2"
TextChanged="EmailAddressInput_TextChanged" LostFocus="EmailAddressInput_LostFocus"/>
<Label x:Name="EmailAddress9Label" Content="E-Mail-Adresse:" Margin="10,250,0,0" Grid.Column="0"/>
<TextBox x:Name="EmailAddress9Input" Margin="0,250,10,0" Grid.Column="1" Grid.ColumnSpan="2"
TextChanged="EmailAddressInput_TextChanged" LostFocus="EmailAddressInput_LostFocus"/>
<ComboBox x:Name="PhoneNr1TypeInput" DisplayMemberPath="Value" Margin="6,70,5,0" FontSize="12" Padding="6,4,4,4"/>
<TextBox x:Name="PhoneNr1Input" Margin="0,70,5,0" Grid.Column="1"
TextChanged="PhoneNrInput_TextChanged" LostFocus="PhoneNrInput_LostFocus"/>
@@ -288,11 +336,11 @@
</Grid.ColumnDefinitions>
<Label Content="IBAN:" Margin="10,10,0,0" Grid.Column="0"/>
<TextBox x:Name="IbanInput" Margin="0,10,10,0" Grid.Column="1"
<TextBox x:Name="IbanInput" Margin="0,10,10,0" Grid.Column="1" Width="290" HorizontalAlignment="Left"
TextChanged="IbanInput_TextChanged" LostFocus="IbanInput_LostFocus"/>
<Label Content="BIC:" Margin="10,40,0,0" Grid.Column="0"/>
<TextBox x:Name="BicInput" Margin="0,40,10,0" Grid.Column="1"
<TextBox x:Name="BicInput" Margin="0,40,10,0" Grid.Column="1" Width="150" HorizontalAlignment="Left"
TextChanged="BicInput_TextChanged" LostFocus="BicInput_LostFocus"/>
</Grid>
</GroupBox>
@@ -319,6 +367,9 @@
<CheckBox x:Name="OrganicInput" Content="Bio" IsEnabled="False"
Checked="CheckBox_Changed" Unchecked="CheckBox_Changed"
Grid.Column="2" HorizontalAlignment="Left" Margin="10,45,0,0" VerticalAlignment="Top" IsChecked="False"/>
<Button x:Name="OrganicButton" Content="easy-cert.com" Height="25" FontSize="12" IsEnabled="False"
Click="OrganicButton_Click"
Grid.Column="2" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="60,40,0,0"/>
</Grid>
</GroupBox>
<GroupBox Header="Rechnungsadresse (optional)" Grid.Column="1" Grid.Row="1" Grid.RowSpan="2" Margin="5,5,5,5">
@@ -391,16 +442,17 @@
Grid.Column="2" VerticalAlignment="Top" HorizontalAlignment="Right" Width="25" Height="25" Margin="10,160,10,10"/>
<Label Content="Anmerkung:" Margin="10,190,0,0" Grid.Column="0"/>
<TextBox x:Name="CommentInput" Margin="0,190,10,0" Grid.Column="1" Grid.ColumnSpan="2"
TextChanged="TextBox_TextChanged"/>
<TextBox x:Name="CommentInput" Margin="0,190,10,70" Grid.Column="1" Grid.ColumnSpan="2"
TextChanged="TextBox_TextChanged"
VerticalAlignment="Stretch" Height="auto" AcceptsReturn="True" TextWrapping="Wrap" VerticalScrollBarVisibility="Visible"/>
<Label Content="Kontaktart:" Margin="10,220,0,0" Grid.Column="0"/>
<Label Content="Kontaktart:" Margin="10,10,0,10" Grid.Column="0" VerticalAlignment="Bottom"/>
<CheckBox x:Name="ContactPostalInput" Content="Post" IsEnabled="False"
Checked="CheckBox_Changed" Unchecked="CheckBox_Changed"
HorizontalAlignment="Left" Margin="0,225,0,0" VerticalAlignment="Top" Grid.Column="1" Grid.ColumnSpan="2"/>
HorizontalAlignment="Left" Margin="0,0,0,15" VerticalAlignment="Bottom" Grid.Column="1" Grid.ColumnSpan="2"/>
<CheckBox x:Name="ContactEmailInput" Content="E-Mail" IsEnabled="False"
Checked="CheckBox_Changed" Unchecked="CheckBox_Changed"
HorizontalAlignment="Left" Margin="60,225,0,0" VerticalAlignment="Top" Grid.Column="1" Grid.ColumnSpan="2"/>
HorizontalAlignment="Left" Margin="60,0,0,15" VerticalAlignment="Bottom" Grid.Column="1" Grid.ColumnSpan="2"/>
<Button x:Name="DeliveryButton" Content="Lieferungen" Click="DeliveryButton_Click" IsEnabled="False"
HorizontalAlignment="Right" Margin="10,00,10,37" Width="150" VerticalAlignment="Bottom" Grid.ColumnSpan="3"/>
+200 -35
View File
@@ -11,20 +11,26 @@ using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using System.Collections.ObjectModel;
using Elwig.Documents;
using System.Diagnostics;
namespace Elwig.Windows {
public partial class MemberAdminWindow : AdministrationWindow {
private List<string> TextFilter = [];
private readonly RoutedCommand CtrlF = new();
private readonly (ComboBox, TextBox, TextBox)[] PhoneNrInputs;
private readonly (ComboBox Type, TextBox Number, TextBox Comment)[] PhoneNrInputs;
private readonly (Label Label, TextBox Address)[] EmailAddressInputs;
private readonly RoutedCommand CtrlF = new("CtrlF", typeof(MemberAdminWindow), [new KeyGesture(Key.F, ModifierKeys.Control)]);
private readonly RoutedCommand CtrlP = new("CtrlP", typeof(MemberAdminWindow), [new KeyGesture(Key.P, ModifierKeys.Control)]);
private readonly RoutedCommand CtrlShiftP = new("CtrlShiftP", typeof(MemberAdminWindow), [new KeyGesture(Key.P, ModifierKeys.Control | ModifierKeys.Shift)]);
private static ObservableCollection<KeyValuePair<string, string>> PhoneNrTypes { get; set; } = new(Utils.PhoneNrTypes);
public MemberAdminWindow() {
InitializeComponent();
CtrlF.InputGestures.Add(new KeyGesture(Key.F, ModifierKeys.Control));
CommandBindings.Add(new CommandBinding(CtrlF, FocusSearchInput));
CommandBindings.Add(new CommandBinding(CtrlP, Menu_Show_MemberDataSheet_Click));
CommandBindings.Add(new CommandBinding(CtrlShiftP, Menu_Print_MemberDataSheet_Click));
ExemptInputs = [
SearchInput, ActiveMemberInput, MemberList,
];
@@ -33,6 +39,17 @@ namespace Elwig.Windows {
AddressInput, PlzInput, OrtInput, BillingOrtInput,
BusinessSharesInput, BranchInput, DefaultKgInput
];
EmailAddressInputs = [
(EmailAddress1Label, EmailAddress1Input),
(EmailAddress2Label, EmailAddress2Input),
(EmailAddress3Label, EmailAddress3Input),
(EmailAddress4Label, EmailAddress4Input),
(EmailAddress5Label, EmailAddress5Input),
(EmailAddress6Label, EmailAddress6Input),
(EmailAddress7Label, EmailAddress7Input),
(EmailAddress8Label, EmailAddress8Input),
(EmailAddress9Label, EmailAddress9Input),
];
PhoneNrInputs = [
(PhoneNr1TypeInput, PhoneNr1Input, PhoneNr1CommentInput),
(PhoneNr2TypeInput, PhoneNr2Input, PhoneNr2CommentInput),
@@ -58,7 +75,7 @@ namespace Elwig.Windows {
Menu_Print_MemberDataSheet.IsEnabled = App.IsPrintingReady;
ActiveMemberInput.IsChecked = true;
UpdatePhoneNrInputVisibility();
UpdateContactInfoVisibility();
LockInputs();
}
@@ -69,14 +86,98 @@ namespace Elwig.Windows {
private async Task RefreshMemberListQuery(bool updateSort = false) {
IQueryable<Member> memberQuery = Context.Members;
if (ActiveMemberInput.IsChecked == true)
memberQuery = memberQuery.Where(m => m.IsActive);
if (ActiveMemberInput.IsChecked == true) memberQuery = memberQuery.Where(m => m.IsActive);
var filterMgNr = new List<int>();
var filterZwst = new List<string>();
var filterKgNr = new List<int>();
var filterLfbisNr = new List<string>();
var filterUstIdNr = new List<string>();
var filterAreaCom = new List<string>();
var filter = TextFilter.ToList();
if (filter.Count > 0) {
var branches = await Context.Branches.ToListAsync();
var mgnr = await Context.Members.ToDictionaryAsync(m => m.MgNr.ToString(), m => m);
var kgs = await Context.WbKgs.ToDictionaryAsync(k => k.AtKg.Name.ToLower(), k => k.AtKg);
var areaComs = await Context.AreaCommitmentTypes.ToDictionaryAsync(t => $"{t.SortId}{t.AttrId}", t => t);
for (int i = 0; i < filter.Count; i++) {
var e = filter[i];
if (e.Length >= 5 && e.Length <= 10 && "funktionär".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) {
memberQuery = memberQuery.Where(m => m.IsFunktionär);
filter.RemoveAt(i--);
} else if (e.Length >= 6 && e.Length <= 11 && e[0] == '!' && "funktionär".StartsWith(e[1..], StringComparison.CurrentCultureIgnoreCase)) {
memberQuery = memberQuery.Where(m => !m.IsFunktionär);
filter.RemoveAt(i--);
} else if (e.Equals("bio", StringComparison.CurrentCultureIgnoreCase)) {
memberQuery = memberQuery.Where(m => m.IsOrganic);
filter.RemoveAt(i--);
} else if (e.Equals("!bio", StringComparison.CurrentCultureIgnoreCase)) {
memberQuery = memberQuery.Where(m => !m.IsOrganic);
filter.RemoveAt(i--);
} else if (e.Length >= 4 && e.Length <= 13 && "volllieferant".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) {
memberQuery = memberQuery.Where(m => m.IsVollLieferant);
filter.RemoveAt(i--);
} else if (e.Length >= 5 && e.Length <= 14 && e[0] == '!' && "volllieferant".StartsWith(e[1..], StringComparison.CurrentCultureIgnoreCase)) {
memberQuery = memberQuery.Where(m => !m.IsVollLieferant);
filter.RemoveAt(i--);
} else if (e.Length >= 5 && e.Length <= 11 && "buchführend".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) {
memberQuery = memberQuery.Where(m => m.IsBuchführend);
filter.RemoveAt(i--);
} else if (e.Length >= 6 && e.Length <= 12 && e[0] == '!' && "buchführend".StartsWith(e[1..], StringComparison.CurrentCultureIgnoreCase)) {
memberQuery = memberQuery.Where(m => !m.IsBuchführend);
filter.RemoveAt(i--);
} else if (e.Length >= 8 && e.Length <= 12 && "pauschaliert".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) {
memberQuery = memberQuery.Where(m => !m.IsBuchführend);
filter.RemoveAt(i--);
} else if (e.Length >= 9 && e.Length <= 13 && e[0] == '!' && "pauschaliert".StartsWith(e[1..], StringComparison.CurrentCultureIgnoreCase)) {
memberQuery = memberQuery.Where(m => m.IsBuchführend);
filter.RemoveAt(i--);
} else if (e.All(char.IsAsciiDigit) && mgnr.ContainsKey(e)) {
filterMgNr.Add(int.Parse(e));
filter.RemoveAt(i--);
} else if (kgs.TryGetValue(e, out var kg)) {
filterKgNr.Add(kg.KgNr);
filter.RemoveAt(i--);
} else if (e.StartsWith("zwst:")) {
try {
filterZwst.Add(branches.Where(b => b.Name.StartsWith(e[5..], StringComparison.CurrentCultureIgnoreCase)).Single().ZwstId);
filter.RemoveAt(i--);
} catch (InvalidOperationException) { }
} else if (e.StartsWith('+') && e[1..].All(char.IsAsciiDigit)) {
memberQuery = memberQuery.Where(m => m.TelephoneNumbers.Any(t => t.Number.Replace(" ", "").StartsWith(e)));
filter.RemoveAt(i--);
} else if (areaComs.ContainsKey(e.ToUpper())) {
filterAreaCom.Add(e.ToUpper());
filter.RemoveAt(i--);
} else if (Validator.CheckLfbisNr(e)) {
filterLfbisNr.Add(e);
filter.RemoveAt(i--);
} else if (Validator.CheckUstIdNr(e.ToUpper())) {
filterUstIdNr.Add(e.ToUpper());
filter.RemoveAt(i--);
} else if (e.Length > 2 && e.StartsWith('"') && e.EndsWith('"')) {
filter[i] = e[1..^1];
} else if (e.Length <= 2) {
filter.RemoveAt(i--);
}
}
if (filterMgNr.Count > 0) memberQuery = memberQuery.Where(m => filterMgNr.Contains(m.MgNr));
if (filterKgNr.Count > 0) memberQuery = memberQuery.Where(m => m.DefaultKgNr != null && filterKgNr.Contains((int)m.DefaultKgNr));
if (filterZwst.Count > 0) memberQuery = memberQuery.Where(m => m.ZwstId != null && filterZwst.Contains(m.ZwstId));
if (filterAreaCom.Count > 0) memberQuery = memberQuery.Where(m => m.AreaCommitments.Where(c => c.YearFrom <= Utils.CurrentYear && (c.YearTo ?? int.MaxValue) >= Utils.CurrentYear).Any(c => filterAreaCom.Contains(c.VtrgId)));
if (filterLfbisNr.Count > 0) memberQuery = memberQuery.Where(m => m.LfbisNr != null && filterLfbisNr.Contains(m.LfbisNr));
if (filterUstIdNr.Count > 0) memberQuery = memberQuery.Where(m => m.UstIdNr != null && filterUstIdNr.Contains(m.UstIdNr));
}
List<Member> members = await memberQuery.ToListAsync();
if (TextFilter.Count > 0) {
if (filter.Count > 0 && members.Count > 0) {
var dict = members.AsParallel()
.ToDictionary(m => m, m => m.SearchScore(TextFilter))
.ToDictionary(m => m, m => m.SearchScore(filter))
.OrderByDescending(a => a.Value)
.ThenBy(a => a.Key.FamilyName)
.ThenBy(a => a.Key.GivenName);
@@ -103,12 +204,14 @@ namespace Elwig.Windows {
DeleteMemberButton.IsEnabled = true;
AreaCommitmentButton.IsEnabled = true;
DeliveryButton.IsEnabled = true;
OrganicButton.IsEnabled = true;
FillInputs(m);
} else {
EditMemberButton.IsEnabled = false;
DeleteMemberButton.IsEnabled = false;
AreaCommitmentButton.IsEnabled = false;
DeliveryButton.IsEnabled = false;
OrganicButton.IsEnabled = false;
ClearOriginalValues();
ClearDefaultValues();
ClearInputs(validate);
@@ -145,27 +248,55 @@ namespace Elwig.Windows {
private void SetPhoneNrInput(int nr, string? type, string? number, string? comment) {
var inputs = PhoneNrInputs[nr];
inputs.Item1.SelectedItem = (type == null) ? null : inputs.Item1.ItemsSource.Cast<KeyValuePair<string, string>>().FirstOrDefault(p => p.Key == type);
inputs.Item2.Text = number;
inputs.Item3.Text = comment;
inputs.Type.SelectedItem = (type == null) ? null : inputs.Type.ItemsSource.Cast<KeyValuePair<string, string>>().FirstOrDefault(p => p.Key == type);
inputs.Number.Text = number;
inputs.Comment.Text = comment;
}
private void SetEmailAddressInput(int nr, string? address) {
var inputs = EmailAddressInputs[nr];
inputs.Address.Text = address;
}
private (string, string, string?)? GetPhoneNrInput(int nr) {
var inputs = PhoneNrInputs[nr];
var number = inputs.Item2.Text;
var number = inputs.Number.Text;
if (string.IsNullOrEmpty(number))
return null;
var type = (inputs.Item1.SelectedItem as KeyValuePair<string, string>?)?.Key ?? (number.StartsWith("+43 ") && number[4] == '6' ? "mobile" : "landline");
var comment = inputs.Item3.Text;
var type = (inputs.Type.SelectedItem as KeyValuePair<string, string>?)?.Key ?? (number.StartsWith("+43 ") && number[4] == '6' ? "mobile" : "landline");
var comment = inputs.Comment.Text;
return (type, number, comment == "" ? null : comment);
}
private void SetPhoneNrInputVisible(int nr, bool visible) {
private string? GetEmailAddressInput(int nr) {
var inputs = EmailAddressInputs[nr];
return inputs.Address.Text == "" ? null : inputs.Address.Text;
}
private void SetPhoneNrInputVisible(int nr, bool visible, int? position = null) {
var inputs = PhoneNrInputs[nr];
if (position is int p) {
var mt = 10 + p * 30;
inputs.Type.Margin = new(6, mt, 5, 0);
inputs.Number.Margin = new(0, mt, 5, 0);
inputs.Comment.Margin = new(0, mt, 10, 0);
}
var vis = visible ? Visibility.Visible : Visibility.Hidden;
inputs.Item1.Visibility = vis;
inputs.Item2.Visibility = vis;
inputs.Item3.Visibility = vis;
inputs.Type.Visibility = vis;
inputs.Number.Visibility = vis;
inputs.Comment.Visibility = vis;
}
private void SetEmailAddressInputVisible(int nr, bool visible, int? position = null) {
var inputs = EmailAddressInputs[nr];
if (position is int p) {
var mt = 10 + p * 30;
inputs.Label.Margin = new(10, mt, 0, 0);
inputs.Address.Margin = new(0, mt, 10, 0);
}
var vis = visible ? Visibility.Visible : Visibility.Hidden;
inputs.Label.Visibility = vis;
inputs.Address.Visibility = vis;
}
private void MemberList_SelectionChanged(object sender, RoutedEventArgs evt) {
@@ -183,7 +314,7 @@ namespace Elwig.Windows {
HideNewEditDeleteButtons();
ShowSaveResetCancelButtons();
UnlockInputs();
UpdatePhoneNrInputVisibility(true);
UpdateContactInfoVisibility(true);
InitInputs();
LockSearchInputs();
}
@@ -198,7 +329,7 @@ namespace Elwig.Windows {
HideNewEditDeleteButtons();
ShowSaveResetCancelButtons();
UnlockInputs();
UpdatePhoneNrInputVisibility(true);
UpdateContactInfoVisibility(true);
LockSearchInputs();
}
@@ -224,7 +355,7 @@ namespace Elwig.Windows {
HideSaveResetCancelButtons();
ShowNewEditDeleteButtons();
LockInputs();
UpdatePhoneNrInputVisibility();
UpdateContactInfoVisibility();
UnlockSearchInputs();
FinishInputFilling();
await RefreshMemberList();
@@ -251,7 +382,7 @@ namespace Elwig.Windows {
ShowNewEditDeleteButtons();
RefreshInputs();
LockInputs();
UpdatePhoneNrInputVisibility();
UpdateContactInfoVisibility();
UnlockSearchInputs();
}
@@ -413,13 +544,24 @@ namespace Elwig.Windows {
ActiveMemberInput.IsEnabled = true;
}
private void UpdatePhoneNrInputVisibility(bool extra = false) {
bool lastVisible = true;
var m = (Member)MemberList.SelectedItem;
private void UpdateContactInfoVisibility(bool extra = false) {
var m = MemberList.SelectedItem as Member;
bool lastVisible;
int num = 0;
lastVisible = true;
for (int i = 0; i < EmailAddressInputs.Length; i++) {
var input = EmailAddressInputs[i];
var vis = !string.IsNullOrEmpty(input.Address.Text) || (m?.EmailAddresses.Any(a => a.Nr - 1 == i) ?? false);
var cVis = vis || (extra && lastVisible);
SetEmailAddressInputVisible(i, cVis, cVis ? num++ : null);
lastVisible = vis;
}
lastVisible = true;
for (int i = 0; i < PhoneNrInputs.Length; i++) {
var input = PhoneNrInputs[i];
var vis = !string.IsNullOrEmpty(input.Item2.Text) || (m?.TelephoneNumbers.Any(p => p.Nr - 1 == i) ?? false);
SetPhoneNrInputVisible(i, vis || (extra && lastVisible));
var vis = !string.IsNullOrEmpty(input.Number.Text) || (m?.TelephoneNumbers.Any(n => n.Nr - 1 == i) ?? false);
var cVis = vis || (extra && lastVisible);
SetPhoneNrInputVisible(i, cVis, cVis ? num++ : null);
lastVisible = vis;
}
}
@@ -514,17 +656,17 @@ namespace Elwig.Windows {
}
}
for (int i = 0; i < 2; i++) {
var input = i == 0 ? EmailAddress1Input : EmailAddress2Input;
for (int i = 0; i < EmailAddressInputs.Length; i++) {
var input = GetEmailAddressInput(i);
var emailAddr = m.EmailAddresses.FirstOrDefault(a => a.Nr - 1 == i);
if (input.Text == "") {
if (input == null || input == "") {
if (emailAddr != null) {
Context.Remove(emailAddr);
}
} else {
MemberEmailAddr a = emailAddr ?? Context.CreateProxy<MemberEmailAddr>();
a.Nr = i + 1;
a.Address = input.Text;
a.Address = input ?? "";
a.Comment = null;
if (emailAddr == null) {
a.MgNr = newMgNr;
@@ -587,8 +729,14 @@ namespace Elwig.Windows {
}
var emailAddrs = m.EmailAddresses.OrderBy(a => a.Nr).ToList();
EmailAddress1Input.Text = emailAddrs.Count > 0 ? emailAddrs[0].Address : "";
EmailAddress2Input.Text = emailAddrs.Count > 1 ? emailAddrs[1].Address : "";
for (int i = 0; i< EmailAddressInputs.Length; i++) {
if (i < emailAddrs.Count) {
var emailAddr = emailAddrs[i];
SetEmailAddressInput(i, emailAddr.Address);
} else {
SetEmailAddressInput(i, null);
}
}
var phoneNrs = m.TelephoneNumbers.OrderBy(p => p.Nr).ToList();
for (int i = 0; i < PhoneNrInputs.Length; i++) {
@@ -599,7 +747,7 @@ namespace Elwig.Windows {
SetPhoneNrInput(i, null, null, null);
}
}
UpdatePhoneNrInputVisibility(IsEditing || IsCreating);
UpdateContactInfoVisibility(IsEditing || IsCreating);
IbanInput.Text = m.Iban;
BicInput.Text = m.Bic;
@@ -695,9 +843,14 @@ namespace Elwig.Windows {
InputLostFocus((TextBox)sender, Validator.CheckPredecessorMgNr);
}
private new void EmailAddressInput_TextChanged(object sender, TextChangedEventArgs evt) {
base.EmailAddressInput_TextChanged(sender, evt);
UpdateContactInfoVisibility(IsEditing || IsCreating);
}
private new void PhoneNrInput_TextChanged(object sender, TextChangedEventArgs evt) {
base.PhoneNrInput_TextChanged(sender, evt);
UpdatePhoneNrInputVisibility(IsEditing || IsCreating);
UpdateContactInfoVisibility(IsEditing || IsCreating);
}
private void KgDetailsButton_Click(object sender, RoutedEventArgs evt) {
@@ -707,5 +860,17 @@ namespace Elwig.Windows {
App.FocusOriginHierarchy();
}
}
private void OrganicButton_Click(object sender, RoutedEventArgs evt) {
if (MemberList.SelectedItem is not Member m)
return;
var url = "https://www.easy-cert.com/htm/suchergebnis.htm?" +
//$"CustomerNumber={m.LfbisNr}&" +
$"Name={(m.BillingAddress?.Name ?? m.Name).Replace(' ', '+')}&" +
$"PostalCode={(m.BillingAddress?.PostalDest ?? m.PostalDest).AtPlz?.Plz}";
Process.Start(new ProcessStartInfo(url) {
UseShellExecute = true,
});
}
}
}
-22
View File
@@ -1,22 +0,0 @@
<Window x:Class="Elwig.Windows.MemberListWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Elwig.Windows"
mc:Ignorable="d"
Title="Mitgliederliste - Elwig" Height="450" Width="800">
<Grid>
<DataGrid x:Name="MemberList" AutoGenerateColumns="False" HeadersVisibility="Column" IsReadOnly="True" GridLinesVisibility="None" SelectionMode="Single"
CanUserDeleteRows="False" CanUserResizeRows="False" CanUserAddRows="False" FontSize="14">
<DataGrid.Columns>
<DataGridTextColumn Header="MgNr." Binding="{Binding MgNr}" Width="70"/>
<DataGridTextColumn Header="Präfix" Binding="{Binding Prefix}" Width="100"/>
<DataGridTextColumn Header="Vorname" Binding="{Binding GivenName}" Width="100"/>
<DataGridTextColumn Header="Weitere Namen" Binding="{Binding MiddleName}" Width="100"/>
<DataGridTextColumn Header="Nachname" Binding="{Binding FamilyName}" Width="100"/>
<DataGridTextColumn Header="Suffix" Binding="{Binding Suffix}" Width="100"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
-27
View File
@@ -1,27 +0,0 @@
using Elwig.Helpers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
namespace Elwig.Windows {
public partial class MemberListWindow : Window {
private readonly AppDbContext Context = new();
public MemberListWindow() {
InitializeComponent();
MemberList.ItemsSource = Context.Members.ToList();
}
}
}
+147 -43
View File
@@ -6,7 +6,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Elwig.Windows"
mc:Ignorable="d"
Title="Auszahlungsvarianten - Elwig" Height="450" Width="800" MinHeight="400" MinWidth="700">
Title="Auszahlungsvarianten - Elwig" Height="510" Width="820" MinHeight="500" MinWidth="820">
<Window.Resources>
<Style TargetType="Label">
<Setter Property="HorizontalAlignment" Value="Left"/>
@@ -41,8 +41,8 @@
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="200"/>
<RowDefinition Height="*"/>
<RowDefinition Height="220"/>
</Grid.RowDefinitions>
<ListBox x:Name="PaymentVariantList" Margin="10,10,35,10" Grid.RowSpan="2" SelectionChanged="PaymentVariantList_SelectionChanged">
@@ -55,56 +55,160 @@
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button x:Name="AddButton" Content="&#xF8AA;" FontFamily="Segoe MDL2 Assets" FontSize="11" Padding="0,1.5,0,0"
Click="AddButton_Click"
VerticalAlignment="Center" HorizontalAlignment="Right" Width="25" Height="25" Margin="5,0,5,60" Grid.RowSpan="2"/>
<Button x:Name="CopyButton" Content="&#xE8C8;" FontFamily="Segoe MDL2 Assets" FontSize="12" Padding="0,0,0,0" IsEnabled="False"
Click="CopyButton_Click"
VerticalAlignment="Center" HorizontalAlignment="Right" Width="25" Height="25" Margin="5,0,5,0" Grid.RowSpan="2"/>
<Button x:Name="DeleteButton" Content="&#xF8AB;" FontFamily="Segoe MDL2 Assets" FontSize="11" Padding="0,1.5,0,0" IsEnabled="False"
Click="DeleteButton_Click"
VerticalAlignment="Center" HorizontalAlignment="Right" Width="25" Height="25" Margin="5,60,5,0" Grid.RowSpan="2"/>
<Button x:Name="AddButton" Content="&#xF8AA;" FontFamily="Segoe MDL2 Assets" FontSize="11" Padding="0,1.5,0,0" ToolTip="Neue Auszahlungsvariante hinzufügen"
VerticalAlignment="Center" HorizontalAlignment="Right" Width="25" Height="25" Margin="5,0,5,60" Grid.RowSpan="2"
Click="AddButton_Click"/>
<Button x:Name="CopyButton" Content="&#xE8C8;" FontFamily="Segoe MDL2 Assets" FontSize="12" Padding="0,0,0,0" IsEnabled="False" ToolTip="Ausgewählte Auszahlungsvariante duplizieren"
VerticalAlignment="Center" HorizontalAlignment="Right" Width="25" Height="25" Margin="5,0,5,0" Grid.RowSpan="2"
Click="CopyButton_Click"/>
<Button x:Name="DeleteButton" Content="&#xF8AB;" FontFamily="Segoe MDL2 Assets" FontSize="11" Padding="0,1.5,0,0" IsEnabled="False" ToolTip="Ausgewählte Auszahlungsvariante löschen"
VerticalAlignment="Center" HorizontalAlignment="Right" Width="25" Height="25" Margin="5,60,5,0" Grid.RowSpan="2"
Click="DeleteButton_Click"/>
<TextBox x:Name="DataInput" Margin="10,200,35,10" Grid.Column="0" Grid.RowSpan="2"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Height="auto"
AcceptsReturn="True" VerticalScrollBarVisibility="Visible" HorizontalScrollBarVisibility="Auto"
FontFamily="Cascadia Code Light" FontSize="13"
TextChanged="DataInput_TextChanged"/>
<Label Content="Name:" Margin="10,10,0,0" Grid.Column="1"/>
<TextBox x:Name="NameInput" Width="200" Grid.Column="2" HorizontalAlignment="Left" Margin="0,10,0,0"/>
<TextBox x:Name="NameInput" Width="200" Grid.Column="2" HorizontalAlignment="Left" Margin="0,10,0,0"
TextChanged="NameInput_TextChanged"/>
<Label Content="Beschreibung:" Margin="10,40,0,0" Grid.Column="1"/>
<TextBox x:Name="CommentInput" Grid.Column="2" HorizontalAlignment="Stretch" Margin="0,40,10,0"/>
<TextBox x:Name="CommentInput" Grid.Column="2" HorizontalAlignment="Stretch" Margin="0,40,10,0"
TextChanged="CommentInput_TextChanged"/>
<Label Content="Erstellt am:" Margin="10,70,0,0" Grid.Column="1"/>
<TextBox x:Name="DateInput" Grid.Column="2" Width="77" HorizontalAlignment="Left" Margin="0,70,10,0"/>
<TextBox x:Name="DateInput" Grid.Column="2" Width="77" HorizontalAlignment="Left" Margin="0,70,10,0" IsReadOnly="True"/>
<Label Content="Überwiesen am:" Margin="10,100,0,0" Grid.Column="1"/>
<TextBox x:Name="TransferDateInput" Grid.Column="2" Width="77" HorizontalAlignment="Left" Margin="0,100,10,0"/>
<Button x:Name="CalculateButton" Content="Berechnen" Grid.Column="1" Grid.ColumnSpan="2"
Click="CalculateButton_Click"
VerticalAlignment="Bottom" HorizontalAlignment="Right" Width="100" Margin="10,10,115,10"/>
<Button x:Name="EditButton" Content="Bearbeiten" Grid.Column="1" Grid.ColumnSpan="2"
Click="EditButton_Click"
VerticalAlignment="Bottom" HorizontalAlignment="Right" Width="100" Margin="10,10,10,10"/>
<TextBox x:Name="TransferDateInput" Grid.Column="2" Width="77" HorizontalAlignment="Left" Margin="0,100,10,0"
TextChanged="TransferDateInput_TextChanged"/>
<GroupBox Header="Abschluss" Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2" Margin="10,0,10,10">
<Grid>
<Button x:Name="CommitButton" FontSize="12" Width="160" Height="40" Margin="0,0,10,55"
Click="CommitButton_Click"
VerticalAlignment="Bottom" HorizontalAlignment="Right">
<TextBlock TextAlignment="Center">Traubengutschriften<LineBreak/>erstellen</TextBlock>
</Button>
<Button x:Name="RevertButton" FontSize="12" Width="160" Height="40" Margin="0,0,10,10"
Click="RevertButton_Click"
VerticalAlignment="Bottom" HorizontalAlignment="Right">
<TextBlock TextAlignment="Center">Traubengutschriften<LineBreak/>löschen</TextBlock>
</Button>
<Label Content="Berücksichtigen:" Margin="90,70,10,10" Grid.Column="2"/>
<CheckBox x:Name="ConsiderModifiersInput" Content="Zu-/Abschläge bei Lieferungen"
Margin="110,95,10,10" Grid.Column="2" HorizontalAlignment="Left" VerticalAlignment="Top"
Checked="ConsiderModifiersInput_Changed" Unchecked="ConsiderModifiersInput_Changed"/>
<CheckBox x:Name="ConsiderPenaltiesInput" Content="Pönalen bei Unterlieferungen (FB)"
Margin="110,115,10,10" Grid.Column="2" HorizontalAlignment="Left" VerticalAlignment="Top"
Checked="ConsiderPenaltiesInput_Changed" Unchecked="ConsiderPenaltiesInput_Changed"/>
<CheckBox x:Name="ConsiderPenaltyInput" Content="Strafen bei Unterlieferungen (GA)"
Margin="110,135,10,10" Grid.Column="2" HorizontalAlignment="Left" VerticalAlignment="Top"
Checked="ConsiderPenaltyInput_Changed" Unchecked="ConsiderPenaltyInput_Changed"/>
<CheckBox x:Name="ConsiderAutoInput" Content="Automatische Nachzeichnungen der GA"
Margin="110,155,10,10" Grid.Column="2" HorizontalAlignment="Left" VerticalAlignment="Top"
Checked="ConsiderAutoInput_Changed" Unchecked="ConsiderAutoInput_Changed"/>
<Label Content="&#xF0AE;" FontFamily="Segoe MDL2 Assets" FontSize="16" Grid.Row="0" Grid.Column="2" Margin="108,175,10,10"/>
<ProgressBar x:Name="ProgressBar" Margin="10,0,0,74" Height="27" Width="180"
VerticalAlignment="Bottom" HorizontalAlignment="Left"/>
<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="180" Margin="10,10,10,10" Height="27" Tag="Print" IsEnabled="False"
Click="PrintButton_Click"
VerticalAlignment="Bottom" HorizontalAlignment="Left"/>
</Grid>
</GroupBox>
<Grid Grid.Column="1" Grid.ColumnSpan="2" VerticalAlignment="Bottom" HorizontalAlignment="Center" Margin="10,10,10,10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="110"/>
<ColumnDefinition Width="27"/>
<ColumnDefinition Width="110"/>
<ColumnDefinition Width="27"/>
<ColumnDefinition Width="110"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="27"/>
<RowDefinition Height="5"/>
<RowDefinition Height="27"/>
</Grid.RowDefinitions>
<Grid.Resources>
<Style TargetType="Label">
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Padding" Value="0"/>
<Setter Property="Height" Value="auto"/>
</Style>
<Style TargetType="Button">
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="FontSize" Value="14"/>
</Style>
</Grid.Resources>
<Button x:Name="ModifierButton" Content="Zu-/Abschläge" Grid.Column="0" Grid.Row="0"
Click="ModifierButton_Click"/>
<Label Content="&#xF0AF;" FontFamily="Segoe MDL2 Assets" FontSize="16" Grid.Row="0" Grid.Column="1" RenderTransformOrigin="0.5,0.5" >
<Label.RenderTransform>
<TransformGroup>
<RotateTransform Angle="45"/>
<TranslateTransform Y="5"/>
</TransformGroup>
</Label.RenderTransform>
</Label>
<Button x:Name="EditButton" Content="Bearbeiten" Grid.Column="0" Grid.Row="2"
Click="EditButton_Click"/>
<Label Content="&#xF0AF;" FontFamily="Segoe MDL2 Assets" FontSize="16" Grid.Row="2" Grid.Column="1"/>
<Button x:Name="CalculateButton" Content="Berechnen" Grid.Column="2" Grid.Row="2"
Click="CalculateButton_Click"/>
<Label Content="&#xF0AF;" FontFamily="Segoe MDL2 Assets" FontSize="16" Grid.Row="2" Grid.Column="3" x:Name="Arrow3"/>
<Button x:Name="CommitButton" Content="Festsetzen" Grid.Column="4" Grid.Row="2"
Click="CommitButton_Click"/>
<Button x:Name="RevertButton" Content="Freigeben" Grid.Column="4" Grid.Row="2"
Click="RevertButton_Click"/>
<Button x:Name="SaveButton" Content="Speichern" Grid.Column="4" Grid.Row="0"
Click="SaveButton_Click"/>
</Grid>
<Grid Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="250"/>
</Grid.ColumnDefinitions>
<GroupBox Header="Abschluss" Margin="10,0,5,10">
<Grid>
<Button x:Name="ExportButton" Content="Exportieren" FontSize="14" Width="180" Margin="10,10,10,10" Height="27" IsEnabled="False"
Click="ExportButton_Click"
VerticalAlignment="Top" HorizontalAlignment="Left"/>
<Button x:Name="TransactionButton" Content="Buchungsliste" FontSize="14" Width="180" Margin="10,42,10,10" Height="27" IsEnabled="False"
Click="TransactionButton_Click"
VerticalAlignment="Top" HorizontalAlignment="Left"/>
<ProgressBar x:Name="ProgressBar" Margin="10,10,10,74" Height="27" Width="180"
VerticalAlignment="Bottom" HorizontalAlignment="Left"/>
<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="180" Margin="10,10,10,10" Height="27" Tag="Print" IsEnabled="False"
Click="PrintButton_Click"
VerticalAlignment="Bottom" HorizontalAlignment="Left"/>
</Grid>
</GroupBox>
<GroupBox Header="Ergebnis" Margin="5,0,10,10" Grid.Column="1">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.Resources>
<Style TargetType="TextBlock">
<Setter Property="Grid.Column" Value="1"/>
<Setter Property="VerticalAlignment" Value="Top"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="TextAlignment" Value="Right"/>
</Style>
</Grid.Resources>
<Label Content="Zu-/Abschläge:" Margin="10,10,0,0"/>
<TextBlock x:Name="ModifierSum" Margin="0,12,10,0"/>
<Label Content="Gesamtbeträge:" Margin="10,40,0,0"/>
<TextBlock x:Name="TotalSum" Margin="0,42,10,0"/>
<Label Content="Mehrwertsteuer:" Margin="10,70,0,0"/>
<TextBlock x:Name="VatSum" Margin="0,72,10,0"/>
<Label Content="Abzüge:" Margin="10,100,0,0"/>
<TextBlock x:Name="DeductionSum" Margin="0,102,10,0"/>
<Label Content="Auszuzahlen:" Margin="10,130,0,0"/>
<TextBlock x:Name="PaymentSum" Margin="0,132,10,0"/>
</Grid>
</GroupBox>
</Grid>
</Grid>
</local:ContextWindow>
+402 -16
View File
@@ -11,16 +11,28 @@ using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using Elwig.Helpers.Billing;
using Elwig.Helpers.Export;
using Microsoft.Win32;
using System.Text.Json;
namespace Elwig.Windows {
public partial class PaymentVariantsWindow : ContextWindow {
public readonly int Year;
public readonly bool SeasonLocked;
private bool DataValid, DataChanged, NameChanged, CommentChanged, TransferDateValid, TransferDateChanged;
private BillingData? BillingData;
private static readonly JsonSerializerOptions JsonOpt = new() { WriteIndented = true };
public PaymentVariantsWindow(int year) {
InitializeComponent();
Year = year;
SeasonLocked = Context.Seasons.Find(Year + 1) != null;
Title = $"Auszahlungsvarianten - Lese {Year} - Elwig";
if (!App.Config.Debug) {
DataInput.Visibility = Visibility.Hidden;
}
}
protected override async Task OnRenewContext() {
@@ -33,27 +45,121 @@ namespace Elwig.Windows {
var locked = !v.TestVariant;
DeleteButton.IsEnabled = !locked;
CalculateButton.IsEnabled = !locked;
CommitButton.IsEnabled = !locked;
RevertButton.IsEnabled = locked;
CommitButton.IsEnabled = !locked && !SeasonLocked;
CommitButton.Visibility = !locked ? Visibility.Visible : Visibility.Hidden;
RevertButton.IsEnabled = locked && !SeasonLocked;
RevertButton.Visibility = locked ? Visibility.Visible : Visibility.Hidden;
Arrow3.Content = locked ? "\xF0B0" : "\xF0AF";
CopyButton.IsEnabled = true;
EditButton.Content = locked ? "Ansehen" : "Bearbeiten";
EditButton.IsEnabled = true;
SaveButton.IsEnabled = !locked;
ShowButton.IsEnabled = true;
PrintButton.IsEnabled = true;
ExportButton.IsEnabled = locked;
TransactionButton.IsEnabled = locked;
NameInput.Text = v.Name;
NameInput.IsReadOnly = false;
CommentInput.Text = v.Comment;
CommentInput.IsReadOnly = false;
DateInput.Text = $"{v.Date:dd.MM.yyyy}";
DateInput.IsReadOnly = false;
TransferDateInput.Text = $"{v.TransferDate:dd.MM.yyyy}";
TransferDateInput.IsReadOnly = false;
try {
BillingData = BillingData.FromJson(v.Data);
ConsiderModifiersInput.IsChecked = BillingData.ConsiderDelieryModifiers;
ConsiderPenaltiesInput.IsChecked = BillingData.ConsiderContractPenalties;
ConsiderPenaltyInput.IsChecked = BillingData.ConsiderTotalPenalty;
ConsiderAutoInput.IsChecked = BillingData.ConsiderAutoBusinessShares;
DataInput.Text = JsonSerializer.Serialize(BillingData.Data, JsonOpt);
} catch {
BillingData = null;
ConsiderModifiersInput.IsChecked = false;
ConsiderPenaltiesInput.IsChecked = false;
ConsiderPenaltyInput.IsChecked = false;
ConsiderAutoInput.IsChecked = false;
DataInput.Text = v.Data;
}
ConsiderModifiersInput.IsEnabled = !locked;
ConsiderPenaltiesInput.IsEnabled = !locked;
ConsiderPenaltyInput.IsEnabled = !locked;
ConsiderAutoInput.IsEnabled = !locked;
DataInput.IsReadOnly = locked;
} else {
EditButton.Content = "Bearbeiten";
EditButton.IsEnabled = false;
SaveButton.IsEnabled = false;
CopyButton.IsEnabled = false;
CalculateButton.IsEnabled = false;
CommitButton.IsEnabled = false;
CommitButton.Visibility = Visibility.Visible;
RevertButton.IsEnabled = false;
RevertButton.Visibility = Visibility.Hidden;
Arrow3.Content = "\xF0AF";
DeleteButton.IsEnabled = false;
ShowButton.IsEnabled = false;
PrintButton.IsEnabled = false;
ExportButton.IsEnabled = false;
TransactionButton.IsEnabled = false;
BillingData = null;
NameInput.Text = "";
NameInput.IsReadOnly = true;
CommentInput.Text = "";
CommentInput.IsReadOnly = true;
DateInput.Text = "";
DateInput.IsReadOnly = true;
TransferDateInput.Text = "";
TransferDateInput.IsReadOnly = true;
ConsiderModifiersInput.IsChecked = false;
ConsiderModifiersInput.IsEnabled = false;
ConsiderPenaltiesInput.IsChecked = false;
ConsiderPenaltiesInput.IsEnabled = false;
ConsiderPenaltyInput.IsChecked = false;
ConsiderPenaltyInput.IsEnabled = false;
ConsiderAutoInput.IsChecked = false;
ConsiderAutoInput.IsEnabled = false;
DataInput.Text = "";
DataInput.IsReadOnly = true;
}
UpdateSums();
UpdateSaveButton();
}
private void UpdateSaveButton() {
SaveButton.IsEnabled = PaymentVariantList.SelectedItem != null &&
((DataChanged && DataValid) || NameChanged || CommentChanged ||
(TransferDateChanged && TransferDateValid) ||
(ConsiderModifiersInput.IsChecked != BillingData?.ConsiderDelieryModifiers) ||
(ConsiderPenaltiesInput.IsChecked != BillingData?.ConsiderContractPenalties) ||
(ConsiderPenaltyInput.IsChecked != BillingData?.ConsiderTotalPenalty) ||
(ConsiderAutoInput.IsChecked != BillingData?.ConsiderAutoBusinessShares));
CalculateButton.IsEnabled = !SaveButton.IsEnabled && PaymentVariantList.SelectedItem is PaymentVar { TestVariant: true };
CommitButton.IsEnabled = CalculateButton.IsEnabled;
}
private void UpdateSums() {
if (PaymentVariantList.SelectedItem is PaymentVar v) {
var sym = v.Season.Currency.Symbol;
ModifierSum.Text = $"{v.DeliveryPartPayments.Sum(p => p.Amount - p.NetAmount):N2} {sym}";
TotalSum.Text = $"{v.MemberPayments.Sum(p => p.Amount):N2} {sym}";
if (v.Credits.Count == 0) {
VatSum.Text = $"- {sym}";
DeductionSum.Text = $"- {sym}";
PaymentSum.Text = $"- {sym}";
} else {
VatSum.Text = $"{v.Credits.Sum(c => c.VatAmount):N2} {sym}";
DeductionSum.Text = $"{-v.Credits.Sum(c => c.Modifiers ?? 0):N2} {sym}";
PaymentSum.Text = $"{v.Credits.Sum(c => c.Amount):N2} {sym}";
}
} else {
ModifierSum.Text = "-";
TotalSum.Text = "-";
VatSum.Text = "-";
DeductionSum.Text = "-";
PaymentSum.Text = "-";
}
}
@@ -61,16 +167,64 @@ namespace Elwig.Windows {
Update();
}
private void AddButton_Click(object sender, RoutedEventArgs evt) {
private async void AddButton_Click(object sender, RoutedEventArgs evt) {
try {
PaymentVar v = Context.CreateProxy<PaymentVar>();
v.Year = Year;
v.AvNr = await Context.NextAvNr(Year);
v.Name = "Neue Auszahlungsvariante";
v.TestVariant = true;
v.DateString = $"{DateTime.Today:yyyy-MM-dd}";
v.Data = "{\"mode\": \"elwig\", \"version\": 1, \"payment\": {}, \"curves\": []}";
await Context.AddAsync(v);
await Context.SaveChangesAsync();
await App.HintContextChange();
ControlUtils.SelectListBoxItem(PaymentVariantList, v, v => (v as PaymentVar)?.AvNr);
} catch (Exception exc) {
var str = "Der Eintrag konnte nicht in der Datenbank aktualisiert werden!\n\n" + exc.Message;
if (exc.InnerException != null) str += "\n\n" + exc.InnerException.Message;
MessageBox.Show(str, "Auszahlungsvariante erstellen", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void CopyButton_Click(object sender, RoutedEventArgs evt) {
private async void CopyButton_Click(object sender, RoutedEventArgs evt) {
if (PaymentVariantList.SelectedItem is not PaymentVar orig) return;
try {
PaymentVar n = Context.CreateProxy<PaymentVar>();
n.Year = orig.Year;
n.AvNr = await Context.NextAvNr(Year);
n.Name = $"{orig.Name} (Kopie)";
n.TestVariant = true;
n.DateString = $"{DateTime.Today:yyyy-MM-dd}";
n.Data = orig.Data;
await Context.AddAsync(n);
await Context.SaveChangesAsync();
await App.HintContextChange();
ControlUtils.SelectListBoxItem(PaymentVariantList, n, v => (v as PaymentVar)?.AvNr);
} catch (Exception exc) {
var str = "Der Eintrag konnte nicht in der Datenbank aktualisiert werden!\n\n" + exc.Message;
if (exc.InnerException != null) str += "\n\n" + exc.InnerException.Message;
MessageBox.Show(str, "Auszahlungsvariante kopieren", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void DeleteButton_Click(object sender, RoutedEventArgs evt) {
private async void DeleteButton_Click(object sender, RoutedEventArgs evt) {
if (PaymentVariantList.SelectedItem is not PaymentVar v || !v.TestVariant) return;
try {
Context.Remove(v);
await Context.SaveChangesAsync();
await HintContextChange();
} catch (Exception exc) {
var str = "Der Eintrag konnte nicht in der Datenbank aktualisiert werden!\n\n" + exc.Message;
if (exc.InnerException != null) str += "\n\n" + exc.InnerException.Message;
MessageBox.Show(str, "Auszahlungsvariante löschen", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private async void CalculateButton_Click(object sender, RoutedEventArgs evt) {
@@ -78,8 +232,13 @@ namespace Elwig.Windows {
return;
CalculateButton.IsEnabled = false;
Mouse.OverrideCursor = Cursors.AppStarting;
var b = new BillingVariant(v.Year, v.AvNr);
await b.Calculate();
try {
var b = new BillingVariant(v.Year, v.AvNr);
await b.Calculate();
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Berechnungsfehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
await App.HintContextChange();
Mouse.OverrideCursor = null;
CalculateButton.IsEnabled = true;
}
@@ -98,28 +257,247 @@ namespace Elwig.Windows {
await Generate(2);
}
private async void CommitButton_Click(object sender, RoutedEventArgs e) {
private async void CommitButton_Click(object sender, RoutedEventArgs evt) {
if (PaymentVariantList.SelectedValue is not PaymentVar v)
return;
CommitButton.IsEnabled = false;
Mouse.OverrideCursor = Cursors.AppStarting;
var b = new BillingVariant(v.Year, v.AvNr);
await b.Commit();
try {
var b = new BillingVariant(v.Year, v.AvNr);
await b.Commit();
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
Mouse.OverrideCursor = null;
RevertButton.IsEnabled = true;
await HintContextChange();
await App.HintContextChange();
}
private async void RevertButton_Click(object sender, RoutedEventArgs e) {
private async void RevertButton_Click(object sender, RoutedEventArgs evt) {
if (PaymentVariantList.SelectedValue is not PaymentVar v)
return;
var res = MessageBox.Show(
"Sollen wirklich alle festgesetzten Traubengutschriften der ausgewählten Auszahlungsvariante unwiderruflich gelöscht werden?\n\n" +
"Dies ist im Allgemeinen nie empfohlen. Handelt es sich um die aktuellste Auszahlungsvariante könnte das eine Ausnahme sein.",
"Traubengutschriften löschen", MessageBoxButton.YesNo, MessageBoxImage.Warning, MessageBoxResult.No);
if (res != MessageBoxResult.Yes)
return;
RevertButton.IsEnabled = false;
Mouse.OverrideCursor = Cursors.AppStarting;
var b = new BillingVariant(v.Year, v.AvNr);
await b.Revert();
await App.HintContextChange();
Mouse.OverrideCursor = null;
CommitButton.IsEnabled = true;
await HintContextChange();
}
private async void ExportButton_Click(object sender, RoutedEventArgs evt) {
if (PaymentVariantList.SelectedValue is not PaymentVar v) {
return;
} else if (v.TransferDate == null) {
MessageBox.Show("Überweisungsdatum muss gesetzt sein!", "Exportieren nicht möglich", MessageBoxButton.OK, MessageBoxImage.Error);
return;
}
var d = new SaveFileDialog() {
FileName = $"{App.Client.NameToken}-Überweisungsdaten-{v.Year}-{v.Name.Trim().Replace(' ', '-')}.{Ebics.FileExtension}",
DefaultExt = Ebics.FileExtension,
Filter = "EBICS-Datei (*.xml)|*.xml",
Title = $"Überweisungsdaten speichern unter - Elwig",
};
if (d.ShowDialog() == true) {
ExportButton.IsEnabled = false;
Mouse.OverrideCursor = Cursors.AppStarting;
using var e = new Ebics(v, d.FileName);
await e.ExportAsync(Transaction.FromPaymentVariant(v));
Mouse.OverrideCursor = null;
ExportButton.IsEnabled = true;
}
}
private async void TransactionButton_Click(object sender, RoutedEventArgs evt) {
if (PaymentVariantList.SelectedValue is not PaymentVar v) {
return;
}
var d = new SaveFileDialog() {
FileName = $"{App.Client.NameToken}-Buchungsliste-{v.Year}-{v.Name.Trim().Replace(' ', '-')}.ods",
DefaultExt = "ods",
Filter = "OpenDocument Format Spreadsheet (*.ods)|*.ods",
Title = $"Buchungsliste speichern unter - Elwig"
};
if (d.ShowDialog() == true) {
TransactionButton.IsEnabled = false;
Mouse.OverrideCursor = Cursors.AppStarting;
try {
var tbl = await CreditNoteData.ForPaymentVariant(Context, v.Year, v.AvNr);
using var ods = new OdsFile(d.FileName);
await ods.AddTable(tbl);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
Mouse.OverrideCursor = null;
TransactionButton.IsEnabled = true;
}
}
private async void SaveButton_Click(object sender, RoutedEventArgs evt) {
if (PaymentVariantList.SelectedItem is not PaymentVar v || BillingData == null) return;
try {
v.Name = NameInput.Text;
v.Comment = (CommentInput.Text != "") ? CommentInput.Text : null;
v.TransferDateString = (TransferDateInput.Text != "") ? string.Join("-", TransferDateInput.Text.Split(".").Reverse()) : null;
var d = App.Config.Debug ? BillingData.FromJson(DataInput.Text) : BillingData;
d.ConsiderDelieryModifiers = ConsiderModifiersInput.IsChecked ?? false;
d.ConsiderContractPenalties = ConsiderPenaltiesInput.IsChecked ?? false;
d.ConsiderTotalPenalty = ConsiderPenaltyInput.IsChecked ?? false;
d.ConsiderAutoBusinessShares = ConsiderAutoInput.IsChecked ?? false;
v.Data = JsonSerializer.Serialize(d.Data);
Context.Update(v);
await Context.SaveChangesAsync();
await App.HintContextChange();
CommentInput_TextChanged(null, null);
ConsiderModifiersInput_Changed(null, null);
ConsiderPenaltiesInput_Changed(null, null);
ConsiderPenaltyInput_Changed(null, null);
ConsiderAutoInput_Changed(null, null);
} catch (Exception exc) {
await HintContextChange();
var str = "Der Eintrag konnte nicht in der Datenbank aktualisiert werden!\n\n" + exc.Message;
if (exc.InnerException != null) str += "\n\n" + exc.InnerException.Message;
MessageBox.Show(str, "Auszahlungsvariante aktualisieren", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void ModifierButton_Click(object sender, RoutedEventArgs evt) {
App.FocusBaseDataSeason(Year);
}
private void NameInput_TextChanged(object sender, TextChangedEventArgs evt) {
if (PaymentVariantList.SelectedItem is not PaymentVar v) {
ControlUtils.ClearInputState(NameInput);
return;
}
if (NameInput.Text != v.Name) {
ControlUtils.SetInputChanged(NameInput);
NameChanged = true;
} else {
ControlUtils.ClearInputState(NameInput);
NameChanged = false;
}
UpdateSaveButton();
}
private void CommentInput_TextChanged(object? sender, TextChangedEventArgs? evt) {
if (PaymentVariantList.SelectedItem is not PaymentVar v) {
ControlUtils.ClearInputState(CommentInput);
return;
}
if (CommentInput.Text != (v.Comment ?? "")) {
ControlUtils.SetInputChanged(CommentInput);
CommentChanged = true;
} else {
ControlUtils.ClearInputState(CommentInput);
CommentChanged = false;
}
UpdateSaveButton();
}
private void TransferDateInput_TextChanged(object sender, TextChangedEventArgs evt) {
if (PaymentVariantList.SelectedItem is not PaymentVar v) {
ControlUtils.ClearInputState(TransferDateInput);
return;
}
var res = Validator.CheckDate(TransferDateInput, false);
if (!res.IsValid) {
ControlUtils.SetInputInvalid(TransferDateInput);
TransferDateValid = false;
} else if (TransferDateInput.Text != $"{v.TransferDate:dd.MM.yyyy}") {
ControlUtils.SetInputChanged(TransferDateInput);
TransferDateValid = true;
TransferDateChanged = true;
} else {
ControlUtils.ClearInputState(TransferDateInput);
TransferDateValid = true;
TransferDateChanged = false;
}
UpdateSaveButton();
}
private void DataInput_TextChanged(object sender, TextChangedEventArgs evt) {
if (PaymentVariantList.SelectedItem is not PaymentVar v) {
ControlUtils.ClearInputState(DataInput);
return;
}
try {
var data = BillingData.FromJson(DataInput.Text);
var origJson = v.Data;
try {
origJson = JsonSerializer.Serialize(BillingData.FromJson(v.Data).Data);
} catch { }
DataValid = true;
if (JsonSerializer.Serialize(data.Data) != origJson) {
ControlUtils.SetInputChanged(DataInput);
DataChanged = true;
} else {
ControlUtils.ClearInputState(DataInput);
DataChanged = false;
}
} catch {
ControlUtils.SetInputInvalid(DataInput);
DataValid = false;
}
UpdateSaveButton();
}
private void ConsiderModifiersInput_Changed(object? sender, RoutedEventArgs? evt) {
if (BillingData == null) {
ControlUtils.ClearInputState(ConsiderModifiersInput);
return;
}
if (BillingData.ConsiderDelieryModifiers != ConsiderModifiersInput.IsChecked) {
ControlUtils.SetInputChanged(ConsiderModifiersInput);
} else {
ControlUtils.ClearInputState(ConsiderModifiersInput);
}
UpdateSaveButton();
}
private void ConsiderPenaltiesInput_Changed(object? sender, RoutedEventArgs? evt) {
if (BillingData == null) {
ControlUtils.ClearInputState(ConsiderPenaltiesInput);
return;
}
if (BillingData.ConsiderContractPenalties != ConsiderPenaltiesInput.IsChecked) {
ControlUtils.SetInputChanged(ConsiderPenaltiesInput);
} else {
ControlUtils.ClearInputState(ConsiderPenaltiesInput);
}
UpdateSaveButton();
}
private void ConsiderPenaltyInput_Changed(object? sender, RoutedEventArgs? evt) {
if (BillingData == null) {
ControlUtils.ClearInputState(ConsiderPenaltyInput);
return;
}
if (BillingData.ConsiderTotalPenalty != ConsiderPenaltyInput.IsChecked) {
ControlUtils.SetInputChanged(ConsiderPenaltyInput);
} else {
ControlUtils.ClearInputState(ConsiderPenaltyInput);
}
UpdateSaveButton();
}
private void ConsiderAutoInput_Changed(object? sender, RoutedEventArgs? evt) {
if (BillingData == null) {
ControlUtils.ClearInputState(ConsiderAutoInput);
return;
}
if (BillingData.ConsiderAutoBusinessShares != ConsiderAutoInput.IsChecked) {
ControlUtils.SetInputChanged(ConsiderAutoInput);
} else {
ControlUtils.ClearInputState(ConsiderAutoInput);
}
UpdateSaveButton();
}
private async Task Generate(int mode) {
@@ -138,11 +516,19 @@ namespace Elwig.Windows {
members = members.OrderBy(m => m.MgNr);
IEnumerable<Member> list = await members.ToListAsync();
var data = await CreditNoteData.ForPaymentVariant(Context.CreditNoteRows, Context.Seasons, v.Year, v.AvNr);
var data = await CreditNoteDeliveryData.ForPaymentVariant(Context.CreditNoteDeliveryRows, Context.Seasons, v.Year, v.AvNr);
var payments = await Context.MemberPayments.Where(p => p.Year == v.Year && p.AvNr == v.AvNr).ToDictionaryAsync(c => c.MgNr);
await Context.GetMemberAreaCommitmentBuckets(Year, 0);
using var doc = Document.Merge(list.Select(m =>
new CreditNote(Context, payments[m.MgNr], data[m.MgNr], Context.GetMemberUnderDelivery(Year, m.MgNr).GetAwaiter().GetResult())
new CreditNote(
Context,
payments[m.MgNr],
data[m.MgNr],
BillingData?.ConsiderContractPenalties ?? false,
BillingData?.ConsiderTotalPenalty ?? false,
BillingData?.ConsiderAutoBusinessShares ?? false,
Context.GetMemberUnderDelivery(Year, m.MgNr).GetAwaiter().GetResult()
)
));
await doc.Generate(new Progress<double>(v => {
ProgressBar.Value = v;
+7 -3
View File
@@ -37,14 +37,18 @@
<Button x:Name="DeliveryConfirmationButton" Content="Anlieferungsbestätigungen"
Click="DeliveryConfirmationButton_Click"
Margin="50,122,0,0"/>
Margin="50,120,0,0"/>
<Button x:Name="OverUnderDeliveryButton" Content="Über-/Unterlieferungen"
Click="OverUnderDeliveryButton_Click"
Margin="50,164,0,0"/>
Margin="50,160,0,0"/>
<Button x:Name="AutoBusinessSharesButton" Content="Autom. GA nachzeichen"
Click="AutoBusinessSharesButton_Click"
Margin="50,200,0,0"/>
<Button x:Name="PaymentButton" Content="Auszahlung"
Click="PaymentButton_Click"
Margin="50,206,0,0"/>
Margin="50,240,0,0"/>
</Grid>
</local:ContextWindow>
+42 -5
View File
@@ -1,8 +1,8 @@
using Elwig.Dialogs;
using Elwig.Helpers;
using Elwig.Helpers.Billing;
using Elwig.Helpers.Export;
using Elwig.Models.Dtos;
using Elwig.Models.Entities;
using Microsoft.Win32;
using System;
using System.Threading.Tasks;
@@ -31,20 +31,36 @@ namespace Elwig.Windows {
CalculateBucketsButton.IsEnabled = valid && last;
DeliveryConfirmationButton.IsEnabled = valid;
OverUnderDeliveryButton.IsEnabled = valid;
AutoBusinessSharesButton.IsEnabled = valid;
PaymentButton.IsEnabled = valid;
AllowAttrIntoLowerInput.IsEnabled = valid && last;
AvoidUnderDeliveriesInput.IsEnabled = valid && last;
HonorGebundenInput.IsEnabled = valid && last;
AllowAttrIntoLowerInput.IsChecked = s0?.Billing_AllowAttrsIntoLower;
AvoidUnderDeliveriesInput.IsChecked = s0?.Billing_AvoidUnderDeliveries;
HonorGebundenInput.IsChecked = s0?.Billing_HonorGebunden;
}
private async void CalculateBucketsButton_Click(object sender, RoutedEventArgs evt) {
if (SeasonInput.Value is not int year)
if (SeasonInput.Value is not int year || await Context.Seasons.FindAsync(year) is not Season s)
return;
CalculateBucketsButton.IsEnabled = false;
Mouse.OverrideCursor = Cursors.AppStarting;
try {
s.Billing_AllowAttrsIntoLower = AllowAttrIntoLowerInput.IsChecked ?? false;
s.Billing_AvoidUnderDeliveries = AvoidUnderDeliveriesInput.IsChecked ?? false;
s.Billing_HonorGebunden = HonorGebundenInput.IsChecked ?? false;
Context.Update(s);
await Context.SaveChangesAsync();
} catch { }
var b = new Billing(year);
await b.FinishSeason();
await b.CalculateBuckets(
AllowAttrIntoLowerInput.IsChecked ?? false,
AvoidUnderDeliveriesInput.IsChecked ?? false,
HonorGebundenInput.IsChecked ?? false);
s.Billing_AllowAttrsIntoLower,
s.Billing_AvoidUnderDeliveries,
s.Billing_HonorGebunden);
Mouse.OverrideCursor = null;
CalculateBucketsButton.IsEnabled = true;
}
@@ -82,6 +98,27 @@ namespace Elwig.Windows {
Mouse.OverrideCursor = null;
}
private async void AutoBusinessSharesButton_Click(object sender, RoutedEventArgs evt) {
if (SeasonInput.Value is not int year)
return;
if (false && App.Client.IsMatzen) {
AutoBusinessSharesButton.IsEnabled = false;
Mouse.OverrideCursor = Cursors.AppStarting;
var b = new Billing(year);
await b.AutoAdjustBusinessShare();
Mouse.OverrideCursor = null;
AutoBusinessSharesButton.IsEnabled = true;
} else {
MessageBox.Show(
"Es ist kein automatisches Nachzeichnen der Geschäftsanteile\n" +
"für diese Genossenschaft eingestellt!\n" +
"Bitte wenden Sie sich an die Programmierer!", "Fehler",
MessageBoxButton.OK, MessageBoxImage.Information);
}
}
private void PaymentButton_Click(object sender, RoutedEventArgs evt) {
if (SeasonInput.Value is not int year)
return;
+1 -1
View File
@@ -48,7 +48,7 @@ namespace Elwig.Windows {
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, ctx.Members.Find(2948)));
await ods.AddTable(await DeliveryConfirmationDeliveryData.ForMember(ctx.DeliveryParts, 2023, ctx.Members.Find(2948)));
}
}
}
+1 -1
View File
@@ -1,5 +1,5 @@
::mkdir "C:\Program Files\Elwig"
::curl -s "http://www.columbia.edu/~em36/PDFtoPrinter.exe" -z "C:\Program Files\Elwig\PDFtoPrinter.exe" -o "C:\Program Files\Elwig\PDFtoPrinter.exe"
::curl -s -L "http://www.columbia.edu/~em36/PDFtoPrinter.exe" -z "C:\Program Files\Elwig\PDFtoPrinter.exe" -o "C:\Program Files\Elwig\PDFtoPrinter.exe"
mkdir "C:\ProgramData\Elwig\resources"
copy /b /y Documents\*.css "C:\ProgramData\Elwig\resources"
copy /b /y Documents\*.cshtml "C:\ProgramData\Elwig\resources"
+3 -3
View File
@@ -25,7 +25,7 @@
</Task>
</UsingTask>
<Target Name="CustomBeforeBuild" BeforeTargets="BeforeBuild">
<Exec Command="curl -s &quot;http://www.columbia.edu/~em36/PDFtoPrinter.exe&quot; -z &quot;$(TargetDir)PDFtoPrinter.exe&quot; -o &quot;$(TargetDir)PDFtoPrinter.exe&quot;" />
<Exec Command="curl -s -L &quot;http://www.columbia.edu/~em36/PDFtoPrinter.exe&quot; -z &quot;$(TargetDir)PDFtoPrinter.exe&quot; -o &quot;$(TargetDir)PDFtoPrinter.exe&quot;" />
<Exec Command="dotnet publish &quot;$(SolutionDir)Elwig\Elwig.csproj&quot; &quot;/p:PublishProfile=$(SolutionDir)\Elwig\Properties\PublishProfiles\FolderProfile.pubxml&quot;" />
<GetFileVersion AssemblyPath="..\Elwig\bin\Publish\Elwig.exe">
<Output TaskParameter="Version" PropertyName="ElwigFileVersion" />
@@ -60,6 +60,6 @@
<None Include="Files\config.ini" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="WixToolset.Heat" Version="4.0.1" />
<PackageReference Include="WixToolset.Heat" Version="4.0.3" />
</ItemGroup>
</Project>
</Project>
+5 -5
View File
@@ -5,15 +5,15 @@
<Cultures>de-AT</Cultures>
</PropertyGroup>
<Target Name="CustomBeforeBuild" BeforeTargets="BeforeBuild">
<Exec Command='curl -L -s "https://go.microsoft.com/fwlink/p/?LinkId=2124703" -z "$(TargetDir)MicrosoftEdgeWebview2Setup.exe" -o "$(TargetDir)MicrosoftEdgeWebview2Setup.exe"' />
<Exec Command='curl -L -s "https://aka.ms/vs/17/release/vc_redist.x86.exe" -z "$(TargetDir)VC_redist.x86.exe" -o "$(TargetDir)VC_redist.x86.exe"' />
<Exec Command='curl -s -L "https://go.microsoft.com/fwlink/p/?LinkId=2124703" -z "$(TargetDir)MicrosoftEdgeWebview2Setup.exe" -o "$(TargetDir)MicrosoftEdgeWebview2Setup.exe"' />
<Exec Command='curl -s -L "https://aka.ms/vs/17/release/vc_redist.x86.exe" -z "$(TargetDir)VC_redist.x86.exe" -o "$(TargetDir)VC_redist.x86.exe"' />
<PropertyGroup>
<DefineConstants>ElwigProjectDir=..\Elwig</DefineConstants>
</PropertyGroup>
</Target>
<ItemGroup>
<ProjectReference Include="..\Installer\Installer.wixproj" />
<PackageReference Include="WixToolset.Bal.wixext" Version="4.0.1" />
<PackageReference Include="WixToolset.Util.wixext" Version="4.0.1" />
<PackageReference Include="WixToolset.Bal.wixext" Version="4.0.3" />
<PackageReference Include="WixToolset.Util.wixext" Version="4.0.3" />
</ItemGroup>
</Project>
</Project>
+34
View File
@@ -0,0 +1,34 @@
using Elwig.Helpers;
using Elwig.Helpers.Billing;
using Microsoft.Data.Sqlite;
using System.Reflection;
namespace Tests {
[SetUpFixture]
public class DatabaseSetup {
private SqliteConnection? Connection;
[OneTimeSetUp]
public async Task SetupDatabase() {
AppDbContext.ConnectionStringOverride = $"Data Source=ElwigTestDB; Mode=Memory; Foreign Keys=True; Cache=Shared";
Connection = await AppDbContext.ConnectAsync();
await AppDbContext.ExecuteEmbeddedScript(Connection, Assembly.GetExecutingAssembly(), "Tests.Resources.Create.sql");
await AppDbContext.ExecuteEmbeddedScript(Connection, Assembly.GetExecutingAssembly(), "Tests.Resources.Insert.sql");
}
[OneTimeSetUp]
public async Task SetupBillingData() {
await BillingData.Init();
}
[OneTimeTearDown]
public async Task TeardownDatabase() {
AppDbContext.ConnectionStringOverride = null;
if (Connection == null) return;
await Connection.DisposeAsync();
Connection = null;
// The in-memory database will be dropped if all connections to it are closed
}
}
}
+504
View File
@@ -0,0 +1,504 @@
using Elwig.Helpers;
using Elwig.Helpers.Billing;
using Elwig.Models.Entities;
using System.Text.Json;
namespace Tests.HelperTests {
[TestFixture]
public class BillingDataTest {
private static readonly JsonSerializerOptions JsonOpts = new() { WriteIndented = true };
private static readonly string[] Vaributes = ["GV", "GVD", "GVK", "GVS", "GVZ", "WR", "WRS", "ZW", "ZWS", "ZWZ"];
private static (string, string?) GetSortIdAttrId(string bucket) {
return (bucket[..2], bucket.Length > 2 ? bucket[2..] : null);
}
private static string GetQualId(double kmw) {
return kmw switch {
>= 17.0 => "KAB",
>= 15.0 => "QUW",
>= 14.0 => "LDW",
>= 10.6 => "RSW",
_ => "WEI",
};
}
private static void TestCalcOe(PaymentBillingData data, string bucket, double oe, decimal expected, string? qualid = null, bool geb = false) {
var (sortid, attrid) = GetSortIdAttrId(bucket);
var kmw = Utils.OeToKmw(oe);
var v = data.CalculatePrice(sortid, attrid, qualid ?? GetQualId(kmw), geb, oe, kmw);
Assert.That(Math.Round(v, 6), Is.EqualTo(expected));
}
private static void TestCalcKmw(PaymentBillingData data, string bucket, double kmw, decimal expected, string? qualid = null, bool geb = false) {
var (sortid, attrid) = GetSortIdAttrId(bucket);
var oe = Utils.KmwToOe(kmw);
var v = data.CalculatePrice(sortid, attrid, qualid ?? GetQualId(kmw), geb, oe, kmw);
Assert.That(Math.Round(v, 6), Is.EqualTo(expected));
}
[Test]
public void TestRead_01_Flatrate() {
var data = PaymentBillingData.FromJson("""
{
"mode": "elwig",
"version": 1,
"payment": 0.5,
"curves": []
}
""", Vaributes);
Assert.Multiple(() => {
TestCalcOe(data, "GV", 73, 0.5m);
TestCalcOe(data, "WRS", 74, 0.5m);
});
}
[Test]
public void TestRead_02_Simple() {
var data = PaymentBillingData.FromJson("""
{
"mode": "elwig",
"version": 1,
"payment": "curve:1",
"curves": [{
"id": 1,
"mode": "oe",
"data": {
"72.0oe": 0.25,
"15.0kmw": 0.5,
"83oe": 1
},
"geb": 0.10
}]
}
""", Vaributes);
Assert.Multiple(() => {
TestCalcOe(data, "GV", 70, 0.25m);
TestCalcOe(data, "GV", 72, 0.25m);
TestCalcOe(data, "GV", 73, 0.50m);
TestCalcOe(data, "GV", 74, 0.55m);
TestCalcOe(data, "GV", 80, 0.85m);
TestCalcOe(data, "GV", 83, 1.00m);
TestCalcOe(data, "GV", 90, 1.00m);
TestCalcOe(data, "GV", 73, 0.60m, geb: true);
TestCalcOe(data, "GV", 74, 0.65m, geb: true);
TestCalcOe(data, "GV", 80, 0.95m, geb: true);
TestCalcOe(data, "GV", 83, 1.10m, geb: true);
TestCalcOe(data, "GV", 90, 1.10m, geb: true);
});
}
[Test]
public void TestRead_03_GreaterThanAndLessThan() {
var data = PaymentBillingData.FromJson("""
{
"mode": "elwig",
"version": 1,
"payment": "curve:1",
"curves": [{
"id": 1,
"mode": "kmw",
"data": {
"<14kmw": 0.1,
"14kmw": 0.2,
"<15kmw": 0.25,
"15kmw": 0.5,
"17kmw": 1,
">17kmw": 1.25
}
}]
}
""", Vaributes);
Assert.Multiple(() => {
TestCalcKmw(data, "GV", 13.00, 0.10m);
TestCalcKmw(data, "GV", 13.50, 0.10m);
TestCalcKmw(data, "GV", 13.99, 0.10m);
TestCalcKmw(data, "GV", 14.00, 0.20m);
TestCalcKmw(data, "GV", 14.50, 0.225m);
TestCalcKmw(data, "GV", 15.00, 0.50m);
TestCalcKmw(data, "GV", 15.50, 0.625m);
TestCalcKmw(data, "GV", 16.00, 0.75m);
TestCalcKmw(data, "GV", 16.50, 0.875m);
TestCalcKmw(data, "GV", 17.00, 1.00m);
TestCalcKmw(data, "GV", 17.01, 1.25m);
TestCalcKmw(data, "GV", 17.50, 1.25m);
TestCalcKmw(data, "GV", 18.00, 1.25m);
TestCalcKmw(data, "GV", 18.50, 1.25m);
});
}
[Test]
public void TestRead_04_VariantsAndAttributes() {
var data = PaymentBillingData.FromJson("""
{
"mode": "elwig",
"version": 1,
"payment": {
"default": 0.10,
"GV/": 0.20,
"ZW": 0.25,
"/S": 0.15,
"GV/K": 0.30
},
"curves": []
}
""", Vaributes);
Assert.Multiple(() => {
TestCalcOe(data, "WR", 73, 0.10m);
TestCalcOe(data, "WRS", 73, 0.15m);
TestCalcOe(data, "GV", 73, 0.20m);
TestCalcOe(data, "GVD", 73, 0.10m);
TestCalcOe(data, "GVK", 73, 0.30m);
TestCalcOe(data, "GVS", 73, 0.15m);
TestCalcOe(data, "GVZ", 73, 0.10m);
TestCalcOe(data, "ZW", 73, 0.25m);
TestCalcOe(data, "ZWS", 73, 0.15m);
TestCalcOe(data, "ZWZ", 73, 0.25m);
});
}
[Test]
public void TestRead_05_QualityLevel() {
var data = PaymentBillingData.FromJson("""
{
"mode": "elwig",
"version": 1,
"payment": 0.5,
"quality": {
"WEI": {
"default": 0.25,
"GV": 0.3,
"/S": 0.2
}
},
"curves": []
}
""", Vaributes);
Assert.Multiple(() => {
TestCalcOe(data, "GV", 75, 0.30m, qualid: "WEI");
TestCalcOe(data, "ZW", 76, 0.25m, qualid: "WEI");
TestCalcOe(data, "GVS", 75, 0.20m, qualid: "WEI");
TestCalcOe(data, "GVK", 74, 0.30m, qualid: "WEI");
TestCalcOe(data, "ZWS", 73, 0.20m, qualid: "WEI");
TestCalcOe(data, "GV", 70, 0.5m);
TestCalcOe(data, "GV", 72, 0.5m);
TestCalcOe(data, "GV", 73, 0.5m);
TestCalcOe(data, "ZWS", 74, 0.5m);
TestCalcOe(data, "GVK", 80, 0.5m);
});
}
[Test]
public void TestRead_06_ModeOeAndKmw() {
var data = PaymentBillingData.FromJson("""
{
"mode": "elwig",
"version": 1,
"payment": {
"default": 1.0,
"GV": "curve:1",
"ZW": "curve:2"
},
"curves": [{
"id": 1,
"mode": "oe",
"data": {
"73oe": 2.0,
"17kmw": 3.0
}
}, {
"id": 2,
"mode": "kmw",
"data": {
"73oe": 2.0,
"17kmw": 3.0
}
}]
}
""", Vaributes);
Assert.Multiple(() => {
TestCalcKmw(data, "GV", 15.0, 2.0m);
TestCalcKmw(data, "GV", 15.5, 2.272727m);
TestCalcKmw(data, "GV", 16.0, 2.454545m);
TestCalcKmw(data, "GV", 16.5, 2.727273m);
TestCalcKmw(data, "GV", 17.0, 3.0m);
TestCalcKmw(data, "ZW", 15.0, 2.0m);
TestCalcKmw(data, "ZW", 15.5, 2.25m);
TestCalcKmw(data, "ZW", 16.0, 2.50m);
TestCalcKmw(data, "ZW", 16.5, 2.75m);
TestCalcKmw(data, "ZW", 17.0, 3.0m);
});
}
[Test]
public void TestRead_07_MultipleCurves() {
var data = PaymentBillingData.FromJson("""
{
"mode": "elwig",
"version": 1,
"payment": {
"default": 0.25,
"/S": "curve:1",
"GV/": 0.75,
"GV/K": "curve:2",
"WR/S": "curve:3"
},
"curves": [{
"id": 3,
"mode": "kmw",
"data": {
"73oe": 0.7,
"17kmw": 0.8
},
"geb": {
"15kmw": 0.8,
"17kmw": 0.95
}
}, {
"id": 1,
"mode": "kmw",
"data": {
"73oe": 0.5,
"17kmw": 0.6
},
"geb": 0.1
}, {
"id": 2,
"mode": "kmw",
"data": {
"15kmw": 0.6,
"17kmw": 0.7
},
"geb": {
"73oe": 0.65,
"17kmw": 0.80
}
}]
}
""", Vaributes);
Assert.Multiple(() => {
TestCalcKmw(data, "GV", 15.0, 0.75m);
TestCalcKmw(data, "GVS", 15.0, 0.50m);
TestCalcKmw(data, "GVS", 16.0, 0.55m);
TestCalcKmw(data, "GVS", 17.0, 0.60m);
TestCalcKmw(data, "GVS", 15.0, 0.60m, geb: true);
TestCalcKmw(data, "GVS", 16.0, 0.65m, geb: true);
TestCalcKmw(data, "GVS", 17.0, 0.70m, geb: true);
TestCalcKmw(data, "GVK", 15.0, 0.60m);
TestCalcKmw(data, "GVK", 16.0, 0.65m);
TestCalcKmw(data, "GVK", 17.0, 0.70m);
TestCalcKmw(data, "GVK", 15.0, 0.65m, geb: true);
TestCalcKmw(data, "GVK", 16.0, 0.725m, geb: true);
TestCalcKmw(data, "GVK", 17.0, 0.80m, geb: true);
TestCalcKmw(data, "WRS", 15.0, 0.70m);
TestCalcKmw(data, "WRS", 16.0, 0.75m);
TestCalcKmw(data, "WRS", 17.0, 0.80m);
TestCalcKmw(data, "WRS", 15.0, 0.80m, geb: true);
TestCalcKmw(data, "WRS", 16.0, 0.875m, geb: true);
TestCalcKmw(data, "WRS", 17.0, 0.95m, geb: true);
});
}
[Test]
public void TestRead_08_WgMaster() {
var data = PaymentBillingData.FromJson("""
{
"mode": "wgmaster",
"Grundbetrag": 0.033,
"GBZS": 0.0,
"Ausgabefaktor": 1.0,
"Rebelzuschlag": 0.0,
"AufschlagVolllieferanten": 0.0,
"AuszahlungSorten": {
"BL/": 0.097,
"BP/": 0.097,
"GV/K": "curve:1",
"SL/": 0.097,
"ZW/": 0.097,
"default": "curve:0"
},
"AuszahlungSortenQualitätsstufe": {
"WEI": 0.005
},
"Kurven": [{
"id": 0,
"mode": "oe",
"data": 0.033,
"geb": 0
}, {
"id": 1,
"mode": "oe",
"data": {
"88oe": 0.032,
"89oe": 0.065
}
}]
}
""", Vaributes);
Assert.Multiple(() => {
TestCalcOe(data, "GVK", 73, 0.032m);
TestCalcOe(data, "ZWS", 74, 0.033m);
TestCalcOe(data, "GV", 75, 0.005m, qualid: "WEI");
TestCalcOe(data, "GVK", 115, 0.065m);
});
}
private static List<Varibute> GetSelection(IEnumerable<string> attVars) {
return attVars.Select(s => {
var sortid = s[..2];
var attrid = s.Length > 2 ? s[2..] : null;
return new Varibute(
new WineVar(sortid, sortid),
attrid == null ? null : new WineAttr(attrid, attrid)
);
}).ToList();
}
[Test]
public void TestWrite_01_Empty() {
List<GraphEntry> entries = [
new GraphEntry(1, 4, BillingData.CurveMode.Oe, new() {
[73] = 0.5m
}, null)
];
var updated = BillingData.FromGraphEntries(entries);
Assert.That(updated.ToJsonString(JsonOpts), Is.EqualTo("""
{
"mode": "elwig",
"version": 1,
"payment": 0,
"curves": []
}
"""));
}
[Test]
public void TestWrite_02_Flatrate() {
List<GraphEntry> entries = [
new GraphEntry(0, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() {
[73] = 0.5m
}, null), GetSelection(["GV"]))
];
var data = BillingData.FromGraphEntries(entries);
Assert.That(data.ToJsonString(JsonOpts), Is.EqualTo("""
{
"mode": "elwig",
"version": 1,
"payment": 0.5,
"curves": []
}
"""));
}
[Test]
public void TestWrite_03_SingleCurve() {
List<GraphEntry> entries = [
new GraphEntry(0, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() {
[73] = 0.5m,
[83] = 1.0m
}, null), GetSelection(["GV"]))
];
var data = BillingData.FromGraphEntries(entries);
Assert.That(data.ToJsonString(JsonOpts), Is.EqualTo("""
{
"mode": "elwig",
"version": 1,
"payment": "curve:1",
"curves": [
{
"id": 1,
"mode": "oe",
"data": {
"73oe": 0.5,
"83oe": 1
}
}
]
}
"""));
}
[Test]
public void TestWrite_04_Simple() {
List<GraphEntry> entries = [
new GraphEntry(0, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() {
[73] = 0.5m,
[84] = 1.0m
}, null), GetSelection(["GV", "ZW"])),
new GraphEntry(10, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() {
[73] = 0.75m,
}, null), GetSelection(["WR"]))
];
var data = BillingData.FromGraphEntries(entries);
Assert.That(data.ToJsonString(JsonOpts), Is.EqualTo("""
{
"mode": "elwig",
"version": 1,
"payment": {
"WR/": 0.75,
"default": "curve:1"
},
"curves": [
{
"id": 1,
"mode": "oe",
"data": {
"73oe": 0.5,
"84oe": 1
}
}
]
}
"""));
}
[Test]
public void TestWrite_05_Attribute() {
List<GraphEntry> entries = [
new GraphEntry(0, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() {
[73] = 0.5m,
[84] = 1.0m
}, null), GetSelection(["GVB", "ZWB"])),
new GraphEntry(2, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() {
[73] = 0.75m,
}, null), GetSelection(["WR", "BL", "RR", "FV"])),
new GraphEntry(4, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() {
[73] = 0.65m,
[84] = 1.2m
}, null), GetSelection(["BP", "SA"]))
];
var data = BillingData.FromGraphEntries(entries);
Assert.That(data.ToJsonString(JsonOpts), Is.EqualTo("""
{
"mode": "elwig",
"version": 1,
"payment": {
"BP/": "curve:2",
"SA/": "curve:2",
"default": 0.75,
"/B": "curve:1"
},
"curves": [
{
"id": 1,
"mode": "oe",
"data": {
"73oe": 0.5,
"84oe": 1
}
},
{
"id": 2,
"mode": "oe",
"data": {
"73oe": 0.65,
"84oe": 1.2
}
}
]
}
"""));
}
}
}
+226
View File
@@ -0,0 +1,226 @@
using Elwig.Helpers;
using Elwig.Helpers.Billing;
using Microsoft.Data.Sqlite;
using System.Reflection;
namespace Tests.HelperTests {
[TestFixture]
public class BillingTest {
private const int Year1 = 2020, Year2 = 2020;
private const int MgNr1 = 101, MgNr2 = 102, MgNr3 = 103, MgNr4 = 104;
private SqliteConnection? Connection;
[OneTimeSetUp]
public async Task SetupDatabase() {
Connection = await AppDbContext.ConnectAsync();
await AppDbContext.ExecuteEmbeddedScript(Connection, Assembly.GetExecutingAssembly(), "Tests.Resources.BillingInsert.sql");
}
[OneTimeTearDown]
public async Task TeardownDatabase() {
if (Connection == null) return;
await AppDbContext.ExecuteEmbeddedScript(Connection, Assembly.GetExecutingAssembly(), "Tests.Resources.BillingDelete.sql");
await Connection.DisposeAsync();
Connection = null;
}
[TearDown]
public async Task CleanupDatabasePayment() {
if (Connection == null) return;
await AppDbContext.ExecuteBatch(Connection, """
DELETE FROM credit;
DELETE FROM payment_variant;
DELETE FROM delivery_part_bucket;
""");
}
private Task<Dictionary<string, AreaComBucket>> GetMemberAreaCommitmentBuckets(int year, int mgnr) {
var ctx = new AppDbContext();
return ctx.GetMemberAreaCommitmentBuckets(year, mgnr, Connection);
}
private Task<Dictionary<string, int>> GetMemberDeliveryBuckets(int year, int mgnr) {
var ctx = new AppDbContext();
return ctx.GetMemberDeliveryBuckets(year, mgnr, Connection);
}
private Task<Dictionary<string, int>> GetMemberPaymentBuckets(int year, int mgnr) {
var ctx = new AppDbContext();
return ctx.GetMemberPaymentBuckets(year, mgnr, Connection);
}
private async Task<Dictionary<(string, string), int>> GetMemberDeliveryPrices(int year, int mgnr) {
var buckets = new Dictionary<(string, string), int>();
using (var cmd = Connection!.CreateCommand()) {
cmd.CommandText = $"""
SELECT lsnr || '/' || d.dpnr, d.sortid || b.discr, price
FROM v_delivery d
LEFT JOIN payment_delivery_part_bucket p ON (p.year, p.did, p.dpnr) = (d.year, d.did, d.dpnr)
LEFT JOIN delivery_part_bucket b ON (b.year, b.did, b.dpnr, b.bktnr) = (p.year, p.did, p.dpnr, p.bktnr)
WHERE d.year = {year} AND mgnr = {mgnr}
""";
using var reader = await cmd.ExecuteReaderAsync();
while (await reader.ReadAsync()) {
var lsnr = reader.GetString(0);
var bucket = reader.GetString(1);
buckets[(lsnr, bucket)] = reader.GetInt32(2);
}
}
return buckets;
}
private Task InsertPaymentVariant(int year, int avnr, string data) {
return AppDbContext.ExecuteBatch(Connection!, $"""
INSERT INTO payment_variant (year, avnr, name, date, transfer_date, test_variant, calc_time, data)
VALUES ({year}, {avnr}, 'Test', '2021-01-15', NULL, TRUE, NULL, '{data}');
""");
}
[Test]
public async Task Test_01_NoActiveAreaComs() {
int mgnr = MgNr1, year = Year1;
await InsertPaymentVariant(year, 1, """
{
"mode": "elwig",
"version": 1,
"payment": {
"GV/": "curve:0",
"GV/B": "curve:1",
"GV/K": "curve:2"
},
"quality": {"WEI": 0.1},
"curves": [{
"id": 0,
"mode": "oe",
"data": {"15kmw": 0.5},
"geb": 0.1
}, {
"id": 1,
"mode": "oe",
"data": {"15kmw": 0.54},
"geb": 0.1
}, {
"id": 2,
"mode": "oe",
"data": {"15kmw": 0.61},
"geb": 0.1
}]
}
""");
var areaCom = await GetMemberAreaCommitmentBuckets(year, mgnr);
Assert.That(areaCom, Is.Empty);
var delivery = await GetMemberDeliveryBuckets(year, mgnr);
Assert.Multiple(() => {
Assert.That(delivery, Has.Count.EqualTo(4));
Assert.That(delivery["GV"], Is.EqualTo(16_000));
Assert.That(delivery["GV_"], Is.EqualTo( 1_000));
Assert.That(delivery["GVB"], Is.EqualTo( 8_000));
Assert.That(delivery["GVK"], Is.EqualTo( 4_000));
});
BillingVariant b = new(year, 1);
await b.CalculateBuckets(false, false, false);
var payment = await GetMemberPaymentBuckets(year, mgnr);
Assert.Multiple(() => {
Assert.That(payment, Has.Count.EqualTo(1));
Assert.That(payment["GV_"], Is.EqualTo(17_000));
});
await b.Calculate();
var prices = await GetMemberDeliveryPrices(year, mgnr);
Assert.Multiple(() => {
Assert.That(prices, Has.Count.EqualTo(6));
Assert.That(prices[("20201001X001/1", "GV_")], Is.EqualTo(0_6100));
Assert.That(prices[("20201001X001/2", "GV_")], Is.EqualTo(0_5000));
Assert.That(prices[("20201001X002/1", "GV_")], Is.EqualTo(0_5400));
Assert.That(prices[("20201001X002/2", "GV_")], Is.EqualTo(0_5400));
Assert.That(prices[("20201001X003/1", "GV_")], Is.EqualTo(0_1000));
Assert.That(prices[("20201001X003/2", "GV_")], Is.EqualTo(0_5000));
});
}
[Test]
public async Task Test_02_SimpleNotStrictAreaComs() {
// TODO
}
[Test]
public async Task Test_03_SimpleNotStrictAreaComs_HonorGebunden() {
// TODO
}
[Test]
public async Task Test_04_ComplexNotStrictAreaComs() {
// TODO
}
[Test]
public async Task Test_05_ComplexNotStrictAreaComs_HonorGebunden() {
// TODO
}
[Test]
public async Task Test_06_StrictAreaComs_NoFillLower_NotAllowed() {
// TODO
}
[Test]
public async Task Test_07_StrictAreaComs_NoFillLower_Allowed() {
// TODO
}
[Test]
public async Task Test_08_StrictAreaComs_NoFillLower_Allowed_AvoidUnderDeliveries() {
// TODO
}
[Test]
public async Task Test_09_StrictAreaComs_FillLowerUntilObligation_NotAllowed() {
// TODO
}
[Test]
public async Task Test_10_StrictAreaComs_FillLowerUntilObligation_Allowed() {
// TODO
}
[Test]
public async Task Test_11_StrictAreaComs_FillLowerUntilObligation_Allowed_AvoidUnderDeliveries() {
// TODO
}
[Test]
public async Task Test_12_StrictAreaComs_FillLowerUntilObligation_NotAllowed() {
// TODO
}
[Test]
public async Task Test_13_StrictAreaComs_FillLowerUntilObligation_Allowed() {
// TODO
}
[Test]
public async Task Test_14_StrictAreaComs_FillLowerUntilObligation_Allowed_AvoidUnderDeliveries() {
// TODO
}
[Test]
public async Task Test_15_StrictAreaComs_FillLowerUntilRight_NotAllowed() {
// TODO
}
[Test]
public async Task Test_16_StrictAreaComs_FillLowerUntilRight_Allowed() {
// TODO
}
[Test]
public async Task Test_17_StrictAreaComs_FillLowerUntilRight_Allowed_AvoidUnderDeliveries() {
// TODO
}
}
}
@@ -1,8 +1,8 @@
using Elwig.Helpers;
namespace Tests {
namespace Tests.HelperTests {
[TestFixture]
public class HelpersUtilsTest {
public class UtilsTest {
private static readonly double[,] Gradation = new double[,] {
{ 14.0, 68.0 },
@@ -1,10 +1,10 @@
using Elwig.Helpers;
using System.Windows.Controls;
namespace Tests {
namespace Tests.HelperTests {
[TestFixture]
[Apartment(ApartmentState.STA)]
public class HelpersValidatorTest {
public class ValidatorTest {
private static TextBox TB(string value, int caret = 0) {
return new() {
+11
View File
@@ -0,0 +1,11 @@
-- deletes for HelperTests.BillingTest
DELETE FROM credit;
DELETE FROM payment_variant
DELETE FROM delivery;
DELETE FROM area_commitment;
DELETE FROM area_commitment_type;
DELETE FROM season;
DELETE FROM member;
DELETE FROM wine_attribute;
DELETE FROM wine_cultivation;
+59
View File
@@ -0,0 +1,59 @@
-- inserts for HelperTests.BillingTest
INSERT INTO wine_cultivation (cultid, name, description) VALUES
('N', 'Normal', NULL),
('K', 'KIP', 'Kontrollierte Integrierte Produktion'),
('B', 'Org. Biologisch', 'Organisch Biologisch');
INSERT INTO wine_attribute (attrid, name, active, max_kg_per_ha, strict, fill_lower) VALUES
('B', 'Bio', TRUE, NULL, FALSE, 0),
('K', 'Kabinett', TRUE, NULL, FALSE, 0),
('D', 'DAC', TRUE, 7500, FALSE, 0),
('S', 'Saft', TRUE, NULL, FALSE, 0),
('Z', 'Sekt', TRUE, NULL, FALSE, 0),
('P', 'Vertrag P', TRUE, NULL, TRUE, 0),
('Q', 'Vertrag Q', TRUE, NULL, TRUE, 1),
('R', 'Vertrag R', TRUE, NULL, TRUE, 2);
INSERT INTO area_commitment_type (vtrgid, sortid, attrid, disc, min_kg_per_ha, penalty_per_kg, penalty_amount, penalty_none) VALUES
('ZW', 'ZW', NULL, NULL, 2500, 600, NULL, NULL),
('GV', 'GV', NULL, NULL, 5000, 500, NULL, NULL),
('GVK', 'GV', 'K', NULL, 5000, 500, NULL, NULL),
('GVD', 'GV', 'D', NULL, 5000, 1000, NULL, NULL),
('GVP', 'GV', 'P', NULL, 5000, 1000, 1000000, NULL),
('GVQ', 'GV', 'Q', NULL, 5000, 1000, 1000000, NULL),
('GVR', 'GV', 'R', NULL, 5000, 1000, 1000000, NULL);
INSERT INTO member (mgnr, given_name, family_name, zwstid, volllieferant, buchführend, country, postal_dest, address, default_kgnr) VALUES
(101, 'Max', 'Mustermann', 'X', FALSE, FALSE, 40, 222303524, 'Winzerstraße 1', 06109),
(102, 'Wernhardt', 'Weinbauer', 'X', FALSE, FALSE, 40, 222303524, 'Winzerstraße 2', 06109),
(103, 'Matthäus', 'Musterbauer', 'X', FALSE, FALSE, 40, 212005138, 'Brünner Straße 10', 15224),
(104, 'Waltraud', 'Winzer', 'X', FALSE, FALSE, 40, 212005138, 'Wiener Straße 15', 15224);
INSERT INTO area_commitment (fbnr, mgnr, vtrgid, cultid, area, kgnr, gstnr, rdnr, year_from, year_to) VALUES
( 1, 101, 'GV', 'K', 10000, 06109, '123/4', NULL, 2000, 2019),
( 2, 101, 'GV', 'B', 10000, 06109, '123/5', NULL, 2025, 2030);
INSERT INTO season (year, currency, min_kg_per_bs, max_kg_per_bs, penalty_per_kg, penalty_amount, penalty_none, start_date, end_date) VALUES
(2020, 'EUR', 1000, 2000, NULL, NULL, NULL, NULL, NULL),
(2021, 'EUR', 2000, 4000, NULL, NULL, NULL, NULL, NULL);
INSERT INTO modifier (year, modid, ordering, name, abs, rel, standard, quick_select) VALUES
(2020, 'S', 0, 'Geschädigte Trauben', NULL, -0.1, FALSE, FALSE),
(2020, 'A', 0, 'Keine Voranmeldung', -1000, NULL, FALSE, FALSE);
INSERT INTO delivery (mgnr, year, did, date, time, zwstid, lnr) VALUES
(101, 2020, 1, '2020-10-01', NULL, 'X', 1),
(101, 2020, 2, '2020-10-01', NULL, 'X', 2),
(101, 2020, 3, '2020-10-01', NULL, 'X', 3);
INSERT INTO delivery_part (year, did, dpnr, sortid, attrid, weight, kmw, qualid, hkid, kgnr, gerebelt, manual_weighing, spl_check, scale_id, weighing_id, weighing_reason) VALUES
(2020, 1, 1, 'GV', 'K', 4000, 17, 'KAB', 'WLNO', 06109, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2020, 1, 2, 'GV', NULL, 4000, 16, 'QUW', 'WLNO', 06109, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2020, 2, 1, 'GV', 'B', 4000, 15, 'QUW', 'WLNO', 06109, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2020, 2, 2, 'GV', 'B', 4000, 16, 'QUW', 'WLNO', 06109, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2020, 3, 1, 'GV', NULL, 500, 15, 'WEI', 'OEST', 06109, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2020, 3, 2, 'GV', NULL, 500, 14, 'LDW', 'WLXX', 06109, TRUE, FALSE, FALSE, NULL, NULL, NULL);
INSERT INTO delivery_part_modifier (year, did, dpnr, modid) VALUES
(2020, 1, 2, 'S');
+53
View File
@@ -0,0 +1,53 @@
-- inserts for DatabaseSetup
INSERT INTO branch (zwstid, name) VALUES
('X', 'Test');
INSERT INTO wb_gl (glnr, name) VALUES
(1, 'Matzner Hügel'),
(2, 'Wolkersdorfer Hochleithen');
INSERT INTO AT_gem (gkz, name) VALUES
(30828, 'Hohenruppersdorf'),
(31655, 'Wolkersdorf im Weinviertel');
INSERT INTO wb_gem (gkz, hkid) VALUES
(30828, 'WLWV'),
(31655, 'WLWV');
INSERT INTO AT_kg (kgnr, gkz, name) VALUES
(06109, 30828, 'Hohenruppersdorf'),
(15209, 31655, 'Münichsthal'),
(15211, 31655, 'Obersdorf'),
(15212, 31655, 'Pfösing'),
(15216, 31655, 'Riedentahl'),
(15224, 31655, 'Wolkersdorf');
INSERT INTO wb_kg (kgnr, glnr) VALUES
(06109, 1),
(15209, 2),
(15211, 2),
(15212, 2),
(15216, 2),
(15224, 2);
INSERT INTO AT_ort (okz, gkz, kgnr, name) VALUES
(03524, 30828, 06109, 'Hohenruppersdorf'),
(05092, 31655, 15211, 'Obersdorf'),
(05135, 31655, 15209, 'Münichsthal'),
(05136, 31655, 15212, 'Pfösing'),
(05137, 31655, 15216, 'Riedenthal'),
(05138, 31655, 15224, 'Wolkersdorf im Weinviertel');
INSERT INTO AT_plz (plz, ort, blnr, type, internal, addressable, po_box) VALUES
(2223, 'Hohenruppersdorf', 3, 'PLZ-Adressierung', FALSE, TRUE, FALSE),
(2120, 'Wolkersdorf im Weinviertel', 3, 'PLZ-Adressierung', FALSE, TRUE, TRUE ),
(2122, 'Ulrichskirchen', 3, 'PLZ-Adressierung', FALSE, TRUE, FALSE);
INSERT INTO AT_plz_dest (plz, okz, dest) VALUES
(2223, 03524, 'Hohenruppersdorf'),
(2120, 05092, 'Obersdorf'),
(2120, 05138, 'Wolkersdorf im Weinviertel'),
(2122, 05135, 'Münichsthal'),
(2122, 05136, 'Pfösing'),
(2122, 05137, 'Riedenthal');
+19 -5
View File
@@ -10,11 +10,25 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
<PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.3.0" />
<PackageReference Include="NUnit.Analyzers" Version="3.5.0" />
<PackageReference Include="coverlet.collector" Version="3.1.2" />
<EmbeddedResource Include="Resources\*.sql" />
</ItemGroup>
<Target Name="FetchResources" BeforeTargets="BeforeBuild">
<Exec Command="call fetch-resources.bat" />
</Target>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="NUnit" Version="4.0.1" />
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
<PackageReference Include="NUnit.Analyzers" Version="3.10.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
+1
View File
@@ -0,0 +1 @@
curl -s -L "https://www.necronda.net/elwig/files/create.sql?v=15" -u "elwig:ganzGeheim123!" -o "Resources\Create.sql"