Billing: Allow users to add custom member modifiers before VAT
All checks were successful
Test / Run tests (push) Successful in 2m8s
All checks were successful
Test / Run tests (push) Successful in 2m8s
This commit is contained in:
@ -40,8 +40,14 @@ namespace Elwig.Documents {
|
||||
Payment = p;
|
||||
Credit = p.Credit;
|
||||
var season = p.Variant.Season;
|
||||
if (considerCustomModifiers) {
|
||||
CustomPayment = ctx.CustomPayments.Find(p.Year, p.MgNr);
|
||||
}
|
||||
|
||||
var mod = App.Client.IsMatzen ? ctx.Modifiers.Where(m => m.Year == season.Year && m.Name.StartsWith("Treue")).FirstOrDefault() : null;
|
||||
if (mod != null) {
|
||||
if (CustomPayment?.ModComment != null) {
|
||||
MemberModifier = CustomPayment.ModComment;
|
||||
} else if (mod != null) {
|
||||
MemberModifier = $"{mod.Name} ({mod.ValueStr})";
|
||||
} else {
|
||||
MemberModifier = "Sonstige Zu-/Abschläge";
|
||||
@ -88,8 +94,5 @@ namespace Elwig.Documents {
|
||||
.Where(u => u.Item3 != 0)
|
||||
.ToList();
|
||||
}
|
||||
if (considerCustomModifiers) {
|
||||
CustomPayment = ctx.CustomPayments.Find(p.Year, p.MgNr);
|
||||
}
|
||||
}
|
||||
}}
|
||||
|
@ -157,9 +157,9 @@
|
||||
@Raw(FormatRow($"Autom. Nachz. von GA ({Model.MemberAutoBusinessShares})", Model.MemberAutoBusinessSharesAmount, add: true));
|
||||
penalty += Model.MemberAutoBusinessSharesAmount;
|
||||
}
|
||||
@if (Model.CustomPayment != null) {
|
||||
@Raw(FormatRow(Model.CustomPayment.Comment ?? (Model.CustomPayment.Amount < 0 ? "Weitere Abzüge" : "Weitere Zuschläge"), Model.CustomPayment.Amount, add: true));
|
||||
penalty += Model.CustomPayment.Amount;
|
||||
@if (Model.CustomPayment?.Amount != null) {
|
||||
@Raw(FormatRow(Model.CustomPayment.Comment ?? ((Model.CustomPayment.Amount.Value) < 0 ? "Weitere Abzüge" : "Weitere Zuschläge"), Model.CustomPayment.Amount.Value, add: true));
|
||||
penalty += Model.CustomPayment.Amount.Value;
|
||||
}
|
||||
|
||||
@if (Model.Credit == null) {
|
||||
|
@ -9,7 +9,7 @@ namespace Elwig.Helpers {
|
||||
public static class AppDbUpdater {
|
||||
|
||||
// Don't forget to update value in Tests/fetch-resources.bat!
|
||||
public static readonly int RequiredSchemaVersion = 27;
|
||||
public static readonly int RequiredSchemaVersion = 28;
|
||||
|
||||
private static int VersionOffset = 0;
|
||||
|
||||
|
@ -29,6 +29,8 @@ namespace Elwig.Helpers.Billing {
|
||||
await CalculatePrices(cnx);
|
||||
if (Data.ConsiderDelieryModifiers) {
|
||||
await CalculateDeliveryModifiers(cnx);
|
||||
}
|
||||
if (Data.ConsiderCustomModifiers) {
|
||||
await CalculateMemberModifiers(cnx);
|
||||
}
|
||||
await tx.CommitAsync();
|
||||
@ -128,6 +130,16 @@ namespace Elwig.Helpers.Billing {
|
||||
mod_rel = mod_rel + excluded.mod_rel
|
||||
""");
|
||||
}
|
||||
await AppDbContext.ExecuteBatch(cnx, $"""
|
||||
INSERT INTO payment_member (year, avnr, mgnr, net_amount, mod_abs, mod_rel)
|
||||
SELECT x.year, {AvNr}, x.mgnr, 0, COALESCE(x.mod_abs * POW(10, s.precision - 2), 0), COALESCE(x.mod_rel, 0)
|
||||
FROM payment_custom x
|
||||
JOIN season s ON s.year = x.year
|
||||
WHERE x.year = {Year}
|
||||
ON CONFLICT DO UPDATE
|
||||
SET mod_abs = mod_abs + excluded.mod_abs,
|
||||
mod_rel = mod_rel + excluded.mod_rel
|
||||
""");
|
||||
}
|
||||
|
||||
protected async Task CalculatePrices(SqliteConnection cnx) {
|
||||
|
@ -11,12 +11,31 @@ namespace Elwig.Models.Entities {
|
||||
[Column("mgnr")]
|
||||
public int MgNr { get; set; }
|
||||
|
||||
[Column("amount")]
|
||||
public long AmountValue { get; set; }
|
||||
[Column("mod_abs")]
|
||||
public long? ModAbsValue { get; set; }
|
||||
[NotMapped]
|
||||
public decimal Amount {
|
||||
get => Utils.DecFromDb(AmountValue, 2);
|
||||
set => AmountValue = Utils.DecToDb(value, 2);
|
||||
public decimal? ModAbs {
|
||||
get => ModAbsValue != null ? Utils.DecFromDb(ModAbsValue.Value, 2) : null;
|
||||
set => ModAbsValue = value != null ? Utils.DecToDb(value.Value, 2) : null;
|
||||
}
|
||||
|
||||
[Column("mod_rel")]
|
||||
public double? ModRelValue { get; set; }
|
||||
[NotMapped]
|
||||
public decimal? ModRel {
|
||||
get => ModRelValue != null ? (decimal)ModRelValue.Value : null;
|
||||
set => ModRelValue = value != null ? (double)value.Value : null;
|
||||
}
|
||||
|
||||
[Column("mod_comment")]
|
||||
public string? ModComment { get; set; }
|
||||
|
||||
[Column("amount")]
|
||||
public long? AmountValue { get; set; }
|
||||
[NotMapped]
|
||||
public decimal? Amount {
|
||||
get => AmountValue != null ? Utils.DecFromDb(AmountValue.Value, 2) : null;
|
||||
set => AmountValue = value != null ? Utils.DecToDb(value.Value, 2) : null;
|
||||
}
|
||||
|
||||
[Column("comment")]
|
||||
|
13
Elwig/Resources/Sql/27-28.sql
Normal file
13
Elwig/Resources/Sql/27-28.sql
Normal file
@ -0,0 +1,13 @@
|
||||
-- schema version 27 to 28
|
||||
|
||||
ALTER TABLE payment_custom ADD COLUMN mod_abs INTEGER DEFAULT NULL;
|
||||
ALTER TABLE payment_custom ADD COLUMN mod_rel REAL DEFAULT NULL;
|
||||
ALTER TABLE payment_custom ADD COLUMN mod_comment TEXT DEFAULT NULL;
|
||||
|
||||
PRAGMA writable_schema = ON;
|
||||
|
||||
UPDATE sqlite_schema SET sql = REPLACE(REPLACE(sql, 'amount INTEGER NOT NULL', 'amount INTEGER DEFAULT NULL'), 'comment TEXT', 'comment TEXT DEFAULT NULL')
|
||||
WHERE type = 'table' AND name = 'payment_custom';
|
||||
|
||||
PRAGMA schema_version = 2701;
|
||||
PRAGMA writable_schema = OFF;
|
@ -6,7 +6,7 @@
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:Elwig.Windows"
|
||||
xmlns:ctrl="clr-namespace:Elwig.Controls"
|
||||
Title="Auszahlung anpassen - Elwig" Height="600" Width="1000" MinHeight="400" MinWidth="850">
|
||||
Title="Auszahlung anpassen - Elwig" Height="600" Width="1000" MinHeight="560" MinWidth="850">
|
||||
<Window.Resources>
|
||||
<ctrl:UnitConverter x:Key="CurrencyConverter" Precision="2" Unit="€"/>
|
||||
<ctrl:UnitConverter x:Key="WeightConverter" Precision="0" Unit="kg"/>
|
||||
@ -83,6 +83,20 @@
|
||||
</Style>
|
||||
</DataGridTextColumn.CellStyle>
|
||||
</DataGridTextColumn>
|
||||
<DataGridTextColumn Header="Abs." Binding="{Binding ModAbs, Converter={StaticResource CurrencyConverter}, StringFormat='{}{0} '}" Width="65">
|
||||
<DataGridTextColumn.CellStyle>
|
||||
<Style>
|
||||
<Setter Property="TextBlock.TextAlignment" Value="Right"/>
|
||||
</Style>
|
||||
</DataGridTextColumn.CellStyle>
|
||||
</DataGridTextColumn>
|
||||
<DataGridTextColumn Header="Rel." Binding="{Binding ModRel, StringFormat='{}{0:P2} '}" Width="65">
|
||||
<DataGridTextColumn.CellStyle>
|
||||
<Style>
|
||||
<Setter Property="TextBlock.TextAlignment" Value="Right"/>
|
||||
</Style>
|
||||
</DataGridTextColumn.CellStyle>
|
||||
</DataGridTextColumn>
|
||||
<DataGridTextColumn Header="Ü.-/U.-Lfrg." Binding="{Binding OverUnder, Converter={StaticResource WeightConverter},StringFormat='{}{0} '}" Width="70">
|
||||
<DataGridTextColumn.CellStyle>
|
||||
<Style>
|
||||
@ -118,7 +132,7 @@
|
||||
</Style>
|
||||
</DataGridTextColumn.CellStyle>
|
||||
</DataGridTextColumn>
|
||||
<DataGridTextColumn Header="Weitere" Binding="{Binding Custom, Converter={StaticResource CurrencyConverter}, StringFormat='{}{0} '}" Width="65">
|
||||
<DataGridTextColumn Header="Weitere" Binding="{Binding CustomAmount, Converter={StaticResource CurrencyConverter}, StringFormat='{}{0} '}" Width="65">
|
||||
<DataGridTextColumn.CellStyle>
|
||||
<Style>
|
||||
<Setter Property="TextBlock.TextAlignment" Value="Right"/>
|
||||
@ -139,7 +153,7 @@
|
||||
<GridSplitter Grid.Column="1" Grid.Row="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
|
||||
|
||||
<Grid Grid.Column="2" Grid.Row="1">
|
||||
<GroupBox Header="Benutzerdefinierte Zu-/Abschläge" Margin="5,10,10,10" Height="180" Width="365"
|
||||
<GroupBox Header="Benutzerdefinierte Zu-/Abschläge" Margin="5,10,10,10" Height="270" Width="365"
|
||||
VerticalAlignment="Top" HorizontalAlignment="Left">
|
||||
<Grid>
|
||||
<Label Content="Mitglied:" Margin="10,10,10,10"/>
|
||||
@ -153,12 +167,23 @@
|
||||
Margin="10,10,10,10" VerticalAlignment="Top" HorizontalAlignment="Right" ToolTip="Zu Mitglied springen"
|
||||
Click="MemberReferenceButton_Click"/>
|
||||
|
||||
<Label Content="Betrag:" Margin="10,40,10,10"/>
|
||||
<ctrl:UnitTextBox x:Name="CustomAmountInput" Width="80" Margin="70,40,10,10" Unit="€"
|
||||
<Label Content="Vor MwSt." Margin="70,37.5,10,10"/>
|
||||
<Label Content="Betrag:" Margin="10,60,10,10"/>
|
||||
<ctrl:UnitTextBox x:Name="CustomModAbsInput" Width="80" Margin="70,60,10,10" Unit="€"
|
||||
TextChanged="CustomModAbsInput_TextChanged"/>
|
||||
<ctrl:UnitTextBox x:Name="CustomModRelInput" Width="60" Margin="155,60,10,10" Unit="%"
|
||||
TextChanged="CustomModRelInput_TextChanged"/>
|
||||
|
||||
<Label Content="Freitext:" Margin="10,90,10,10"/>
|
||||
<TextBox x:Name="CustomModCommentInput" Margin="70,90,10,10"/>
|
||||
|
||||
<Label Content="Nach MwSt." Margin="70,117.5,10,10"/>
|
||||
<Label Content="Betrag:" Margin="10,140,10,10"/>
|
||||
<ctrl:UnitTextBox x:Name="CustomAmountInput" Width="80" Margin="70,140,10,10" Unit="€"
|
||||
TextChanged="CustomAmountInput_TextChanged"/>
|
||||
|
||||
<Label Content="Freitext:" Margin="10,70,10,10"/>
|
||||
<TextBox x:Name="CustomCommentInput" Margin="70,70,10,10"/>
|
||||
<Label Content="Freitext:" Margin="10,170,10,10"/>
|
||||
<TextBox x:Name="CustomCommentInput" Margin="70,170,10,10"/>
|
||||
|
||||
<Button x:Name="SaveCustomButton" Content="Speichern" Margin="0,0,125,10" Width="120"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Bottom"
|
||||
|
@ -103,7 +103,7 @@ namespace Elwig.Windows {
|
||||
}).Sum() : (decimal?)null,
|
||||
m.Adjust,
|
||||
m.AdjustAmount,
|
||||
Custom = CustomPayments!.GetValueOrDefault(m.MgNr, null)?.Amount,
|
||||
Custom = CustomPayments!.GetValueOrDefault(m.MgNr, null),
|
||||
})
|
||||
.Select(m => new {
|
||||
m.MgNr, m.Name, m.GivenName,
|
||||
@ -112,23 +112,25 @@ namespace Elwig.Windows {
|
||||
PenaltyAc = m.PenaltyAc == null ? (decimal?)null : Math.Round((decimal)m.PenaltyAc, 2),
|
||||
m.Adjust,
|
||||
AdjustAmount = m.AdjustAmount == null ? (decimal?)null : Math.Round((decimal)m.AdjustAmount, 2),
|
||||
m.Custom
|
||||
CustomAmount = m.Custom?.Amount,
|
||||
ModAbs = m.Custom?.ModAbs,
|
||||
ModRel = m.Custom?.ModRel,
|
||||
})
|
||||
.Select(m => new {
|
||||
m.MgNr, m.Name, m.GivenName,
|
||||
m.BusinessShares, m.Weight, m.OverUnder,
|
||||
m.PenaltyBs, m.PenaltyAc, m.Adjust, m.AdjustAmount, m.Custom,
|
||||
Total = (m.PenaltyBs ?? 0) + (m.PenaltyAc ?? 0) + (m.AdjustAmount ?? 0) + (m.Custom ?? 0),
|
||||
m.PenaltyBs, m.PenaltyAc, m.Adjust, m.AdjustAmount, m.CustomAmount, m.ModAbs, m.ModRel,
|
||||
Total = (m.PenaltyBs ?? 0) + (m.PenaltyAc ?? 0) + (m.AdjustAmount ?? 0) + (m.CustomAmount ?? 0),
|
||||
})
|
||||
.Select(m => new {
|
||||
m.MgNr, m.Name, m.GivenName,
|
||||
m.BusinessShares, m.Weight, m.OverUnder,
|
||||
m.PenaltyBs, m.PenaltyAc, m.Adjust, m.AdjustAmount, m.Custom,
|
||||
m.PenaltyBs, m.PenaltyAc, m.Adjust, m.AdjustAmount, m.CustomAmount, m.ModAbs, m.ModRel,
|
||||
m.Total,
|
||||
Background = m.Weight == 0 ? Brushes.Orange : m.Weight / 2 < -m.Total ? Brushes.Red : Brushes.White,
|
||||
Foreground = m.Total == 0 ? Brushes.Gray : Brushes.Black,
|
||||
})
|
||||
.Where(m => m.OverUnder != null || m.Adjust != null || m.PenaltyBs != null || m.PenaltyAc != null || m.Custom != null)
|
||||
.Where(m => m.OverUnder != null || m.Adjust != null || m.PenaltyBs != null || m.PenaltyAc != null || m.CustomAmount != null || m.ModAbs != null || m.ModRel != null)
|
||||
.OrderByDescending(m => m.OverUnder ?? 0)
|
||||
.ThenBy(m => m.Name)
|
||||
.ThenBy(m => m.GivenName)
|
||||
@ -141,7 +143,7 @@ namespace Elwig.Windows {
|
||||
PenaltyBusinessShares.Text = $"{list.Count(r => r.PenaltyBs != null && r.PenaltyBs != 0)} Mg. / {list.Sum(r => r.PenaltyBs):N2} {sym}";
|
||||
PenaltyAreaCommitments.Text = $"{list.Count(r => r.PenaltyAc != null && r.PenaltyAc != 0)} Mg. / {list.Sum(r => r.PenaltyAc):N2} {sym}";
|
||||
AutoBusinessShareAdjustment.Text = $"{list.Count(r => r.Adjust > 0)} Mg. / {list.Sum(r => r.Adjust)} GA / {list.Sum(r => r.AdjustAmount):N2} {sym}";
|
||||
CustomModifiers.Text = $"{list.Count(r => r.Custom != null)} Mg. / {list.Sum(r => r.Custom):N2} {sym}";
|
||||
CustomModifiers.Text = $"{list.Count(r => r.CustomAmount != null)} Mg. / {list.Sum(r => r.CustomAmount):N2} {sym}";
|
||||
TotalModifiers.Text = $"{list.Count(r => r.Total != 0)} Mg. / {list.Sum(r => r.Total):N2} {sym}";
|
||||
NonDeliveries.Text = $"{list.Count(r => r.Weight == 0):N0}";
|
||||
|
||||
@ -239,13 +241,22 @@ namespace Elwig.Windows {
|
||||
MemberList.ScrollIntoView(MemberList.SelectedItem);
|
||||
|
||||
if (CustomPayments?.TryGetValue(m.MgNr, out var p) == true) {
|
||||
CustomModAbsInput.Text = $"{p.ModAbs:N2}";
|
||||
CustomModRelInput.Text = $"{p.ModRel * 100:N2}";
|
||||
CustomModCommentInput.Text = p.ModComment ?? "";
|
||||
CustomAmountInput.Text = $"{p.Amount:N2}";
|
||||
CustomCommentInput.Text = p.Comment ?? "";
|
||||
} else {
|
||||
CustomModAbsInput.Text = "";
|
||||
CustomModRelInput.Text = "";
|
||||
CustomModCommentInput.Text = "";
|
||||
CustomAmountInput.Text = "";
|
||||
CustomCommentInput.Text = "";
|
||||
}
|
||||
} else {
|
||||
CustomModAbsInput.Text = "";
|
||||
CustomModRelInput.Text = "";
|
||||
CustomModCommentInput.Text = "";
|
||||
CustomAmountInput.Text = "";
|
||||
CustomCommentInput.Text = "";
|
||||
}
|
||||
@ -256,6 +267,14 @@ namespace Elwig.Windows {
|
||||
App.FocusMember(m.MgNr);
|
||||
}
|
||||
|
||||
private void CustomModAbsInput_TextChanged(object sender, TextChangedEventArgs evt) {
|
||||
Validator.CheckDecimal((TextBox)sender, false, 4, 2, true);
|
||||
}
|
||||
|
||||
private void CustomModRelInput_TextChanged(object sender, TextChangedEventArgs evt) {
|
||||
Validator.CheckDecimal((TextBox)sender, false, 3, 2, true);
|
||||
}
|
||||
|
||||
private void CustomAmountInput_TextChanged(object sender, TextChangedEventArgs evt) {
|
||||
Validator.CheckDecimal((TextBox)sender, false, 4, 2, true);
|
||||
}
|
||||
@ -268,12 +287,19 @@ namespace Elwig.Windows {
|
||||
if (CustomPayments?.TryGetValue(m.MgNr, out var p) == true) {
|
||||
ctx.Remove(p);
|
||||
}
|
||||
if (sender == SaveCustomButton && decimal.TryParse(CustomAmountInput.Text, out var num)) {
|
||||
if (sender == SaveCustomButton) {
|
||||
var modAbs = decimal.TryParse(CustomModAbsInput.Text, out var n1) ? (decimal?)n1 : null;
|
||||
var modRel = decimal.TryParse(CustomModRelInput.Text, out var n2) ? (decimal?)n2 / 100 : null;
|
||||
var amount = decimal.TryParse(CustomAmountInput.Text, out var n3) ? (decimal?)n3 : null;
|
||||
var modText = CustomModCommentInput.Text.Trim();
|
||||
var text = CustomCommentInput.Text.Trim();
|
||||
ctx.Add(new PaymentCustom {
|
||||
MgNr = m.MgNr,
|
||||
Year = Year,
|
||||
Amount = num,
|
||||
ModAbs = modAbs,
|
||||
ModRel = modRel,
|
||||
ModComment = modText == "" ? null : modText,
|
||||
Amount = amount,
|
||||
Comment = text == "" ? null : text,
|
||||
});
|
||||
}
|
||||
|
@ -128,6 +128,12 @@
|
||||
HorizontalAlignment="Left" VerticalAlignment="Top"
|
||||
TextChanged="WeightModifierInput_TextChanged" LostFocus="WeightModifierInput_LostFocus"/>
|
||||
|
||||
<TextBlock x:Name="MatzenNote" Grid.ColumnSpan="2" Margin="20,170,10,10" HorizontalAlignment="Left" VerticalAlignment="Top">
|
||||
Ob ein Mitglied den Treuebonus<LineBreak/>
|
||||
bekommt ist in der Buchungsliste<LineBreak/>
|
||||
(Spalte Zuschlag) ersichtlich.
|
||||
</TextBlock>
|
||||
|
||||
<Label Content="Berücksichtigen:" Margin="90,70,10,10" Grid.Column="1"/>
|
||||
<CheckBox x:Name="ConsiderModifiersInput" Content="Zu-/Abschläge bei Lieferungen"
|
||||
Margin="110,95,10,10" Grid.Column="1" HorizontalAlignment="Left" VerticalAlignment="Top"
|
||||
|
@ -44,6 +44,11 @@ namespace Elwig.Windows {
|
||||
if (!App.Config.Debug) {
|
||||
DataInput.Visibility = Visibility.Hidden;
|
||||
}
|
||||
if (App.Client.IsMatzen) {
|
||||
ConsiderCustomInput.Content = ConsiderCustomInput.Content.ToString()?.Replace("Benutzerdefinierte", "Bentz.def.").Replace("Mitglied", "Mg.") + " (inkl. Treuebonus)";
|
||||
} else {
|
||||
MatzenNote.Visibility = Visibility.Hidden;
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task OnRenewContext(AppDbContext ctx) {
|
||||
|
@ -1 +1 @@
|
||||
curl --fail -s -L "https://elwig.at/files/create.sql?v=27" -u "elwig:ganzGeheim123!" -o "Resources\Sql\Create.sql"
|
||||
curl --fail -s -L "https://elwig.at/files/create.sql?v=28" -u "elwig:ganzGeheim123!" -o "Resources\Sql\Create.sql"
|
||||
|
Reference in New Issue
Block a user