diff --git a/Elwig/Documents/CreditNote.cshtml b/Elwig/Documents/CreditNote.cshtml
index f6bf043..a841b3d 100644
--- a/Elwig/Documents/CreditNote.cshtml
+++ b/Elwig/Documents/CreditNote.cshtml
@@ -27,7 +27,7 @@
@part.DPNr |
@part.Variant.Name |
- @string.Join(" / ", part.Attributes) |
+ @part.Attribute?.Name |
@part.Quality.Name |
@($"{part.Oe:N0}") |
@($"{part.Kmw:N1}") |
diff --git a/Elwig/Helpers/AppDbContext.cs b/Elwig/Helpers/AppDbContext.cs
index 7db9138..f093bff 100644
--- a/Elwig/Helpers/AppDbContext.cs
+++ b/Elwig/Helpers/AppDbContext.cs
@@ -40,7 +40,6 @@ namespace Elwig.Helpers {
public DbSet Modifiers { get; private set; }
public DbSet Deliveries { get; private set; }
public DbSet DeliveryParts { get; private set; }
- public DbSet DeliveryPartAttributes { get; private set; }
public DbSet DeliveryPartModifiers { get; private set; }
public DbSet PaymentVariants { get; private set; }
public DbSet MemberPayments { get; private set; }
@@ -166,28 +165,6 @@ namespace Elwig.Helpers {
.LastOrDefaultAsync();
}
- public async Task UpdateDeliveryPartAttributes(DeliveryPart part, IEnumerable attributes) {
- foreach (var a in WineAttributes) {
- var attr = part.PartAttributes.Where(pa => pa.AttrId == a.AttrId).FirstOrDefault();
- if (attributes.Contains(a)) {
- DeliveryPartAttr dpa = attr ?? this.CreateProxy();
- dpa.Year = part.Year;
- dpa.DId = part.DId;
- dpa.DPNr = part.DPNr;
- dpa.AttrId = a.AttrId;
- if (attr == null) {
- await AddAsync(dpa);
- } else {
- Update(dpa);
- }
- } else {
- if (attr != null) {
- Remove(attr);
- }
- }
- }
- }
-
public async Task UpdateDeliveryPartModifiers(DeliveryPart part, IEnumerable modifiers) {
foreach (var m in Modifiers.Where(m => m.Year == part.Year)) {
var mod = part.PartModifiers.Where(pa => pa.ModId == m.ModId).FirstOrDefault();
diff --git a/Elwig/Helpers/AppDbUpdater.cs b/Elwig/Helpers/AppDbUpdater.cs
index c3a9a99..fbe1f6b 100644
--- a/Elwig/Helpers/AppDbUpdater.cs
+++ b/Elwig/Helpers/AppDbUpdater.cs
@@ -4,11 +4,11 @@ using System;
namespace Elwig.Helpers {
public static class AppDbUpdater {
- public static readonly int RequiredSchemaVersion = 4;
+ public static readonly int RequiredSchemaVersion = 5;
private static int _versionOffset = 0;
private static readonly Action[] _updaters = new[] {
- UpdateDbSchema_1_To_2, UpdateDbSchema_2_To_3, UpdateDbSchema_3_To_4
+ UpdateDbSchema_1_To_2, UpdateDbSchema_2_To_3, UpdateDbSchema_3_To_4, UpdateDbSchema_4_To_5
};
private static void ExecuteNonQuery(SqliteConnection cnx, string sql) {
@@ -65,11 +65,14 @@ namespace Elwig.Helpers {
}
ExecuteNonQuery(cnx, "PRAGMA locking_mode = EXCLUSIVE");
+ ExecuteNonQuery(cnx, "PRAGMA foreign_keys = OFF");
ExecuteNonQuery(cnx, "BEGIN EXCLUSIVE");
for (int i = fromVersion; i < toVersion; i++) {
_updaters[i - 1](cnx);
}
+ ExecuteNonQuery(cnx, "PRAGMA foreign_key_check");
ExecuteNonQuery(cnx, "COMMIT");
+ ExecuteNonQuery(cnx, "PRAGMA foreign_keys = ON");
ExecuteNonQuery(cnx, "VACUUM");
ExecuteNonQuery(cnx, $"PRAGMA schema_version = {toVersion * 100 + _versionOffset}");
}
@@ -213,5 +216,119 @@ namespace Elwig.Helpers {
ORDER BY s.year, c.mgnr, LENGTH(c.vtrgid) DESC, c.vtrgid;
""");
}
+
+ private static void UpdateDbSchema_4_To_5(SqliteConnection cnx) {
+ ExecuteNonQuery(cnx, """
+ CREATE TABLE _area_commitment_type (
+ vtrgid TEXT NOT NULL CHECK (vtrgid = sortid || COALESCE(attrid, '') || disc),
+ sortid TEXT NOT NULL,
+ attrid TEXT,
+ disc TEXT DEFAULT NULL CHECK (disc REGEXP '^[A-Z0-9]+$'),
+
+ min_kg_per_ha INTEGER,
+ max_kg_per_ha INTEGER,
+ penalty_amount INTEGER,
+
+ CONSTRAINT pk_area_commitment_type PRIMARY KEY (vtrgid),
+ CONSTRAINT sk_area_commitment_type_sort_attr UNIQUE (sortid, attrid, disc),
+ CONSTRAINT fk_area_commitment_type_wine_variety FOREIGN KEY (sortid) REFERENCES wine_variety (sortid)
+ ON UPDATE CASCADE
+ ON DELETE RESTRICT,
+ CONSTRAINT fk_area_commitment_type_wine_attribute FOREIGN KEY (attrid) REFERENCES wine_attribute (attrid)
+ ON UPDATE CASCADE
+ ON DELETE RESTRICT
+ ) STRICT;
+ """);
+ ExecuteNonQuery(cnx, """
+ INSERT INTO _area_commitment_type (vtrgid, sortid, attrid, disc, min_kg_per_ha, max_kg_per_ha, penalty_amount)
+ SELECT vtrgid, sortid, attrid_1, disc, min_kg_per_ha, max_kg_per_ha, penalty_amount FROM area_commitment_type
+ """);
+ ExecuteNonQuery(cnx, "PRAGMA writable_schema = ON");
+ ExecuteNonQuery(cnx, "DROP TABLE area_commitment_type");
+ ExecuteNonQuery(cnx, "ALTER TABLE _area_commitment_type RENAME TO area_commitment_type");
+ ExecuteNonQuery(cnx, "PRAGMA writable_schema = OFF");
+
+ ExecuteNonQuery(cnx, """
+ ALTER TABLE delivery_part ADD COLUMN attrid TEXT DEFAULT NULL
+ REFERENCES wine_attribute (attrid)
+ ON UPDATE CASCADE
+ ON DELETE RESTRICT
+ """);
+ ExecuteNonQuery(cnx, """
+ UPDATE delivery_part
+ SET attrid = (SELECT attrid
+ FROM delivery_part_attribute a
+ WHERE (delivery_part.year, delivery_part.did, delivery_part.dpnr) = (a.year, a.did, a.dpnr)
+ ORDER BY attrid DESC
+ LIMIT 1)
+ """);
+ ExecuteNonQuery(cnx, "DROP TRIGGER t_delivery_part_attribute_i_mtime_delivery_part");
+ ExecuteNonQuery(cnx, "DROP TRIGGER t_delivery_part_attribute_u_mtime_delivery_part");
+ ExecuteNonQuery(cnx, "DROP TRIGGER t_delivery_part_attribute_d_mtime_delivery_part");
+ ExecuteNonQuery(cnx, "DROP TABLE delivery_part_attribute");
+
+ ExecuteNonQuery(cnx, "DROP VIEW v_delivery");
+ ExecuteNonQuery(cnx, """
+ CREATE VIEW v_delivery AS
+ SELECT p.year, p.did, p.dpnr,
+ d.date, d.time, d.zwstid, d.lnr, d.lsnr,
+ m.mgnr, m.family_name, m.given_name,
+ p.sortid, a.attrid,
+ p.weight, p.kmw, ROUND(p.kmw * (4.54 + 0.022 * p.kmw), 0) AS oe, p.qualid,
+ p.hkid, p.kgnr, p.rdnr,
+ p.gerebelt, p.gebunden,
+ p.qualid IN (SELECT l.qualid FROM wine_quality_level l WHERE NOT l.predicate AND (p.kmw >= l.min_kmw OR l.min_kmw IS NULL) ORDER BY l.min_kmw DESC LIMIT 1,100) AS abgewertet,
+ p.qualid NOT IN ('WEI', 'RSW', 'LDW') AS min_quw,
+ COALESCE(a.fill_lower_bins, 0) AS attribute_prio,
+ GROUP_CONCAT(o.modid) AS modifiers,
+ d.comment, p.comment AS part_comment
+ FROM delivery_part p
+ JOIN delivery d ON (d.year, d.did) = (p.year, p.did)
+ JOIN member m ON m.mgnr = d.mgnr
+ LEFT JOIN wine_attribute a ON a.attrid = p.attrid
+ LEFT JOIN delivery_part_modifier o ON (o.year, o.did, o.dpnr) = (p.year, p.did, p.dpnr)
+ GROUP BY p.year, p.did, p.dpnr
+ ORDER BY p.year, p.did, p.dpnr, o.modid;
+ """);
+
+ ExecuteNonQuery(cnx, "DROP VIEW v_delivery_bin");
+ ExecuteNonQuery(cnx, """
+ CREATE VIEW v_delivery_bin AS
+ SELECT year, mgnr,
+ sortid || IIF(min_quw, COALESCE(attrid, ''), '_') AS bin,
+ SUM(weight) AS weight
+ FROM v_delivery
+ GROUP BY year, mgnr, bin
+ ORDER BY year, mgnr, LENGTH(bin) DESC, bin;
+ """);
+
+ ExecuteNonQuery(cnx, "DROP VIEW v_stat_attr");
+ ExecuteNonQuery(cnx, """
+ CREATE VIEW v_stat_attr AS
+ SELECT year, attrid,
+ SUM(weight) as sum,
+ ROUND(SUM(kmw * weight) / SUM(weight), 2) AS kmw,
+ ROUND(SUM(oe * weight) / SUM(weight), 1) AS oe,
+ COUNT(DISTINCT did) AS lieferungen,
+ COUNT(DISTINCT mgnr) AS mitglieder
+ FROM v_delivery
+ GROUP BY year, attrid
+ ORDER BY year, attrid;
+ """);
+
+ ExecuteNonQuery(cnx, "DROP VIEW v_stat_sort_attr");
+ ExecuteNonQuery(cnx, """
+ CREATE VIEW v_stat_sort_attr AS
+ SELECT year, sortid, attrid,
+ SUM(weight) as sum,
+ ROUND(SUM(kmw * weight) / SUM(weight), 2) AS kmw,
+ ROUND(SUM(oe * weight) / SUM(weight), 1) AS oe,
+ COUNT(DISTINCT did) AS lieferungen,
+ COUNT(DISTINCT mgnr) AS mitglieder
+ FROM v_delivery
+ GROUP BY year, sortid, attrid
+ ORDER BY year, sortid, attrid;
+ """);
+ }
}
}
diff --git a/Elwig/Helpers/Billing/Billing.cs b/Elwig/Helpers/Billing/Billing.cs
index ff97ac7..0698e7d 100644
--- a/Elwig/Helpers/Billing/Billing.cs
+++ b/Elwig/Helpers/Billing/Billing.cs
@@ -11,14 +11,14 @@ namespace Elwig.Helpers.Billing {
protected readonly AppDbContext Context;
protected readonly Dictionary Attributes;
protected readonly Dictionary Modifiers;
- protected readonly Dictionary AreaComTypes;
+ protected readonly Dictionary AreaComTypes;
public Billing(int year) {
Year = year;
Context = new AppDbContext();
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.AttrId1, v.AttrId2, v.Discriminator, v.MinKgPerHa, v.MaxKgPerHa, v.PenaltyAmount));
+ AreaComTypes = Context.AreaCommitmentTypes.ToDictionary(v => v.VtrgId, v => (v.SortId, v.AttrId, v.Discriminator, v.MinKgPerHa, v.MaxKgPerHa, v.PenaltyAmount));
}
public async Task FinishSeason() {
diff --git a/Elwig/Helpers/NullItem.cs b/Elwig/Helpers/NullItem.cs
index 9ac9f61..81f8cae 100644
--- a/Elwig/Helpers/NullItem.cs
+++ b/Elwig/Helpers/NullItem.cs
@@ -1,5 +1,14 @@
namespace Elwig.Helpers {
public class NullItem {
- public static string Name => "- Keine Angabe -";
+
+ public string Name { get; private set; }
+
+ public NullItem(string name = "- Keine Angabe -") {
+ Name = name;
+ }
+
+ public override string ToString() {
+ return Name;
+ }
}
}
diff --git a/Elwig/Models/AreaComType.cs b/Elwig/Models/AreaComType.cs
index ce532de..c9e411b 100644
--- a/Elwig/Models/AreaComType.cs
+++ b/Elwig/Models/AreaComType.cs
@@ -4,7 +4,7 @@ using System.ComponentModel.DataAnnotations.Schema;
using IndexAttribute = Microsoft.EntityFrameworkCore.IndexAttribute;
namespace Elwig.Models {
- [Table("area_commitment_type"), PrimaryKey("VtrgId"), Index("SortId", "AttrId1", "AttrId2", "Discriminator")]
+ [Table("area_commitment_type"), PrimaryKey("VtrgId"), Index("SortId", "AttrId", "Discriminator")]
public class AreaComType {
[Column("vtrgid")]
public string VtrgId { get; set; }
@@ -12,11 +12,8 @@ namespace Elwig.Models {
[Column("sortid")]
public string SortId { get; set; }
- [Column("attrid_1")]
- public string? AttrId1 { get; set; }
-
- [Column("attrid_2")]
- public string? AttrId2 { get; set; }
+ [Column("attrid")]
+ public string? AttrId { get; set; }
[Column("disc")]
public string? Discriminator { get; set; }
@@ -39,14 +36,10 @@ namespace Elwig.Models {
[ForeignKey("SortId")]
public virtual WineVar WineVar { get; private set; }
- [ForeignKey("AttrId1")]
- public virtual WineAttr? WineAttr1 { get; private set; }
-
- [ForeignKey("AttrId2")]
- public virtual WineAttr? WineAttr2 { get; private set; }
+ [ForeignKey("AttrId")]
+ public virtual WineAttr? WineAttr { get; private set; }
[NotMapped]
- public string DisplayName => WineVar.Name + (WineAttr1 != null ? $" {WineAttr1.Name}" : "") +
- (WineAttr2 != null ? $" {WineAttr2.Name}" : "") + (Discriminator != null ? $" ({Discriminator})" : "");
+ public string DisplayName => WineVar.Name + (WineAttr != null ? $" {WineAttr.Name}" : "") + (Discriminator != null ? $" ({Discriminator})" : "");
}
}
diff --git a/Elwig/Models/DeliveryPart.cs b/Elwig/Models/DeliveryPart.cs
index d74015a..3554e17 100644
--- a/Elwig/Models/DeliveryPart.cs
+++ b/Elwig/Models/DeliveryPart.cs
@@ -25,6 +25,12 @@ namespace Elwig.Models {
[ForeignKey("SortId")]
public virtual WineVar Variant { get; private set; }
+ [Column("attrid")]
+ public string? AttrId { get; set; }
+
+ [ForeignKey("AttrId")]
+ public virtual WineAttr? Attribute { get; private set; }
+
[Column("weight")]
public int Weight { get; set; }
@@ -96,15 +102,6 @@ namespace Elwig.Models {
[Column("comment")]
public string? Comment { get; set; }
- [InverseProperty("Part")]
- public virtual ISet PartAttributes { get; private set; }
-
- [NotMapped]
- public IEnumerable Attributes => PartAttributes.Select(a => a.Attr);
-
- [NotMapped]
- public string AttributesString => string.Join(" / ", Attributes);
-
[InverseProperty("Part")]
public virtual ISet PartModifiers { get; private set; }
diff --git a/Elwig/Models/DeliveryPartAttr.cs b/Elwig/Models/DeliveryPartAttr.cs
deleted file mode 100644
index 5e3e611..0000000
--- a/Elwig/Models/DeliveryPartAttr.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-using Microsoft.EntityFrameworkCore;
-using System.ComponentModel.DataAnnotations.Schema;
-
-namespace Elwig.Models {
- [Table("delivery_part_attribute"), PrimaryKey("Year", "DId", "DPNr", "AttrId")]
- public class DeliveryPartAttr {
- [Column("year")]
- public int Year { get; set; }
-
- [Column("did")]
- public int DId { get; set; }
-
- [Column("dpnr")]
- public int DPNr { get; set; }
-
- [ForeignKey("Year, DId, DPNr")]
- public virtual DeliveryPart Part { get; private set; }
-
- [Column("attrid")]
- public string AttrId { get; set; }
-
- [ForeignKey("AttrId")]
- public virtual WineAttr Attr { get; private set; }
- }
-}
diff --git a/Elwig/Windows/DeliveryAdminWindow.xaml b/Elwig/Windows/DeliveryAdminWindow.xaml
index 72f7d2a..5c0ddd9 100644
--- a/Elwig/Windows/DeliveryAdminWindow.xaml
+++ b/Elwig/Windows/DeliveryAdminWindow.xaml
@@ -271,10 +271,10 @@
ItemTemplate="{StaticResource WineVarietyTemplate}" TextSearch.TextPath="Name"
SelectionChanged="WineVarietyInput_SelectionChanged" KeyUp="Input_KeyUp"/>
-
-
+
+
@@ -403,7 +403,7 @@
-
+
diff --git a/Elwig/Windows/DeliveryAdminWindow.xaml.cs b/Elwig/Windows/DeliveryAdminWindow.xaml.cs
index 17c7b3a..8d4ba6f 100644
--- a/Elwig/Windows/DeliveryAdminWindow.xaml.cs
+++ b/Elwig/Windows/DeliveryAdminWindow.xaml.cs
@@ -280,7 +280,7 @@ namespace Elwig.Windows {
if (ctrl == MgNrInput || ctrl == MemberInput) {
SortIdInput.Focus();
SortIdInput.SelectAll();
- } else if (ctrl == SortIdInput || ctrl == WineVarietyInput || ctrl == AttributesInput) {
+ } else if (ctrl == SortIdInput || ctrl == WineVarietyInput || ctrl == AttributeInput) {
GradationOeInput.Focus();
GradationOeInput.SelectAll();
} else if (ctrl == GradationKmwInput || ctrl == GradationOeInput || ctrl == WineQualityLevelInput) {
@@ -486,12 +486,8 @@ namespace Elwig.Windows {
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)
- foreach (var a in filterAttr)
- dpq = dpq.Where(p => p.PartAttributes.Select(a => a.Attr.AttrId).Contains(a));
- if (filterNotAttr.Count > 0)
- foreach (var a in filterNotAttr)
- dpq = dpq.Where(p => !p.PartAttributes.Select(a => a.Attr.AttrId).Contains(a));
+ 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);
@@ -570,7 +566,7 @@ namespace Elwig.Windows {
if (n > 0 && (n <= 200 || TodayOnlyInput.IsChecked == true)) {
var parts = await deliveryParts.ToListAsync();
var groups = parts
- .GroupBy(p => string.Join("/", p.Attributes.Select(a => a.Name)))
+ .GroupBy(p => p.Attribute?.Name)
.Select(g => (g.Key, g.Sum(p => p.Weight), g.Min(p => p.Kmw), Utils.AggregateDeliveryPartsKmw(g), g.Max(p => p.Kmw)))
.OrderByDescending(g => g.Item2)
.ToList();
@@ -636,7 +632,9 @@ namespace Elwig.Windows {
ControlUtils.RenewItemsSource(MemberInput, await Context.Members.Where(m => m.IsActive || !IsCreating).OrderBy(m => m.FamilyName).ThenBy(m => m.GivenName).ToListAsync(), i => (i as Member)?.MgNr);
ControlUtils.RenewItemsSource(BranchInput, await Context.Branches.OrderBy(b => b.Name).ToListAsync(), i => (i as Branch)?.ZwstId);
ControlUtils.RenewItemsSource(WineVarietyInput, await Context.WineVarieties.OrderBy(v => v.Name).ToListAsync(), i => (i as WineVar)?.SortId);
- ControlUtils.RenewItemsSource(AttributesInput, await Context.WineAttributes.Where(a => !IsCreating || a.IsActive).OrderBy(a => a.Name).ToListAsync(), i => (i as WineAttr)?.AttrId);
+ var attrList = await Context.WineAttributes.Where(a => !IsCreating || a.IsActive).OrderBy(a => a.Name).Cast