Implemented billing calculation.
This commit is contained in:
@@ -61,6 +61,7 @@
|
|||||||
<TabItem Visibility="Collapsed"
|
<TabItem Visibility="Collapsed"
|
||||||
Selector.Selected="OnDetailsTabSelected">
|
Selector.Selected="OnDetailsTabSelected">
|
||||||
<Frame Source="Pages/DetailsPage.xaml"
|
<Frame Source="Pages/DetailsPage.xaml"
|
||||||
|
LoadCompleted="OnDetailsFrameLoadCompleted"
|
||||||
x:Name="DetailsFrame"
|
x:Name="DetailsFrame"
|
||||||
Focusable="False" />
|
Focusable="False" />
|
||||||
</TabItem>
|
</TabItem>
|
||||||
|
|||||||
@@ -132,6 +132,12 @@ namespace UCalc
|
|||||||
page.ParentWindow = this;
|
page.ParentWindow = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnDetailsFrameLoadCompleted(object sender, NavigationEventArgs e)
|
||||||
|
{
|
||||||
|
var page = (DetailsPage) ((Frame) sender).Content;
|
||||||
|
page.Model = Model;
|
||||||
|
}
|
||||||
|
|
||||||
public bool Save(bool rename = false)
|
public bool Save(bool rename = false)
|
||||||
{
|
{
|
||||||
if (Model.Root.Errors.Count > 0)
|
if (Model.Root.Errors.Count > 0)
|
||||||
@@ -195,7 +201,7 @@ namespace UCalc
|
|||||||
|
|
||||||
private void OnDetailsTabSelected(object sender, RoutedEventArgs e)
|
private void OnDetailsTabSelected(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
((DetailsPage) DetailsFrame.Content).Compute(Model);
|
((DetailsPage) DetailsFrame.Content).Compute();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -108,28 +108,6 @@ namespace UCalc.Controls
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class DatePickerTextToDateTimeConverter : IValueConverter
|
|
||||||
{
|
|
||||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
|
||||||
{
|
|
||||||
var dateTime = (DateTime?) value;
|
|
||||||
|
|
||||||
return dateTime == null ? "" : dateTime.Value.ToString(Constants.DateFormat);
|
|
||||||
}
|
|
||||||
|
|
||||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
|
||||||
{
|
|
||||||
var str = (string) value;
|
|
||||||
|
|
||||||
if (DateTime.TryParseExact(str, Constants.DateFormat, null, DateTimeStyles.None, out var d))
|
|
||||||
{
|
|
||||||
return d;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class NameToTextConverter : IValueConverter
|
public class NameToTextConverter : IValueConverter
|
||||||
{
|
{
|
||||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ namespace UCalc.Controls
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsBetween(this DateTime dt, DateTime start, DateTime end)
|
public static bool IsBetween(this DateTime dt, DateTime start, DateTime end)
|
||||||
{
|
{
|
||||||
return dt >= start && dt <= end;
|
return dt >= start && dt <= end;
|
||||||
}
|
}
|
||||||
@@ -42,5 +42,25 @@ namespace UCalc.Controls
|
|||||||
{
|
{
|
||||||
return $"0.{new string('0', precision - optional)}{new string('#', optional)}";
|
return $"0.{new string('0', precision - optional)}{new string('#', optional)}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static decimal Ceil2(this decimal d, int precision = Constants.DisplayPrecision)
|
||||||
|
{
|
||||||
|
return Math.Ceiling(d * (decimal) Math.Pow(10, precision)) / (decimal) Math.Pow(10, precision);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string CeilToString(this decimal d, int precision = Constants.DisplayPrecision, int optional = 0)
|
||||||
|
{
|
||||||
|
return Ceil2(d).ToString(PrecisionToFormat(precision, optional));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string CeilAmountToString(this decimal d, int precision = Constants.DisplayPrecision)
|
||||||
|
{
|
||||||
|
return d.CeilToString(precision, precision - 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string CeilUnitCountToString(this decimal d, int precision = Constants.DisplayPrecision)
|
||||||
|
{
|
||||||
|
return d.CeilToString(precision, precision - 3);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -18,7 +18,6 @@
|
|||||||
<Window.Resources>
|
<Window.Resources>
|
||||||
<controls:NegateConverter x:Key="NegateConverter" />
|
<controls:NegateConverter x:Key="NegateConverter" />
|
||||||
<controls:FlatToAffectedConverter x:Key="FlatToAffectedConverter" />
|
<controls:FlatToAffectedConverter x:Key="FlatToAffectedConverter" />
|
||||||
<controls:DatePickerTextToDateTimeConverter x:Key="DatePickerTextToDateTimeConverter" />
|
|
||||||
<controls:EmptyMultiPropertyToVisibilityConverter x:Key="EmptyMultiPropertyToVisibilityConverter" />
|
<controls:EmptyMultiPropertyToVisibilityConverter x:Key="EmptyMultiPropertyToVisibilityConverter" />
|
||||||
</Window.Resources>
|
</Window.Resources>
|
||||||
|
|
||||||
@@ -120,7 +119,7 @@
|
|||||||
Foreground="{x:Static local:Constants.SubMainColor}" />
|
Foreground="{x:Static local:Constants.SubMainColor}" />
|
||||||
|
|
||||||
<CheckBox
|
<CheckBox
|
||||||
IsChecked="{Binding Path=Cost.IncludeUnrented.Value, RelativeSource={RelativeSource AncestorType=local:CostWindow}, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" />
|
IsChecked="{Binding Path=Cost.ShiftUnrented.Value, RelativeSource={RelativeSource AncestorType=local:CostWindow}, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" />
|
||||||
|
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
|
|
||||||
@@ -246,9 +245,7 @@
|
|||||||
|
|
||||||
<DatePicker VerticalAlignment="Center"
|
<DatePicker VerticalAlignment="Center"
|
||||||
MinHeight="22"
|
MinHeight="22"
|
||||||
DisplayDateStart="{Binding Path=Model.StartDate, RelativeSource={RelativeSource AncestorType=local:CostWindow}, Mode=OneWay}"
|
SelectedDate="{Binding Path=StartDate.Value, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" />
|
||||||
DisplayDateEnd="{Binding Path=Model.EndDate, RelativeSource={RelativeSource AncestorType=local:CostWindow}, Mode=OneWay}"
|
|
||||||
Text="{Binding Path=StartDate.Value, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay, Converter={StaticResource DatePickerTextToDateTimeConverter}}" />
|
|
||||||
|
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
|
|
||||||
@@ -264,9 +261,7 @@
|
|||||||
|
|
||||||
<DatePicker VerticalAlignment="Center"
|
<DatePicker VerticalAlignment="Center"
|
||||||
MinHeight="22"
|
MinHeight="22"
|
||||||
DisplayDateStart="{Binding Path=Model.StartDate, RelativeSource={RelativeSource AncestorType=local:CostWindow}, Mode=OneWay}"
|
SelectedDate="{Binding Path=EndDate.Value, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" />
|
||||||
DisplayDateEnd="{Binding Path=Model.EndDate, RelativeSource={RelativeSource AncestorType=local:CostWindow}, Mode=OneWay}"
|
|
||||||
Text="{Binding Path=EndDate.Value, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay, Converter={StaticResource DatePickerTextToDateTimeConverter}}" />
|
|
||||||
|
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
|
|
||||||
|
|||||||
@@ -472,8 +472,8 @@ namespace UCalc.Data
|
|||||||
[JsonProperty(PropertyName = "affectsAll"), JsonRequired]
|
[JsonProperty(PropertyName = "affectsAll"), JsonRequired]
|
||||||
public bool AffectsAll { get; set; }
|
public bool AffectsAll { get; set; }
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "includeUnrented"), JsonRequired]
|
[JsonProperty(PropertyName = "shiftUnrented"), JsonRequired]
|
||||||
public bool IncludeUnrented { get; set; }
|
public bool ShiftUnrented { get; set; }
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "affectedFlats"), JsonRequired, JsonConverter(typeof(FlatSerializationConverter))]
|
[JsonProperty(PropertyName = "affectedFlats"), JsonRequired, JsonConverter(typeof(FlatSerializationConverter))]
|
||||||
public HashSet<Flat> AffectedFlats { get; set; }
|
public HashSet<Flat> AffectedFlats { get; set; }
|
||||||
@@ -494,7 +494,7 @@ namespace UCalc.Data
|
|||||||
private bool Equals(Cost other)
|
private bool Equals(Cost other)
|
||||||
{
|
{
|
||||||
return Name == other.Name && Division == other.Division && AffectsAll == other.AffectsAll &&
|
return Name == other.Name && Division == other.Division && AffectsAll == other.AffectsAll &&
|
||||||
IncludeUnrented == other.IncludeUnrented && AffectedFlats.SequenceEqual(other.AffectedFlats) &&
|
ShiftUnrented == other.ShiftUnrented && AffectedFlats.SequenceEqual(other.AffectedFlats) &&
|
||||||
Entries.SequenceEqual(other.Entries) && DisplayInBill == other.DisplayInBill;
|
Entries.SequenceEqual(other.Entries) && DisplayInBill == other.DisplayInBill;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -512,7 +512,7 @@ namespace UCalc.Data
|
|||||||
Name = Name,
|
Name = Name,
|
||||||
Division = Division,
|
Division = Division,
|
||||||
AffectsAll = AffectsAll,
|
AffectsAll = AffectsAll,
|
||||||
IncludeUnrented = IncludeUnrented,
|
ShiftUnrented = ShiftUnrented,
|
||||||
AffectedFlats = new HashSet<Flat>(AffectedFlats.Select(flat => flatMapper[flat])),
|
AffectedFlats = new HashSet<Flat>(AffectedFlats.Select(flat => flatMapper[flat])),
|
||||||
Entries = new List<CostEntry>(Entries.Select(entry => entry.Clone())),
|
Entries = new List<CostEntry>(Entries.Select(entry => entry.Clone())),
|
||||||
DisplayInBill = DisplayInBill
|
DisplayInBill = DisplayInBill
|
||||||
|
|||||||
@@ -0,0 +1,631 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using UCalc.Controls;
|
||||||
|
|
||||||
|
namespace UCalc.Data
|
||||||
|
{
|
||||||
|
public class TenantCalculationResult
|
||||||
|
{
|
||||||
|
public Tenant Tenant { get; }
|
||||||
|
public IReadOnlyDictionary<Cost, CostCalculationResult> Costs { get; }
|
||||||
|
public decimal TotalAmount { get; }
|
||||||
|
public string Details { get; }
|
||||||
|
public string DetailsForLandlord { get; }
|
||||||
|
|
||||||
|
public TenantCalculationResult(Tenant tenant, IReadOnlyDictionary<Cost, CostCalculationResult> costs,
|
||||||
|
decimal totalAmount, string details, string detailsForLandlord)
|
||||||
|
{
|
||||||
|
Tenant = tenant;
|
||||||
|
Costs = costs;
|
||||||
|
TotalAmount = totalAmount;
|
||||||
|
Details = details;
|
||||||
|
DetailsForLandlord = detailsForLandlord;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CostCalculationResult
|
||||||
|
{
|
||||||
|
public decimal TotalAmount { get; }
|
||||||
|
public string Details { get; }
|
||||||
|
public string DetailsForLandlord { get; }
|
||||||
|
public bool AffectsTenant { get; }
|
||||||
|
|
||||||
|
public CostCalculationResult(decimal totalAmount, string details, string detailsForLandlord, bool affectsTenant)
|
||||||
|
{
|
||||||
|
TotalAmount = totalAmount;
|
||||||
|
Details = details;
|
||||||
|
DetailsForLandlord = detailsForLandlord;
|
||||||
|
AffectsTenant = affectsTenant;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class BillingCalculator
|
||||||
|
{
|
||||||
|
private class EventCause
|
||||||
|
{
|
||||||
|
public Tenant Tenant { get; }
|
||||||
|
public bool Entry { get; }
|
||||||
|
|
||||||
|
public EventCause(Tenant tenant, bool entry)
|
||||||
|
{
|
||||||
|
Tenant = tenant;
|
||||||
|
Entry = entry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Event
|
||||||
|
{
|
||||||
|
public DateTime Date { get; }
|
||||||
|
public List<EventCause> Causes { get; }
|
||||||
|
|
||||||
|
public string Cause
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (Causes.Count == 0)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.Join(" / ",
|
||||||
|
Causes.Select(cause =>
|
||||||
|
cause.Entry
|
||||||
|
? $"Einzug von \"{cause.Tenant.Name}\""
|
||||||
|
: $"Auszug von \"{cause.Tenant.Name}\""));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Event(DateTime date, List<EventCause> causes)
|
||||||
|
{
|
||||||
|
Date = date;
|
||||||
|
Causes = causes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool AffectsTenant(Tenant tenant, Cost cost)
|
||||||
|
{
|
||||||
|
return cost.AffectsAll || cost.AffectedFlats.Intersect(tenant.RentedFlats).Any();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<Tenant> AffectedTenants(Billing billing, Cost cost)
|
||||||
|
{
|
||||||
|
if (cost.AffectsAll)
|
||||||
|
{
|
||||||
|
return billing.Tenants;
|
||||||
|
}
|
||||||
|
|
||||||
|
return billing.Tenants.Where(tenant => tenant.RentedFlats.Any(flat => cost.AffectedFlats.Contains(flat)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<Event> CalculateEvents(Billing billing, IEnumerable<Tenant> affectedTenants)
|
||||||
|
{
|
||||||
|
var events = new Dictionary<DateTime, Event>();
|
||||||
|
|
||||||
|
void AddEvent(DateTime date, Tenant tenant, bool entry)
|
||||||
|
{
|
||||||
|
if (events.TryGetValue(date, out var @event))
|
||||||
|
{
|
||||||
|
@event.Causes.Add(new EventCause(tenant, entry));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
events.Add(date, new Event(date, new List<EventCause> {new EventCause(tenant, entry)}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var tenant in affectedTenants)
|
||||||
|
{
|
||||||
|
if (tenant.EntryDate != null && tenant.EntryDate.Value.IsBetween(billing.StartDate, billing.EndDate))
|
||||||
|
{
|
||||||
|
AddEvent(tenant.EntryDate.Value, tenant, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tenant.DepartureDate != null &&
|
||||||
|
tenant.DepartureDate.Value.IsBetween(billing.StartDate, billing.EndDate))
|
||||||
|
{
|
||||||
|
AddEvent(tenant.DepartureDate.Value, tenant, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return events.Values.OrderBy(@event => @event.Date).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int GetFirstAffectedEvent(DateTime date, IReadOnlyList<Event> events)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < events.Count; ++i)
|
||||||
|
{
|
||||||
|
if (events[i].Date > date)
|
||||||
|
{
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return events.Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Tuple<DateTime, DateTime> GetEffectiveCostEntryStartAndEnd(Billing billing, Tenant tenant,
|
||||||
|
CostEntry entry, out bool coversPast, out bool coversFuture)
|
||||||
|
{
|
||||||
|
coversPast = false;
|
||||||
|
coversFuture = false;
|
||||||
|
|
||||||
|
var entryStartDate = entry.StartDate;
|
||||||
|
var entryEndDate = entry.EndDate;
|
||||||
|
|
||||||
|
if (entryEndDate < billing.StartDate || entryStartDate > billing.EndDate)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entryStartDate < billing.StartDate)
|
||||||
|
{
|
||||||
|
coversPast = true;
|
||||||
|
entryStartDate = billing.StartDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entryEndDate > billing.EndDate)
|
||||||
|
{
|
||||||
|
coversFuture = true;
|
||||||
|
entryEndDate = billing.EndDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tenant.EntryDate != null)
|
||||||
|
{
|
||||||
|
if (tenant.EntryDate > entryEndDate)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tenant.EntryDate > entryStartDate)
|
||||||
|
{
|
||||||
|
entryStartDate = tenant.EntryDate.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tenant.DepartureDate != null)
|
||||||
|
{
|
||||||
|
if (tenant.DepartureDate < entryStartDate)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tenant.DepartureDate < entryEndDate)
|
||||||
|
{
|
||||||
|
entryEndDate = tenant.DepartureDate.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Tuple<DateTime, DateTime>(entryStartDate, entryEndDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static decimal CalculateCostPerDay(CostEntry entry, StringBuilder details)
|
||||||
|
{
|
||||||
|
decimal totalAmount;
|
||||||
|
|
||||||
|
if (entry.Details.TotalAmount != 0)
|
||||||
|
{
|
||||||
|
var amountPerUnit = entry.Details.TotalAmount / entry.Details.UnitCount;
|
||||||
|
details.Append("Gesamtverbrauch = ");
|
||||||
|
details.Append(entry.Details.UnitCount.CeilUnitCountToString(Constants.InternalPrecision));
|
||||||
|
details.Append(" m³\n");
|
||||||
|
details.Append("Preis pro m³ = ");
|
||||||
|
details.Append(entry.Details.TotalAmount.CeilAmountToString(Constants.InternalPrecision));
|
||||||
|
details.Append(" € ÷ ");
|
||||||
|
details.Append(entry.Details.UnitCount.CeilUnitCountToString(Constants.InternalPrecision));
|
||||||
|
details.Append(" m³ ≈ ");
|
||||||
|
details.Append(amountPerUnit.CeilAmountToString(Constants.InternalPrecision));
|
||||||
|
details.Append(" €/m³\n");
|
||||||
|
|
||||||
|
var t = entry.Details.UnitCount;
|
||||||
|
details.Append("Verbrauch mit Abzügen = ");
|
||||||
|
details.Append(entry.Details.UnitCount.CeilUnitCountToString(Constants.InternalPrecision));
|
||||||
|
details.Append(" m³");
|
||||||
|
|
||||||
|
foreach (var discountInUnits in entry.Details.DiscountsInUnits)
|
||||||
|
{
|
||||||
|
if (discountInUnits != 0)
|
||||||
|
{
|
||||||
|
details.Append(" - ");
|
||||||
|
details.Append(discountInUnits.CeilUnitCountToString(Constants.InternalPrecision));
|
||||||
|
details.Append(" m³");
|
||||||
|
}
|
||||||
|
|
||||||
|
t -= discountInUnits;
|
||||||
|
}
|
||||||
|
|
||||||
|
details.Append(" ≈ ");
|
||||||
|
details.Append(t.CeilUnitCountToString(Constants.InternalPrecision));
|
||||||
|
details.Append(" m³\n");
|
||||||
|
|
||||||
|
totalAmount = t * amountPerUnit;
|
||||||
|
details.Append("Kosten = ");
|
||||||
|
details.Append(t.CeilUnitCountToString(Constants.InternalPrecision));
|
||||||
|
details.Append(" m³ · ");
|
||||||
|
details.Append(amountPerUnit.CeilAmountToString(Constants.InternalPrecision));
|
||||||
|
details.Append(" €/m³ ≈ ");
|
||||||
|
details.Append(totalAmount.CeilAmountToString(Constants.InternalPrecision));
|
||||||
|
details.Append(" €\n");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
totalAmount = entry.Amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
var costPerDay = totalAmount / ((entry.EndDate - entry.StartDate).Days + 1);
|
||||||
|
details.Append("Kosten pro Tag = ");
|
||||||
|
details.Append(totalAmount.CeilAmountToString(Constants.InternalPrecision));
|
||||||
|
details.Append(" € ÷ ");
|
||||||
|
details.Append(((entry.EndDate - entry.StartDate).Days + 1).ToString());
|
||||||
|
details.Append(" Tage ≈ ");
|
||||||
|
details.Append(costPerDay.CeilAmountToString(Constants.InternalPrecision));
|
||||||
|
details.Append(" €\n\n");
|
||||||
|
|
||||||
|
return costPerDay;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<Flat> AffectedFlats(this Cost cost, Billing billing)
|
||||||
|
{
|
||||||
|
return cost.AffectsAll ? (IEnumerable<Flat>) billing.House.Flats : cost.AffectedFlats;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<Flat> AffectedFlats(this Cost cost, Billing billing, DateTime spanStartDate,
|
||||||
|
DateTime spanEndDate)
|
||||||
|
{
|
||||||
|
var flats = cost.AffectedFlats(billing);
|
||||||
|
|
||||||
|
if (cost.ShiftUnrented)
|
||||||
|
{
|
||||||
|
flats = flats.Where(flat => flat.IsRented(billing, spanStartDate, spanEndDate));
|
||||||
|
}
|
||||||
|
|
||||||
|
return flats;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsRentedBy(this Flat flat, Tenant tenant, DateTime spanStartDate, DateTime spanEndDate)
|
||||||
|
{
|
||||||
|
return tenant.RentedFlats.Contains(flat) &&
|
||||||
|
(tenant.EntryDate == null || tenant.EntryDate.Value <= spanEndDate) &&
|
||||||
|
(tenant.DepartureDate == null || tenant.DepartureDate.Value >= spanStartDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsRented(this Flat flat, Billing billing, DateTime spanStartDate, DateTime spanEndDate)
|
||||||
|
{
|
||||||
|
return billing.Tenants.Any(tenant => flat.IsRentedBy(tenant, spanStartDate, spanEndDate));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int AffectedPersonCount(this Cost cost, Billing billing, DateTime spanStartDate,
|
||||||
|
DateTime spanEndDate)
|
||||||
|
{
|
||||||
|
return cost.AffectedFlats(billing).Select(flat =>
|
||||||
|
billing.Tenants.Select(tenant =>
|
||||||
|
flat.IsRentedBy(tenant, spanStartDate, spanEndDate) ? tenant.PersonCount : 0).Sum()).Sum();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static decimal AffectedSize(this Cost cost, Billing billing, DateTime spanStartDate,
|
||||||
|
DateTime spanEndDate)
|
||||||
|
{
|
||||||
|
return cost.AffectedFlats(billing, spanStartDate, spanEndDate)
|
||||||
|
.Aggregate((decimal) 0, (sum, flat) => sum + flat.Size);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int AffectedFlatCount(this Cost cost, Billing billing, DateTime spanStartDate,
|
||||||
|
DateTime spanEndDate)
|
||||||
|
{
|
||||||
|
return cost.AffectedFlats(billing, spanStartDate, spanEndDate).Count();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static decimal CalculateCostEntryForTimeSpan(Billing billing, Tenant tenant, Cost cost,
|
||||||
|
StringBuilder details, DateTime spanStartDate, DateTime spanEndDate, string eventCause, decimal costPerDay)
|
||||||
|
{
|
||||||
|
details.Append("Von ");
|
||||||
|
details.Append(spanStartDate.ToString(Constants.DateFormat));
|
||||||
|
details.Append(" bis ");
|
||||||
|
details.Append(spanEndDate.ToString(Constants.DateFormat));
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(eventCause))
|
||||||
|
{
|
||||||
|
details.Append(" (");
|
||||||
|
details.Append(eventCause);
|
||||||
|
details.Append(")");
|
||||||
|
}
|
||||||
|
|
||||||
|
details.Append(":\n");
|
||||||
|
|
||||||
|
decimal amount;
|
||||||
|
switch (cost.Division)
|
||||||
|
{
|
||||||
|
case CostDivision.Person:
|
||||||
|
var totalPersonCount = cost.AffectedPersonCount(billing, spanStartDate, spanEndDate);
|
||||||
|
var personCount = tenant.PersonCount;
|
||||||
|
amount = costPerDay * ((spanEndDate - spanStartDate).Days + 1) / totalPersonCount * personCount;
|
||||||
|
|
||||||
|
details.Append("Zwischensumme = ");
|
||||||
|
details.Append(costPerDay.CeilAmountToString(Constants.InternalPrecision));
|
||||||
|
details.Append(" € · ");
|
||||||
|
details.Append(((spanEndDate - spanStartDate).Days + 1).ToString());
|
||||||
|
details.Append(" Tage ÷ ");
|
||||||
|
details.Append(totalPersonCount.ToString());
|
||||||
|
details.Append(" Personen · ");
|
||||||
|
details.Append(personCount.ToString());
|
||||||
|
details.Append(" Personen ≈ ");
|
||||||
|
details.Append(amount.CeilAmountToString(Constants.InternalPrecision));
|
||||||
|
details.Append(" €\n\n");
|
||||||
|
|
||||||
|
return amount;
|
||||||
|
case CostDivision.Flat:
|
||||||
|
var totalFlatCount = cost.AffectedFlatCount(billing, spanStartDate, spanEndDate);
|
||||||
|
var flatCount = tenant.RentedFlats.Count;
|
||||||
|
amount = costPerDay * ((spanEndDate - spanStartDate).Days + 1) / totalFlatCount * flatCount;
|
||||||
|
|
||||||
|
details.Append("Zwischensumme = ");
|
||||||
|
details.Append(costPerDay.CeilAmountToString(Constants.InternalPrecision));
|
||||||
|
details.Append(" € · ");
|
||||||
|
details.Append(((spanEndDate - spanStartDate).Days + 1).ToString());
|
||||||
|
details.Append(" Tage ÷ ");
|
||||||
|
details.Append(totalFlatCount.ToString());
|
||||||
|
if (cost.ShiftUnrented)
|
||||||
|
{
|
||||||
|
details.Append(" bewohnte");
|
||||||
|
}
|
||||||
|
|
||||||
|
details.Append(" Wohnungen · ");
|
||||||
|
details.Append(flatCount.ToString());
|
||||||
|
details.Append(" Wohnungen ≈ ");
|
||||||
|
details.Append(amount.CeilAmountToString(Constants.InternalPrecision));
|
||||||
|
details.Append(" €\n\n");
|
||||||
|
|
||||||
|
return amount;
|
||||||
|
case CostDivision.Size:
|
||||||
|
var totalSize = cost.AffectedSize(billing, spanStartDate, spanEndDate);
|
||||||
|
var size = tenant.RentedFlats.Aggregate((decimal) 0, (sum, flat) => sum + flat.Size);
|
||||||
|
|
||||||
|
amount = costPerDay * ((spanEndDate - spanStartDate).Days + 1) / totalSize * size;
|
||||||
|
details.Append("Zwischensumme = ");
|
||||||
|
details.Append(costPerDay.CeilAmountToString(Constants.InternalPrecision));
|
||||||
|
details.Append(" € · ");
|
||||||
|
details.Append(((spanEndDate - spanStartDate).Days + 1).ToString());
|
||||||
|
details.Append(" Tage ÷ ");
|
||||||
|
details.Append(totalSize.CeilUnitCountToString(Constants.InternalPrecision));
|
||||||
|
details.Append(" m² · ");
|
||||||
|
details.Append(size.CeilUnitCountToString(Constants.InternalPrecision));
|
||||||
|
details.Append(" m² ≈ ");
|
||||||
|
details.Append(amount.CeilAmountToString(Constants.InternalPrecision));
|
||||||
|
details.Append(" €\n\n");
|
||||||
|
|
||||||
|
return amount;
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static decimal CalculateCostEntry(Billing billing, Tenant tenant, Cost cost, CostEntry entry,
|
||||||
|
IReadOnlyList<Event> events, StringBuilder details, ICollection<CostEntry> pastCoveringEntries,
|
||||||
|
ICollection<CostEntry> futureCoveringEntries)
|
||||||
|
{
|
||||||
|
var entryDates =
|
||||||
|
GetEffectiveCostEntryStartAndEnd(billing, tenant, entry, out var coversPast, out var coversFuture);
|
||||||
|
|
||||||
|
if (coversPast)
|
||||||
|
{
|
||||||
|
pastCoveringEntries.Add(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (coversFuture)
|
||||||
|
{
|
||||||
|
futureCoveringEntries.Add(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entryDates == null)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var (entryStartDate, entryEndDate) = entryDates;
|
||||||
|
var index = GetFirstAffectedEvent(entryStartDate, events);
|
||||||
|
var costPerDay = CalculateCostPerDay(entry, details);
|
||||||
|
|
||||||
|
decimal totalAmount = 0;
|
||||||
|
var prevDate = entryStartDate;
|
||||||
|
|
||||||
|
while (index < events.Count && events[index].Date <= entryEndDate)
|
||||||
|
{
|
||||||
|
var newDate = events[index].Date;
|
||||||
|
|
||||||
|
if (prevDate != newDate)
|
||||||
|
{
|
||||||
|
totalAmount += CalculateCostEntryForTimeSpan(billing, tenant, cost, details, prevDate,
|
||||||
|
newDate, events[index].Cause, costPerDay);
|
||||||
|
newDate = newDate.AddDays(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
prevDate = newDate;
|
||||||
|
++index;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prevDate <= entryEndDate)
|
||||||
|
{
|
||||||
|
totalAmount += CalculateCostEntryForTimeSpan(billing, tenant, cost, details, prevDate,
|
||||||
|
entryEndDate, null, costPerDay);
|
||||||
|
}
|
||||||
|
|
||||||
|
return totalAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void CalculateCostInPast(Billing billing, Tenant tenant, Cost cost, StringBuilder details,
|
||||||
|
IEnumerable<CostEntry> pastCoveringEntries)
|
||||||
|
{
|
||||||
|
details.Append("In vergangener Abrechnung bereits gezahlt (geschätzt) = ");
|
||||||
|
decimal totalAmount = 0;
|
||||||
|
var dummy = new StringBuilder();
|
||||||
|
|
||||||
|
foreach (var entry in pastCoveringEntries)
|
||||||
|
{
|
||||||
|
var entryStartDate = entry.StartDate;
|
||||||
|
var entryEndDate = billing.StartDate.AddDays(-1);
|
||||||
|
|
||||||
|
if (tenant.EntryDate != null)
|
||||||
|
{
|
||||||
|
if (tenant.EntryDate.Value > entryEndDate)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tenant.EntryDate.Value > entryStartDate)
|
||||||
|
{
|
||||||
|
entryStartDate = tenant.EntryDate.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tenant.DepartureDate != null)
|
||||||
|
{
|
||||||
|
if (tenant.DepartureDate.Value < entryStartDate)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tenant.DepartureDate.Value < entryEndDate)
|
||||||
|
{
|
||||||
|
entryEndDate = tenant.DepartureDate.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var costPerDay = CalculateCostPerDay(entry, dummy);
|
||||||
|
totalAmount += CalculateCostEntryForTimeSpan(billing, tenant, cost, dummy, entryStartDate,
|
||||||
|
entryEndDate, null, costPerDay);
|
||||||
|
}
|
||||||
|
|
||||||
|
details.Append(totalAmount.CeilToString());
|
||||||
|
details.Append(" €\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void CalculateCostInFuture(Billing billing, Tenant tenant, Cost cost, StringBuilder details,
|
||||||
|
IEnumerable<CostEntry> futureCoveringEntries)
|
||||||
|
{
|
||||||
|
details.Append("In nächster Abrechnung erwartet (geschätzt) = ");
|
||||||
|
decimal totalAmount = 0;
|
||||||
|
var dummy = new StringBuilder();
|
||||||
|
|
||||||
|
foreach (var entry in futureCoveringEntries)
|
||||||
|
{
|
||||||
|
var entryStartDate = billing.EndDate.AddDays(1);
|
||||||
|
var entryEndDate = entry.EndDate;
|
||||||
|
|
||||||
|
if (tenant.EntryDate != null)
|
||||||
|
{
|
||||||
|
if (tenant.EntryDate.Value > entryEndDate)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tenant.EntryDate.Value > entryStartDate)
|
||||||
|
{
|
||||||
|
entryStartDate = tenant.EntryDate.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tenant.DepartureDate != null)
|
||||||
|
{
|
||||||
|
if (tenant.DepartureDate.Value < entryStartDate)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tenant.DepartureDate.Value < entryEndDate)
|
||||||
|
{
|
||||||
|
entryEndDate = tenant.DepartureDate.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var costPerDay = CalculateCostPerDay(entry, dummy);
|
||||||
|
totalAmount += CalculateCostEntryForTimeSpan(billing, tenant, cost, dummy, entryStartDate,
|
||||||
|
entryEndDate, null, costPerDay);
|
||||||
|
}
|
||||||
|
|
||||||
|
details.Append(totalAmount.CeilToString());
|
||||||
|
details.Append(" €\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static CostCalculationResult CalculateCost(Billing billing, Tenant tenant, Cost cost)
|
||||||
|
{
|
||||||
|
if (!AffectsTenant(tenant, cost))
|
||||||
|
{
|
||||||
|
return new CostCalculationResult(0, "", "", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
var details = new StringBuilder("Kostenpunkt: ");
|
||||||
|
details.Append(cost.Name);
|
||||||
|
details.Append("\n\n");
|
||||||
|
|
||||||
|
var affectedTenants = AffectedTenants(billing, cost);
|
||||||
|
var events = CalculateEvents(billing, affectedTenants);
|
||||||
|
|
||||||
|
var pastCoveringEntries = new List<CostEntry>();
|
||||||
|
var futureCoveringEntries = new List<CostEntry>();
|
||||||
|
decimal totalAmount = 0;
|
||||||
|
|
||||||
|
foreach (var entry in cost.Entries)
|
||||||
|
{
|
||||||
|
totalAmount += CalculateCostEntry(billing, tenant, cost, entry, events, details, pastCoveringEntries,
|
||||||
|
futureCoveringEntries);
|
||||||
|
}
|
||||||
|
|
||||||
|
totalAmount = totalAmount.Ceil2();
|
||||||
|
details.Append("Betrag = ");
|
||||||
|
details.Append(totalAmount.CeilToString());
|
||||||
|
details.Append(" €\n");
|
||||||
|
|
||||||
|
var detailsLandlord = new StringBuilder(details.ToString());
|
||||||
|
CalculateCostInPast(billing, tenant, cost, detailsLandlord, pastCoveringEntries);
|
||||||
|
CalculateCostInFuture(billing, tenant, cost, detailsLandlord, futureCoveringEntries);
|
||||||
|
details.Append("\n");
|
||||||
|
detailsLandlord.Append("\n");
|
||||||
|
|
||||||
|
return new CostCalculationResult(totalAmount, details.ToString(), detailsLandlord.ToString(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TenantCalculationResult CalculateForTenant(Billing billing, Tenant tenant)
|
||||||
|
{
|
||||||
|
var costResults = new Dictionary<Cost, CostCalculationResult>();
|
||||||
|
var details = new StringBuilder();
|
||||||
|
var detailsLandlord = new StringBuilder();
|
||||||
|
|
||||||
|
decimal totalAmount = 0;
|
||||||
|
|
||||||
|
foreach (var cost in billing.Costs)
|
||||||
|
{
|
||||||
|
var costResult = CalculateCost(billing, tenant, cost);
|
||||||
|
if (!costResult.AffectsTenant)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
costResults.Add(cost, costResult);
|
||||||
|
|
||||||
|
totalAmount += costResult.TotalAmount;
|
||||||
|
|
||||||
|
details.Append(costResult.Details);
|
||||||
|
details.Append("---\n\n");
|
||||||
|
|
||||||
|
detailsLandlord.Append(costResult.DetailsForLandlord);
|
||||||
|
detailsLandlord.Append("---\n\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var str = $"Zwischensumme: {totalAmount.CeilToString()} €\n";
|
||||||
|
str += $"Bereits bezahlt: {tenant.PaidRent.CeilToString()} €\n\n";
|
||||||
|
|
||||||
|
totalAmount -= tenant.PaidRent;
|
||||||
|
totalAmount = totalAmount.Ceil2();
|
||||||
|
|
||||||
|
str += $"Summe: {totalAmount.CeilToString()} €";
|
||||||
|
|
||||||
|
details.Append(str);
|
||||||
|
detailsLandlord.Append(str);
|
||||||
|
|
||||||
|
return new TenantCalculationResult(tenant, costResults, totalAmount, details.ToString(),
|
||||||
|
detailsLandlord.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -107,7 +107,7 @@ namespace UCalc.Models
|
|||||||
public NotEmptyStringProperty Name { get; }
|
public NotEmptyStringProperty Name { get; }
|
||||||
public AlwaysValidProperty<int> Division { get; }
|
public AlwaysValidProperty<int> Division { get; }
|
||||||
public AffectsAllProperty AffectsAll { get; }
|
public AffectsAllProperty AffectsAll { get; }
|
||||||
public AlwaysValidProperty<bool> IncludeUnrented { get; }
|
public AlwaysValidProperty<bool> ShiftUnrented { get; }
|
||||||
public AffectedFlatsProperty AffectedFlats { get; }
|
public AffectedFlatsProperty AffectedFlats { get; }
|
||||||
public CostEntriesProperty Entries { get; }
|
public CostEntriesProperty Entries { get; }
|
||||||
public AlwaysValidProperty<bool> DisplayInBill { get; }
|
public AlwaysValidProperty<bool> DisplayInBill { get; }
|
||||||
@@ -119,8 +119,8 @@ namespace UCalc.Models
|
|||||||
Division = Add(new AlwaysValidProperty<int>(model, this, "Aufteilung", (int) cost.Division));
|
Division = Add(new AlwaysValidProperty<int>(model, this, "Aufteilung", (int) cost.Division));
|
||||||
AffectsAll = Add(new AffectsAllProperty(model, this, "Betrifft alle", cost.AffectsAll));
|
AffectsAll = Add(new AffectsAllProperty(model, this, "Betrifft alle", cost.AffectsAll));
|
||||||
AffectedFlats = Add(new AffectedFlatsProperty(model, this, cost.AffectedFlats, flatToProperty));
|
AffectedFlats = Add(new AffectedFlatsProperty(model, this, cost.AffectedFlats, flatToProperty));
|
||||||
IncludeUnrented =
|
ShiftUnrented =
|
||||||
Add(new AlwaysValidProperty<bool>(model, this, "Unvermietete einbeziehen", cost.IncludeUnrented));
|
Add(new AlwaysValidProperty<bool>(model, this, "Unvermietete einbeziehen", cost.ShiftUnrented));
|
||||||
Entries = Add(new CostEntriesProperty(model, this, cost.Entries));
|
Entries = Add(new CostEntriesProperty(model, this, cost.Entries));
|
||||||
DisplayInBill = Add(new AlwaysValidProperty<bool>(model, this, "In Rechnung anzeigen", cost.DisplayInBill));
|
DisplayInBill = Add(new AlwaysValidProperty<bool>(model, this, "In Rechnung anzeigen", cost.DisplayInBill));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -250,7 +250,7 @@ namespace UCalc.Models
|
|||||||
Name = cost.Name.Value,
|
Name = cost.Name.Value,
|
||||||
Division = (CostDivision) cost.Division.Value,
|
Division = (CostDivision) cost.Division.Value,
|
||||||
AffectsAll = cost.AffectsAll.Value,
|
AffectsAll = cost.AffectsAll.Value,
|
||||||
IncludeUnrented = cost.IncludeUnrented.Value,
|
ShiftUnrented = cost.ShiftUnrented.Value,
|
||||||
AffectedFlats =
|
AffectedFlats =
|
||||||
new HashSet<Flat>(cost.AffectedFlats.Select(rentedFlat => flatPropertyToFlat[rentedFlat])),
|
new HashSet<Flat>(cost.AffectedFlats.Select(rentedFlat => flatPropertyToFlat[rentedFlat])),
|
||||||
Entries = cost.Entries.Select(entry => new CostEntry
|
Entries = cost.Entries.Select(entry => new CostEntry
|
||||||
|
|||||||
@@ -381,6 +381,8 @@ namespace UCalc.Models
|
|||||||
{
|
{
|
||||||
property.ResetModified();
|
property.ResetModified();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
base.ResetModified();
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerator<T> GetEnumerator()
|
public IEnumerator<T> GetEnumerator()
|
||||||
|
|||||||
@@ -62,7 +62,7 @@
|
|||||||
ItemsSource="{x:Static local:App.RecentlyOpenedList}">
|
ItemsSource="{x:Static local:App.RecentlyOpenedList}">
|
||||||
<ComboBox.ItemTemplate>
|
<ComboBox.ItemTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<Label Content="{Binding DisplayText}" />
|
<TextBlock Text="{Binding DisplayText}" />
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</ComboBox.ItemTemplate>
|
</ComboBox.ItemTemplate>
|
||||||
</ComboBox>
|
</ComboBox>
|
||||||
|
|||||||
@@ -3,7 +3,47 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:local="clr-namespace:UCalc.Pages"
|
xmlns:local="clr-namespace:UCalc"
|
||||||
|
xmlns:pages="clr-namespace:UCalc.Pages"
|
||||||
|
xmlns:controls="clr-namespace:UCalc.Controls"
|
||||||
mc:Ignorable="d">
|
mc:Ignorable="d">
|
||||||
<Grid />
|
|
||||||
|
<DockPanel>
|
||||||
|
<controls:SectionHeader Header="Parameter"
|
||||||
|
DockPanel.Dock="Top" />
|
||||||
|
|
||||||
|
<DockPanel Margin="12, 12, 12, 0"
|
||||||
|
DockPanel.Dock="Top">
|
||||||
|
<Label DockPanel.Dock="Left"
|
||||||
|
Content="Mieter:"
|
||||||
|
Width="180"
|
||||||
|
Foreground="{x:Static local:Constants.SubMainColor}" />
|
||||||
|
|
||||||
|
<ComboBox
|
||||||
|
ItemsSource="{Binding Path=Tenants, RelativeSource={RelativeSource AncestorType=pages:DetailsPage}}"
|
||||||
|
SelectionChanged="OnSelectedTenantChanged"
|
||||||
|
x:Name="TenantComboBox">
|
||||||
|
<ComboBox.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<TextBlock Text="{Binding Name}" />
|
||||||
|
</DataTemplate>
|
||||||
|
</ComboBox.ItemTemplate>
|
||||||
|
</ComboBox>
|
||||||
|
</DockPanel>
|
||||||
|
|
||||||
|
<controls:SectionHeader Header="Berechnungen"
|
||||||
|
Margin="0, 12, 0, 0"
|
||||||
|
DockPanel.Dock="Top" />
|
||||||
|
|
||||||
|
<TextBox x:Name="CalculationTextBox"
|
||||||
|
Margin="12"
|
||||||
|
HorizontalScrollBarVisibility="Auto"
|
||||||
|
VerticalScrollBarVisibility="Auto"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
IsReadOnly="True"
|
||||||
|
BorderThickness="0"
|
||||||
|
FontSize="13"
|
||||||
|
Foreground="{x:Static local:Constants.SubMainColor}"/>
|
||||||
|
</DockPanel>
|
||||||
|
|
||||||
</Page>
|
</Page>
|
||||||
@@ -1,19 +1,57 @@
|
|||||||
using System;
|
using System.Collections.ObjectModel;
|
||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
|
using UCalc.Data;
|
||||||
using UCalc.Models;
|
using UCalc.Models;
|
||||||
|
|
||||||
namespace UCalc.Pages
|
namespace UCalc.Pages
|
||||||
{
|
{
|
||||||
public partial class DetailsPage : Page
|
public partial class DetailsPage
|
||||||
{
|
{
|
||||||
|
public Model Model { get; set; }
|
||||||
|
private Billing _billing;
|
||||||
|
public ObservableCollection<Tenant> Tenants { get; }
|
||||||
|
|
||||||
public DetailsPage()
|
public DetailsPage()
|
||||||
{
|
{
|
||||||
|
Tenants = new ObservableCollection<Tenant>();
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Compute(Model model)
|
public void Compute()
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
if (Model.Root.Errors.Count > 0)
|
||||||
|
{
|
||||||
|
TenantComboBox.IsEnabled = false;
|
||||||
|
CalculationTextBox.Text =
|
||||||
|
"Bitte beheben Sie zunächst alle Fehler bevor eine Berechnung vorgenommen werden kann.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_billing = Model.Dump();
|
||||||
|
Tenants.Clear();
|
||||||
|
foreach (var tenant in _billing.Tenants)
|
||||||
|
{
|
||||||
|
Tenants.Add(tenant);
|
||||||
|
}
|
||||||
|
|
||||||
|
TenantComboBox.IsEnabled = true;
|
||||||
|
TenantComboBox.SelectedIndex = -1;
|
||||||
|
OnSelectedTenantChanged(TenantComboBox, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSelectedTenantChanged(object sender, SelectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
var tenant = (Tenant) ((ComboBox) sender).SelectedItem;
|
||||||
|
|
||||||
|
if (tenant == null)
|
||||||
|
{
|
||||||
|
CalculationTextBox.Text = "Bitte wählen Sie einen Mieter aus, um die Berechnungen anzuzeigen.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = BillingCalculator.CalculateForTenant(_billing, tenant);
|
||||||
|
|
||||||
|
CalculationTextBox.Text = result.DetailsForLandlord;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -17,7 +17,6 @@
|
|||||||
|
|
||||||
<Window.Resources>
|
<Window.Resources>
|
||||||
<controls:FlatToRentedConverter x:Key="FlatToRentedConverter" />
|
<controls:FlatToRentedConverter x:Key="FlatToRentedConverter" />
|
||||||
<controls:DatePickerTextToDateTimeConverter x:Key="DatePickerTextToDateTimeConverter" />
|
|
||||||
</Window.Resources>
|
</Window.Resources>
|
||||||
|
|
||||||
<DockPanel>
|
<DockPanel>
|
||||||
@@ -155,9 +154,7 @@
|
|||||||
|
|
||||||
<DatePicker VerticalAlignment="Center"
|
<DatePicker VerticalAlignment="Center"
|
||||||
MinHeight="22"
|
MinHeight="22"
|
||||||
DisplayDateStart="{Binding Path=Model.StartDate, RelativeSource={RelativeSource AncestorType=local:TenantWindow}, Mode=OneWay}"
|
SelectedDate="{Binding Path=Tenant.EntryDate.Value, RelativeSource={RelativeSource AncestorType=local:TenantWindow}, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" />
|
||||||
DisplayDateEnd="{Binding Path=Model.EndDate, RelativeSource={RelativeSource AncestorType=local:TenantWindow}, Mode=OneWay}"
|
|
||||||
Text="{Binding Path=Tenant.EntryDate.Value, RelativeSource={RelativeSource AncestorType=local:TenantWindow}, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay, Converter={StaticResource DatePickerTextToDateTimeConverter}}" />
|
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
|
|
||||||
<DockPanel Margin="12, 8, 12, 0">
|
<DockPanel Margin="12, 8, 12, 0">
|
||||||
@@ -173,9 +170,7 @@
|
|||||||
|
|
||||||
<DatePicker VerticalAlignment="Center"
|
<DatePicker VerticalAlignment="Center"
|
||||||
MinHeight="22"
|
MinHeight="22"
|
||||||
DisplayDateStart="{Binding Path=Model.StartDate, RelativeSource={RelativeSource AncestorType=local:TenantWindow}, Mode=OneWay}"
|
SelectedDate="{Binding Path=Tenant.DepartureDate.Value, RelativeSource={RelativeSource AncestorType=local:TenantWindow}, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" />
|
||||||
DisplayDateEnd="{Binding Path=Model.EndDate, RelativeSource={RelativeSource AncestorType=local:TenantWindow}, Mode=OneWay}"
|
|
||||||
Text="{Binding Path=Tenant.DepartureDate.Value, RelativeSource={RelativeSource AncestorType=local:TenantWindow}, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay, Converter={StaticResource DatePickerTextToDateTimeConverter}}" />
|
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
|
|
||||||
<controls:SectionHeader Header="Gemietete Wohnungen"
|
<controls:SectionHeader Header="Gemietete Wohnungen"
|
||||||
|
|||||||
Reference in New Issue
Block a user