diff --git a/ucalc/BillingWindow.xaml b/ucalc/BillingWindow.xaml
index 88188a6..82044a2 100644
--- a/ucalc/BillingWindow.xaml
+++ b/ucalc/BillingWindow.xaml
@@ -17,26 +17,33 @@
+ LoadCompleted="OnSideBarFrameLoadCompleted"
+ Focusable="False" />
+ LoadCompleted="OnLandlordFrameLoadCompleted"
+ Focusable="False" />
+ LoadCompleted="OnHouseFrameLoadCompleted"
+ Focusable="False" />
-
+
-
+
-
+
diff --git a/ucalc/BillingWindow.xaml.cs b/ucalc/BillingWindow.xaml.cs
index a95181a..e496078 100644
--- a/ucalc/BillingWindow.xaml.cs
+++ b/ucalc/BillingWindow.xaml.cs
@@ -10,18 +10,16 @@ namespace UCalc
{
public partial class BillingWindow
{
- public Billing Billing { get; }
- public BillingModel Model { get; }
+ public Model Model { get; }
- public BillingWindow(Billing savedBilling, Billing billing)
+ public BillingWindow(Billing billing)
{
- Billing = billing;
+ Model = new Model(billing);
+
InitializeComponent();
- Model = new BillingModel(Billing);
-
Title =
- $"MietRechner - Abrechnung von {billing.StartDate.ToShortDateString()} - {billing.EndDate.Date.ToShortDateString()}";
+ $"MietRechner - Abrechnung von {billing.StartDate.ToString(Constants.DateFormat)} - {billing.EndDate.Date.ToString(Constants.DateFormat)}";
}
private void OnClosed(object sender, EventArgs e)
@@ -41,13 +39,21 @@ namespace UCalc
private void OnLandlordFrameLoadCompleted(object sender, NavigationEventArgs e)
{
var page = (LandlordPage) ((Frame) sender).Content;
- page.Model = Model.LandlordModel;
+ page.Landlord = Model.Root.Landlord;
}
private void OnHouseFrameLoadCompleted(object sender, NavigationEventArgs e)
{
var page = (HousePage) ((Frame) sender).Content;
- page.Model = Model.HouseModel;
+ page.House = Model.Root.House;
+ page.ParentWindow = this;
+ }
+
+ private void OnTenantsFrameLoadCompleted(object sender, NavigationEventArgs e)
+ {
+ var page = (TenantsPage) ((Frame) sender).Content;
+ page.Tenants = Model.Root.Tenants;
+ page.House = Model.Root.House;
page.ParentWindow = this;
}
}
diff --git a/ucalc/Constants.cs b/ucalc/Constants.cs
index 088da6f..199cbe3 100644
--- a/ucalc/Constants.cs
+++ b/ucalc/Constants.cs
@@ -11,6 +11,9 @@ namespace UCalc
public static readonly SolidColorBrush MainColor = Brushes.White;
public static readonly SolidColorBrush SubMainColor = new SolidColorBrush(Color.FromRgb(0x00, 0x7A, 0xCC));
+ public const string DecimalFormat = ".00";
+ public const string DateFormat = "dd.MM.yyyy";
+
public static readonly ImmutableList SalutationStrs =
((Salutation[]) Enum.GetValues(typeof(Salutation))).Select(value => value.AsString()).ToImmutableList();
}
diff --git a/ucalc/Controls/Converters.cs b/ucalc/Controls/Converters.cs
index d31dcdc..16f26dd 100644
--- a/ucalc/Controls/Converters.cs
+++ b/ucalc/Controls/Converters.cs
@@ -1,19 +1,20 @@
using System;
using System.Collections.Generic;
+using System.Globalization;
using System.Windows;
using System.Windows.Data;
+using UCalc.Models;
namespace UCalc.Controls
{
public class ErrorsToVisibilityConverter : IValueConverter
{
- public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (((ICollection) value)?.Count ?? 0) == 0 ? Visibility.Collapsed : Visibility.Visible;
}
- public object ConvertBack(object value, Type targetType, object parameter,
- System.Globalization.CultureInfo culture)
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new InvalidOperationException();
}
@@ -23,31 +24,93 @@ namespace UCalc.Controls
{
private readonly ErrorsToVisibilityConverter _converter = new ErrorsToVisibilityConverter();
- public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var result = _converter.Convert(value, targetType, parameter, culture) as Visibility?;
return result == Visibility.Collapsed ? Visibility.Visible : Visibility.Collapsed;
}
- public object ConvertBack(object value, Type targetType, object parameter,
- System.Globalization.CultureInfo culture)
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new InvalidOperationException();
}
}
-
- public class ErrorCountToVisibilityConverter : IValueConverter
+ public class ErrorsToCountConverter : IValueConverter
{
- public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
- return (int?) value != 0 ? Visibility.Visible : Visibility.Collapsed;
+ return ((ICollection) value)?.Count ?? 0;
}
- public object ConvertBack(object value, Type targetType, object parameter,
- System.Globalization.CultureInfo culture)
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new InvalidOperationException();
}
}
+
+ public class ErrorsToToolTipConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ var errors = (IReadOnlyList) value;
+
+ return (errors?.Count ?? 0) == 0 ? null : string.Join("\n", errors);
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new InvalidOperationException();
+ }
+ }
+
+ public class FlatToRentedConverter : IValueConverter
+ {
+ public TenantProperty Tenant { get; set; }
+
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ return Tenant.RentedFlats.Contains((FlatProperty) value);
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new InvalidOperationException();
+ }
+ }
+
+ public class EmptyMultiPropertyToVisibilityConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ return value?.Equals(0) ?? false ? Visibility.Visible : Visibility.Collapsed;
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new InvalidOperationException();
+ }
+ }
+
+ 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;
+ }
+ }
}
\ No newline at end of file
diff --git a/ucalc/Controls/ErrorCounter.xaml b/ucalc/Controls/ErrorCounter.xaml
index deb2844..ec22e23 100644
--- a/ucalc/Controls/ErrorCounter.xaml
+++ b/ucalc/Controls/ErrorCounter.xaml
@@ -7,16 +7,17 @@
mc:Ignorable="d">
-
+
+
+ Visibility="{Binding Path=Property.Errors, RelativeSource={RelativeSource AncestorType=local:ErrorCounter}, Converter={StaticResource ErrorsToVisibilityConverter}}">
diff --git a/ucalc/Controls/ErrorCounter.xaml.cs b/ucalc/Controls/ErrorCounter.xaml.cs
index a7071c0..56182f2 100644
--- a/ucalc/Controls/ErrorCounter.xaml.cs
+++ b/ucalc/Controls/ErrorCounter.xaml.cs
@@ -5,10 +5,10 @@ namespace UCalc.Controls
{
public partial class ErrorCounter
{
- public Model Model { get; set; }
+ public Property Property { get; set; }
- public static readonly DependencyProperty ModelProperty = DependencyProperty.Register(
- "Model", typeof(Model), typeof(ErrorCounter), new PropertyMetadata((Model) null));
+ public static readonly DependencyProperty PropertyProperty = DependencyProperty.Register(
+ "Property", typeof(Property), typeof(ErrorCounter), new PropertyMetadata((Property) null));
public ErrorCounter()
{
diff --git a/ucalc/Controls/ErrorIcon.xaml b/ucalc/Controls/ErrorIcon.xaml
index 6fdebc1..7743cde 100644
--- a/ucalc/Controls/ErrorIcon.xaml
+++ b/ucalc/Controls/ErrorIcon.xaml
@@ -11,11 +11,12 @@
+
-
+
diff --git a/ucalc/FlatWindow.xaml.cs b/ucalc/FlatWindow.xaml.cs
index 440ddad..e4c0b7b 100644
--- a/ucalc/FlatWindow.xaml.cs
+++ b/ucalc/FlatWindow.xaml.cs
@@ -5,11 +5,11 @@ namespace UCalc
{
public partial class FlatWindow
{
- public FlatModel Model { get; }
+ public FlatProperty Flat { get; }
- public FlatWindow(FlatModel model)
+ public FlatWindow(FlatProperty flat)
{
- Model = model;
+ Flat = flat;
InitializeComponent();
}
diff --git a/ucalc/MainWindow.xaml b/ucalc/MainWindow.xaml
index f561b6a..a3c5280 100644
--- a/ucalc/MainWindow.xaml
+++ b/ucalc/MainWindow.xaml
@@ -8,6 +8,7 @@
Title="MietRechner"
Height="400"
Width="500"
+ WindowStartupLocation="CenterScreen"
ResizeMode="CanMinimize">
diff --git a/ucalc/MainWindow.xaml.cs b/ucalc/MainWindow.xaml.cs
index 0343b3c..74fdf7c 100644
--- a/ucalc/MainWindow.xaml.cs
+++ b/ucalc/MainWindow.xaml.cs
@@ -22,7 +22,7 @@ namespace UCalc
if (newWindow.ShowDialog() == true)
{
- new BillingWindow(newWindow.SavedBilling, newWindow.Billing).Show();
+ new BillingWindow(newWindow.Billing).Show();
Hide();
}
}
@@ -75,7 +75,7 @@ namespace UCalc
var billing = new BillingLoader().Load(path);
App.RecentlyOpenedList.Add(new RecentlyOpenedItem(path));
- new BillingWindow(billing, billing.Clone()).Show();
+ new BillingWindow(billing).Show();
Hide();
}
catch (IOException)
diff --git a/ucalc/Models/AddressModel.cs b/ucalc/Models/AddressModel.cs
deleted file mode 100644
index 43d1bd7..0000000
--- a/ucalc/Models/AddressModel.cs
+++ /dev/null
@@ -1,39 +0,0 @@
-using UCalc.Data;
-
-namespace UCalc.Models
-{
- public class AddressModel : Model
- {
- private readonly Address _data;
- public ModelProperty Street { get; }
- public ModelProperty HouseNumber { get; }
- public ModelProperty City { get; }
- public ModelProperty Postcode { get; }
-
- public AddressModel(Address data)
- {
- _data = data;
-
- Street = Add(new ModelProperty("Straße", _data.Street, ModelPropertyValidators.IsNotEmpty));
- HouseNumber =
- Add(new ModelProperty("Hausnummer", _data.HouseNumber, ModelPropertyValidators.IsNotEmpty));
- City = Add(new ModelProperty("Stadt", _data.City, ModelPropertyValidators.IsNotEmpty));
- Postcode = Add(new ModelProperty("PLZ", _data.Postcode, ModelPropertyValidators.IsNotEmpty));
- }
-
- public override void Apply()
- {
- _data.Street = Street.Value;
- Street.ResetModified();
-
- _data.HouseNumber = HouseNumber.Value;
- HouseNumber.ResetModified();
-
- _data.City = City.Value;
- City.ResetModified();
-
- _data.Postcode = Postcode.Value;
- Postcode.ResetModified();
- }
- }
-}
\ No newline at end of file
diff --git a/ucalc/Models/AddressProperty.cs b/ucalc/Models/AddressProperty.cs
new file mode 100644
index 0000000..80abc0b
--- /dev/null
+++ b/ucalc/Models/AddressProperty.cs
@@ -0,0 +1,20 @@
+using UCalc.Data;
+
+namespace UCalc.Models
+{
+ public class AddressProperty : NestedProperty
+ {
+ public NotEmptyStringProperty Street { get; }
+ public NotEmptyStringProperty HouseNumber { get; }
+ public NotEmptyStringProperty City { get; }
+ public NotEmptyStringProperty Postcode { get; }
+
+ public AddressProperty(Model model, Property parent, Address data) : base(model, parent)
+ {
+ Street = Add(new NotEmptyStringProperty(model, this, "Straße", data.Street));
+ HouseNumber = Add(new NotEmptyStringProperty(model, this, "Hausnummer", data.HouseNumber));
+ City = Add(new NotEmptyStringProperty(model, this, "Stadt", data.City));
+ Postcode = Add(new NotEmptyStringProperty(model, this, "PLZ", data.Postcode));
+ }
+ }
+}
\ No newline at end of file
diff --git a/ucalc/Models/BankAccountModel.cs b/ucalc/Models/BankAccountModel.cs
deleted file mode 100644
index 8160bc0..0000000
--- a/ucalc/Models/BankAccountModel.cs
+++ /dev/null
@@ -1,34 +0,0 @@
-using UCalc.Data;
-
-namespace UCalc.Models
-{
- public class BankAccountModel : Model
- {
- private readonly BankAccount _data;
- public ModelProperty Iban { get; }
- public ModelProperty Bic { get; }
- public ModelProperty BankName { get; }
-
- public BankAccountModel(BankAccount data)
- {
- _data = data;
-
- Iban = Add(new ModelProperty("IBAN", _data.Iban, ModelPropertyValidators.IsNotEmpty));
- Bic = Add(new ModelProperty("BIC", _data.Bic, ModelPropertyValidators.IsNotEmpty));
- BankName = Add(new ModelProperty("Name der Bank", _data.BankName,
- ModelPropertyValidators.IsNotEmpty));
- }
-
- public override void Apply()
- {
- _data.Iban = Iban.Value;
- Iban.ResetModified();
-
- _data.Bic = Bic.Value;
- Bic.ResetModified();
-
- _data.BankName = BankName.Value;
- BankName.ResetModified();
- }
- }
-}
\ No newline at end of file
diff --git a/ucalc/Models/BankAccountProperty.cs b/ucalc/Models/BankAccountProperty.cs
new file mode 100644
index 0000000..dfac11c
--- /dev/null
+++ b/ucalc/Models/BankAccountProperty.cs
@@ -0,0 +1,18 @@
+using UCalc.Data;
+
+namespace UCalc.Models
+{
+ public class BankAccountProperty : NestedProperty
+ {
+ public NotEmptyStringProperty Iban { get; }
+ public NotEmptyStringProperty Bic { get; }
+ public NotEmptyStringProperty BankName { get; }
+
+ public BankAccountProperty(Model model, Property parent, BankAccount data) : base(model, parent)
+ {
+ Iban = Add(new NotEmptyStringProperty(model, this, "IBAN", data.Iban));
+ Bic = Add(new NotEmptyStringProperty(model, this, "BIC", data.Bic));
+ BankName = Add(new NotEmptyStringProperty(model, this, "Name der Bank", data.BankName));
+ }
+ }
+}
\ No newline at end of file
diff --git a/ucalc/Models/BillingModel.cs b/ucalc/Models/BillingModel.cs
deleted file mode 100644
index 5d6bcc5..0000000
--- a/ucalc/Models/BillingModel.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-using UCalc.Data;
-
-namespace UCalc.Models
-{
- public class BillingModel : Model
- {
- public LandlordModel LandlordModel { get; }
- public HouseModel HouseModel { get; }
-
- public BillingModel(Billing billing)
- {
- LandlordModel = new LandlordModel(billing.Landlord);
- HouseModel = new HouseModel(billing.House);
- }
-
- public override void Apply()
- {
- LandlordModel.Apply();
- HouseModel.Apply();
- }
- }
-}
\ No newline at end of file
diff --git a/ucalc/Models/BillingProperty.cs b/ucalc/Models/BillingProperty.cs
new file mode 100644
index 0000000..bcdf237
--- /dev/null
+++ b/ucalc/Models/BillingProperty.cs
@@ -0,0 +1,29 @@
+using System.Collections.Generic;
+using UCalc.Data;
+
+namespace UCalc.Models
+{
+ public class BillingProperty : NestedProperty
+ {
+ public LandlordProperty Landlord { get; }
+ public HouseProperty House { get; }
+ public TenantsProperty Tenants { get; }
+
+
+ // TODO: Costs
+
+ public BillingProperty(Model model, Property parent, Billing data) : base(model, parent)
+ {
+ Landlord = Add(new LandlordProperty(model, this, data.Landlord));
+ House = Add(new HouseProperty(model, this, data.House));
+
+ var flatToProperty = new Dictionary();
+ for (var i = 0; i < data.House.Flats.Count; ++i)
+ {
+ flatToProperty.Add(data.House.Flats[i], House.Flats[i]);
+ }
+
+ Tenants = Add(new TenantsProperty(model, this, data.Tenants, flatToProperty));
+ }
+ }
+}
\ No newline at end of file
diff --git a/ucalc/Models/FlatModel.cs b/ucalc/Models/FlatModel.cs
deleted file mode 100644
index 6cd98d1..0000000
--- a/ucalc/Models/FlatModel.cs
+++ /dev/null
@@ -1,40 +0,0 @@
-using System.Collections.Generic;
-using System.Linq;
-using UCalc.Data;
-
-namespace UCalc.Models
-{
- public class FlatModel : Model
- {
- public Flat Data { get; }
- public ModelProperty Name { get; }
- public ModelProperty Size { get; }
-
- public FlatModel(Flat data, IReadOnlyList flats)
- {
- Data = data;
-
- Name = Add(new ModelProperty("Name", Data.Name, (name, value) =>
- {
- var error = ModelPropertyValidators.IsNotEmpty(name, value);
-
- if (error == "" && flats.Any(flat => !ReferenceEquals(flat, Data) && flat.Name == value))
- {
- error = $"{name}: Der Wert \"{value}\" ist nicht eindeutig.";
- }
-
- return error;
- }));
- Size = Add(new ModelProperty("Größe", Data.Size.ToString(), ModelPropertyValidators.IsNaturalInt));
- }
-
- public override void Apply()
- {
- Data.Name = Name.Value;
- Name.ResetModified();
-
- Data.Size = int.TryParse(Size.Value, out var n) && n > 0 ? n : 0;
- Size.ResetModified();
- }
- }
-}
\ No newline at end of file
diff --git a/ucalc/Models/FlatProperty.cs b/ucalc/Models/FlatProperty.cs
new file mode 100644
index 0000000..f8f3027
--- /dev/null
+++ b/ucalc/Models/FlatProperty.cs
@@ -0,0 +1,39 @@
+using System.Linq;
+using UCalc.Data;
+
+namespace UCalc.Models
+{
+ public class FlatNameProperty : ValueProperty
+ {
+ public FlatNameProperty(Model model, Property parent, string name, string value) : base(model, parent, name,
+ value)
+ {
+ }
+
+ protected override string ValidateValue()
+ {
+ var error = "";
+ using var validator = Model.BeginValidation();
+
+ if (Model.Root.House.Flats.Any(flat => !ReferenceEquals(this, flat.Name) && flat.Name.Value == Value))
+ {
+ error = $"{Name}: Der Name ist nicht eindeutig.";
+ }
+
+ validator.ValidateRange(Model.Root.House.Flats.Select(flat => flat.Name));
+ return error;
+ }
+ }
+
+ public class FlatProperty : NestedProperty
+ {
+ public FlatNameProperty Name { get; }
+ public NaturalNumberProperty Size { get; }
+
+ public FlatProperty(Model model, Property parent, Flat data) : base(model, parent)
+ {
+ Name = Add(new FlatNameProperty(model, this, "Name", data.Name));
+ Size = Add(new NaturalNumberProperty(model, this, "Größe", data.Size));
+ }
+ }
+}
\ No newline at end of file
diff --git a/ucalc/Models/FlatsProperty.cs b/ucalc/Models/FlatsProperty.cs
new file mode 100644
index 0000000..fa74019
--- /dev/null
+++ b/ucalc/Models/FlatsProperty.cs
@@ -0,0 +1,55 @@
+using System.Collections.Generic;
+using System.Linq;
+using UCalc.Data;
+
+namespace UCalc.Models
+{
+ public class FlatsProperty : MultiProperty
+ {
+ public FlatsProperty(Model model, Property parent, IEnumerable data) : base(model, parent,
+ "Wohnungen: Geben Sie eine oder mehr Wohnungen an.")
+ {
+ using var validator = Model.BeginValidation();
+
+ foreach (var flat in data)
+ {
+ base.Add(new FlatProperty(Model, this, flat));
+ }
+
+ Modified = false;
+ }
+
+ public void Add()
+ {
+ FlatProperty flat;
+ {
+ using var validator = Model.BeginValidation();
+
+ flat = new FlatProperty(Model, this, new Flat {Name = $"Wohnung {Properties.Count + 1}"});
+ base.Add(flat);
+ }
+
+ {
+ using var validator = Model.BeginValidation();
+ validator.Validate(flat);
+ }
+ }
+
+ public new void Remove(FlatProperty flat)
+ {
+ using var validator = Model.BeginValidation();
+
+ base.Remove(flat);
+
+ // Revalidate flat names and flat lists in tenants and costs
+ validator.ValidateRange(Properties.Select(otherFlat => otherFlat.Name));
+
+ foreach (var tenant in Model.Root.Tenants)
+ {
+ tenant.RentedFlats.Remove(flat);
+ }
+
+ // TODO: Revalidate all flats + revalidate tenant rent lists + costs flat lists
+ }
+ }
+}
\ No newline at end of file
diff --git a/ucalc/Models/HouseModel.cs b/ucalc/Models/HouseModel.cs
deleted file mode 100644
index 9ad0cd0..0000000
--- a/ucalc/Models/HouseModel.cs
+++ /dev/null
@@ -1,57 +0,0 @@
-using UCalc.Data;
-
-namespace UCalc.Models
-{
- public class HouseModel : Model
- {
- private readonly House _data;
- public NestedModelProperty Address { get; }
- public MultiModelProperty Flats { get; }
-
- public HouseModel(House data)
- {
- _data = data;
-
- Address = Add(new NestedModelProperty(new AddressModel(_data.Address)));
- Flats = Add(new MultiModelProperty());
-
- foreach (var flat in data.Flats)
- {
- Flats.Add(new FlatModel(flat, _data.Flats));
- }
- }
-
- public override void Apply()
- {
- Address.Model.Apply();
-
- foreach (var model in Flats.Models)
- {
- model.Apply();
- }
- }
-
- public FlatModel AddFlat()
- {
- var flat = new Flat {Name = $"Wohnung {_data.Flats.Count + 1}"};
- _data.Flats.Add(flat);
-
- var model = new FlatModel(flat, _data.Flats);
- Flats.Add(model);
-
- return model;
- }
-
- public void RemoveFlat(FlatModel model)
- {
- _data.Flats.Remove(model.Data);
-
- Flats.Remove(model);
-
- if (model.Errors.Count > 0)
- {
- OnPropertyChanged("Errors");
- }
- }
- }
-}
\ No newline at end of file
diff --git a/ucalc/Models/HouseProperty.cs b/ucalc/Models/HouseProperty.cs
new file mode 100644
index 0000000..65bbcb0
--- /dev/null
+++ b/ucalc/Models/HouseProperty.cs
@@ -0,0 +1,16 @@
+using UCalc.Data;
+
+namespace UCalc.Models
+{
+ public class HouseProperty : NestedProperty
+ {
+ public AddressProperty Address { get; }
+ public FlatsProperty Flats { get; }
+
+ public HouseProperty(Model model, Property parent, House data) : base(model, parent)
+ {
+ Address = Add(new AddressProperty(model, this, data.Address));
+ Flats = Add(new FlatsProperty(model, this, data.Flats));
+ }
+ }
+}
\ No newline at end of file
diff --git a/ucalc/Models/LandlordModel.cs b/ucalc/Models/LandlordModel.cs
deleted file mode 100644
index adf3658..0000000
--- a/ucalc/Models/LandlordModel.cs
+++ /dev/null
@@ -1,47 +0,0 @@
-using UCalc.Data;
-
-namespace UCalc.Models
-{
- public class LandlordModel : Model
- {
- private readonly Landlord _data;
- public ModelProperty Salutation { get; }
- public ModelProperty Name { get; }
- public ModelProperty MailAddress { get; }
- public ModelProperty Phone { get; }
- public NestedModelProperty Address { get; }
- public NestedModelProperty BankAccount { get; }
-
- public LandlordModel(Landlord data)
- {
- _data = data;
-
- Salutation = Add(new ModelProperty("Anrede", (int) _data.Salutation, null));
- Name = Add(new ModelProperty("Name", _data.Name, ModelPropertyValidators.IsNotEmpty));
- MailAddress =
- Add(new ModelProperty("Email Adresse", _data.MailAddress, ModelPropertyValidators.IsNotEmpty));
- Phone = Add(new ModelProperty("Telefonnummer", _data.Phone, ModelPropertyValidators.IsNotEmpty));
- Address = Add(new NestedModelProperty(new AddressModel(_data.Address)));
- BankAccount = Add(new NestedModelProperty(new BankAccountModel(_data.BankAccount)));
- }
-
- public override void Apply()
- {
- _data.Salutation = (Salutation) Salutation.Value;
- Salutation.ResetModified();
-
- _data.Name = Name.Value;
- Name.ResetModified();
-
- _data.MailAddress = MailAddress.Value;
- MailAddress.ResetModified();
-
- _data.Phone = Phone.Value;
- Phone.ResetModified();
-
- Address.Model.Apply();
-
- BankAccount.Model.Apply();
- }
- }
-}
\ No newline at end of file
diff --git a/ucalc/Models/LandlordProperty.cs b/ucalc/Models/LandlordProperty.cs
new file mode 100644
index 0000000..ea22858
--- /dev/null
+++ b/ucalc/Models/LandlordProperty.cs
@@ -0,0 +1,24 @@
+using UCalc.Data;
+
+namespace UCalc.Models
+{
+ public class LandlordProperty : NestedProperty
+ {
+ public SalutationProperty Salutation { get; }
+ public NotEmptyStringProperty Name { get; }
+ public NotEmptyStringProperty MailAddress { get; }
+ public NotEmptyStringProperty Phone { get; }
+ public AddressProperty Address { get; }
+ public BankAccountProperty BankAccount { get; }
+
+ public LandlordProperty(Model model, Property parent, Landlord data) : base(model, parent)
+ {
+ Salutation = Add(new SalutationProperty(model, this, "Anrede", data.Salutation));
+ Name = Add(new NotEmptyStringProperty(model, this, "Name", data.Name));
+ MailAddress = Add(new NotEmptyStringProperty(model, this, "Email Adresse", data.MailAddress));
+ Phone = Add(new NotEmptyStringProperty(model, this, "Telefonnummer", data.Phone));
+ Address = Add(new AddressProperty(model, this, data.Address));
+ BankAccount = Add(new BankAccountProperty(model, this, data.BankAccount));
+ }
+ }
+}
\ No newline at end of file
diff --git a/ucalc/Models/Model.cs b/ucalc/Models/Model.cs
index c280799..378bdef 100644
--- a/ucalc/Models/Model.cs
+++ b/ucalc/Models/Model.cs
@@ -1,234 +1,152 @@
using System;
using System.Collections.Generic;
-using System.Collections.ObjectModel;
using System.ComponentModel;
-using System.Linq;
using System.Runtime.CompilerServices;
using UCalc.Annotations;
+using UCalc.Data;
namespace UCalc.Models
{
- public abstract class ModelBaseProperty : INotifyPropertyChanged
+ public class Model : INotifyPropertyChanged
{
- protected static readonly List EmptyList = new List();
- public abstract IReadOnlyCollection Errors { get; }
- public abstract bool Modified { get; }
-
- protected void OnChildPropertyChanged(object sender, PropertyChangedEventArgs args)
+ public class Validator : IDisposable
{
- if (args.PropertyName == "Errors" || args.PropertyName == "Modified")
+ private readonly Model _model;
+ private int _counter;
+ private readonly HashSet _validated;
+ private readonly HashSet> _notifications;
+
+ public Validator(Model model)
{
- OnPropertyChanged(args.PropertyName);
+ _model = model;
+ _validated = new HashSet();
+ _notifications = new HashSet>();
}
- }
- public event PropertyChangedEventHandler PropertyChanged;
-
- [NotifyPropertyChangedInvocator]
- protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
- {
- PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
- }
- }
-
- public class NestedModelProperty : ModelBaseProperty where T : Model
- {
- public T Model { get; }
-
- public NestedModelProperty(T model)
- {
- Model = model;
-
- Model.PropertyChanged += OnChildPropertyChanged;
- }
-
- public override IReadOnlyCollection Errors => Model.Errors;
-
- public override bool Modified => Model.Modified;
- }
-
- public class MultiModelProperty : ModelBaseProperty where T : Model
- {
- private readonly ObservableCollection _models;
-
- public MultiModelProperty()
- {
- _models = new ObservableCollection();
- }
-
- public void Add(T model)
- {
- _models.Add(model);
-
- model.PropertyChanged += OnChildPropertyChanged;
-
- if (model.Errors.Count > 0)
+ public void Notify(Property property, string propertyName)
{
- OnPropertyChanged("Errors");
- }
- }
+ _notifications.Add(new Tuple(property, propertyName));
- public void Remove(T model)
- {
- model.PropertyChanged -= OnChildPropertyChanged;
-
- _models.Remove(model);
-
- if (model.Errors.Count > 0)
- {
- OnPropertyChanged("Errors");
- }
- }
-
- public IReadOnlyList Models => _models;
-
- public override IReadOnlyCollection Errors
- {
- get
- {
- var errors = new List();
-
- foreach (var model in _models)
+ if (property != null)
{
- errors.AddRange(model.Errors);
+ property = property.Parent;
+
+ while (property != null)
+ {
+ _notifications.Add(new Tuple(property, propertyName));
+ property = property.Parent;
+ }
+
+ _notifications.Add(new Tuple(null, propertyName));
}
-
- return errors;
}
- }
- public override bool Modified => _models.Select(model => model.Modified).Any();
- }
-
- public class ModelProperty : ModelBaseProperty
- {
- public string Name { get; }
- private T _value;
- private readonly List _errors;
- private readonly Func _validate;
- private bool _modified;
-
- public T Value
- {
- get => _value;
- set
+ public void Validate(Property property)
{
- if (_value.Equals(value))
+ if (property == null)
{
return;
}
- var oldError = _errors[0];
- _errors[0] = _validate?.Invoke(Name, value) ?? "";
-
- if (oldError != _errors[0])
+ if (_validated.Add(property))
{
- OnPropertyChanged("Errors");
+ property.Validate();
}
+ }
- if (!_modified)
+ public void ValidateRange(IEnumerable properties)
+ {
+ foreach (var property in properties)
{
- OnPropertyChanged("Modified");
+ Validate(property);
}
+ }
- _modified = true;
- _value = value;
+ public void IncValidationCounter()
+ {
+ ++_counter;
+ }
- OnPropertyChanged("Value");
+ public void Dispose()
+ {
+ --_counter;
+
+ if (_counter == 0)
+ {
+ _validated.Clear();
+
+ var sortedNotifications = new List>(_notifications);
+ sortedNotifications.Sort((x, y) =>
+ {
+ var (prop1, propName1) = x;
+ var (prop2, propName2) = y;
+
+ if (prop1 == null)
+ {
+ return prop2 == null ? string.CompareOrdinal(propName1, propName2) : 1;
+ }
+
+ if (prop2 == null)
+ {
+ return -1;
+ }
+
+ if (ReferenceEquals(prop1, prop2))
+ {
+ return string.CompareOrdinal(propName1, propName2);
+ }
+
+ if (prop1.IsParentOf(prop2))
+ {
+ return 1;
+ }
+
+ if (prop2.IsParentOf(prop1))
+ {
+ return -1;
+ }
+
+ return 0;
+ });
+
+ foreach (var (property, propertyName) in sortedNotifications)
+ {
+ if (property == null)
+ {
+ _model.OnPropertyChanged(propertyName);
+ }
+ else
+ {
+ property.OnPropertyChanged(propertyName);
+ }
+ }
+
+ _notifications.Clear();
+ }
}
}
- public ModelProperty(string name, T value, Func validate)
+ private readonly Validator _validator;
+ public BillingProperty Root { get; }
+
+ public Model(Billing billing)
{
- Name = name;
- _value = value;
- _errors = new List {validate?.Invoke(Name, _value) ?? ""};
- _validate = validate;
- _modified = false;
+ _validator = new Validator(this);
+
+ using var validator = BeginValidation();
+ Root = new BillingProperty(this, null, billing);
}
- public override IReadOnlyCollection Errors => _errors[0] != "" ? _errors : EmptyList;
-
- public override bool Modified => _modified;
-
- public void ResetModified()
+ public Validator BeginValidation()
{
- _modified = false;
+ _validator.IncValidationCounter();
+ return _validator;
}
- }
-
- public static class ModelPropertyValidators
- {
- public static string IsNotEmpty(string name, string data)
- {
- return string.IsNullOrEmpty(data) ? $"{name}: Geben Sie einen Wert ein." : "";
- }
-
- public static string IsNaturalInt(string name, string data)
- {
- return !int.TryParse(data, out var n) || n <= 0
- ? $"{name}: Der eingegebene Wert ist keine gültige positive Zahl > 0."
- : "";
- }
- }
-
- public abstract class Model : INotifyPropertyChanged
- {
- private readonly List _properties;
-
- protected Model()
- {
- _properties = new List();
-
- PropertyChanged += (sender, args) =>
- {
- if (args.PropertyName == "Errors")
- {
- OnPropertyChanged("ErrorCount");
- }
- };
- }
-
- protected T Add(T property) where T : ModelBaseProperty
- {
- _properties.Add(property);
-
- property.PropertyChanged += (sender, args) =>
- {
- if (args.PropertyName == "Errors" || args.PropertyName == "Modified")
- {
- OnPropertyChanged(args.PropertyName);
- }
- };
-
- return property;
- }
-
- public bool Modified => _properties.Select(property => property.Modified).Any();
-
- public IReadOnlyCollection Errors
- {
- get
- {
- var errors = new List();
-
- foreach (var property in _properties)
- {
- errors.AddRange(property.Errors);
- }
-
- return errors;
- }
- }
-
- public int ErrorCount => Errors.Count;
-
- public abstract void Apply();
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
- protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
+ private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
diff --git a/ucalc/Models/Properties.cs b/ucalc/Models/Properties.cs
new file mode 100644
index 0000000..fbea7c5
--- /dev/null
+++ b/ucalc/Models/Properties.cs
@@ -0,0 +1,326 @@
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.ComponentModel;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using UCalc.Annotations;
+
+namespace UCalc.Models
+{
+ public abstract class Property : INotifyPropertyChanged
+ {
+ protected static readonly IReadOnlyList EmptyList = new List();
+
+ protected readonly Model Model;
+ public Property Parent { get; }
+
+ public abstract IReadOnlyList Errors { get; }
+
+ private bool _modified;
+
+ public bool Modified
+ {
+ get => _modified;
+ protected set
+ {
+ if (_modified == value)
+ {
+ return;
+ }
+
+ _modified = value;
+
+ using var validator = Model.BeginValidation();
+ validator.Notify(this, "Modified");
+ }
+ }
+
+ protected Property(Model model, Property parent)
+ {
+ Model = model;
+ Parent = parent;
+ }
+
+ public abstract void Validate();
+
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ [NotifyPropertyChangedInvocator]
+ public virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
+ {
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }
+
+ public bool IsParentOf(Property property)
+ {
+ while (property != null)
+ {
+ if (ReferenceEquals(this, property))
+ {
+ return true;
+ }
+
+ property = property.Parent;
+ }
+
+ return false;
+ }
+ }
+
+ public abstract class ValueProperty : Property
+ {
+ protected string Name;
+ private T _value;
+ private readonly List _errors;
+
+ public T Value
+ {
+ get => _value;
+ set
+ {
+ if (_value.Equals(value))
+ {
+ return;
+ }
+
+ _value = value;
+ OnPropertyChanged();
+
+ using var validator = Model.BeginValidation();
+ Modified = true;
+ validator.Validate(this);
+ }
+ }
+
+ protected ValueProperty(Model model, Property parent, string name, T value) : base(model, parent)
+ {
+ Name = name;
+ _value = value;
+ _errors = new List {""};
+
+ using var validator = Model.BeginValidation();
+ validator.Validate(this);
+ }
+
+ public override IReadOnlyList Errors => _errors[0] != "" ? _errors : EmptyList;
+
+ public sealed override void Validate()
+ {
+ var oldError = _errors[0];
+ _errors[0] = ValidateValue();
+
+ if (oldError != _errors[0])
+ {
+ using var validator = Model.BeginValidation();
+ validator.Notify(this, "Errors");
+ }
+ }
+
+ protected abstract string ValidateValue();
+ }
+
+ public class AlwaysValidProperty : ValueProperty
+ {
+ public AlwaysValidProperty(Model model, Property parent, string name, T value) : base(model, parent,
+ name, value)
+ {
+ }
+
+ protected override string ValidateValue()
+ {
+ return "";
+ }
+ }
+
+ public class NotEmptyStringProperty : ValueProperty
+ {
+ public NotEmptyStringProperty(Model model, Property parent, string name, string value) : base(model, parent,
+ name, value)
+ {
+ }
+
+ protected override string ValidateValue()
+ {
+ return string.IsNullOrEmpty(Value) ? $"{Name}: Geben Sie einen Wert ein." : "";
+ }
+ }
+
+ public class NaturalNumberProperty : ValueProperty
+ {
+ public NaturalNumberProperty(Model model, Property parent, string name, int value) : base(model, parent, name,
+ value.ToString())
+ {
+ }
+
+ protected override string ValidateValue()
+ {
+ return !int.TryParse(Value, out var n) || n <= 0
+ ? $"{Name}: Der eingegebene Wert ist keine gültige Zahl > 0."
+ : "";
+ }
+ }
+
+ public class PositiveDecimalProperty : ValueProperty
+ {
+ public PositiveDecimalProperty(Model model, Property parent, string name, decimal value) : base(model, parent,
+ name,
+ value.ToString(Constants.DecimalFormat))
+ {
+ }
+
+ protected override string ValidateValue()
+ {
+ return !decimal.TryParse(Value, out var n) || n < 0
+ ? $"{Name}: Der eingegebene Wert ist kein gültiger Betrag."
+ : "";
+ }
+ }
+
+ public abstract class NestedProperty : Property
+ {
+ private readonly List _properties;
+ private readonly List _errors;
+ public override IReadOnlyList Errors => _errors;
+
+ protected NestedProperty(Model model, Property parent) : base(model, parent)
+ {
+ _properties = new List();
+ _errors = new List();
+ }
+
+ protected T Add(T property) where T : Property
+ {
+ _properties.Add(property);
+ return property;
+ }
+
+ public override void Validate()
+ {
+ using var validator = Model.BeginValidation();
+ validator.ValidateRange(_properties);
+ }
+
+ public override void OnPropertyChanged(string propertyName = null)
+ {
+ switch (propertyName)
+ {
+ case "Errors":
+ _errors.Clear();
+
+ foreach (var property in _properties)
+ {
+ _errors.AddRange(property.Errors);
+ }
+
+ break;
+ case "Modified":
+ Modified = _properties.Any(property => property.Modified);
+ break;
+ }
+
+ base.OnPropertyChanged(propertyName);
+ }
+ }
+
+ public abstract class MultiProperty : Property, IReadOnlyList, INotifyCollectionChanged where T : Property
+ {
+ private readonly string _errorOnEmpty;
+ private readonly List _properties;
+ protected IReadOnlyList Properties => _properties;
+ private readonly List _errors;
+ public override IReadOnlyList Errors => _errors;
+
+ protected MultiProperty(Model model, Property parent, string errorOnEmpty = null) : base(model, parent)
+ {
+ _errorOnEmpty = errorOnEmpty;
+ _properties = new List();
+ _errors = new List();
+
+ OnPropertyChanged("Errors");
+ }
+
+ protected void Add(T property)
+ {
+ _properties.Add(property);
+
+ using var validator = Model.BeginValidation();
+ validator.Notify(this, "Errors");
+ Modified = true;
+
+ OnPropertyChanged("Count");
+
+ CollectionChanged?.Invoke(this,
+ new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, property,
+ _properties.Count - 1));
+ }
+
+ protected void Remove(T property)
+ {
+ var index = _properties.IndexOf(property);
+ _properties.RemoveAt(index);
+
+ using var validator = Model.BeginValidation();
+ validator.Notify(this, "Errors");
+ Modified = true;
+
+ OnPropertyChanged("Count");
+
+ CollectionChanged?.Invoke(this,
+ new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, property, index));
+ }
+
+ public override void Validate()
+ {
+ using var validator = Model.BeginValidation();
+ validator.ValidateRange(_properties);
+ }
+
+ public sealed override void OnPropertyChanged(string propertyName = null)
+ {
+ switch (propertyName)
+ {
+ case "Errors":
+ _errors.Clear();
+
+ if (_properties.Count == 0)
+ {
+ if (!string.IsNullOrEmpty(_errorOnEmpty))
+ {
+ _errors.Add(_errorOnEmpty);
+ }
+ }
+ else
+ {
+ foreach (var property in _properties)
+ {
+ _errors.AddRange(property.Errors);
+ }
+ }
+
+ break;
+ case "Modified":
+ Modified = Modified || _properties.Any(property => property.Modified);
+ break;
+ }
+
+ base.OnPropertyChanged(propertyName);
+ }
+
+ public IEnumerator GetEnumerator()
+ {
+ return _properties.GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ public int Count => _properties.Count;
+
+ public T this[int index] => _properties[index];
+
+ public event NotifyCollectionChangedEventHandler CollectionChanged;
+ }
+}
\ No newline at end of file
diff --git a/ucalc/Models/SalutationProperty.cs b/ucalc/Models/SalutationProperty.cs
new file mode 100644
index 0000000..d4ec12a
--- /dev/null
+++ b/ucalc/Models/SalutationProperty.cs
@@ -0,0 +1,12 @@
+using UCalc.Data;
+
+namespace UCalc.Models
+{
+ public class SalutationProperty : AlwaysValidProperty
+ {
+ public SalutationProperty(Model model, Property parent, string name, Salutation value) : base(model, parent,
+ name, (int) value)
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/ucalc/Models/TenantProperty.cs b/ucalc/Models/TenantProperty.cs
new file mode 100644
index 0000000..1624e6a
--- /dev/null
+++ b/ucalc/Models/TenantProperty.cs
@@ -0,0 +1,203 @@
+using System;
+using System.Collections.Generic;
+using UCalc.Controls;
+using UCalc.Data;
+
+namespace UCalc.Models
+{
+ public class EntryDateProperty : ValueProperty
+ {
+ public EntryDateProperty(Model model, Property parent, string name, DateTime? value) : base(model, parent, name,
+ value)
+ {
+ }
+
+ protected override string ValidateValue()
+ {
+ using var validator = Model.BeginValidation();
+
+ validator.Validate(((TenantProperty) Parent).DepartureDate);
+
+ return "";
+ }
+ }
+
+ public class DepatureDateProperty : ValueProperty
+ {
+ public DepatureDateProperty(Model model, Property parent, string name, DateTime? value) : base(model, parent,
+ name, value)
+ {
+ }
+
+ protected override string ValidateValue()
+ {
+ if (Value != null)
+ {
+ var entryDate = ((TenantProperty) Parent).EntryDate.Value;
+
+ if (entryDate != null && entryDate > Value)
+ {
+ return $"{Name}: Das Datum liegt vor dem Einzug.";
+ }
+ }
+
+ using var validator = Model.BeginValidation();
+ validator.Validate(((TenantProperty) Parent).RentedFlats);
+
+ return "";
+ }
+ }
+
+ public class RentedFlatsProperty : Property
+ {
+ private const string NoFlatsError = "Gemietete Wohnungen: Es wurde keine Wohnung zugewiesen.";
+ private readonly HashSet _flatProperties;
+ private readonly List _errors;
+
+ public RentedFlatsProperty(Model model, Property parent, IEnumerable data,
+ IReadOnlyDictionary flatToProperty = null) : base(model, parent)
+ {
+ _flatProperties = new HashSet();
+ _errors = new List();
+
+ foreach (var flat in data)
+ {
+ _flatProperties.Add(flatToProperty?[flat] ?? throw new InvalidOperationException());
+ }
+
+ Validate();
+ }
+
+ public override IReadOnlyList Errors => _errors;
+
+ public void Add(FlatProperty flat)
+ {
+ if (!_flatProperties.Add(flat))
+ {
+ return;
+ }
+
+ using var validator = Model.BeginValidation();
+ validator.Validate(this);
+ Modified = true;
+ }
+
+ public void Remove(FlatProperty flat)
+ {
+ if (!_flatProperties.Remove(flat))
+ {
+ return;
+ }
+
+ using var validator = Model.BeginValidation();
+ validator.Validate(this);
+ Modified = true;
+ }
+
+ public bool Contains(FlatProperty flat)
+ {
+ return _flatProperties.Contains(flat);
+ }
+
+ public sealed override void Validate()
+ {
+ _errors.Clear();
+
+ if (_flatProperties.Count == 0)
+ {
+ _errors.Add(NoFlatsError);
+ return;
+ }
+
+ var entryDate = ((TenantProperty) Parent).EntryDate.Value;
+ var departureDate = ((TenantProperty) Parent).DepartureDate.Value;
+
+ foreach (var flatProperty in _flatProperties)
+ {
+ foreach (var tenant in Model.Root.Tenants)
+ {
+ if (ReferenceEquals(this, tenant.RentedFlats))
+ {
+ continue;
+ }
+
+ if (tenant.RentedFlats.IsRented(flatProperty, entryDate, departureDate))
+ {
+ var error = $"Gemietete Wohnungen: Die Wohnung \"{flatProperty.Name.Value}\" ist bereits ";
+
+ if (entryDate != null)
+ {
+ if (departureDate != null)
+ {
+ _errors.Add(
+ $"von {entryDate.Value.ToString(Constants.DateFormat)} - {departureDate.Value.ToString(Constants.DateFormat)} ");
+ }
+ else
+ {
+ _errors.Add($"seit dem {entryDate.Value.ToString(Constants.DateFormat)} ");
+ }
+ }
+ else if (departureDate != null)
+ {
+ _errors.Add($"bis zum {departureDate.Value.ToString(Constants.DateFormat)} ");
+ }
+
+ error += "an einen anderen Mieter vermietet.";
+ _errors.Add(error);
+ break;
+ }
+ }
+ }
+
+ using var validator = Model.BeginValidation();
+ validator.Notify(this, "Errors");
+
+ validator.ValidateRange(Model.Root.Tenants);
+ }
+
+ private bool IsRented(FlatProperty flat, DateTime? start, DateTime? end)
+ {
+ if (!_flatProperties.Contains(flat))
+ {
+ return false;
+ }
+
+ var thisStart = ((TenantProperty) Parent).EntryDate.Value ?? DateTime.MinValue;
+ var thisEnd = ((TenantProperty) Parent).DepartureDate.Value ?? DateTime.MaxValue;
+
+ var otherStart = start ?? DateTime.MinValue;
+ var otherEnd = end ?? DateTime.MinValue;
+
+ return thisStart.Intersects(thisEnd, otherStart, otherEnd);
+ }
+ }
+
+ public class TenantProperty : NestedProperty
+ {
+ public SalutationProperty Salutation { get; }
+ public NotEmptyStringProperty Name { get; }
+ public NaturalNumberProperty PersonCount { get; }
+ public BankAccountProperty BankAccount { get; }
+ public EntryDateProperty EntryDate { get; }
+ public DepatureDateProperty DepartureDate { get; }
+ public RentedFlatsProperty RentedFlats { get; }
+ public PositiveDecimalProperty PaidRent { get; }
+ public AlwaysValidProperty CustomMessage1 { get; }
+ public AlwaysValidProperty CustomMessage2 { get; }
+
+ public TenantProperty(Model model, Property parent, Tenant data,
+ IReadOnlyDictionary flatToProperty = null) : base(model, parent)
+ {
+ Salutation = Add(new SalutationProperty(model, this, "Anrede", data.Salutation));
+ Name = Add(new NotEmptyStringProperty(model, this, "Name", data.Name));
+ PersonCount = Add(new NaturalNumberProperty(model, this, "Personenanzahl", data.PersonCount));
+ BankAccount = Add(new BankAccountProperty(model, this, data.BankAccount));
+ EntryDate = Add(new EntryDateProperty(model, this, "Einzugsdatum", data.EntryDate));
+ DepartureDate = Add(new DepatureDateProperty(model, this, "Auszugsdatum", data.DepartureDate));
+ RentedFlats = Add(new RentedFlatsProperty(model, this, data.RentedFlats, flatToProperty));
+ PaidRent = Add(new PositiveDecimalProperty(model, this, "Bereits gezahlt", data.PaidRent));
+ CustomMessage1 = Add(new AlwaysValidProperty(model, this, "Nachricht 1", data.CustomMessage1));
+ CustomMessage2 = Add(new AlwaysValidProperty(model, this, "Nachricht 2", data.CustomMessage2));
+ }
+ }
+}
\ No newline at end of file
diff --git a/ucalc/Models/TenantsProperty.cs b/ucalc/Models/TenantsProperty.cs
new file mode 100644
index 0000000..fd8b33a
--- /dev/null
+++ b/ucalc/Models/TenantsProperty.cs
@@ -0,0 +1,39 @@
+using System.Collections.Generic;
+using System.Linq;
+using UCalc.Data;
+
+namespace UCalc.Models
+{
+ public class TenantsProperty : MultiProperty
+ {
+ public TenantsProperty(Model model, Property parent, IEnumerable data,
+ IReadOnlyDictionary flatToProperty = null) : base(model, parent,
+ "Mieter: Geben Sie einen oder mehr Mieter an.")
+ {
+ using var validator = Model.BeginValidation();
+
+ foreach (var tenant in data)
+ {
+ base.Add(new TenantProperty(Model, this, tenant, flatToProperty));
+ }
+
+ Modified = false;
+ }
+
+ public void Add()
+ {
+ using var validator = Model.BeginValidation();
+
+ base.Add(new TenantProperty(Model, this, new Tenant()));
+ }
+
+ public new void Remove(TenantProperty tenant)
+ {
+ using var validator = Model.BeginValidation();
+
+ base.Remove(tenant);
+
+ validator.ValidateRange(Properties.Select(otherTenant => otherTenant.RentedFlats));
+ }
+ }
+}
\ No newline at end of file
diff --git a/ucalc/NewWindow.xaml.cs b/ucalc/NewWindow.xaml.cs
index 6eca657..6b684c5 100644
--- a/ucalc/NewWindow.xaml.cs
+++ b/ucalc/NewWindow.xaml.cs
@@ -71,7 +71,7 @@ namespace UCalc
return;
}
- // Remove depatured renters and costs that were only paid once
+ // Remove departured renters and costs that were only paid once
string summary = "";
billing.StartDate = (DateTime) StartCalendar.SelectedDate;
billing.EndDate = (DateTime) EndCalendar.SelectedDate;
diff --git a/ucalc/Pages/HousePage.xaml b/ucalc/Pages/HousePage.xaml
index 685c7d0..506b90b 100644
--- a/ucalc/Pages/HousePage.xaml
+++ b/ucalc/Pages/HousePage.xaml
@@ -8,6 +8,10 @@
xmlns:controls="clr-namespace:UCalc.Controls"
mc:Ignorable="d">
+
+
+
+
@@ -22,12 +26,12 @@
+ Property="{Binding Path=House.Address.Street, RelativeSource={RelativeSource AncestorType=pages:HousePage}}" />
+ Text="{Binding Path=House.Address.Street.Value, RelativeSource={RelativeSource AncestorType=pages:HousePage}, UpdateSourceTrigger=PropertyChanged}" />
@@ -39,12 +43,12 @@
+ Property="{Binding Path=House.Address.HouseNumber, RelativeSource={RelativeSource AncestorType=pages:HousePage}}" />
+ Text="{Binding Path=House.Address.HouseNumber.Value, RelativeSource={RelativeSource AncestorType=pages:HousePage}, UpdateSourceTrigger=PropertyChanged}" />
@@ -56,12 +60,12 @@
+ Property="{Binding Path=House.Address.City, RelativeSource={RelativeSource AncestorType=pages:HousePage}}" />
+ Text="{Binding Path=House.Address.City.Value, RelativeSource={RelativeSource AncestorType=pages:HousePage}, UpdateSourceTrigger=PropertyChanged}" />
@@ -73,45 +77,53 @@
+ Property="{Binding Path=House.Address.Postcode, RelativeSource={RelativeSource AncestorType=pages:HousePage}}" />
+ Text="{Binding Path=House.Address.Postcode.Value, RelativeSource={RelativeSource AncestorType=pages:HousePage}, UpdateSourceTrigger=PropertyChanged}" />
-
-
-
-
+
+
-
-
+
+
+
+
+
+
+
+
@@ -143,7 +155,7 @@
HighlightBackground="{x:Static local:Constants.MainColor}"
Click="OnFlatEditClick">
-
diff --git a/ucalc/Pages/HousePage.xaml.cs b/ucalc/Pages/HousePage.xaml.cs
index 026de8b..658ecf4 100644
--- a/ucalc/Pages/HousePage.xaml.cs
+++ b/ucalc/Pages/HousePage.xaml.cs
@@ -7,7 +7,7 @@ namespace UCalc.Pages
public partial class HousePage
{
public Window ParentWindow { get; set; }
- public HouseModel Model { get; set; }
+ public HouseProperty House { get; set; }
public HousePage()
{
@@ -16,25 +16,25 @@ namespace UCalc.Pages
private void OnAddFlatClick(object sender, RoutedEventArgs e)
{
- Model.AddFlat();
+ House.Flats.Add();
}
private void OnFlatDeleteClick(object sender, RoutedEventArgs e)
{
- var flatModel = (FlatModel) ((HighlightButton) sender).DataContext;
+ var flat = (FlatProperty) ((HighlightButton) sender).DataContext;
- if (MessageBox.Show($"Möchten Sie die Wohnung \"{flatModel.Data.Name}\" wirlick löschen?", "Löschen?",
+ if (MessageBox.Show($"Möchten Sie die Wohnung \"{flat.Name.Value}\" wirlick löschen?", "Löschen?",
MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes)
{
- Model.RemoveFlat(flatModel);
+ House.Flats.Remove(flat);
}
}
private void OnFlatEditClick(object sender, RoutedEventArgs e)
{
- var flatModel = (FlatModel) ((HighlightButton) sender).DataContext;
+ var flat = (FlatProperty) ((HighlightButton) sender).DataContext;
- new FlatWindow(flatModel) {Owner = ParentWindow}.ShowDialog();
+ new FlatWindow(flat) {Owner = ParentWindow}.ShowDialog();
}
}
}
\ No newline at end of file
diff --git a/ucalc/Pages/LandlordPage.xaml b/ucalc/Pages/LandlordPage.xaml
index 8033dea..952530e 100644
--- a/ucalc/Pages/LandlordPage.xaml
+++ b/ucalc/Pages/LandlordPage.xaml
@@ -21,7 +21,7 @@
+ SelectedIndex="{Binding Path=Landlord.Salutation.Value, RelativeSource={RelativeSource AncestorType=pages:LandlordPage}}"/>
@@ -33,12 +33,12 @@
+ Property="{Binding Path=Landlord.Name, RelativeSource={RelativeSource AncestorType=pages:LandlordPage}}" />
+ Text="{Binding Path=Landlord.Name.Value, RelativeSource={RelativeSource AncestorType=pages:LandlordPage}, UpdateSourceTrigger=PropertyChanged}" />
@@ -50,12 +50,12 @@
+ Property="{Binding Path=Landlord.Phone, RelativeSource={RelativeSource AncestorType=pages:LandlordPage}}" />
+ Text="{Binding Path=Landlord.Phone.Value, RelativeSource={RelativeSource AncestorType=pages:LandlordPage}, UpdateSourceTrigger=PropertyChanged}" />
@@ -67,12 +67,12 @@
+ Property="{Binding Path=Landlord.MailAddress, RelativeSource={RelativeSource AncestorType=pages:LandlordPage}}" />
+ Text="{Binding Path=Landlord.MailAddress.Value, RelativeSource={RelativeSource AncestorType=pages:LandlordPage}, UpdateSourceTrigger=PropertyChanged}" />
+ Property="{Binding Path=Landlord.Address.Street, RelativeSource={RelativeSource AncestorType=pages:LandlordPage}}" />
+ Text="{Binding Path=Landlord.Address.Street.Value, RelativeSource={RelativeSource AncestorType=pages:LandlordPage}, UpdateSourceTrigger=PropertyChanged}" />
@@ -104,12 +104,12 @@
+ Property="{Binding Path=Landlord.Address.HouseNumber, RelativeSource={RelativeSource AncestorType=pages:LandlordPage}}" />
+ Text="{Binding Path=Landlord.Address.HouseNumber.Value, RelativeSource={RelativeSource AncestorType=pages:LandlordPage}, UpdateSourceTrigger=PropertyChanged}" />
@@ -121,12 +121,12 @@
+ Property="{Binding Path=Landlord.Address.City, RelativeSource={RelativeSource AncestorType=pages:LandlordPage}}" />
+ Text="{Binding Path=Landlord.Address.City.Value, RelativeSource={RelativeSource AncestorType=pages:LandlordPage}, UpdateSourceTrigger=PropertyChanged}" />
@@ -138,12 +138,12 @@
+ Property="{Binding Path=Landlord.Address.Postcode, RelativeSource={RelativeSource AncestorType=pages:LandlordPage}}" />
+ Text="{Binding Path=Landlord.Address.Postcode.Value, RelativeSource={RelativeSource AncestorType=pages:LandlordPage}, UpdateSourceTrigger=PropertyChanged}" />
+ Property="{Binding Path=Landlord.BankAccount.Iban, RelativeSource={RelativeSource AncestorType=pages:LandlordPage}}" />
+ Text="{Binding Path=Landlord.BankAccount.Iban.Value, RelativeSource={RelativeSource AncestorType=pages:LandlordPage}, UpdateSourceTrigger=PropertyChanged}" />
@@ -175,12 +175,12 @@
+ Property="{Binding Path=Landlord.BankAccount.Bic, RelativeSource={RelativeSource AncestorType=pages:LandlordPage}}" />
+ Text="{Binding Path=Landlord.BankAccount.Bic.Value, RelativeSource={RelativeSource AncestorType=pages:LandlordPage}, UpdateSourceTrigger=PropertyChanged}" />
@@ -192,12 +192,12 @@
+ Property="{Binding Path=Landlord.BankAccount.BankName, RelativeSource={RelativeSource AncestorType=pages:LandlordPage}}" />
+ Text="{Binding Path=Landlord.BankAccount.BankName.Value, RelativeSource={RelativeSource AncestorType=pages:LandlordPage}, UpdateSourceTrigger=PropertyChanged}" />
diff --git a/ucalc/Pages/LandlordPage.xaml.cs b/ucalc/Pages/LandlordPage.xaml.cs
index 1432500..b5817ba 100644
--- a/ucalc/Pages/LandlordPage.xaml.cs
+++ b/ucalc/Pages/LandlordPage.xaml.cs
@@ -4,7 +4,7 @@ namespace UCalc.Pages
{
public partial class LandlordPage
{
- public LandlordModel Model { get; set; }
+ public LandlordProperty Landlord { get; set; }
public LandlordPage()
{
diff --git a/ucalc/Pages/SideBar.xaml b/ucalc/Pages/SideBar.xaml
index f78380e..7b5e1f5 100644
--- a/ucalc/Pages/SideBar.xaml
+++ b/ucalc/Pages/SideBar.xaml
@@ -119,7 +119,7 @@
+ Property="{Binding Path=Model.Root.Landlord, RelativeSource={RelativeSource AncestorType=pages:SideBar}}"/>
+ Property="{Binding Path=Model.Root.House, RelativeSource={RelativeSource AncestorType=pages:SideBar}}"/>