<repo_name>medranSolus/ZooManager<file_sep>/Test/GetModelsTest.cs
﻿using Moq;
using ZooService;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections.Generic;

namespace Test
{
    [TestClass]
    public class GetModelsTest
    {
        ZooService.ZooService service;
        Mock<ZooContext> IZooContext;
        List<Animal> animals;

        [TestInitialize]
        public void SetUp()
        {
            IZooContext = new Mock<ZooContext>();
            service = new ZooService.ZooService(1111);
            
            animals = new List<Animal>();
            
            var animal = new Animal();

            animals.Add(animal);
        }

        [TestMethod]
        public void Returns_not_empty_list()
        {
            IZooContext.Setup(x => x.GetModels<Animal>()).Returns(animals);

            var models = service.GetModels(IZooContext.Object, ZooService.ModelType.Animal);

            Assert.IsTrue(models is List<ZooDataModel>);
            Assert.IsTrue(models != null);
            Assert.IsTrue(models.Count > 0);

        }

        [TestMethod]
        public void Returns_empty_list()
        {
            IZooContext.Setup(x => x.GetModels<Overtime>()).Returns(new List<Overtime>());
            IZooContext.Setup(x => x.GetModels<Place>()).Returns(new List<Place>());
            IZooContext.Setup(x => x.GetModels<Worker>()).Returns(new List<Worker>());
            IZooContext.Setup(x => x.GetModels<Attraction>()).Returns(new List<Attraction>());
            IZooContext.Setup(x => x.GetModels<BalanceType>()).Returns(new List<BalanceType>());
            IZooContext.Setup(x => x.GetModels<CashBalance>()).Returns(new List<CashBalance>());
            IZooContext.Setup(x => x.GetModels<Food>()).Returns(new List<Food>());

            var models = service.GetModels(IZooContext.Object, ZooService.ModelType.Overtime);
            Assert.IsTrue(models != null);
            Assert.IsTrue(models.Count == 0);
            models = service.GetModels(IZooContext.Object, ZooService.ModelType.Place);
            Assert.IsTrue(models != null);
            Assert.IsTrue(models.Count == 0);
            models = service.GetModels(IZooContext.Object, ZooService.ModelType.Worker);
            Assert.IsTrue(models != null);
            Assert.IsTrue(models.Count == 0);
            models = service.GetModels(IZooContext.Object, ZooService.ModelType.Attraction);
            Assert.IsTrue(models != null);
            Assert.IsTrue(models.Count == 0);
            models = service.GetModels(IZooContext.Object, ZooService.ModelType.BalanceType);
            Assert.IsTrue(models != null);
            Assert.IsTrue(models.Count == 0);
            models = service.GetModels(IZooContext.Object, ZooService.ModelType.CashBalance);
            Assert.IsTrue(models != null);
            Assert.IsTrue(models.Count == 0);
            models = service.GetModels(IZooContext.Object, ZooService.ModelType.Food);
            Assert.IsTrue(models != null);
            Assert.IsTrue(models.Count == 0);

        }
        
        [TestMethod]
        public void Returns_null()
        {
            var models = service.GetModels(IZooContext.Object, ZooService.ModelType.Model);
            Assert.IsNull(models);
        }
    }
}
<file_sep>/ZooService/Program.cs
﻿using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;

namespace ZooService
{

    class Program
    {
        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        static extern bool SetForegroundWindow(IntPtr hWnd);

        static void Main(string[] args)
        {
            bool createdNew = true;
            using (Mutex mutex = new Mutex(true, "ZooService", out createdNew))
            {
                if (createdNew)
                {
                    const int port = 2560;
                    Console.WriteLine($"Starting Zoo Service on port {port}, please wait...");
                    ZooService service = new ZooService(port);
                    service.Start();
                    Console.WriteLine("Zoo Service is ON. Press [E] to stop.");
                    while (Console.ReadKey().Key == ConsoleKey.E) ;
                }
                else
                {
                    Process current = Process.GetCurrentProcess();
                    foreach (Process process in Process.GetProcessesByName(current.ProcessName))
                    {
                        if (process.Id != current.Id)
                        {
                            SetForegroundWindow(process.MainWindowHandle);
                            break;
                        }
                    }
                }
            }
        }
    }
}
<file_sep>/ZooManager/Windows/MainWindow.xaml.cs
﻿using System;
using System.Windows;
using System.Linq;
using System.Threading;
using System.Collections.Generic;
using ZooManager.Views;
using ZooManager.Windows.TabAnimals;
using ZooManager.Windows.TabPlaces;
using ZooManager.Windows.TabWorkers;
using ZooManager.Windows.TabCash;

namespace ZooManager.Windows
{
    public partial class MainWindow : Window
    {
        Thread threadUpdate;
        volatile bool update = true;
        volatile bool dataGridWorkersShouldClear = false;
        volatile bool dataGridAnimalsShouldClear = false;
        volatile bool dataGridFoodsShouldClear = false;
        volatile bool dataGridPlacesShouldClear = false;
        volatile bool dataGridAttractionsShouldClear = false;

        List<object> animals = new List<object>();
        List<object> attractions = new List<object>();
        List<object> cashBalances = new List<object>();
        List<object> overtimes = new List<object>();
        List<object> workers = new List<object>();

        public List<object> BalanceTypes { get; private set; } = new List<object>();
        public List<object> Foods { get; private set; } = new List<object>();
        public List<object> Places { get; private set; } = new List<object>();
        public ZooClient ZooConnection { get; private set; }

        public MainWindow()
        {
            InitializeComponent();
            const int port = 2560;
            try
            {
                ZooConnection = new ZooClient(port);
            }
            catch (Exception e)
            {
                Logger.LogError($"Cannot load ZooCom.dll: {e.ToString()}", GetType(), "MainWindow()");
                Application.Current.Shutdown(-1);
            }
            dataGridFoods.DataContext = ZooConnection.GetModelType(ModelType.Food);
            dataGridPlaces.DataContext = ZooConnection.GetModelType(ModelType.Place);
            threadUpdate = new Thread(() =>
            {
                TimeSpan waitTime = new TimeSpan(0, 5, 0);
                while (update)
                {
                    UpdateAnimals();
                    UpdateAttractions();
                    UpdateCashBalances();
                    UpdateFoods();
                    UpdateOvertimes();
                    UpdatePlaces();
                    UpdateWorkers();
                    Thread.Sleep(waitTime);
                }
            });
            threadUpdate.Start();
        }

        public IEnumerable<WorkerView> GetWorkerViews()
        {
            lock (Places)
            {
                return workers.Join(Places,
                    worker => ZooClient.GetProperty(worker, "PlaceID"), place => ZooClient.GetProperty(place, "ID"),
                    (worker, place) => new WorkerView()
                    {
                        ID = (int)ZooClient.GetProperty(worker, "ID"),
                        Surname = (string)ZooClient.GetProperty(worker, "Surname"),
                        Name = (string)ZooClient.GetProperty(worker, "Name"),
                        Age = (int)ZooClient.GetProperty(worker, "Age"),
                        Salary = (decimal)ZooClient.GetProperty(worker, "Salary"),
                        StartDate = (DateTime)ZooClient.GetProperty(worker, "StartDate"),
                        Place = (string)ZooClient.GetProperty(place, "Name")
                    });
            }
        }

        #region Update
        void UpdateAnimals()
        {
            new Thread(() =>
            {
                IEnumerable<AnimalView> source;
                lock (animals)
                {
                    animals = ZooConnection.GetModels(ModelType.Animal);
                    lock (Places)
                        lock (Foods)
                        {
                            source = animals.Join(Places,
                                animal => ZooClient.GetProperty(animal, "PlaceID"), place => ZooClient.GetProperty(place, "ID"),
                                (animal, place) => new
                                {
                                    ID = (int)ZooClient.GetProperty(animal, "ID"),
                                    Name = (string)ZooClient.GetProperty(animal, "Name"),
                                    Count = (int)ZooClient.GetProperty(animal, "Count"),
                                    MaintenanceCost = (decimal)ZooClient.GetProperty(animal, "MaintenanceCost"),
                                    Place = (string)ZooClient.GetProperty(place, "Name"),
                                    FoodID = (int)ZooClient.GetProperty(animal, "FoodID")
                                }).Join(Foods,
                                anon => ZooClient.GetProperty(anon, "FoodID"), food => ZooClient.GetProperty(food, "ID"),
                                (anon, food) => new AnimalView()
                                {
                                    ID = anon.ID,
                                    Name = anon.Name,
                                    Count = anon.Count,
                                    MaintenanceCost = anon.MaintenanceCost,
                                    Place = anon.Place,
                                    Food = (string)ZooClient.GetProperty(food, "Name")
                                });
                        }
                }
                dataGridAnimals.Dispatcher.Invoke(() =>
                {
                    buttonModifyAnimal.IsEnabled = false;
                    buttonRemoveAnimal.IsEnabled = false;
                    dataGridAnimals.ItemsSource = source;
                    labelAnimalSpeciesCount.Content = source.Count().ToString();
                    labelAnimalsCount.Content = source.Sum(animal => animal.Count).ToString();
                });
            }).Start();
        }

        void UpdateFoods(int? foodId = null)
        {
            new Thread(() =>
            {
                IEnumerable<object> filtered;
                lock (Foods)
                {
                    Foods = ZooConnection.GetModels(ModelType.Food);
                    if (foodId.HasValue)
                        filtered = Foods.Where(food => (int)ZooClient.GetProperty(food, "ID") == foodId.Value);
                    else
                        filtered = Foods;
                }
                dataGridFoods.Dispatcher.Invoke(() =>
                {
                    buttonModifyFood.IsEnabled = false;
                    buttonRemoveFood.IsEnabled = false;
                    dataGridFoods.ItemsSource = filtered;
                    labelFoodsCount.Content = filtered.Sum(food => (double)ZooClient.GetProperty(food, "Amount")).ToString();
                });
            }).Start();
        }

        void UpdateWorkers()
        {
            new Thread(() =>
            {
                IEnumerable<WorkerView> source;
                lock (workers)
                {
                    workers = ZooConnection.GetModels(ModelType.Worker);
                    source = GetWorkerViews();
                }
                dataGridWorkers.Dispatcher.Invoke(() =>
                {
                    buttonModifyWorker.IsEnabled = false;
                    buttonRemoveWorker.IsEnabled = false;
                    buttonSubmitOvertime.IsEnabled = false;
                    dataGridWorkers.ItemsSource = source;
                    labelWorkersCount.Content = source.Count().ToString();
                });
            }).Start();
        }

        void UpdateOvertimes(int? workerId = null)
        {
            new Thread(() =>
            {
                IEnumerable<object> filtered;
                lock (overtimes)
                {
                    overtimes = ZooConnection.GetModels(ModelType.Overtime);
                    if (workerId.HasValue)
                        filtered = overtimes.Where(overtime => (int)ZooClient.GetProperty(overtime, "WorkerID") == workerId.Value);
                    else
                        filtered = overtimes;
                    lock (workers)
                    {
                        filtered = filtered.Join(workers,
                            overtime => ZooClient.GetProperty(overtime, "WorkerID"), worker => ZooClient.GetProperty(worker, "ID"),
                            (overtime, worker) => CreateOvertimeView(overtime, worker));
                    }
                }
                dataGridOvertimes.Dispatcher.Invoke(() =>
                {
                    dataGridOvertimes.ItemsSource = filtered;
                });
            }).Start();
        }

        void UpdateCashBalances()
        {
            new Thread(() =>
            {
                IEnumerable<CashBalanceView> source;
                lock (BalanceTypes)
                {
                    BalanceTypes = ZooConnection.GetModels(ModelType.BalanceType);
                    lock (cashBalances)
                    {
                        cashBalances = ZooConnection.GetModels(ModelType.CashBalance);
                        source = cashBalances.Join(BalanceTypes,
                            balance => ZooClient.GetProperty(balance, "BalanceTypeID"), type => ZooClient.GetProperty(type, "ID"),
                            (balance, type) => new CashBalanceView()
                            {
                                ID = (int)ZooClient.GetProperty(balance, "ID"),
                                SubmitDate = (DateTime)ZooClient.GetProperty(balance, "SubmitDate"),
                                Money = (decimal)ZooClient.GetProperty(balance, "Money"),
                                Type = (string)ZooClient.GetProperty(type, "Description"),
                                DetailedDescription = (string)ZooClient.GetProperty(balance, "DetailedDescription")
                            });
                    }
                }
                dataGridCashBalances.Dispatcher.Invoke(() =>
                {
                    dataGridCashBalances.ItemsSource = source.OrderByDescending(balance => balance.SubmitDate);
                });
            }).Start();
        }

        void UpdatePlaces()
        {
            new Thread(() =>
            {
                lock (Places)
                    Places = ZooConnection.GetModels(ModelType.Place);
                dataGridPlaces.Dispatcher.Invoke(() =>
                {
                    buttonModifyPlace.IsEnabled = false;
                    buttonRemovePlace.IsEnabled = false;
                    dataGridPlaces.ItemsSource = Places;
                    labelPlacesCount.Content = Places.Count.ToString();
                });
            }).Start();
        }

        void UpdateAttractions(int? placeId = null)
        {
            new Thread(() =>
            {
                IEnumerable<AttractionView> source;
                lock (attractions)
                {
                    attractions = ZooConnection.GetModels(ModelType.Attraction);
                    IEnumerable<object> filtered;
                    if (placeId.HasValue)
                        filtered = attractions.Where(attraction => (int)ZooClient.GetProperty(attraction, "PlaceID") == placeId.Value);
                    else
                        filtered = attractions;
                    lock (Places)
                        lock (workers)
                        {
                            source = filtered.Join(Places,
                                attraction => ZooClient.GetProperty(attraction, "PlaceID"), place => ZooClient.GetProperty(place, "ID"),
                                (attraction, place) => new
                                {
                                    ID = (int)ZooClient.GetProperty(attraction, "ID"),
                                    Name = (string)ZooClient.GetProperty(attraction, "Name"),
                                    Description = (string)ZooClient.GetProperty(attraction, "Description"),
                                    AttractionManagerID = (int)ZooClient.GetProperty(attraction, "AttractionManagerID"),
                                    Place = (string)ZooClient.GetProperty(place, "Name")
                                }).Join(workers,
                                anon => ZooClient.GetProperty(anon, "AttractionManagerID"), worker => ZooClient.GetProperty(worker, "ID"),
                                (anon, worker) => new AttractionView()
                                {
                                    ID = anon.ID,
                                    Name = anon.Name,
                                    Description = anon.Description,
                                    AttractionManager = (string)ZooClient.GetProperty(worker, "Name") + " " + (string)ZooClient.GetProperty(worker, "Surname"),
                                    Place = anon.Place
                                });
                        }
                    dataGridAttractions.Dispatcher.Invoke(() =>
                    {
                        buttonModifyAttraction.IsEnabled = false;
                        buttonRemoveAttraction.IsEnabled = false;
                        dataGridAttractions.ItemsSource = source;
                        labelAttractionsCount.Content = source.Count();
                    });
                }
            }).Start();
        }
        #endregion Update

        OvertimeView CreateOvertimeView(object overtime, object worker)
        {
            return new OvertimeView()
            {
                ID = (int)ZooClient.GetProperty(overtime, "ID"),
                Date = (DateTime)ZooClient.GetProperty(overtime, "Date"),
                Hours = (int)ZooClient.GetProperty(overtime, "Hours"),
                PaymentPercentage = (int)ZooClient.GetProperty(overtime, "PaymentPercentage"),
                Employee = (string)ZooClient.GetProperty(worker, "Name") + " " + (string)ZooClient.GetProperty(worker, "Surname")
            };
        }

        #region Animals Tab
        void ButtonRefreshAnimals_Click(object sender, RoutedEventArgs e)
        {
            UpdateFoods();
            UpdatePlaces();
            UpdateAnimals();
        }

        void DataGridAnimals_MouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            if (dataGridAnimalsShouldClear && dataGridAnimals.SelectedItem != null)
            {
                buttonModifyAnimal.IsEnabled = false;
                buttonRemoveAnimal.IsEnabled = false;
                dataGridAnimals.SelectedItem = null;
                DataGridFoods_MouseLeftButtonUp(sender, e);
                UpdateFoods();
            }
            else
                dataGridAnimalsShouldClear = true;
        }

        void DataGridAnimals_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
        {
            if (dataGridAnimals.SelectedItem != null)
            {
                buttonModifyAnimal.IsEnabled = true;
                buttonRemoveAnimal.IsEnabled = true;
                dataGridAnimalsShouldClear = false;
                DataGridFoods_SelectionChanged(sender, e);
                int id = (int)ZooClient.GetProperty(dataGridAnimals.SelectedItem, "ID");
                int foodId = 0;
                lock (animals)
                    foodId = (int)ZooClient.GetProperty(animals.FirstOrDefault(animal => (int)ZooClient.GetProperty(animal, "ID") == id), "FoodID");
                UpdateFoods(foodId);
            }
        }

        void DataGridFoods_MouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            if (dataGridFoodsShouldClear && dataGridFoods.SelectedItem != null)
            {
                buttonModifyFood.IsEnabled = false;
                buttonRemoveFood.IsEnabled = false;
                dataGridFoods.SelectedItem = null;
            }
            else
                dataGridFoodsShouldClear = true;
        }

        void DataGridFoods_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
        {
            if (dataGridFoods.SelectedItem != null)
            {
                buttonModifyFood.IsEnabled = true;
                buttonRemoveFood.IsEnabled = true;
                dataGridFoodsShouldClear = false;
            }
        }

        void ButtonAddAnimal_Click(object sender, RoutedEventArgs e)
        {
            AddAnimalWindow addAnimalWindow = new AddAnimalWindow(this);
            bool? isAdded = addAnimalWindow.ShowDialog();
            if (isAdded.HasValue && isAdded.Value)
            {
                UpdateAnimals();
                UpdateFoods();
            }
        }

        void ButtonModifyAnimal_Click(object sender, RoutedEventArgs e)
        {
            if (dataGridAnimals.SelectedItem != null)
            {
                object animal = null;
                lock (animals)
                    animal = animals.SingleOrDefault(a => (int)ZooClient.GetProperty(a, "ID") == ((AnimalView)dataGridAnimals.SelectedItem).ID);
                ModifyAnimalWindow modifyAnimalWindow = new ModifyAnimalWindow(this, animal);
                bool? isModified = modifyAnimalWindow.ShowDialog();
                if (isModified.HasValue && isModified.Value)
                {
                    UpdateAnimals();
                    UpdateFoods();
                }
            }
        }

        void ButtonRemoveAnimal_Click(object sender, RoutedEventArgs e)
        {
            if (dataGridAnimals.SelectedItem != null)
            {
                int id = ((AnimalView)dataGridAnimals.SelectedItem).ID;
                new Thread(() =>
                {
                    Tuple<bool, byte> status = ZooConnection.DeleteModel(ModelType.Animal, id);
                    if (status == null)
                        MessageBox.Show("[ERROR] Cannot remove animal from Zoo, server connection error!");
                    else if (!status.Item1)
                        MessageBox.Show(ZooClient.DecodeDeleteError(status.Item2));
                    else
                    {
                        UpdateAnimals();
                        UpdateFoods();
                    }
                }).Start();
            }
        }

        void ButtonAddFood_Click(object sender, RoutedEventArgs e)
        {
            AddFoodWindow addFoodWindow = new AddFoodWindow(this);
            bool? isAdded = addFoodWindow.ShowDialog();
            if (isAdded.HasValue && isAdded.Value)
            {
                UpdateAnimals();
                UpdateFoods();
            }
        }

        void ButtonModifyFoodCount_Click(object sender, RoutedEventArgs e)
        {
            if (dataGridFoods.SelectedItem != null)
            {
                object food = null;
                lock (Foods)
                    food = Foods.SingleOrDefault(f => (int)ZooClient.GetProperty(f, "ID") == (int)ZooClient.GetProperty(dataGridFoods.SelectedItem, "ID"));
                ModifyFoodWindow modifyFoodWindow = new ModifyFoodWindow(this, food);
                bool? isModified = modifyFoodWindow.ShowDialog();
                if (isModified.HasValue && isModified.Value)
                    UpdateFoods();
            }
        }

        void ButtonRemoveFood_Click(object sender, RoutedEventArgs e)
        {
            if (dataGridFoods.SelectedItem != null)
            {
                int id = (int)ZooClient.GetProperty(dataGridFoods.SelectedItem, "ID");
                new Thread(() =>
                {
                    Tuple<bool, byte> status = ZooConnection.DeleteModel(ModelType.Food, id);
                    if (status == null)
                        MessageBox.Show("[ERROR] Cannot remove food type, server connection error!");
                    else if (!status.Item1)
                        MessageBox.Show(ZooClient.DecodeDeleteError(status.Item2));
                    else
                    {
                        UpdateAnimals();
                        UpdateFoods();
                    }
                }).Start();
            }
        }
        #endregion Animals Tab

        #region Workers Tab
        void ButtonRefreshWorkers_Click(object sender, RoutedEventArgs e)
        {
            UpdateWorkers();
            UpdateOvertimes();
        }

        void DataGridWorkers_MouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            if (dataGridWorkersShouldClear && dataGridWorkers.SelectedItem != null)
            {
                buttonModifyWorker.IsEnabled = false;
                buttonRemoveWorker.IsEnabled = false;
                buttonSubmitOvertime.IsEnabled = false;
                dataGridWorkers.SelectedItem = null;
                UpdateOvertimes();
            }
            else
                dataGridWorkersShouldClear = true;
        }

        void DataGridWorkers_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
        {
            if (dataGridWorkers.SelectedItem != null)
            {
                buttonModifyWorker.IsEnabled = true;
                buttonRemoveWorker.IsEnabled = true;
                buttonSubmitOvertime.IsEnabled = true;
                dataGridWorkersShouldClear = false;
                UpdateOvertimes(((WorkerView)dataGridWorkers.SelectedItem).ID);
            }
        }

        void ButtonAddWorker_Click(object sender, RoutedEventArgs e)
        {
            AddWorkerWindow addWorkerWindow = new AddWorkerWindow(this);
            bool? isAdded = addWorkerWindow.ShowDialog();
            if (isAdded.HasValue && isAdded.Value)
            {
                UpdateWorkers();
                UpdateOvertimes();
            }
        }

        void ButtonModifyWorker_Click(object sender, RoutedEventArgs e)
        {
            if (dataGridWorkers.SelectedItem != null)
            {
                object worker = null;
                lock (workers)
                    worker = workers.SingleOrDefault(w => (int)ZooClient.GetProperty(w, "ID") == ((WorkerView)dataGridWorkers.SelectedItem).ID);
                ModifyWorkerWindow modifyWorkerWindow = new ModifyWorkerWindow(this, worker);
                bool? isModified = modifyWorkerWindow.ShowDialog();
                if (isModified.HasValue && isModified.Value)
                {
                    UpdateWorkers();
                    UpdateOvertimes();
                }
            }
        }

        void ButtonRemoveWorker_Click(object sender, RoutedEventArgs e)
        {
            if (dataGridWorkers.SelectedItem != null)
            {
                int id = ((WorkerView)dataGridWorkers.SelectedItem).ID;
                new Thread(() =>
                {
                    Tuple<bool, byte> status = ZooConnection.DeleteModel(ModelType.Worker, id);
                    if (status == null)
                        MessageBox.Show("[ERROR] Cannot delete worker, server connection error!");
                    else if (!status.Item1)
                        MessageBox.Show(ZooClient.DecodeDeleteError(status.Item2));
                    else
                    {
                        UpdateOvertimes();
                        UpdateWorkers();
                    }
                }).Start();
            }
        }

        void ButtonSubmitOvertime_Click(object sender, RoutedEventArgs e)
        {
            if (dataGridWorkers.SelectedItem != null)
            {
                int id = ((WorkerView)dataGridWorkers.SelectedItem).ID;
                SubmitOvertimeWindow submitOvertimeWindow = new SubmitOvertimeWindow(this, id);
                bool? isAdded = submitOvertimeWindow.ShowDialog();
                if (isAdded.HasValue && isAdded.Value)
                    UpdateOvertimes(id);
            }
        }
        #endregion Workers Tab

        #region Cash Tab
        void ButtonRefreshCash_Click(object sender, RoutedEventArgs e)
        {
            UpdateCashBalances();
        }

        void ButtonAddOperation_Click(object sender, RoutedEventArgs e)
        {
            AddOperationWindow addOperationWindow = new AddOperationWindow(this);
            bool? isAdded = addOperationWindow.ShowDialog();
            if (isAdded.HasValue && isAdded.Value)
                UpdateCashBalances();
        }

        void ButtonPayMonthSalary_Click(object sender, RoutedEventArgs e)
        {
            new Thread(() =>
            {
                object operation = ZooConnection.CreateModel(ModelType.CashBalance);
                ZooClient.SetProperty(operation, "BalanceTypeID", 0);
                if (!ZooConnection.AddModel(operation))
                    MessageBox.Show("[ERROR] Cannot pay month salary to employees, cannot add new operation to database, check log for detailed cause.");
                else
                    MessageBox.Show("Succesfully pay month salary to employees.");
            }).Start();
        }
        #endregion Cash Tab

        #region Places Tab
        void ButtonRefreshPlaces_Click(object sender, RoutedEventArgs e)
        {
            UpdatePlaces();
            UpdateWorkers();
            UpdateAttractions();
        }
        void DataGridPlaces_MouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            if (dataGridPlacesShouldClear && dataGridPlaces.SelectedItem != null)
            {
                buttonModifyPlace.IsEnabled = false;
                buttonRemovePlace.IsEnabled = false;
                dataGridPlaces.SelectedItem = null;
                DataGridAttractions_MouseLeftButtonUp(sender, e);
                UpdateAttractions();
            }
            else
                dataGridPlacesShouldClear = true;
        }

        void DataGridPlaces_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
        {
            if (dataGridPlaces.SelectedItem != null)
            {
                buttonModifyPlace.IsEnabled = true;
                buttonRemovePlace.IsEnabled = true;
                dataGridPlacesShouldClear = false;
                DataGridAttractions_SelectionChanged(sender, e);
                UpdateAttractions((int)ZooClient.GetProperty(dataGridPlaces.SelectedItem, "ID"));
            }
        }

        void DataGridAttractions_MouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            if (dataGridAttractionsShouldClear && dataGridAttractions.SelectedItem != null)
            {
                buttonModifyAttraction.IsEnabled = false;
                buttonRemoveAttraction.IsEnabled = false;
                dataGridAttractions.SelectedItem = null;
            }
            else
                dataGridAttractionsShouldClear = true;
        }

        void DataGridAttractions_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
        {
            if (dataGridAttractions.SelectedItem != null)
            {
                buttonModifyAttraction.IsEnabled = true;
                buttonRemoveAttraction.IsEnabled = true;
                dataGridAttractionsShouldClear = false;
            }
        }

        void ButtonAddPlace_Click(object sender, RoutedEventArgs e)
        {
            AddPlaceWindow addPlaceWindow = new AddPlaceWindow(this);
            bool? isAdded = addPlaceWindow.ShowDialog();
            if (isAdded.HasValue && isAdded.Value)
            {
                UpdatePlaces();
                UpdateAttractions();
            }
        }

        void ButtonModifyPlace_Click(object sender, RoutedEventArgs e)
        {
            if (dataGridPlaces.SelectedItem != null)
            {
                object place = null;
                lock (Places)
                    place = Places.SingleOrDefault(p => (int)ZooClient.GetProperty(p, "ID") == (int)ZooClient.GetProperty(dataGridPlaces.SelectedItem, "ID"));
                ModifyPlaceWindow modifyPlaceWindow = new ModifyPlaceWindow(this, place);
                bool? isModified = modifyPlaceWindow.ShowDialog();
                if (isModified.HasValue && isModified.Value)
                {
                    UpdatePlaces();
                    UpdateAttractions();
                }
            }
        }

        void ButtonRemovePlace_Click(object sender, RoutedEventArgs e)
        {
            if (dataGridPlaces.SelectedItem != null)
            {
                int id = (int)ZooClient.GetProperty(dataGridPlaces.SelectedItem, "ID");
                new Thread(() =>
                {
                    Tuple<bool, byte> status = ZooConnection.DeleteModel(ModelType.Place, id);
                    if (status == null)
                        MessageBox.Show("[ERROR] Cannot remove place from Zoo, server connection error!");
                    else if (!status.Item1)
                        MessageBox.Show(ZooClient.DecodeDeleteError(status.Item2));
                    else
                    {
                        UpdatePlaces();
                        UpdateAttractions();
                    }
                }).Start();
            }
        }

        void ButtonAddAttraction_Click(object sender, RoutedEventArgs e)
        {
            AddAttractionWindow addAttractionWindow = new AddAttractionWindow(this);
            bool? isAdded = addAttractionWindow.ShowDialog();
            if (isAdded.HasValue && isAdded.Value)
            {
                UpdatePlaces();
                UpdateAttractions();
            }
        }

        void ButtonModifyAttraction_Click(object sender, RoutedEventArgs e)
        {
            if (dataGridAttractions.SelectedItem != null)
            {
                object attraction = null;
                lock (attractions)
                    attraction = attractions.SingleOrDefault(a => (int)ZooClient.GetProperty(a, "ID") == (int)ZooClient.GetProperty(dataGridAttractions.SelectedItem, "ID"));
                ModifyAttractionWindow modifyAttractionWindow = new ModifyAttractionWindow(this, attraction);
                bool? isModified = modifyAttractionWindow.ShowDialog();
                if (isModified.HasValue && isModified.Value)
                    UpdateAttractions();
            }
        }

        void ButtonRemoveAttraction_Click(object sender, RoutedEventArgs e)
        {
            if (dataGridAttractions.SelectedItem != null)
            {
                int id = (int)ZooClient.GetProperty(dataGridAttractions.SelectedItem, "ID");
                new Thread(() =>
                {
                    Tuple<bool, byte> status = ZooConnection.DeleteModel(ModelType.Attraction, id);
                    if (status == null)
                        MessageBox.Show("[ERROR] Cannot remove attraction from Zoo, server connection error!");
                    else if (!status.Item1)
                        MessageBox.Show(ZooClient.DecodeDeleteError(status.Item2));
                    else
                    {
                        UpdatePlaces();
                        UpdateAttractions();
                    }
                }).Start();
            }
        }
        #endregion Places Tab

        void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            update = false;
            if (threadUpdate.ThreadState == ThreadState.WaitSleepJoin)
                threadUpdate.Abort();
        }
    }
}
<file_sep>/ZooManager/Windows/TabWorkers/SubmitOvertimeWindow.xaml.cs
﻿using System;
using System.Windows;

namespace ZooManager.Windows.TabWorkers
{
    public partial class SubmitOvertimeWindow : Window
    {
        MainWindow parentWindow;
        int workerID;

        public SubmitOvertimeWindow(MainWindow parent, int id)
        {
            InitializeComponent();
            parentWindow = parent;
            workerID = id;
            datePickerDate.DisplayDateStart = new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1);
            datePickerDate.SelectedDate = null;
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            if (IsDataInputValid())
            {
                object overtime = parentWindow.ZooConnection.CreateModel(ModelType.Overtime);
                ZooClient.SetProperty(overtime, "Date", datePickerDate.SelectedDate);
                ZooClient.SetProperty(overtime, "Hours", int.Parse(textBoxHours.Text));
                ZooClient.SetProperty(overtime, "WorkerID", workerID);
                if (!parentWindow.ZooConnection.AddModel(overtime))
                {
                    MessageBox.Show("[ERROR] Cannot submit new overtime to database, check log for detailed cause.");
                    DialogResult = false;
                }
                else
                    DialogResult = true;
                Close();
            }
        }

        bool IsDataInputValid()
        {
            if (!int.TryParse(textBoxHours.Text, out int value))
            {
                MessageBox.Show(this, "[ERROR] Overtime hours must be specified!");
                return false;
            }
            else if (datePickerDate.SelectedDate == null)
            {
                MessageBox.Show(this, "[ERROR] Overtime date must be specified!");
                return false;
            }
            return true;
        }
    }
}
<file_sep>/ZooManager/Windows/TabAnimals/AddFoodWindow.xaml.cs
﻿using System.Windows;

namespace ZooManager.Windows.TabAnimals
{
    public partial class AddFoodWindow : Window
    {
        MainWindow parentWindow;

        public AddFoodWindow(MainWindow parent)
        {
            InitializeComponent();
            parentWindow = parent;
        }

        void Button_Click(object sender, RoutedEventArgs e)
        {
            if(IsDataInputValid())
            {
                object food = parentWindow.ZooConnection.CreateModel(ModelType.Food);
                ZooClient.SetProperty(food, "Name", textBoxName.Text);
                ZooClient.SetProperty(food, "Amount", double.Parse(textBoxAmount.Text));
                if (!parentWindow.ZooConnection.AddModel(food))
                {
                    MessageBox.Show("[ERROR] Cannot add new food to database, check log for detailed cause.");
                    DialogResult = false;
                }
                else
                    DialogResult = true;
                Close();
            }
        }

        bool IsDataInputValid()
        {
            if (string.IsNullOrWhiteSpace(textBoxName.Text))
            {
                MessageBox.Show("[ERROR] Food's name must be specified!");
                return false;
            }
            else if (!double.TryParse(textBoxAmount.Text, out double value) || value < 0)
            {
                MessageBox.Show("[ERROR] Food's amouont must be specified and be at least 0!");
                return false;
            }
            return true;
        }
    }
}
<file_sep>/ZooCom/Model/BalanceType.cs
﻿namespace ZooCom.Model
{
    [System.Serializable]
    public class BalanceType : Model
    {
        public string Description { get; set; }

        public override byte TypeID()
        {
            return (byte)ModelType.BalanceType;
        }
    }
}
<file_sep>/Test/DeleteModelTest.cs
﻿using Moq;
using ZooService;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Test
{
    [TestClass]
    public class DeleteModelTest
    {
        MockSequence ctxSeq;
        Mock<ZooContext> ctx;
        ZooService.ZooService service;

        [TestInitialize]
        public void Setup()
        {
            ctxSeq = new MockSequence();
            ctx = new Mock<ZooContext>();
            ctx.Setup(x => x.DeleteWorker(2)).Returns(new System.Tuple<bool, byte>(false, (byte)ModelType.Attraction));
            ctx.InSequence(ctxSeq).Setup(x => x.DeleteAttraction(1)).Returns(new System.Tuple<bool, byte>(true, 0));
            ctx.InSequence(ctxSeq).Setup(x => x.DeleteWorker(2)).Returns(new System.Tuple<bool, byte>(true, 0));

            service = new ZooService.ZooService(1111);
        }

        [TestMethod]
        public void Delete_worker_before_attraction_fail()
        {
            var ret = service.DeleteModel(ctx.Object, ModelType.Worker, 2);
            Assert.IsFalse(ret.Item1);
            Assert.IsTrue(ret.Item2 == (byte)ModelType.Attraction);
        }

        [TestMethod]
        public void Delete_worker_after_deleting_his_attraction()
        {
            var ret1 = service.DeleteModel(ctx.Object, ModelType.Attraction, 1);
            Assert.IsTrue(ret1.Item1);
            Assert.IsTrue(ret1.Item2 == 0);

            var ret2 = service.DeleteModel(ctx.Object, ModelType.Worker, 2);
            Assert.IsTrue(ret2.Item1);
            Assert.IsTrue(ret2.Item2 == 0);

            ctx.Verify(x => x.DeleteAttraction(1));
            ctx.Verify(x => x.DeleteWorker(2));
        }
    }
}
<file_sep>/ZooManager/Windows/TabPlaces/AddAttractionWindow.xaml.cs
﻿using System.Windows;

namespace ZooManager.Windows.TabPlaces
{
    public partial class AddAttractionWindow : Window
    {
        MainWindow parentWindow;

        public AddAttractionWindow(MainWindow parent)
        {
            InitializeComponent();
            parentWindow = parent;
            dataGridPlaces.DataContext = parentWindow.ZooConnection.GetModelType(ModelType.Place);
            lock (parentWindow.Places)
                dataGridPlaces.ItemsSource = parentWindow.Places;
        }

        void Button_Click(object sender, RoutedEventArgs e)
        {
            if (IsDataInputValid())
            {
                object attraction = parentWindow.ZooConnection.CreateModel(ModelType.Attraction);
                ZooClient.SetProperty(attraction, "Name", textBoxName.Text);
                if (!string.IsNullOrWhiteSpace(textBoxDescription.Text))
                    ZooClient.SetProperty(attraction, "Description", textBoxDescription.Text);
                ZooClient.SetProperty(attraction, "PlaceID", ZooClient.GetProperty(dataGridPlaces.SelectedItem, "ID"));
                if (!parentWindow.ZooConnection.AddModel(attraction))
                {
                    MessageBox.Show("[ERROR] Cannot add new worker to database, check log for detailed cause.");
                    DialogResult = false;
                }
                else
                    DialogResult = true;
                Close();
            }
        }

        bool IsDataInputValid()
        {
            if (string.IsNullOrWhiteSpace(textBoxName.Text))
            {
                MessageBox.Show("[ERROR] Attraction's name must be specified!");
                return false;
            }
            else if (dataGridPlaces.SelectedItems.Count <= 0)
            {
                MessageBox.Show("[ERROR] Must assign attraction to place, select one from the list!");
                return false;
            }
            return true;
        }
    }
}
<file_sep>/ZooManager/Windows/TabPlaces/ModifyAttractionWindow.xaml.cs
﻿using System.Windows;
using System.Linq;

namespace ZooManager.Windows.TabPlaces
{
    public partial class ModifyAttractionWindow : Window
    {
        MainWindow parentWindow;
        object attraction;

        public ModifyAttractionWindow(MainWindow parent, object selectedAttraction)
        {
            InitializeComponent();
            if (selectedAttraction == null)
            {
                Logger.LogWarning("Error selecting attraction to modify, process terminated.", GetType(), "ModifyAttractionWindow(parent: MainWindow, selectedAttraction: null)");
                Close();
            }
            attraction = selectedAttraction;
            parentWindow = parent;
            dataGridPlaces.DataContext = parentWindow.ZooConnection.GetModelType(ModelType.Place);
            lock (parentWindow.Places)
            {
                dataGridPlaces.ItemsSource = parentWindow.Places;
                int id = (int)ZooClient.GetProperty(attraction, "PlaceID");
                dataGridPlaces.SelectedItem = parentWindow.Places.FirstOrDefault(place => (int)ZooClient.GetProperty(place, "ID") == id);
            }
            textBoxName.Text = (string)ZooClient.GetProperty(attraction, "Name");
            textBoxDescription.Text = (string)ZooClient.GetProperty(attraction, "Description");
        }

        void Button_Click(object sender, RoutedEventArgs e)
        {
            if (IsDataInputValid())
            {
                ZooClient.SetProperty(attraction, "Name", textBoxName.Text);
                if (!string.IsNullOrWhiteSpace(textBoxDescription.Text))
                    ZooClient.SetProperty(attraction, "Description", textBoxDescription.Text);
                ZooClient.SetProperty(attraction, "PlaceID", ZooClient.GetProperty(dataGridPlaces.SelectedItem, "ID"));
                if (!parentWindow.ZooConnection.ModifyModel(attraction))
                {
                    MessageBox.Show("[ERROR] Cannot add new worker to database, check log for detailed cause.");
                    DialogResult = false;
                }
                else
                    DialogResult = true;
                Close();
            }
        }

        bool IsDataInputValid()
        {
            if (string.IsNullOrWhiteSpace(textBoxName.Text))
            {
                MessageBox.Show("[ERROR] Attraction's name must be specified!");
                return false;
            }
            else if (dataGridPlaces.SelectedItems.Count <= 0)
            {
                MessageBox.Show("[ERROR] Must assign attraction to place, select one from the list!");
                return false;
            }
            return true;
        }
    }
}
<file_sep>/ZooCom/Model/Animal.cs
﻿namespace ZooCom.Model
{
    [System.Serializable]
    public class Animal : Model
    {
        decimal _maintenanceCost = 0;
        
        public string Name { get; set; }
        public int Count { get; set; } = 0;
        public decimal MaintenanceCost
        {
            get { return _maintenanceCost; }
            set
            {
                if (value < 0)
                    throw new System.ArgumentOutOfRangeException($"Animal: MaintenanceCost cannot be lower than 0. Provided \"{value}\"");
                _maintenanceCost = value;
            }
        }
        public int PlaceID { get; set; }
        public int FoodID { get; set; }

        public override byte TypeID()
        {
            return (byte)ModelType.Animal;
        }
    }
}
<file_sep>/ZooCom/Model/CashBalance.cs
﻿namespace ZooCom.Model
{
    [System.Serializable]
    public class CashBalance : Model
    {
        public System.DateTime SubmitDate { get; set; } = System.DateTime.Today;
        public decimal Money { get; set; } = 0;
        public int BalanceTypeID { get; set; }
        public string DetailedDescription { get; set; }

        public override byte TypeID()
        {
            return (byte)ModelType.CashBalance;
        }
    }
}
<file_sep>/README.md
# Zoo Manager
Project for simple zoo managment. Current components consist of:
- Client app (ZooManager)
- Communication DLL (ZooCom)
- Server (ZooService)

**1. Client app**

  WPF application connecting through DLL module with server to obtain data about Zoo.
  
**2. Communication DLL**

  DLL module responsible for transfering data between client app and server. Protocols of how client app will receive data from database are described here.
  It is possible to define another method of communication other than TCP connection by replacing ZooCom.dll in client app directory.
  Also data types are defined here used both by server and client app to send and receive database data.
  
**3. Server**

  TCP server responsible for communicating with client app and local database. Database is based on SQL Server (schema and initial data in ZooSchema.sql).
  To access database Entity Framework is used and standard SqlConnection.
  
Current versions of used programs:
  - IDE: Visual Studio 2017
  - Database: SQL Server 2017 Express
  - ORM: Entity Framework 6
<file_sep>/ZooManager/Views/AnimalView.cs
﻿namespace ZooManager.Views
{
    public class AnimalView
    {
        public int ID { get; set; }
        public string Name { get; set; }
        public int Count { get; set; }
        public decimal MaintenanceCost { get; set; }
        public string Place { get; set; }
        public string Food { get; set; }
    }
}
<file_sep>/ZooService/ZooService.cs
﻿ using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Reflection;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
using System.Linq;
using System.Data.SqlClient;

namespace ZooService
{
    public enum ModelType { Model = 0, Animal = 1, Attraction = 2, BalanceType = 4, CashBalance = 8, Food = 16, Overtime = 32, Place = 64, Worker = 128}

    public class ZooService
    {
        Assembly zooComDll;
        readonly Dictionary<ModelType, Type> modelTypes = new Dictionary<ModelType, Type>(Enum.GetNames(typeof(ModelType)).Length);

        TcpListener serverModifyModel;
        Thread threadModifyModel;
        TcpListener serverAddModel;
        Thread threadAddModel;
        TcpListener serverDeleteModel;
        Thread threadDeleteModel;
        TcpListener serverGetModels;
        Thread threadGetModels;
        readonly int tcpPort;
        readonly IPAddress localIp;
        volatile bool isServerRunning = false;

        public ZooService(int port)
        {
            zooComDll = Assembly.LoadFile($@"D:\Projects\Visual Studio Projects\ZooManager\FitNesse\FitSharp\ZooCom.dll");
            foreach (ModelType type in (ModelType[])Enum.GetValues(typeof(ModelType)))
            {
                modelTypes[type] = zooComDll.GetType($"ZooCom.Model.{Enum.GetName(typeof(ModelType), type)}");
            }
            tcpPort = port;
            IPHostEntry host = Dns.GetHostEntry("localhost");
            foreach (IPAddress ip in host.AddressList)
            {
                if (ip.AddressFamily == AddressFamily.InterNetwork)
                {
                    localIp = ip;
                    break;
                }
            }
        }

        public virtual void Start()
        {
            serverModifyModel = new TcpListener(localIp, tcpPort + 3);
            serverAddModel = new TcpListener(localIp, tcpPort + 2);
            serverDeleteModel = new TcpListener(localIp, tcpPort + 1);
            serverGetModels = new TcpListener(localIp, tcpPort);
            threadModifyModel = new Thread(() =>
            {
                serverModifyModel.Start();
                while (isServerRunning)
                {
                    Socket client = serverModifyModel.AcceptSocket();
                    new Thread(() =>
                    {
                        Logger.LogInfo("New modify model request", GetType(), "threadModifyModel");
                        byte[] data = new byte[1];
                        client.Receive(data, 1, SocketFlags.None);
                        ModelType type = (ModelType)data[0];
                        data = new byte[sizeof(int)];
                        client.Receive(data, sizeof(int), SocketFlags.None);
                        data = new byte[BitConverter.ToInt32(data, 0)];
                        client.Receive(data, data.Length, SocketFlags.None);
                        object model = new BinaryFormatter().Deserialize(new MemoryStream(data));
                        using (ZooContext context = new ZooContext())
                        {
                            if (ModifyModel(context, type, ConvertFromZooComModel(model, type)))
                                Logger.LogInfo($"Modified {type.ToString()}", GetType(), "threadModifyModel");
                            else
                                Logger.LogWarning($"Cannot modify {type.ToString()}", GetType(), "threadModifyModel");
                        }
                        client.Close();
                    }).Start();
                }
                serverModifyModel.Stop();
            });
            threadAddModel = new Thread(() =>
            {
                serverAddModel.Start();
                while (isServerRunning)
                {
                    Socket client = serverAddModel.AcceptSocket();
                    new Thread(() =>
                    {
                        Logger.LogInfo("New add model request", GetType(), "threadAddModel");
                        byte[] data = new byte[1];
                        client.Receive(data, 1, SocketFlags.None);
                        ModelType type = (ModelType)data[0];
                        data = new byte[sizeof(int)];
                        client.Receive(data, sizeof(int), SocketFlags.None);
                        data = new byte[BitConverter.ToInt32(data, 0)];
                        client.Receive(data, data.Length, SocketFlags.None);
                        object model = new BinaryFormatter().Deserialize(new MemoryStream(data));
                        using (ZooContext context = new ZooContext())
                        {
                            if (AddModel(context, type, ConvertFromZooComModel(model, type)))
                                Logger.LogInfo($"Added new {type.ToString()}", GetType(), "threadAddModel");
                            else
                                Logger.LogWarning($"Cannot add new {type.ToString()}", GetType(), "threadAddModel");
                        }
                        client.Close();
                    }).Start();
                }
                serverAddModel.Stop();
            });
            threadDeleteModel = new Thread(() =>
            {
                serverDeleteModel.Start();
                while (isServerRunning)
                {
                    Socket client = serverDeleteModel.AcceptSocket();
                    new Thread(() =>
                    {
                        Logger.LogInfo("New delete model request", GetType(), "threadDeleteModel");
                        byte[] data = new byte[1];
                        client.Receive(data, 1, SocketFlags.None);
                        ModelType type = (ModelType)data[0];
                        data = new byte[sizeof(int)];
                        client.Receive(data, sizeof(int), SocketFlags.None);
                        using (ZooContext context = new ZooContext())
                        {
                            int id = BitConverter.ToInt32(data, 0);
                            Tuple<bool, byte> operationDetails = DeleteModel(context, type, id);
                            client.Send(BitConverter.GetBytes(operationDetails.Item1));
                            if (operationDetails.Item1)
                                Logger.LogInfo($"Deleted model of type {type.ToString()}, ID = {id}", GetType(), "threadDeleteModel");
                            else
                            {
                                Logger.LogWarning($"Cannot delete model of type {type.ToString()}, ID = {id}", GetType(), "threadDeleteModel");
                                client.Send(new byte[1] { operationDetails.Item2 });
                            }
                        }
                        client.Close();
                    }).Start();
                }
                serverDeleteModel.Stop();
            });
            threadGetModels = new Thread(() =>
            {
                serverGetModels.Start();
                while (isServerRunning)
                {
                    Socket client = serverGetModels.AcceptSocket();
                    new Thread(() =>
                    {
                        Logger.LogInfo("New get models request", GetType(), "threadGetModels");
                        byte[] data = new byte[1];
                        client.Receive(data, 1, SocketFlags.None);
                        ModelType type = (ModelType)data[0];
                        using (MemoryStream dataStream = new MemoryStream())
                        using (ZooContext context = new ZooContext())
                        {
                            try
                            {
                                new BinaryFormatter().Serialize(dataStream, GetModels(context, type).Select(model => ConvertToZooComModel(type, model)).ToList());
                                client.Send(BitConverter.GetBytes(dataStream.ToArray().Length), sizeof(int), SocketFlags.None);
                                client.Send(dataStream.ToArray());
                                Logger.LogInfo($"Send models of type {type.ToString()}", GetType(), "threadGetModels");
                            }
                            catch (Exception e)
                            {
                                Logger.LogError(e.ToString());
                            }
                        }
                        client.Close();
                    }).Start();
                }
                serverGetModels.Stop();
            });
            isServerRunning = true;
            threadModifyModel.Start();
            threadAddModel.Start();
            threadDeleteModel.Start();
            threadGetModels.Start();
        }

        public virtual void Stop()
        {
            isServerRunning = false;
        }

        public virtual bool ModifyModel(ZooContext context, ModelType type, ZooDataModel model)
        {
            switch (type)
            {
                case ModelType.Animal:
                    return context.ModifyModel((Animal)model);
                case ModelType.Attraction:
                    return context.ModifyModel((Attraction)model);
                case ModelType.BalanceType:
                    return context.ModifyModel((BalanceType)model);
                case ModelType.CashBalance:
                    return context.ModifyModel((CashBalance)model);
                case ModelType.Food:
                    return context.ModifyModel((Food)model);
                case ModelType.Overtime:
                    return context.ModifyModel((Overtime)model);
                case ModelType.Place:
                    return context.ModifyModel((Place)model);
                case ModelType.Worker:
                    return context.ModifyModel((Worker)model);
                default:
                    return false;
            }
        }

        public virtual bool AddModel(ZooContext context, ModelType type, ZooDataModel model)
        {
            switch (type)
            {
                case ModelType.Animal:
                    return context.AddModel((Animal)model);
                case ModelType.Attraction:
                    return context.AddModel((Attraction)model);
                case ModelType.BalanceType:
                    return context.AddModel((BalanceType)model);
                case ModelType.CashBalance:
                    CashBalance balance = (CashBalance)model;
                    if(balance.BalanceTypeID == 0)
                    {
                        PayMonthSalary();
                        return true;
                    }
                    else
                        return context.AddModel(balance);
                case ModelType.Food:
                    return context.AddModel((Food)model);
                case ModelType.Overtime:
                    return context.AddModel((Overtime)model);
                case ModelType.Place:
                    return context.AddModel((Place)model);
                case ModelType.Worker:
                    return context.AddModel((Worker)model);
                default:
                    return false;
            }
        }

        public virtual Tuple<bool, byte> DeleteModel(ZooContext context, ModelType type, int id)
        {
            switch (type)
            {
                case ModelType.Animal:
                    return context.DeleteAnimal(id);
                case ModelType.Attraction:
                    return context.DeleteAttraction(id);
                case ModelType.Food:
                    return context.DeleteFood(id);
                case ModelType.Place:
                    return context.DeletePlace(id);
                case ModelType.Worker:
                    return context.DeleteWorker(id);
                default:
                    return new Tuple<bool, byte>(false, 0);
            }
        }

        public virtual List<ZooDataModel> GetModels(ZooContext context, ModelType type)
        {
            switch (type)
            {
                case ModelType.Animal:
                    return context.GetModels<Animal>().Cast<ZooDataModel>().ToList();
                case ModelType.Attraction:
                    return context.GetModels<Attraction>().Cast<ZooDataModel>().ToList();
                case ModelType.BalanceType:
                    return context.GetModels<BalanceType>().Cast<ZooDataModel>().ToList();
                case ModelType.CashBalance:
                    return context.GetModels<CashBalance>().Cast<ZooDataModel>().ToList();
                case ModelType.Food:
                    return context.GetModels<Food>().Cast<ZooDataModel>().ToList();
                case ModelType.Overtime:
                    return context.GetModels<Overtime>().Cast<ZooDataModel>().ToList();
                case ModelType.Place:
                    return context.GetModels<Place>().Cast<ZooDataModel>().ToList();
                case ModelType.Worker:
                    return context.GetModels<Worker>().Cast<ZooDataModel>().ToList();
                default:
                    return null;
            }
        }

        public virtual object GetProperty(object model, string name)
        {
            return model.GetType().GetProperty(name).GetValue(model);
        }

        public virtual ZooDataModel ConvertFromZooComModel(object model, ModelType type)
        {
            switch (type)
            {
                case ModelType.Animal:
                    return new Animal()
                    {
                        ID = (int)GetProperty(model, "ID"),
                        Name = (string)GetProperty(model, "Name"),
                        Count = (int)GetProperty(model, "Count"),
                        MaintenanceCost = (decimal)GetProperty(model, "MaintenanceCost"),
                        PlaceID = (int)GetProperty(model, "PlaceID"),
                        FoodID = (int)GetProperty(model, "FoodID")
                    };
                case ModelType.Attraction:
                    return new Attraction()
                    {
                        ID = (int)GetProperty(model, "ID"),
                        Name = (string)GetProperty(model, "Name"),
                        Description = (string)GetProperty(model, "Description"),
                        AttractionManagerID = (int)GetProperty(model, "AttractionManagerID"),
                        PlaceID = (int)GetProperty(model, "PlaceID")
                    };
                case ModelType.BalanceType:
                    return new BalanceType()
                    {
                        ID = (int)GetProperty(model, "ID"),
                        Description = (string)GetProperty(model, "Description")
                    };
                case ModelType.CashBalance:
                    return new CashBalance()
                    {
                        ID = (int)GetProperty(model, "ID"),
                        SubmitDate = (DateTime)GetProperty(model, "SubmitDate"),
                        Money = (decimal)GetProperty(model, "Money"),
                        BalanceTypeID = (int)GetProperty(model, "BalanceTypeID"),
                        DetailedDescription = (string)GetProperty(model, "DetailedDescription")
                    };
                case ModelType.Food:
                    return new Food()
                    {
                        ID = (int)GetProperty(model, "ID"),
                        Name = (string)GetProperty(model, "Name"),
                        Amount = (double)GetProperty(model, "Amount")
                    };
                case ModelType.Overtime:
                    Overtime overtime = new Overtime()
                    {
                        ID = (int)GetProperty(model, "ID"),
                        Date = (DateTime)GetProperty(model, "Date"),
                        Hours = (int)GetProperty(model, "Hours"),
                        WorkerID = (int)GetProperty(model, "WorkerID")
                    };
                    overtime.SetPercentage();
                    return overtime;
                case ModelType.Place:
                    return new Place()
                    {
                        ID = (int)GetProperty(model, "ID"),
                        Name = (string)GetProperty(model, "Name"),
                        OpenTime = (TimeSpan)GetProperty(model, "OpenTime"),
                        CloseTime = (TimeSpan)GetProperty(model, "CloseTime"),
                        MaintenanceCost = (decimal)GetProperty(model, "MaintenanceCost")
                    };
                case ModelType.Worker:
                    return new Worker()
                    {
                        ID = (int)GetProperty(model, "ID"),
                        Surname = (string)GetProperty(model, "Surname"),
                        Name = (string)GetProperty(model, "Name"),
                        Age = (int)GetProperty(model, "Age"),
                        Salary = (decimal)GetProperty(model, "Salary"),
                        StartDate = (DateTime)GetProperty(model, "StartDate"),
                        PlaceID = (int)GetProperty(model, "PlaceID")
                    };
                default:
                    return null;
            }
        }

        public virtual object ConvertToZooComModel(ModelType type, ZooDataModel model)
        {
            if (ModelType.Model == type)
                return null;
            object comModel = Activator.CreateInstance(modelTypes[type]);
            SetProperty(comModel, "ID", model.ID);
            switch (type)
            {
                case ModelType.Animal:
                    if (model is Animal animal)
                    {
                        SetProperty(comModel, "Name", animal.Name);
                        SetProperty(comModel, "Count", animal.Count);
                        SetProperty(comModel, "MaintenanceCost", animal.MaintenanceCost);
                        SetProperty(comModel, "PlaceID", animal.PlaceID);
                        SetProperty(comModel, "FoodID", animal.FoodID);
                    }
                    break;
                case ModelType.Attraction:
                    if (model is Attraction attraction)
                    {
                        SetProperty(comModel, "Name", attraction.Name);
                        SetProperty(comModel, "Description", attraction.Description);
                        SetProperty(comModel, "AttractionManagerID", attraction.AttractionManagerID);
                        SetProperty(comModel, "PlaceID", attraction.PlaceID);
                    }
                    break;
                case ModelType.BalanceType:
                    if (model is BalanceType balanceType)
                    {
                        SetProperty(comModel, "Description", balanceType.Description);
                    }
                    break;
                case ModelType.CashBalance:
                    if (model is CashBalance cashBalance)
                    {
                        SetProperty(comModel, "SubmitDate", cashBalance.SubmitDate);
                        SetProperty(comModel, "Money", cashBalance.Money);
                        SetProperty(comModel, "BalanceTypeID", cashBalance.BalanceTypeID);
                        SetProperty(comModel, "DetailedDescription", cashBalance.DetailedDescription);
                    }
                    break;
                case ModelType.Food:
                    if (model is Food food)
                    {
                        SetProperty(comModel, "Name", food.Name);
                        SetProperty(comModel, "Amount", food.Amount);
                    }
                    break;
                case ModelType.Overtime:
                    if (model is Overtime overtime)
                    {
                        SetProperty(comModel, "Date", overtime.Date);
                        SetProperty(comModel, "Hours", overtime.Hours);
                        SetProperty(comModel, "PaymentPercentage", overtime.PaymentPercentage);
                        SetProperty(comModel, "WorkerID", overtime.WorkerID);
                    }
                    break;
                case ModelType.Place:
                    if (model is Place place)
                    {
                        SetProperty(comModel, "Name", place.Name);
                        SetProperty(comModel, "OpenTime", place.OpenTime);
                        SetProperty(comModel, "CloseTime", place.CloseTime);
                        SetProperty(comModel, "MaintenanceCost", place.MaintenanceCost);
                    }
                    break;
                case ModelType.Worker:
                    if (model is Worker worker)
                    {
                        SetProperty(comModel, "Surname", worker.Surname);
                        SetProperty(comModel, "Name", worker.Name);
                        SetProperty(comModel, "Age", worker.Age);
                        SetProperty(comModel, "Salary", worker.Salary);
                        SetProperty(comModel, "StartDate", worker.StartDate);
                        SetProperty(comModel, "PlaceID", worker.PlaceID);
                    }
                    break;
            }
            return comModel;
        }

        public virtual void SetProperty(object model, string name, object value)
        {
            model.GetType().GetProperty(name).SetValue(model, value);
        }

        public virtual void PayMonthSalary()
        {
            using (SqlConnection connection = new SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["ZooContextMonthSalary"].ConnectionString))
            using (SqlCommand command = new SqlCommand("EXEC CreateMonthPayment", connection))
            {
                connection.Open();
                command.ExecuteNonQuery();
                connection.Close();
            }
        }
    }
}
<file_sep>/ZooCom/Model/Place.cs
﻿namespace ZooCom.Model
{
    [System.Serializable]
    public class Place : Model
    {
        decimal _maintenanceCost = 0;
        
        public string Name { get; set; }
        public System.TimeSpan OpenTime { get; set; } = new System.TimeSpan(8, 0, 0);
        public System.TimeSpan CloseTime { get; set; } = new System.TimeSpan(18, 0, 0);
        public decimal MaintenanceCost
        {
            get { return _maintenanceCost; }
            set
            {
                if (value < 0)
                    throw new System.ArgumentOutOfRangeException($"Place: MaintenanceCost cannot be lower than 0. Provided \"{value}\"");
                _maintenanceCost = value;
            }
        }

        public override byte TypeID()
        {
            return (byte)ModelType.Place;
        }
    }
}
<file_sep>/Test/ConvertToZooComModelTest.cs
﻿using ZooService;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;

namespace Test
{
    [TestClass]
    public class ConvertToZooComModelTest
    {
        ZooService.ZooService service;
        Animal animalUnit;
        object comModel;

        const int ID = 1;
        const decimal decimalTest = 5;
        const string stringTest = "testString";
        const int intTest = 50;

        [TestInitialize]
        public void SetUp()
        {
            service = new ZooService.ZooService(1111);

            comModel = new ZooCom.Model.Animal
            {
                ID = ID,
                MaintenanceCost = decimalTest,
                Name = stringTest,
                PlaceID = ID,
                FoodID = ID,
                Count = intTest
            };

            animalUnit = new Animal
            {
                ID = ID,
                MaintenanceCost = decimalTest,
                Name = stringTest,
                PlaceID = ID,
                FoodID = ID,
                Count = intTest
            };
        }

        [TestMethod]
        public void Has_the_same_fields_values_after_conversion()
        {
            var objectResponse = service.ConvertToZooComModel(ModelType.Animal, animalUnit);

            Assert.IsInstanceOfType(objectResponse, comModel.GetType());
            ZooCom.Model.Animal animalObjectAfterConvertion = (ZooCom.Model.Animal)objectResponse;

            Assert.AreEqual(animalObjectAfterConvertion.ID, ID);
            Assert.AreEqual(animalObjectAfterConvertion.PlaceID, ID);
            Assert.AreEqual(animalObjectAfterConvertion.FoodID, ID);
            Assert.AreEqual(animalObjectAfterConvertion.Count, intTest);
            Assert.AreEqual(animalObjectAfterConvertion.Name, stringTest);
            Assert.AreEqual(animalObjectAfterConvertion.MaintenanceCost, decimalTest);
            Assert.AreNotSame(comModel, animalObjectAfterConvertion);
        }

        [TestMethod]
        public void Returns_NULL()
        {
            var response = service.ConvertToZooComModel(ModelType.Model, animalUnit);

            Assert.IsNull(response);
        }

        [TestMethod]
        public void Should_throw_Exception()
        {
            Animal emptyObject = null;

            Assert.ThrowsException<NullReferenceException>(() =>
            {
                service.ConvertToZooComModel(ModelType.Animal, emptyObject);
            });

            Assert.ThrowsException<NullReferenceException>(() =>
            {
                service.ConvertToZooComModel(ModelType.Attraction, emptyObject);
            });

            Assert.ThrowsException<NullReferenceException>(() =>
            {
                service.ConvertToZooComModel(ModelType.BalanceType, emptyObject);
            });

            Assert.ThrowsException<NullReferenceException>(() =>
            {
                service.ConvertToZooComModel(ModelType.CashBalance, emptyObject);
            });

            Assert.ThrowsException<NullReferenceException>(() =>
            {
                service.ConvertToZooComModel(ModelType.Food, emptyObject);
            });

            Assert.ThrowsException<NullReferenceException>(() =>
            {
                service.ConvertToZooComModel(ModelType.Overtime, emptyObject);
            });

            Assert.ThrowsException<NullReferenceException>(() =>
            {
                service.ConvertToZooComModel(ModelType.Place, emptyObject);
            });

            Assert.ThrowsException<NullReferenceException>(() =>
            {
                service.ConvertToZooComModel(ModelType.Worker, emptyObject);
            });
        }
    }
}
<file_sep>/ZooCom/Model/Model.cs
﻿using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;

namespace ZooCom.Model
{
    public enum ModelType { Model = 0, Animal = 1, Attraction = 2, BalanceType = 4, CashBalance = 8, Food = 16, Overtime = 32, Place = 64, Worker = 128 }

    [Serializable]
    public abstract class Model
    {
        public int ID { get; set; }
        
        public long SerializedBytes()
        {
            using (MemoryStream stream = new MemoryStream())
            {
                new BinaryFormatter().Serialize(stream, this);
                return stream.Length;
            }
        }

        public virtual byte TypeID()
        {
            return (byte)ModelType.Model;
        }
    }
}<file_sep>/FitTesto/DeleteModelTest.cs
﻿using Moq;
using ZooService;
using fitlibrary;

namespace FitTesto
{
    public class DeleteModelTest : DoFixture
    {
        MockSequence ctxSeq;
        Mock<ZooContext> ctx;
        ZooService.ZooService service;
        
        public void SetUp()
        {
            ctxSeq = new MockSequence();
            ctx = new Mock<ZooContext>();
            ctx.Setup(x => x.DeleteWorker(2)).Returns(new System.Tuple<bool, byte>(false, (byte)ModelType.Attraction));
            ctx.InSequence(ctxSeq).Setup(x => x.DeleteAttraction(1)).Returns(new System.Tuple<bool, byte>(true, 0));
            ctx.InSequence(ctxSeq).Setup(x => x.DeleteWorker(2)).Returns(new System.Tuple<bool, byte>(true, 0));

            service = new ZooService.ZooService(1111);
        }
        
        public bool Delete_worker_before_attraction_fail()
        {
            var ret = service.DeleteModel(ctx.Object, ModelType.Worker, 2);
            return ret.Item1 == false && ret.Item2 == (byte)ModelType.Attraction;
        }
        
        public bool Delete_worker_after_deleting_his_attraction()
        {
            var ret1 = service.DeleteModel(ctx.Object, ModelType.Attraction, 1);
            if (ret1.Item1 != true || ret1.Item2 != 0)
                return false;

            var ret2 = service.DeleteModel(ctx.Object, ModelType.Worker, 2);
            
            return ret2.Item1 && ret2.Item2 == 0;
        }
    }
}
<file_sep>/Test/AddModelTest.cs
﻿using Moq;
using ZooService;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Test
{
    [TestClass]
    public class AddModelTest
    {

        Mock<ZooContext> IZooContext;
        ZooService.ZooService service;

        [TestInitialize]
        public void Setup()
        {
            IZooContext = new Mock<ZooContext>();
            service = new ZooService.ZooService(1111);
        }

        [TestMethod]
        public void Should_add_object()
        {
            Animal unit = new Animal()
            {
                ID = 1,
                Name = "tiger",
                Count = 50,
                FoodID = 1,
                PlaceID = 1,
                MaintenanceCost = 5
            };

            IZooContext.Setup(x => x.AddModel<Animal>(unit)).Returns(true);

            var response = service.AddModel(IZooContext.Object, ZooService.ModelType.Animal, unit);

            Assert.IsTrue(response);
        }

        [TestMethod]
        public void Should_return_false_on_non_existing_method()
        {
            Animal unit = new Animal()
            {
                ID = 1,
                Name = "tiger",
                Count = 50,
                FoodID = 1,
                PlaceID = 1,
                MaintenanceCost = 5
            };

            IZooContext.Setup(x => x.AddModel<Animal>(unit)).Returns(true);

            var response = service.AddModel(IZooContext.Object, ZooService.ModelType.Model, (ZooDataModel) unit);

            Assert.IsFalse(response);
        }
    }
}
<file_sep>/ZooManager/Windows/TabCash/AddOperationWindow.xaml.cs
﻿using System.Windows;

namespace ZooManager.Windows.TabCash
{
    public partial class AddOperationWindow : Window
    {
        MainWindow parentWindow;

        public AddOperationWindow(MainWindow parent)
        {
            InitializeComponent();
            parentWindow = parent;
            dataGridTypes.DataContext = parentWindow.ZooConnection.GetModelType(ModelType.BalanceType);
            lock (parentWindow.BalanceTypes)
                dataGridTypes.ItemsSource = parentWindow.BalanceTypes;
        }

        void Button_Click(object sender, RoutedEventArgs e)
        {
            if(IsDataInputValid())
            {
                object operation = parentWindow.ZooConnection.CreateModel(ModelType.CashBalance);
                ZooClient.SetProperty(operation, "SubmitDate", datePickerSubmit.SelectedDate);
                ZooClient.SetProperty(operation, "Money", decimal.Parse(textBoxMoney.Text));
                ZooClient.SetProperty(operation, "BalanceTypeID", ZooClient.GetProperty(dataGridTypes.SelectedItem, "ID"));
                if (string.IsNullOrWhiteSpace(textBoxDescription.Text))
                    ZooClient.SetProperty(operation, "DetailedDescription", textBoxDescription.Text);
                if (!parentWindow.ZooConnection.AddModel(operation))
                {
                    MessageBox.Show("[ERROR] Cannot add new finance operation to database, check log for detailed cause.");
                    DialogResult = false;
                }
                else
                    DialogResult = true;
                Close();
            }
        }

        bool IsDataInputValid()
        {
            if (!decimal.TryParse(textBoxMoney.Text, out decimal val))
            {
                MessageBox.Show("[ERROR] Operation's money must be specified!");
                return false;
            }
            else if (dataGridTypes.SelectedItems.Count <= 0)
            {
                MessageBox.Show("[ERROR] Must specify operation type first, select one from the list!");
                return false;
            }
            return true;
        }
    }
}
<file_sep>/ZooCom/Model/Attraction.cs
﻿namespace ZooCom.Model
{
    [System.Serializable]
    public class Attraction : Model
    {
        public string Name { get; set; }
        public string Description { get; set; }
        public int AttractionManagerID { get; set; }
        public int PlaceID { get; set; }

        public override byte TypeID()
        {
            return (byte)ModelType.Attraction;
        }
    }
}
<file_sep>/ZooManager/Views/CashBalanceView.cs
﻿namespace ZooManager.Views
{
    public class CashBalanceView
    {
        public int ID { get; set; }
        public System.DateTime SubmitDate { get; set; }
        public decimal Money { get; set; }
        public string Type { get; set; }
        public string DetailedDescription { get; set; }
    }
}
<file_sep>/ZooCom/Model/Food.cs
﻿namespace ZooCom.Model
{
    [System.Serializable]
    public class Food : Model
    {
        double _amount = 0.0;
        
        public string Name { get; set; }
        public double Amount
        {
            get { return _amount; }
            set
            {
                if (value < 0.0)
                    throw new System.ArgumentOutOfRangeException($"Food: Amount cannot be lower than 0. Provided \"{value}\"");
                _amount = value;
            }
        }

        public override byte TypeID()
        {
            return (byte)ModelType.Food;
        }
    }
}
<file_sep>/ZooManager/Logger.cs
﻿using System;
using System.IO;
using System.Threading;

namespace ZooManager
{
    public static class Logger
    {
        public static void LogError(string msg, Type type = null, string method = null)
        {
            Log("ERROR", msg, type, method);
        }

        public static void LogWarning(string msg, Type type = null, string method = null)
        {
            Log("WARNING", msg, type, method);
        }
        public static void LogInfo(string msg, Type type = null, string method = null)
        {
            Log("INFO", msg, type, method);
        }

        static void Log(string type, string msg, Type objType, string method)
        {
            new Thread(() =>
            {
                const string path = "log_manager.txt";
                if (objType == null)
                    method = $"unknown:{method}";
                else
                    method = $"{objType.ToString()}:{method}";
                bool isFree = false;
                do
                {
                    try
                    {
                        using (Mutex writeLock = new Mutex(true, "ZooManager.Logger", out isFree))
                        {
                            if (isFree)
                            {
                                if (!File.Exists(path))
                                    File.Create(path).Close();
                                using (TextWriter writer = new StreamWriter(path, true))
                                    writer.WriteLine($"<{DateTime.Now.ToString()}> [{type}] @{method}: {msg}");
                                writeLock.ReleaseMutex();
                            }
                        }
                    }
                    catch (Exception)
                    {
                        isFree = false;
                    }
                    if (!isFree)
                        Thread.Sleep(1000);
                } while (!isFree);
            }).Start();
        }

        static bool IsFileReady(string filename)
        {
            try
            {
                using (FileStream inputStream = File.Open(filename, FileMode.Append, FileAccess.Write, FileShare.None))
                    return true;
            }
            catch (Exception)
            {
                return false;
            }
        }
    }
}
<file_sep>/Test/ConvertFromZooComModelTest.cs
﻿using ZooService;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;

namespace Test
{
    [TestClass]
    public class ConvertFromZooComModelTest
    {
        ZooService.ZooService service;
        Animal animalUnit;
        object comModel;

        const int ID = 1;
        const decimal decimalTest = 5;
        const string stringTest = "testString";
        const int intTest = 50;
                
        [TestInitialize]
        public void SetUp()
        {
            service = new ZooService.ZooService(1111);

            comModel = new ZooCom.Model.Animal
            {
                ID = ID,
                MaintenanceCost = decimalTest,
                Name = stringTest,
                PlaceID = ID,
                FoodID = ID,
                Count = intTest
            };

            animalUnit = new Animal
            {
                ID = ID,
                MaintenanceCost = decimalTest,
                Name = stringTest,
                PlaceID = ID,
                FoodID = ID,
                Count = intTest
            };
        }

        [TestMethod]
        public void Has_the_same_fields_values_after_conversion()
        {
            var objectResponse = service.ConvertFromZooComModel(comModel, ZooService.ModelType.Animal);

            Assert.IsInstanceOfType(objectResponse, animalUnit.GetType());
            Animal animalObjectAfterConvertion = (Animal)objectResponse;

            Assert.AreEqual(animalObjectAfterConvertion.ID, ID);
            Assert.AreEqual(animalObjectAfterConvertion.PlaceID, ID);
            Assert.AreEqual(animalObjectAfterConvertion.FoodID, ID);
            Assert.AreEqual(animalObjectAfterConvertion.Count, intTest);
            Assert.AreEqual(animalObjectAfterConvertion.Name, stringTest);
            Assert.AreEqual(animalObjectAfterConvertion.MaintenanceCost,decimalTest);
            Assert.AreNotSame(animalUnit, animalObjectAfterConvertion);
        }

        [TestMethod]
        public void Returns_NULL()
        {
            var response = service.ConvertFromZooComModel(comModel, ZooService.ModelType.Model);

            Assert.IsNull(response);
        }

        [TestMethod]
        public void Should_throw_Exception()
        {
            ZooCom.Model.Animal emptyObject = null;

            Assert.ThrowsException<NullReferenceException>(() =>
            {
                service.ConvertFromZooComModel(emptyObject, ZooService.ModelType.Animal);
            });

            Assert.ThrowsException<NullReferenceException>(() =>
            {
                service.ConvertFromZooComModel(emptyObject, ZooService.ModelType.Attraction);
            });

            Assert.ThrowsException<NullReferenceException>(() =>
            {
                service.ConvertFromZooComModel(emptyObject, ZooService.ModelType.BalanceType);
            });

            Assert.ThrowsException<NullReferenceException>(() =>
            {
                service.ConvertFromZooComModel(emptyObject, ZooService.ModelType.CashBalance);
            });

            Assert.ThrowsException<NullReferenceException>(() =>
            {
                service.ConvertFromZooComModel(emptyObject, ZooService.ModelType.Food);
            });

            Assert.ThrowsException<NullReferenceException>(() =>
            {
                service.ConvertFromZooComModel(emptyObject, ZooService.ModelType.Overtime);
            });
            
            Assert.ThrowsException<NullReferenceException>(() =>
            {
                service.ConvertFromZooComModel(emptyObject, ZooService.ModelType.Place);
            });

            Assert.ThrowsException<NullReferenceException>(() =>
            {
                service.ConvertFromZooComModel(emptyObject, ZooService.ModelType.Worker);
            });
        }
    }
}
<file_sep>/ZooManager/ZooClient.cs
﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Linq;
using System.Text;

namespace ZooManager
{
    public enum ModelType { Model = 0, Animal = 1, Attraction = 2, BalanceType = 4, CashBalance = 8, Food = 16, Overtime = 32, Place = 64, Worker = 128 }

    public class ZooClient
    {
        Assembly zooComDll;
        Type zooComClientType;
        readonly Dictionary<ModelType, Type> modelTypes = new Dictionary<ModelType, Type>(Enum.GetNames(typeof(ModelType)).Length);
        Dictionary<string, MethodInfo> zooComMethods = new Dictionary<string, MethodInfo>(3);
        readonly object zooComClient;

        public ZooClient(int port)
        {
            zooComDll = Assembly.LoadFile($@"{AppDomain.CurrentDomain.BaseDirectory}ZooCom.dll");
            zooComClientType = zooComDll.GetType("ZooCom.ZooComClient");
            foreach (ModelType type in (ModelType[])Enum.GetValues(typeof(ModelType)))
            {
                modelTypes[type] = zooComDll.GetType($"ZooCom.Model.{Enum.GetName(typeof(ModelType), type)}");
            }
            zooComClient = Activator.CreateInstance(zooComClientType, new object[] { port });
            foreach (string method in new string[] { "ModifyModel", "AddModel", "DeleteModel", "GetModels" })
            {
                zooComMethods.Add(method, zooComClientType.GetMethod(method));
            }
        }

        public bool ModifyModel(object model)
        {
            if (!modelTypes.ContainsValue(model.GetType()))
            {
                Logger.LogWarning($"Unknown object passed to method of type: {model.GetType()}", GetType(), $"ModifyModel(model: {model})");
                return false;
            }
            try
            {
                if ((bool)zooComMethods["ModifyModel"].MakeGenericMethod(model.GetType()).Invoke(zooComClient, new object[] { model }))
                    return true;
                Logger.LogError("Cannot connect to database, internal connection error!", GetType(), $"ModifyModel(model: {model})");
            }
            catch (Exception e)
            {
                Logger.LogError($"Cannot modify object {model.GetType()}. Exception {e.ToString()}", GetType(), $"ModifyModel(model: {model})");
            }
            return false;
        }

        public bool AddModel(object model)
        {
            if (!modelTypes.ContainsValue(model.GetType()))
            {
                Logger.LogWarning($"Unknown object passed to method of type: {model.GetType()}", GetType(), $"AddModel(model: {model})");
                return false;
            }
            try
            {
                if ((bool)zooComMethods["AddModel"].MakeGenericMethod(model.GetType()).Invoke(zooComClient, new object[] { model }))
                    return true;
                Logger.LogError("Cannot connect to database, internal connection error!", GetType(), $"AddModel(model: {model})");
            }
            catch (Exception e)
            {
                Logger.LogError($"Cannot add object {model.GetType()} to database. Exception {e.ToString()}", GetType(), $"AddModel(model: {model})");
            }
            return false;
        }

        public Tuple<bool, byte> DeleteModel(ModelType type, int id)
        {
            try
            {
                Tuple<bool, byte> response = (Tuple<bool, byte>)zooComMethods["DeleteModel"].MakeGenericMethod(modelTypes[type]).Invoke(zooComClient, new object[] { id });
                if (response == null)
                    Logger.LogError("Cannot connect to database, internal connection error!", GetType(), $"DeleteModel(type: {type.ToString()}, id: {id})");
                else if (!response.Item1)
                    Logger.LogError($"Cannot delete model due to its contraints, error code: {Convert.ToString(response.Item2, 2)}", GetType(), $"DeleteModel(type: {type.ToString()}, id: {id})");
                return response;
            }
            catch (Exception e)
            {
                Logger.LogError($"Cannot delete {type.ToString()} with ID: {id}. Exception {e.ToString()}", GetType(), $"DeleteModel(type: {type}, id: {id})");
            }
            return null;
        }

        public List<object> GetModels(ModelType type)
        {
            try
            {
                IList models = (IList)zooComMethods["GetModels"].MakeGenericMethod(modelTypes[type]).Invoke(zooComClient, null);
                if (models != null)
                    return models.Cast<object>().ToList();
                Logger.LogError("Cannot connect to database, internal connection error!", GetType(), $"GetModels(type: {type.ToString()})");
            }
            catch (Exception e)
            {
                Logger.LogError($"Cannot get objects {type.ToString()}. Exception {e.ToString()}", GetType(), $"GetModels(model: {type})");
            }
            return null;
        }

        public Type GetModelType(ModelType type)
        {
            return modelTypes[type];
        }

        public object CreateModel(ModelType type)
        {
            return Activator.CreateInstance(modelTypes[type]);
        }

        public static object GetProperty(object model, string name)
        {
            return model.GetType().GetProperty(name).GetValue(model);
        }

        public static void SetProperty(object model, string name, object value)
        {
            model.GetType().GetProperty(name).SetValue(model, value);
        }

        public static string DecodeDeleteError(byte code)
        {
            StringBuilder msg = new StringBuilder("[ERROR] ");
            if (code == (byte)ModelType.Model)
                msg.Append("Unknown database internal error!");
            else
            {
                msg.Append("Cannot delete model due to existing contraints on following tables: ");
                if ((code & (byte)ModelType.Animal) > 0)
                    msg.Append("Animals, ");
                if ((code & (byte)ModelType.Attraction) > 0)
                    msg.Append("Attractions, ");
                if ((code & (byte)ModelType.BalanceType) > 0)
                    msg.Append("BalanceTypes, ");
                if ((code & (byte)ModelType.CashBalance) > 0)
                    msg.Append("CashBalances, ");
                if ((code & (byte)ModelType.Food) > 0)
                    msg.Append("Foods, ");
                if ((code & (byte)ModelType.Overtime) > 0)
                    msg.Append("Overtimes, ");
                if ((code & (byte)ModelType.Place) > 0)
                    msg.Append("Places, ");
                if ((code & (byte)ModelType.Worker) > 0)
                    msg.Append("Workers, ");
                msg.Remove(msg.Length - 2, 2);
                msg.Append(".");
            }
            return msg.ToString();
        }
    }
}
<file_sep>/ZooCom/Model/Worker.cs
﻿namespace ZooCom.Model
{
    [System.Serializable]
    public class Worker : Model
    {
        int _age = 0;
        decimal _salary = 0;
        
        public string Surname { get; set; }
        public string Name { get; set; }
        public int Age
        {
            get { return _age; }
            set
            {
                if (value < 16)
                    throw new System.ArgumentOutOfRangeException($"Worker: Age cannot be lower than 16. Provided \"{value}\"");
                _age = value;
            }
        }
        public decimal Salary
        {
            get { return _salary; }
            set
            {
                if (value < 0)
                    throw new System.ArgumentOutOfRangeException($"Worker: Salary cannot be lower than 16. Provided \"{value}\"");
                _salary = value;
            }
        }
        public System.DateTime StartDate { get; set; } = System.DateTime.Today;
        public int PlaceID { get; set; }

        public override byte TypeID()
        {
            return (byte)ModelType.Worker;
        }
    }
}
<file_sep>/ZooManager/Windows/TabPlaces/AddPlaceWindow.xaml.cs
﻿using System.Windows;

namespace ZooManager.Windows.TabPlaces
{
    public partial class AddPlaceWindow : Window
    {
        MainWindow parentWindow;

        public AddPlaceWindow(MainWindow parent)
        {
            InitializeComponent();
            parentWindow = parent;
        }

        void Button_Click(object sender, RoutedEventArgs e)
        {
            if(IsDataInputValid())
            {
                object place = parentWindow.ZooConnection.CreateModel(ModelType.Place);
                ZooClient.SetProperty(place, "Name", textBoxName.Text);
                ZooClient.SetProperty(place, "OpenTime", timePickerOpen.Value.Value.TimeOfDay);
                ZooClient.SetProperty(place, "CloseTime", timePickerClose.Value.Value.TimeOfDay);
                ZooClient.SetProperty(place, "MaintenanceCost", decimal.Parse(textBoxCost.Text));
                if (!parentWindow.ZooConnection.AddModel(place))
                {
                    MessageBox.Show("[ERROR] Cannot add new place to database, check log for detailed cause.");
                    DialogResult = false;
                }
                else
                    DialogResult = true;
                Close();
            }
        }

        bool IsDataInputValid()
        {
            if (string.IsNullOrWhiteSpace(textBoxName.Text))
            {
                MessageBox.Show("[ERROR] Place's name must be specified!");
                return false;
            }
            else if (timePickerClose.Value.Value.TimeOfDay <= timePickerOpen.Value.Value.TimeOfDay)
            {
                MessageBox.Show("[ERROR] Close time cannot be equal or bigger than open time!");
                return false;
            }
            else if (!decimal.TryParse(textBoxCost.Text, out decimal val))
            {
                MessageBox.Show("[ERROR] Place's maintenace cost must be specified!");
                return false;
            }
            return true;
        }
    }
}
<file_sep>/ZooManager/Windows/TabPlaces/ModifyPlaceWindow.xaml.cs
﻿using System.Windows;

namespace ZooManager.Windows.TabPlaces
{
    public partial class ModifyPlaceWindow : Window
    {
        MainWindow parentWindow;
        object place;

        public ModifyPlaceWindow(MainWindow parent, object selectedPlace)
        {
            InitializeComponent();
            if (selectedPlace == null)
            {
                Logger.LogWarning("Error selecting place to modify, process terminated.", GetType(), "ModifyPlaceWindow(parent: MainWindow, selectedPlace: null)");
                Close();
            }
            place = selectedPlace;
            parentWindow = parent;
            textBoxName.Text = (string)ZooClient.GetProperty(place, "Name");
            timePickerOpen.Value = System.DateTime.Now.Date + ((System.TimeSpan)ZooClient.GetProperty(place, "OpenTime"));
            timePickerClose.Value = System.DateTime.Now.Date + ((System.TimeSpan)ZooClient.GetProperty(place, "CloseTime"));
            textBoxCost.Text = ((decimal)ZooClient.GetProperty(place, "MaintenanceCost")).ToString();
        }

        void Button_Click(object sender, RoutedEventArgs e)
        {
            if (IsDataInputValid())
            {
                object place = parentWindow.ZooConnection.CreateModel(ModelType.Place);
                ZooClient.SetProperty(place, "Name", textBoxName.Text);
                ZooClient.SetProperty(place, "OpenTime", timePickerOpen.Value.Value.TimeOfDay);
                ZooClient.SetProperty(place, "CloseTime", timePickerClose.Value.Value.TimeOfDay);
                ZooClient.SetProperty(place, "MaintenanceCost", decimal.Parse(textBoxCost.Text));
                if (!parentWindow.ZooConnection.ModifyModel(place))
                {
                    MessageBox.Show("[ERROR] Cannot modify place, check log for detailed cause.");
                    DialogResult = false;
                }
                else
                    DialogResult = true;
                Close();
            }
        }

        bool IsDataInputValid()
        {
            if (string.IsNullOrWhiteSpace(textBoxName.Text))
            {
                MessageBox.Show("[ERROR] Place's name must be specified!");
                return false;
            }
            else if (timePickerClose.Value.Value.TimeOfDay <= timePickerOpen.Value.Value.TimeOfDay)
            {
                MessageBox.Show("[ERROR] Close time cannot be equal or bigger than open time!");
                return false;
            }
            else if (!decimal.TryParse(textBoxCost.Text, out decimal val))
            {
                MessageBox.Show("[ERROR] Place's maintenace cost must be specified!");
                return false;
            }
            return true;
        }
    }
}
<file_sep>/ZooManager/Views/WorkerView.cs
﻿namespace ZooManager.Views
{
    public class WorkerView
    {
        public int ID { get; set; }
        public string Surname { get; set; }
        public string Name { get; set; }
        public int Age { get; set; }
        public decimal Salary { get; set; }
        public System.DateTime StartDate { get; set; }
        public string Place { get; set; }

    }
}
<file_sep>/ZooCom/Model/Overtime.cs
﻿namespace ZooCom.Model
{
    [System.Serializable]
    public class Overtime : Model
    {
        public System.DateTime Date { get; set; }
        public int Hours { get; set; }
        public int PaymentPercentage { get; set; }
        public int WorkerID { get; set; }

        public override byte TypeID()
        {
            return (byte)ModelType.Overtime;
        }
    }
}
<file_sep>/ZooManager/Windows/TabAnimals/ModifyFoodWindow.xaml.cs
﻿using System.Windows;

namespace ZooManager.Windows.TabAnimals
{
    public partial class ModifyFoodWindow : Window
    {
        MainWindow parentWindow;
        object food;

        public ModifyFoodWindow(MainWindow parent, object selectedFood)
        {
            InitializeComponent();
            if (selectedFood == null)
            {
                Logger.LogWarning("Error selecting food to modify, process terminated.", GetType(), "ModifyFoodWindow(parent: MainWindow, selectedFood: null)");
                Close();
            }
            food = selectedFood;
            parentWindow = parent;
            textBoxName.Text = (string)ZooClient.GetProperty(food, "Name");
            textBoxAmount.Text = ((double)ZooClient.GetProperty(food, "Amount")).ToString();
        }

        void Button_Click(object sender, RoutedEventArgs e)
        {
            if (IsDataInputValid())
            {
                ZooClient.SetProperty(food, "Name", textBoxName.Text);
                ZooClient.SetProperty(food, "Amount", double.Parse(textBoxAmount.Text));
                if (!parentWindow.ZooConnection.ModifyModel(food))
                {
                    MessageBox.Show("[ERROR] Cannot add new food to database, check log for detailed cause.");
                    DialogResult = false;
                }
                else
                    DialogResult = true;
                Close();
            }
        }

        bool IsDataInputValid()
        {
            if (string.IsNullOrWhiteSpace(textBoxName.Text))
            {
                MessageBox.Show("[ERROR] Food's name must be specified!");
                return false;
            }
            else if (!double.TryParse(textBoxAmount.Text, out double value) || value < 0)
            {
                MessageBox.Show("[ERROR] Food's amouont must be specified and be at least 0!");
                return false;
            }
            return true;
        }
    }
}
<file_sep>/ZooCom/ZooComClient.cs
﻿using System;
using System.IO;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Runtime.Serialization.Formatters.Binary;
using System.Linq;

namespace ZooCom
{
    public class ZooComClient
    {
        readonly int tcpPort;

        public ZooComClient(int port)
        {
            tcpPort = port;
        }

        public bool ModifyModel<T>(T model) where T : Model.Model
        {
            using (TcpClient tcpClient = Connect(tcpPort + 3))
            {
                if (tcpClient == null)
                    return false;
                using (NetworkStream netStream = tcpClient.GetStream())
                using (MemoryStream dataStream = new MemoryStream())
                {
                    netStream.WriteByte(model.TypeID());
                    netStream.Flush();
                    new BinaryFormatter().Serialize(dataStream, model);
                    byte[] data = dataStream.ToArray();
                    netStream.Write(BitConverter.GetBytes(data.Length), 0, sizeof(int));
                    netStream.Flush();
                    netStream.Write(data, 0, data.Length);
                    netStream.Flush();
                }
                return true;
            }
        }
        
        public bool AddModel<T>(T model) where T : Model.Model
        {
            using (TcpClient tcpClient = Connect(tcpPort + 2))
            {
                if (tcpClient == null)
                    return false;
                using (NetworkStream netStream = tcpClient.GetStream())
                using (MemoryStream dataStream = new MemoryStream())
                {
                    netStream.WriteByte(model.TypeID());
                    netStream.Flush();
                    new BinaryFormatter().Serialize(dataStream, model);
                    byte[] data = dataStream.ToArray();
                    netStream.Write(BitConverter.GetBytes(data.Length), 0, sizeof(int));
                    netStream.Flush();
                    netStream.Write(data, 0, data.Length);
                    netStream.Flush();
                }
                return true;
            }
        }

        public Tuple<bool, byte> DeleteModel<T>(int id) where T : Model.Model, new()
        {
            using (TcpClient tcpClient = Connect(tcpPort + 1))
            {
                if (tcpClient == null)
                    return null;
                using (NetworkStream netStream = tcpClient.GetStream())
                {
                    netStream.WriteByte(new T().TypeID());
                    netStream.Write(BitConverter.GetBytes(id), 0, sizeof(int));
                    netStream.Flush();
                    byte[] response = new byte[sizeof(bool)];
                    netStream.Read(response, 0, response.Length);
                    bool status = BitConverter.ToBoolean(response, 0);
                    byte errorCode = (byte)Model.ModelType.Model;
                    if (!status)
                    {
                        netStream.Read(response, 0, 1);
                        errorCode = response[0];

                    }
                    return new Tuple<bool, byte>(status, errorCode);
                }
            }
        }

        public List<T> GetModels<T>() where T : Model.Model, new()
        {
            using (TcpClient tcpClient = Connect(tcpPort))
            {
                if (tcpClient == null)
                    return null;
                List<T> models = null;
                using (NetworkStream netStream = tcpClient.GetStream())
                {
                    netStream.WriteByte(new T().TypeID());
                    netStream.Flush();
                    byte[] data = new byte[sizeof(int)];
                    netStream.Read(data, 0, sizeof(int));
                    data = new byte[BitConverter.ToInt32(data, 0)];
                    netStream.Read(data, 0, data.Length);
                    models = ((List<object>)new BinaryFormatter().Deserialize(new MemoryStream(data))).Cast<T>().ToList();
                    netStream.Close();
                }
                return models;
            }
        }

        TcpClient Connect(int port, bool isFirstCall = true)
        {
            try
            {
                return new TcpClient("localhost", port)
                {
                    NoDelay = true
                };
            }
            catch (Exception e)
            {
                if (isFirstCall && System.Diagnostics.Process.GetProcessesByName("ZooService").Length <= 0)
                {
                    Logger.LogWarning("ZooService is down, starting up...", GetType(), $"Connect(port: {port}, isFirstCall: {isFirstCall})");
                    try
                    {
                        System.Diagnostics.Process.Start("ZooService.exe");
                        System.Threading.Thread.Sleep(2000);
                        return Connect(port, false);
                    }
                    catch (Exception)
                    {
                        Logger.LogError("Cannot start ZooService!", GetType(), $"Connect(port: {port}, isFirstCall: {isFirstCall})");
                        return null;
                    }
                }
                Logger.LogError($"Cannot open tcp connection on port. Exception: {e.ToString()}", GetType(), $"Connect(port: {port}, isFirstCall: {isFirstCall})");
                return null;
            }
        }
    }
}
<file_sep>/ZooManager/Windows/TabAnimals/ModifyAnimalWindow.xaml.cs
﻿using System.Linq;
using System.Windows;

namespace ZooManager.Windows.TabAnimals
{
    public partial class ModifyAnimalWindow : Window
    {
        MainWindow parentWindow;
        object animal;

        public ModifyAnimalWindow(MainWindow parent, object selectedAnimal)
        {
            InitializeComponent();
            if (selectedAnimal == null)
            {
                Logger.LogWarning("Error selecting animal to modify, process terminated.", GetType(), "ModifyAnimalWindow(parent: MainWindow, selectedAnimal: null)");
                Close();
            }
            animal = selectedAnimal;
            parentWindow = parent;
            textBoxName.Text = (string)ZooClient.GetProperty(animal, "Name");
            textBoxCount.Text = ((int)ZooClient.GetProperty(animal, "Count")).ToString();
            textBoxCost.Text = ((decimal)ZooClient.GetProperty(animal, "MaintenanceCost")).ToString();
            dataGridPlaces.DataContext = parentWindow.ZooConnection.GetModelType(ModelType.Place);
            lock (parentWindow.Places)
            {
                dataGridPlaces.ItemsSource = parentWindow.Places;
                int id = (int)ZooClient.GetProperty(animal, "PlaceID");
                dataGridPlaces.SelectedItem = parentWindow.Places.FirstOrDefault(place => (int)ZooClient.GetProperty(place, "ID") == id);
            }
            dataGridFoods.DataContext = parentWindow.ZooConnection.GetModelType(ModelType.Food);
            lock (parentWindow.Foods)
            {
                dataGridFoods.ItemsSource = parentWindow.Foods;
                int id = (int)ZooClient.GetProperty(animal, "FoodID");
                dataGridFoods.SelectedItem = parentWindow.Foods.FirstOrDefault(food => (int)ZooClient.GetProperty(food, "ID") == id);
            }
            }

        void Button_Click(object sender, RoutedEventArgs e)
        {
            if (IsDataInputValid())
            {
                ZooClient.SetProperty(animal, "Name", textBoxName.Text);
                ZooClient.SetProperty(animal, "Count", int.Parse(textBoxCount.Text));
                ZooClient.SetProperty(animal, "MaintenanceCost", decimal.Parse(textBoxCost.Text));
                ZooClient.SetProperty(animal, "PlaceID", ZooClient.GetProperty(dataGridPlaces.SelectedItem, "ID"));
                ZooClient.SetProperty(animal, "FoodID", ZooClient.GetProperty(dataGridFoods.SelectedItem, "ID"));
                if (!parentWindow.ZooConnection.ModifyModel(animal))
                {
                    MessageBox.Show("[ERROR] Cannot add new animal to database, check log for detailed cause.");
                    DialogResult = false;
                }
                else
                    DialogResult = true;
                Close();
            }
        }

        bool IsDataInputValid()
        {
            if (string.IsNullOrWhiteSpace(textBoxName.Text))
            {
                MessageBox.Show("[ERROR] Animal's name must be specified!");
                return false;
            }
            else if (!int.TryParse(textBoxCount.Text, out int value) || value < 0)
            {
                MessageBox.Show("[ERROR] Animal's count must be specified and be at least 0!");
                return false;
            }
            else if (dataGridPlaces.SelectedItems.Count <= 0)
            {
                MessageBox.Show("[ERROR] Must assign animal to place, select one from the list!");
                return false;
            }
            else if (dataGridFoods.SelectedItems.Count <= 0)
            {
                MessageBox.Show("[ERROR] Must assign food to animal, select one from the list!");
                return false;
            }
            else if (!decimal.TryParse(textBoxCost.Text, out decimal val))
            {
                MessageBox.Show("[ERROR] Animal's maintenance cost must be specified!");
                return false;
            }
            return true;
        }
    }
}
<file_sep>/FitTesto/ConvertToZooComModelTest.cs
﻿using System;
using ZooService;
using fitlibrary;

namespace FitTesto
{
    public class ConvertToZooComModelTest : DoFixture
    {
        ZooService.ZooService service = new ZooService.ZooService(1111);
        Animal animalUnit;
        object comModel;

        int ID = 1;
        int placeID = 3;
        int foodID = 5;
        decimal maintenanceCost = 21;
        string name = "Cowabunga";
        int count = 42;
        
        public void SetUp()
        {
            comModel = new ZooCom.Model.Animal
            {
                ID = ID,
                MaintenanceCost = maintenanceCost,
                Name = name,
                PlaceID = placeID,
                FoodID = foodID,
                Count = count
            };

            animalUnit = new Animal
            {
                ID = ID,
                MaintenanceCost = maintenanceCost,
                Name = name,
                PlaceID = placeID,
                FoodID = foodID,
                Count = count
            };
        }

        public bool Is_COM_MODEL_after_conversion()
        {
            var objectResponse = service.ConvertToZooComModel(ModelType.Animal, animalUnit);

            return objectResponse.GetType() == comModel.GetType();
        }

        public bool Has_same_ID_after_conversion()
        {
            var objectResponse = service.ConvertToZooComModel(ModelType.Animal, animalUnit);
            
            ZooCom.Model.Animal animalObjectAfterConvertion = (ZooCom.Model.Animal)objectResponse;

            return animalObjectAfterConvertion.ID == ID;
        }

        public bool Has_same_PlaceID_after_conversion()
        {
            var objectResponse = service.ConvertToZooComModel(ModelType.Animal, animalUnit);
            
            ZooCom.Model.Animal animalObjectAfterConvertion = (ZooCom.Model.Animal)objectResponse;

            return animalObjectAfterConvertion.PlaceID == placeID;
        }

        public bool Has_same_FoodID_after_conversion()
        {
            var objectResponse = service.ConvertToZooComModel(ModelType.Animal, animalUnit);
            
            ZooCom.Model.Animal animalObjectAfterConvertion = (ZooCom.Model.Animal)objectResponse;

            return animalObjectAfterConvertion.FoodID == foodID;
        }

        public bool Has_same_Count_after_conversion()
        {
            var objectResponse = service.ConvertToZooComModel(ModelType.Animal, animalUnit);
            
            ZooCom.Model.Animal animalObjectAfterConvertion = (ZooCom.Model.Animal)objectResponse;

            return animalObjectAfterConvertion.Count == count;
        }

        public bool Has_same_Name_after_conversion()
        {
            var objectResponse = service.ConvertToZooComModel(ModelType.Animal, animalUnit);
            
            ZooCom.Model.Animal animalObjectAfterConvertion = (ZooCom.Model.Animal)objectResponse;
            
            return animalObjectAfterConvertion.Name == name;
        }

        public bool Has_same_Maintenance_Cost_after_conversion()
        {
            var objectResponse = service.ConvertToZooComModel(ModelType.Animal, animalUnit);
            
            ZooCom.Model.Animal animalObjectAfterConvertion = (ZooCom.Model.Animal)objectResponse;
            
            return animalObjectAfterConvertion.MaintenanceCost == maintenanceCost;
        }

        public bool Are_not_same_objects_after_conversion()
        {
            var objectResponse = service.ConvertToZooComModel(ModelType.Animal, animalUnit);
            
            ZooCom.Model.Animal animalObjectAfterConvertion = (ZooCom.Model.Animal)objectResponse;
            
            return comModel != animalObjectAfterConvertion;
        }
        
        public bool Returns_NULL()
        {
            var response = service.ConvertToZooComModel(ModelType.Model, animalUnit);

            return response == null;
        }
        
        public bool Should_throw_Exception()
        {
            Animal emptyObject = null;

            try
            {
                service.ConvertToZooComModel(ModelType.Animal, emptyObject);
                return false;
            }
            catch (NullReferenceException) { }
            catch
            {
                return false;
            }

            try
            {
                service.ConvertToZooComModel(ModelType.Attraction, emptyObject);
                return false;
            }
            catch (NullReferenceException) { }
            catch
            {
                return false;
            }

            try
            {
                service.ConvertToZooComModel(ModelType.BalanceType, emptyObject);
                return false;
            }
            catch (NullReferenceException) { }
            catch
            {
                return false;
            }

            try
            {
                service.ConvertToZooComModel(ModelType.CashBalance, emptyObject);
                return false;
            }
            catch (NullReferenceException) { }
            catch
            {
                return false;
            }

            try
            {
                service.ConvertToZooComModel(ModelType.Food, emptyObject);
                return false;
            }
            catch (NullReferenceException) { }
            catch
            {
                return false;
            }

            try
            {
                service.ConvertToZooComModel(ModelType.Overtime, emptyObject);
                return false;
            }
            catch (NullReferenceException) { }
            catch
            {
                return false;
            }

            try
            {
                service.ConvertToZooComModel(ModelType.Place, emptyObject);
                return false;
            }
            catch (NullReferenceException) { }
            catch
            {
                return false;
            }

            try
            {
                service.ConvertToZooComModel(ModelType.Worker, emptyObject);
                return false;
            }
            catch (NullReferenceException) { }
            catch
            {
                return false;
            }

            return true;
        }
    }
}
<file_sep>/ZooManager/Views/OvertimeView.cs
﻿namespace ZooManager.Views
{
    public class OvertimeView
    {
        public int ID { get; set; }
        public System.DateTime Date { get; set; }
        public int Hours { get; set; }
        public int PaymentPercentage { get; set; }
        public string Employee { get; set; }
    }
}
<file_sep>/ZooManager/Windows/TabWorkers/ModifyWorkerWindow.xaml.cs
﻿using System;
using System.Linq;
using System.Windows;

namespace ZooManager.Windows.TabWorkers
{
    public partial class ModifyWorkerWindow : Window
    {
        MainWindow parentWindow;
        object worker;

        public ModifyWorkerWindow(MainWindow parent, object selectedWorker)
        {
            InitializeComponent();
            if (selectedWorker == null)
            {
                Logger.LogWarning("Error selecting worker to modify, process terminated.", GetType(), "ModifyWorkerWindow(parent: MainWindow, selectedWorker: null)");
                Close();
            }
            worker = selectedWorker;
            parentWindow = parent;
            dataGridPlaces.DataContext = parentWindow.ZooConnection.GetModelType(ModelType.Place);
            lock (parentWindow.Places)
            {
                dataGridPlaces.ItemsSource = parentWindow.Places;
                int id = (int)ZooClient.GetProperty(worker, "PlaceID");
                dataGridPlaces.SelectedItem = parentWindow.Places.FirstOrDefault(place => (int)ZooClient.GetProperty(place, "ID") == id);
            }
            textBoxSurname.Text = (string)ZooClient.GetProperty(worker, "Surname");
            textBoxName.Text = (string)ZooClient.GetProperty(worker, "Name");
            textBoxAge.Text = ((int)ZooClient.GetProperty(worker, "Age")).ToString();
            textBoxSalary.Text = ((decimal)ZooClient.GetProperty(worker, "Salary")).ToString();
            datePickerStartDate.SelectedDate = (DateTime)ZooClient.GetProperty(worker, "StartDate");
        }

        void Button_Click(object sender, RoutedEventArgs e)
        {
            if (IsDataInputValid())
            {
                ZooClient.SetProperty(worker, "Surname", textBoxSurname.Text);
                ZooClient.SetProperty(worker, "Name", textBoxName.Text);
                ZooClient.SetProperty(worker, "Age", int.Parse(textBoxAge.Text));
                ZooClient.SetProperty(worker, "Salary", decimal.Parse(textBoxSalary.Text));
                ZooClient.SetProperty(worker, "StartDate", datePickerStartDate.SelectedDate);
                ZooClient.SetProperty(worker, "PlaceID", ZooClient.GetProperty(dataGridPlaces.SelectedItem, "ID"));
                if (!parentWindow.ZooConnection.ModifyModel(worker))
                {
                    MessageBox.Show("[ERROR] Cannot modify worker, check log for detailed cause.");
                    DialogResult = false;
                }
                else
                    DialogResult = true;
                Close();
            }
        }

        bool IsDataInputValid()
        {
            if (string.IsNullOrWhiteSpace(textBoxName.Text))
            {
                MessageBox.Show("[ERROR] Worker's name must be specified!");
                return false;
            }
            else if (string.IsNullOrWhiteSpace(textBoxSurname.Text))
            {
                MessageBox.Show("[ERROR] Worker's surname must be specified!");
                return false;
            }
            else if (!int.TryParse(textBoxAge.Text, out int value) || value < 16)
            {
                MessageBox.Show("[ERROR] Worker's age must be specified and be at least 16!");
                return false;
            }
            else if (!decimal.TryParse(textBoxSalary.Text, out decimal val))
            {
                MessageBox.Show("[ERROR] Worker's salary must be specified!");
                return false;
            }
            else if (dataGridPlaces.SelectedItems.Count <= 0)
            {
                MessageBox.Show("[ERROR] Must assign worker to working place, select one from the list!");
                return false;
            }
            return true;
        }
    }
}
