From bdbc9711ebc8ce55e23c09fff867f28ff1ce947a Mon Sep 17 00:00:00 2001 From: ahewbu Date: Wed, 17 Dec 2025 19:33:23 +0400 Subject: [PATCH 01/20] init --- .../CarRental.Domain/CarRental.Domain..csproj | 10 ++++++++++ CarRental/CarRental/CarRental.slnx | 4 ++++ CarRental/CarRental/CarRental/CarRental.csproj | 10 ++++++++++ CarRental/CarRental/CarRental/Program.cs | 2 ++ 4 files changed, 26 insertions(+) create mode 100644 CarRental/CarRental/CarRental.Domain/CarRental.Domain..csproj create mode 100644 CarRental/CarRental/CarRental.slnx create mode 100644 CarRental/CarRental/CarRental/CarRental.csproj create mode 100644 CarRental/CarRental/CarRental/Program.cs diff --git a/CarRental/CarRental/CarRental.Domain/CarRental.Domain..csproj b/CarRental/CarRental/CarRental.Domain/CarRental.Domain..csproj new file mode 100644 index 000000000..7d6a53484 --- /dev/null +++ b/CarRental/CarRental/CarRental.Domain/CarRental.Domain..csproj @@ -0,0 +1,10 @@ + + + + net8.0 + CarRental.Domain_ + enable + enable + + + diff --git a/CarRental/CarRental/CarRental.slnx b/CarRental/CarRental/CarRental.slnx new file mode 100644 index 000000000..d053a8239 --- /dev/null +++ b/CarRental/CarRental/CarRental.slnx @@ -0,0 +1,4 @@ + + + + diff --git a/CarRental/CarRental/CarRental/CarRental.csproj b/CarRental/CarRental/CarRental/CarRental.csproj new file mode 100644 index 000000000..e7874f6f9 --- /dev/null +++ b/CarRental/CarRental/CarRental/CarRental.csproj @@ -0,0 +1,10 @@ + + + + Exe + net8.0 + enable + enable + + + \ No newline at end of file diff --git a/CarRental/CarRental/CarRental/Program.cs b/CarRental/CarRental/CarRental/Program.cs new file mode 100644 index 000000000..3751555cb --- /dev/null +++ b/CarRental/CarRental/CarRental/Program.cs @@ -0,0 +1,2 @@ +// See https://aka.ms/new-console-template for more information +Console.WriteLine("Hello, World!"); From 75665764195f921b640be7a91fb3bd791c5efa46 Mon Sep 17 00:00:00 2001 From: ahewbu Date: Wed, 17 Dec 2025 19:57:33 +0400 Subject: [PATCH 02/20] =?UTF-8?q?=D0=BA=D0=BB=D0=B0=D1=81=D1=81=D1=8B=20?= =?UTF-8?q?=D0=BF=D0=BE=20=D0=BC=D0=BE=D0=B5=D0=B9=20=D0=BF=D1=80=D0=B5?= =?UTF-8?q?=D0=B4=D0=BC=D0=B5=D1=82=D0=BD=D0=BE=D0=B9=20=D0=BE=D0=B1=D0=BB?= =?UTF-8?q?=D0=B0=D1=81=D1=82=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CarRental.Domain/Models/BodyType.cs | 43 +++++++++++++++++++ .../CarRental.Domain/Models/CarClass.cs | 28 ++++++++++++ .../CarRental.Domain/Models/DriveType.cs | 29 +++++++++++++ .../Models/TransmissionType.cs | 33 ++++++++++++++ 4 files changed, 133 insertions(+) create mode 100644 CarRental/CarRental/CarRental.Domain/Models/BodyType.cs create mode 100644 CarRental/CarRental/CarRental.Domain/Models/CarClass.cs create mode 100644 CarRental/CarRental/CarRental.Domain/Models/DriveType.cs create mode 100644 CarRental/CarRental/CarRental.Domain/Models/TransmissionType.cs diff --git a/CarRental/CarRental/CarRental.Domain/Models/BodyType.cs b/CarRental/CarRental/CarRental.Domain/Models/BodyType.cs new file mode 100644 index 000000000..dae8c928a --- /dev/null +++ b/CarRental/CarRental/CarRental.Domain/Models/BodyType.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CarRental.Domain_.Models; + +/// +/// Перечисление типов кузова +/// +public enum BodyType +{ + /// + /// Седан (4-5 мест, закрытый) + /// + Sedan, + + /// + /// Хэтчбек (компактный, спортивный) + /// + Hatchback, + + /// + /// Купе (спортивный, 2-3 места) + /// + Coupe, + + /// + /// Внедорожник (SUV, повышенная проходимость) + /// + SUV, + + /// + /// Универсал (седан с большим багажником) + /// + Wagon, + + /// + /// Пикап (с открытым кузовом сзади) + /// + Pickup +} diff --git a/CarRental/CarRental/CarRental.Domain/Models/CarClass.cs b/CarRental/CarRental/CarRental.Domain/Models/CarClass.cs new file mode 100644 index 000000000..fe6f9baed --- /dev/null +++ b/CarRental/CarRental/CarRental.Domain/Models/CarClass.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CarRental.Domain_.Models; + +/// +/// Перечисление классов автомобилей +/// +public enum CarClass +{ + /// + /// Эконом класс (дешевле, простые машины) + /// + Economy, + + /// + /// Средний класс (среднее качество и цена) + /// + Middle, + + /// + /// Премиум класс (люксовые машины) + /// + Premium +} diff --git a/CarRental/CarRental/CarRental.Domain/Models/DriveType.cs b/CarRental/CarRental/CarRental.Domain/Models/DriveType.cs new file mode 100644 index 000000000..6170ea4be --- /dev/null +++ b/CarRental/CarRental/CarRental.Domain/Models/DriveType.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CarRental.Domain_.Models; + + +/// +/// Перечисление типов привода +/// +public enum DriveType +{ + /// + /// Передний привод (FWD) + /// + FrontWheelDrive, + + /// + /// Задний привод (RWD) + /// + RearWheelDrive, + + /// + /// Полный привод (AWD) + /// + AllWheelDrive +} diff --git a/CarRental/CarRental/CarRental.Domain/Models/TransmissionType.cs b/CarRental/CarRental/CarRental.Domain/Models/TransmissionType.cs new file mode 100644 index 000000000..b8eba41dc --- /dev/null +++ b/CarRental/CarRental/CarRental.Domain/Models/TransmissionType.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CarRental.Domain_.Models; + +/// +/// Перечисление типов коробки передач +/// +public enum TransmissionType +{ + /// + /// Механическая коробка передач (МКПП) + /// + Manual, + + /// + /// Автоматическая коробка передач (АКПП) + /// + Automatic, + + /// + /// Вариатор (бесступенчатая трансмиссия) + /// + CVT, + + /// + /// Робот (автоматизированная механика) + /// + Robot +} From 71025a0751905ed1b00a4a645696b85e7267b7b1 Mon Sep 17 00:00:00 2001 From: ahewbu Date: Wed, 17 Dec 2025 20:10:08 +0400 Subject: [PATCH 03/20] =?UTF-8?q?=D0=B4=D0=BE=D0=BF=D0=BE=D0=BB=D0=BD?= =?UTF-8?q?=D0=B8=D1=82=D0=B5=D0=BB=D1=8C=D0=BD=D1=8B=D0=B5=20=D0=BA=D0=BB?= =?UTF-8?q?=D0=B0=D1=81=D1=81=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CarRental.Domain/CarRental.Domain..csproj | 4 ++ .../CarRental.Domain/Models/CarModel.cs | 49 +++++++++++++++++ .../Models/ModelGeneration.cs | 55 +++++++++++++++++++ 3 files changed, 108 insertions(+) create mode 100644 CarRental/CarRental/CarRental.Domain/Models/CarModel.cs create mode 100644 CarRental/CarRental/CarRental.Domain/Models/ModelGeneration.cs diff --git a/CarRental/CarRental/CarRental.Domain/CarRental.Domain..csproj b/CarRental/CarRental/CarRental.Domain/CarRental.Domain..csproj index 7d6a53484..0786a09f7 100644 --- a/CarRental/CarRental/CarRental.Domain/CarRental.Domain..csproj +++ b/CarRental/CarRental/CarRental.Domain/CarRental.Domain..csproj @@ -7,4 +7,8 @@ enable + + + + diff --git a/CarRental/CarRental/CarRental.Domain/Models/CarModel.cs b/CarRental/CarRental/CarRental.Domain/Models/CarModel.cs new file mode 100644 index 000000000..26a81d011 --- /dev/null +++ b/CarRental/CarRental/CarRental.Domain/Models/CarModel.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CarRental.Domain_.Models; + +/// +/// Модель автомобиля (справочник) +/// +public class CarModel +{ + /// + /// Уникальный идентификатор модели + /// + public required int ModelId { get; set; } + + /// + /// Название модели (например, "Toyota Camry") + /// + public required string Name { get; set; } + + /// + /// Тип привода + /// + public required DriveType DriveType { get; set; } + + /// + /// Количество посадочных мест + /// + public required int SeatsCount { get; set; } + + /// + /// Тип кузова + /// + public required BodyType BodyType { get; set; } + + /// + /// Класс автомобиля + /// + public required CarClass CarClass { get; set; } + + /// + /// Коллекция поколений этой модели + /// + public ICollection Generations { get; set; } = new List(); +} + diff --git a/CarRental/CarRental/CarRental.Domain/Models/ModelGeneration.cs b/CarRental/CarRental/CarRental.Domain/Models/ModelGeneration.cs new file mode 100644 index 000000000..c7e7c1576 --- /dev/null +++ b/CarRental/CarRental/CarRental.Domain/Models/ModelGeneration.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.ConstrainedExecution; +using System.Text; +using System.Threading.Tasks; + +namespace CarRental.Domain_.Models; + +/// +/// Поколение модели (справочник) +/// Пример: Toyota Camry 2020, Toyota Camry 2022 +/// +public class ModelGeneration +{ + /// + /// Уникальный идентификатор поколения + /// + public required int GenerationId { get; set; } + + /// + /// Внешний ключ на модель + /// + public required int ModelId { get; set; } + + /// + /// Год выпуска + /// + public required int Year { get; set; } + + /// + /// Объем двигателя в литрах + /// + public required double EngineVolume { get; set; } + + /// + /// Тип коробки передач + /// + public required TransmissionType TransmissionType { get; set; } + + /// + /// Стоимость аренды в час + /// + public required decimal HourlyRate { get; set; } + + /// + /// Ссылка на модель (навигационное свойство) + /// + public CarModel? Model { get; set; } + + /// + /// Коллекция автомобилей этого поколения + /// + public ICollection Cars { get; set; } = new List(); +} From 3d845ee8c9a05b4cd12a8e7d386b5cf6acb3872a Mon Sep 17 00:00:00 2001 From: ahewbu Date: Wed, 24 Dec 2025 07:38:13 +0400 Subject: [PATCH 04/20] =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D1=82=D0=B5=D1=81=D1=82=D0=BE=D0=B2?= =?UTF-8?q?=20=D0=B8=20=D0=BD=D0=B5=D0=B4=D0=BE=D1=81=D1=82=D0=B0=D1=8E?= =?UTF-8?q?=D1=89=D0=B8=D1=85=20=D0=BA=D0=BB=D0=B0=D1=81=D1=81=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CarRental.Domain/Data/CarRentalFixture.cs | 197 ++++++++++++++++++ .../CarRental/CarRental.Domain/Models/Car.cs | 44 ++++ .../CarRental.Domain/Models/Customer.cs | 38 ++++ .../CarRental.Domain/Models/RentalContract.cs | 55 +++++ .../CarRental.Tests/CarRental.Tests.csproj | 27 +++ .../CarRental.Tests/CarRentalTests.cs | 142 +++++++++++++ CarRental/CarRental/CarRental.slnx | 1 + 7 files changed, 504 insertions(+) create mode 100644 CarRental/CarRental/CarRental.Domain/Data/CarRentalFixture.cs create mode 100644 CarRental/CarRental/CarRental.Domain/Models/Car.cs create mode 100644 CarRental/CarRental/CarRental.Domain/Models/Customer.cs create mode 100644 CarRental/CarRental/CarRental.Domain/Models/RentalContract.cs create mode 100644 CarRental/CarRental/CarRental.Tests/CarRental.Tests.csproj create mode 100644 CarRental/CarRental/CarRental.Tests/CarRentalTests.cs diff --git a/CarRental/CarRental/CarRental.Domain/Data/CarRentalFixture.cs b/CarRental/CarRental/CarRental.Domain/Data/CarRentalFixture.cs new file mode 100644 index 000000000..be9a0af60 --- /dev/null +++ b/CarRental/CarRental/CarRental.Domain/Data/CarRentalFixture.cs @@ -0,0 +1,197 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CarRental.Domain_.Data; + +using CarRental.Domain_.Models; +using global::CarRental.Domain_.Models; +using System.Runtime.ConstrainedExecution; + +/// +/// Fixture с тестовыми данными +/// +public class CarRentalFixture +{ + /// + /// Список всех моделей + /// + public List Models => + [ + new() + { + ModelId = 1, + Name = "Toyota Camry", + DriveType = DriveType.FrontWheelDrive, + SeatsCount = 5, + BodyType = BodyType.Sedan, + CarClass = CarClass.Middle + }, + new() + { + ModelId = 2, + Name = "BMW X5", + DriveType = DriveType.AllWheelDrive, + SeatsCount = 7, + BodyType = BodyType.SUV, + CarClass = CarClass.Premium + }, + new() + { + ModelId = 3, + Name = "Volkswagen Golf", + DriveType = DriveType.FrontWheelDrive, + SeatsCount = 5, + BodyType = BodyType.Hatchback, + CarClass = CarClass.Economy + }, + new() + { + ModelId = 4, + Name = "Audi A6", + DriveType = DriveType.AllWheelDrive, + SeatsCount = 5, + BodyType = BodyType.Sedan, + CarClass = CarClass.Premium + }, + new() + { + ModelId = 5, + Name = "Mercedes C-Class", + DriveType = DriveType.RearWheelDrive, + SeatsCount = 5, + BodyType = BodyType.Sedan, + CarClass = CarClass.Premium + } + ]; + + /// + /// Список всех поколений моделей + /// + public List Generations => + [ + // Toyota Camry + new() + { + GenerationId = 1, + ModelId = 1, + Year = 2020, + EngineVolume = 2.5, + TransmissionType = TransmissionType.Automatic, + HourlyRate = 50 + }, + new() + { + GenerationId = 2, + ModelId = 1, + Year = 2022, + EngineVolume = 2.5, + TransmissionType = TransmissionType.Automatic, + HourlyRate = 60 + }, + // BMW X5 + new() + { + GenerationId = 3, + ModelId = 2, + Year = 2019, + EngineVolume = 3.0, + TransmissionType = TransmissionType.Automatic, + HourlyRate = 120 + }, + new() + { + GenerationId = 4, + ModelId = 2, + Year = 2023, + EngineVolume = 3.0, + TransmissionType = TransmissionType.Automatic, + HourlyRate = 150 + }, + // VW Golf + new() + { + GenerationId = 5, + ModelId = 3, + Year = 2021, + EngineVolume = 1.5, + TransmissionType = TransmissionType.Manual, + HourlyRate = 30 + }, + // Audi A6 + new() + { + GenerationId = 6, + ModelId = 4, + Year = 2020, + EngineVolume = 2.0, + TransmissionType = TransmissionType.Automatic, + HourlyRate = 100 + }, + // Mercedes C-Class + new() + { + GenerationId = 7, + ModelId = 5, + Year = 2021, + EngineVolume = 2.0, + TransmissionType = TransmissionType.Automatic, + HourlyRate = 110 + } + ]; + + /// + /// Список всех автомобилей (экземпляров) + /// + public List Cars => + [ + new() { CarId = 1, GenerationId = 1, LicensePlate = "А001РС77", Color = "Белый" }, + new() { CarId = 2, GenerationId = 1, LicensePlate = "А002РС77", Color = "Черный" }, + new() { CarId = 3, GenerationId = 2, LicensePlate = "А003РС77", Color = "Серебристый" }, + new() { CarId = 4, GenerationId = 3, LicensePlate = "В001РС77", Color = "Черный" }, + new() { CarId = 5, GenerationId = 4, LicensePlate = "В002РС77", Color = "Белый" }, + new() { CarId = 6, GenerationId = 5, LicensePlate = "В003РС77", Color = "Синий" }, + new() { CarId = 7, GenerationId = 5, LicensePlate = "В004РС77", Color = "Красный" }, + new() { CarId = 8, GenerationId = 6, LicensePlate = "А004РС77", Color = "Серебристый" }, + new() { CarId = 9, GenerationId = 6, LicensePlate = "А005РС77", Color = "Черный" }, + new() { CarId = 10, GenerationId = 7, LicensePlate = "А006РС77", Color = "Серебристый" } + ]; + + /// + /// Список всех клиентов + /// + public List Customers => + [ + new() { CustomerId = 1, DriverLicenseNumber = "7701123456", FullName = "Иван Петров", DateOfBirth = new DateOnly(1990, 5, 15) }, + new() { CustomerId = 2, DriverLicenseNumber = "7701234567", FullName = "Мария Сидорова", DateOfBirth = new DateOnly(1985, 8, 22) }, + new() { CustomerId = 3, DriverLicenseNumber = "7701345678", FullName = "Алексей Смирнов", DateOfBirth = new DateOnly(1992, 3, 10) }, + new() { CustomerId = 4, DriverLicenseNumber = "7701456789", FullName = "Ольга Волкова", DateOfBirth = new DateOnly(1988, 11, 30) }, + new() { CustomerId = 5, DriverLicenseNumber = "7701567890", FullName = "Сергей Морозов", DateOfBirth = new DateOnly(1995, 6, 18) }, + new() { CustomerId = 6, DriverLicenseNumber = "7701678901", FullName = "Елена Николаева", DateOfBirth = new DateOnly(1989, 2, 25) }, + new() { CustomerId = 7, DriverLicenseNumber = "7701789012", FullName = "Дмитрий Соколов", DateOfBirth = new DateOnly(1987, 9, 12) }, + new() { CustomerId = 8, DriverLicenseNumber = "7701890123", FullName = "Анна Федорова", DateOfBirth = new DateOnly(1991, 4, 8) }, + new() { CustomerId = 9, DriverLicenseNumber = "7701901234", FullName = "Борис Козлов", DateOfBirth = new DateOnly(1993, 7, 20) }, + new() { CustomerId = 10, DriverLicenseNumber = "7702012345", FullName = "Валентина Романова", DateOfBirth = new DateOnly(1986, 12, 5) } + ]; + + /// + /// Список всех контрактов аренды + /// + public List Contracts => + [ + new() { ContractId = 1, CarId = 1, CustomerId = 1, IssuanceTime = new DateTime(2025, 1, 5, 10, 0, 0), DurationHours = 24, ReturnTime = new DateTime(2025, 1, 6, 10, 0, 0) }, + new() { ContractId = 2, CarId = 1, CustomerId = 1, IssuanceTime = new DateTime(2025, 1, 20, 14, 0, 0), DurationHours = 8, ReturnTime = new DateTime(2025, 1, 20, 22, 0, 0) }, + new() { ContractId = 3, CarId = 1, CustomerId = 2, IssuanceTime = new DateTime(2025, 1, 10, 9, 0, 0), DurationHours = 48, ReturnTime = new DateTime(2025, 1, 12, 9, 0, 0) }, + new() { ContractId = 4, CarId = 2, CustomerId = 3, IssuanceTime = new DateTime(2025, 1, 8, 12, 0, 0), DurationHours = 16, ReturnTime = new DateTime(2025, 1, 9, 4, 0, 0) }, + new() { ContractId = 5, CarId = 4, CustomerId = 4, IssuanceTime = new DateTime(2025, 1, 3, 11, 0, 0), DurationHours = 72, ReturnTime = new DateTime(2025, 1, 6, 11, 0, 0) }, + new() { ContractId = 6, CarId = 5, CustomerId = 5, IssuanceTime = new DateTime(2025, 1, 15, 8, 0, 0), DurationHours = 24, ReturnTime = new DateTime(2025, 1, 16, 8, 0, 0) }, + new() { ContractId = 7, CarId = 6, CustomerId = 6, IssuanceTime = new DateTime(2025, 1, 12, 10, 0, 0), DurationHours = 6, ReturnTime = new DateTime(2025, 1, 12, 16, 0, 0) }, + new() { ContractId = 8, CarId = 7, CustomerId = 7, IssuanceTime = new DateTime(2025, 1, 18, 15, 0, 0), DurationHours = 12, ReturnTime = new DateTime(2025, 1, 19, 3, 0, 0) }, + new() { ContractId = 9, CarId = 8, CustomerId = 8, IssuanceTime = new DateTime(2025, 1, 7, 13, 0, 0), DurationHours = 20, ReturnTime = new DateTime(2025, 1, 8, 9, 0, 0) }, + new() { ContractId = 10, CarId = 10, CustomerId = 9, IssuanceTime = new DateTime(2025, 1, 11, 9, 0, 0), DurationHours = 36, ReturnTime = new DateTime(2025, 1, 12, 21, 0, 0) }, + new() { ContractId = 11, CarId = 3, CustomerId = 10, IssuanceTime = new DateTime(2025, 1, 14, 12, 0, 0), DurationHours = 8, ReturnTime = new DateTime(2025, 1, 14, 20, 0, 0) }, + new() { ContractId = 12, CarId = 4, CustomerId = 6, IssuanceTime = new DateTime(2025, 1, 21, 10, 0, 0), DurationHours = 24, ReturnTime = new DateTime(2025, 1, 22, 10, 0, 0) } + ]; +} diff --git a/CarRental/CarRental/CarRental.Domain/Models/Car.cs b/CarRental/CarRental/CarRental.Domain/Models/Car.cs new file mode 100644 index 000000000..e3e73b557 --- /dev/null +++ b/CarRental/CarRental/CarRental.Domain/Models/Car.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CarRental.Domain_.Models; + +/// +/// Автомобиль в парке +/// +public class Car +{ + /// + /// Уникальный ID автомобиля в парке + /// + public required int CarId { get; set; } + + /// + /// Внешний ключ на поколение модели + /// + public required int GenerationId { get; set; } + + /// + /// Государственный номер + /// + public required string LicensePlate { get; set; } + + /// + /// Цвет кузова + /// + public required string Color { get; set; } + + /// + /// Ссылка на поколение модели этого автомобиля + /// + public ModelGeneration? Generation { get; set; } + + /// + /// История аренд этого автомобиля + /// + public ICollection RentalContracts { get; set; } = new List(); +} + diff --git a/CarRental/CarRental/CarRental.Domain/Models/Customer.cs b/CarRental/CarRental/CarRental.Domain/Models/Customer.cs new file mode 100644 index 000000000..e43503de9 --- /dev/null +++ b/CarRental/CarRental/CarRental.Domain/Models/Customer.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CarRental.Domain_.Models; + +/// +/// Клиент пункта проката +/// +public class Customer +{ + /// + /// Уникальный ID клиента + /// + public required int CustomerId { get; set; } + + /// + /// Номер водительского удостоверения (уникален) + /// + public required string DriverLicenseNumber { get; set; } + + /// + /// Полное имя клиента (ФИО) + /// + public required string FullName { get; set; } + + /// + /// Дата рождения + /// + public required DateOnly DateOfBirth { get; set; } + + /// + /// История аренд этого клиента + /// + public ICollection RentalContracts { get; set; } = new List(); +} diff --git a/CarRental/CarRental/CarRental.Domain/Models/RentalContract.cs b/CarRental/CarRental/CarRental.Domain/Models/RentalContract.cs new file mode 100644 index 000000000..8597a4d93 --- /dev/null +++ b/CarRental/CarRental/CarRental.Domain/Models/RentalContract.cs @@ -0,0 +1,55 @@ +using CarRental.Domain_.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CarRental.Domain_.Models; + +/// +/// Договор аренды (контракт) +/// Фиксирует факт выдачи автомобиля клиенту +/// +public class RentalContract +{ + /// + /// Уникальный ID контракта + /// + public required int ContractId { get; set; } + + /// + /// Внешний ключ на автомобиль + /// + public required int CarId { get; set; } + + /// + /// Внешний ключ на клиента + /// + public required int CustomerId { get; set; } + + /// + /// Дата и время выдачи автомобиля клиенту + /// + public required DateTime IssuanceTime { get; set; } + + /// + /// Длительность аренды в часах + /// + public required int DurationHours { get; set; } + + /// + /// Дата и время возврата (может быть null, если машина ещё в аренде) + /// + public DateTime? ReturnTime { get; set; } + + /// + /// Ссылка на арендуемый автомобиль + /// + public Car? Car { get; set; } + + /// + /// Ссылка на клиента, арендовавшего машину + /// + public Customer? Customer { get; set; } +} diff --git a/CarRental/CarRental/CarRental.Tests/CarRental.Tests.csproj b/CarRental/CarRental/CarRental.Tests/CarRental.Tests.csproj new file mode 100644 index 000000000..d842ec838 --- /dev/null +++ b/CarRental/CarRental/CarRental.Tests/CarRental.Tests.csproj @@ -0,0 +1,27 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + + + diff --git a/CarRental/CarRental/CarRental.Tests/CarRentalTests.cs b/CarRental/CarRental/CarRental.Tests/CarRentalTests.cs new file mode 100644 index 000000000..c936bb554 --- /dev/null +++ b/CarRental/CarRental/CarRental.Tests/CarRentalTests.cs @@ -0,0 +1,142 @@ +namespace CarRental.Tests; + +using CarRental.Domain_.Data; +using CarRental.Domain_.Models; +using CarRental.Domain_.Data; +using Xunit; + +/// +/// - +/// +/// Fixture +public class CarRentalTests(CarRentalFixture fixture) : IClassFixture +{ + /// + /// 1: , + /// , . + /// + [Fact] + public void GetCustomersForModel_ShouldReturnCustomersOrderedByName() + { + const string modelName = "Toyota Camry"; + const int expectedCount = 3; + var expectedNames = new List { " ", " ", " " }; + + var targetModel = fixture.Models.First(m => m.Name == modelName); + + var customersForModel = fixture.Contracts + .Where(c => fixture.Cars.First(car => car.CarId == c.CarId).GenerationId is 1 or 2) + .Select(c => fixture.Customers.First(cust => cust.CustomerId == c.CustomerId)) + .Distinct() + .OrderBy(c => c.FullName) + .ToList(); + + Assert.Equal(expectedCount, customersForModel.Count); + Assert.Equal(expectedNames, customersForModel.Select(c => c.FullName)); + } + + /// + /// 2: , . + /// + [Fact] + public void GetCarsInRental_ShouldReturnCarsWithoutReturnTime() + { + var contractWithoutReturn = new RentalContract + { + ContractId = 999, + CarId = 6, + CustomerId = 1, + IssuanceTime = DateTime.Now.AddHours(-5), + DurationHours = 24, + ReturnTime = null + }; + fixture.Contracts.Add(contractWithoutReturn); + + var carsInRental = fixture.Contracts + .Where(c => c.ReturnTime == null) + .Select(c => fixture.Cars.First(car => car.CarId == c.CarId)) + .Distinct() + .ToList(); + + Assert.NotEmpty(carsInRental); + Assert.Contains(carsInRental, c => c.CarId == 6); + + fixture.Contracts.Remove(contractWithoutReturn); + } + + /// + /// 3: 5 . + /// + [Fact] + public void GetTop5MostRentedCars_ShouldReturnTopCars() + { + var top5Cars = fixture.Contracts + .GroupBy(c => c.CarId) + .OrderByDescending(g => g.Count()) + .Take(5) + .Select(g => new + { + Car = fixture.Cars.First(c => c.CarId == g.Key), + RentalCount = g.Count() + }) + .ToList(); + + Assert.NotEmpty(top5Cars); + Assert.True(top5Cars.Count <= 5); + Assert.Contains(top5Cars, x => x.Car.CarId == 1); + } + + /// + /// 4: . + /// + [Fact] + public void GetRentalCountPerCar_ShouldReturnCorrectCounts() + { + var rentalCountPerCar = fixture.Contracts + .GroupBy(c => c.CarId) + .Select(g => new + { + Car = fixture.Cars.First(c => c.CarId == g.Key), + RentalCount = g.Count() + }) + .OrderBy(x => x.Car.CarId) + .ToList(); + + Assert.NotEmpty(rentalCountPerCar); + var car1Rentals = rentalCountPerCar.First(x => x.Car.CarId == 1); + Assert.Equal(3, car1Rentals.RentalCount); + + var car4Rentals = rentalCountPerCar.First(x => x.Car.CarId == 4); + Assert.Equal(2, car4Rentals.RentalCount); + } + + /// + /// 5: 5 . + /// + [Fact] + public void GetTop5CustomersByRentalCost_ShouldReturnTopCustomers() + { + var top5CustomersBySpent = fixture.Contracts + .GroupBy(c => c.CustomerId) + .Select(g => new + { + Customer = fixture.Customers.First(cust => cust.CustomerId == g.Key), + TotalSpent = g.Sum(c => + { + var car = fixture.Cars.First(car => car.CarId == c.CarId); + var generation = fixture.Generations.First(gen => gen.GenerationId == car.GenerationId); + return (decimal)(c.DurationHours * (double)generation.HourlyRate); + }) + }) + .OrderByDescending(x => x.TotalSpent) + .Take(5) + .ToList(); + + Assert.NotEmpty(top5CustomersBySpent); + Assert.True(top5CustomersBySpent.Count <= 5); + if (top5CustomersBySpent.Count > 1) + { + Assert.True(top5CustomersBySpent[0].TotalSpent >= top5CustomersBySpent[1].TotalSpent); + } + } +} diff --git a/CarRental/CarRental/CarRental.slnx b/CarRental/CarRental/CarRental.slnx index d053a8239..98e0684aa 100644 --- a/CarRental/CarRental/CarRental.slnx +++ b/CarRental/CarRental/CarRental.slnx @@ -1,4 +1,5 @@ + From 86b2ff130c2d205b108d4ae0490f4a7f3fd2bbe2 Mon Sep 17 00:00:00 2001 From: ahewbu Date: Wed, 24 Dec 2025 08:59:32 +0400 Subject: [PATCH 05/20] =?UTF-8?q?=D0=BD=D0=B5=D0=BA=D0=BE=D1=82=D0=BE?= =?UTF-8?q?=D1=80=D1=8B=D0=B5=20=D0=BF=D1=80=D0=B0=D0=B2=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CarRental.Domain/Data/CarRentalFixture.cs | 250 ++++++------------ .../CarRental.Tests/CarRentalTests.cs | 6 +- 2 files changed, 79 insertions(+), 177 deletions(-) diff --git a/CarRental/CarRental/CarRental.Domain/Data/CarRentalFixture.cs b/CarRental/CarRental/CarRental.Domain/Data/CarRentalFixture.cs index be9a0af60..ada973149 100644 --- a/CarRental/CarRental/CarRental.Domain/Data/CarRentalFixture.cs +++ b/CarRental/CarRental/CarRental.Domain/Data/CarRentalFixture.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.Contracts; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -13,185 +14,86 @@ namespace CarRental.Domain_.Data; /// /// Fixture с тестовыми данными /// +/// +/// Fixture с тестовыми данными для ЛР1 +/// public class CarRentalFixture { - /// - /// Список всех моделей - /// - public List Models => - [ - new() - { - ModelId = 1, - Name = "Toyota Camry", - DriveType = DriveType.FrontWheelDrive, - SeatsCount = 5, - BodyType = BodyType.Sedan, - CarClass = CarClass.Middle - }, - new() - { - ModelId = 2, - Name = "BMW X5", - DriveType = DriveType.AllWheelDrive, - SeatsCount = 7, - BodyType = BodyType.SUV, - CarClass = CarClass.Premium - }, - new() - { - ModelId = 3, - Name = "Volkswagen Golf", - DriveType = DriveType.FrontWheelDrive, - SeatsCount = 5, - BodyType = BodyType.Hatchback, - CarClass = CarClass.Economy - }, - new() - { - ModelId = 4, - Name = "Audi A6", - DriveType = DriveType.AllWheelDrive, - SeatsCount = 5, - BodyType = BodyType.Sedan, - CarClass = CarClass.Premium - }, - new() - { - ModelId = 5, - Name = "Mercedes C-Class", - DriveType = DriveType.RearWheelDrive, - SeatsCount = 5, - BodyType = BodyType.Sedan, - CarClass = CarClass.Premium - } - ]; + private readonly List _models; + private readonly List _generations; + private readonly List _cars; + private readonly List _customers; + private readonly List _contracts; - /// - /// Список всех поколений моделей - /// - public List Generations => - [ - // Toyota Camry - new() - { - GenerationId = 1, - ModelId = 1, - Year = 2020, - EngineVolume = 2.5, - TransmissionType = TransmissionType.Automatic, - HourlyRate = 50 - }, - new() + public CarRentalFixture() + { + _models = new List { - GenerationId = 2, - ModelId = 1, - Year = 2022, - EngineVolume = 2.5, - TransmissionType = TransmissionType.Automatic, - HourlyRate = 60 - }, - // BMW X5 - new() - { - GenerationId = 3, - ModelId = 2, - Year = 2019, - EngineVolume = 3.0, - TransmissionType = TransmissionType.Automatic, - HourlyRate = 120 - }, - new() - { - GenerationId = 4, - ModelId = 2, - Year = 2023, - EngineVolume = 3.0, - TransmissionType = TransmissionType.Automatic, - HourlyRate = 150 - }, - // VW Golf - new() - { - GenerationId = 5, - ModelId = 3, - Year = 2021, - EngineVolume = 1.5, - TransmissionType = TransmissionType.Manual, - HourlyRate = 30 - }, - // Audi A6 - new() - { - GenerationId = 6, - ModelId = 4, - Year = 2020, - EngineVolume = 2.0, - TransmissionType = TransmissionType.Automatic, - HourlyRate = 100 - }, - // Mercedes C-Class - new() + new() { ModelId = 1, Name = "Toyota Camry", DriveType = DriveType.FrontWheelDrive, SeatsCount = 5, BodyType = BodyType.Sedan, CarClass = CarClass.Middle }, + new() { ModelId = 2, Name = "BMW X5", DriveType = DriveType.AllWheelDrive, SeatsCount = 7, BodyType = BodyType.SUV, CarClass = CarClass.Premium }, + new() { ModelId = 3, Name = "Volkswagen Golf", DriveType = DriveType.FrontWheelDrive, SeatsCount = 5, BodyType = BodyType.Hatchback, CarClass = CarClass.Economy }, + new() { ModelId = 4, Name = "Audi A6", DriveType = DriveType.AllWheelDrive, SeatsCount = 5, BodyType = BodyType.Sedan, CarClass = CarClass.Premium }, + new() { ModelId = 5, Name = "Mercedes C-Class", DriveType = DriveType.RearWheelDrive, SeatsCount = 5, BodyType = BodyType.Sedan, CarClass = CarClass.Premium } + }; + + _generations = new List { - GenerationId = 7, - ModelId = 5, - Year = 2021, - EngineVolume = 2.0, - TransmissionType = TransmissionType.Automatic, - HourlyRate = 110 - } - ]; + new() { GenerationId = 1, ModelId = 1, Year = 2020, EngineVolume = 2.5, TransmissionType = TransmissionType.Automatic, HourlyRate = 50 }, + new() { GenerationId = 2, ModelId = 1, Year = 2022, EngineVolume = 2.5, TransmissionType = TransmissionType.Automatic, HourlyRate = 60 }, + new() { GenerationId = 3, ModelId = 2, Year = 2019, EngineVolume = 3.0, TransmissionType = TransmissionType.Automatic, HourlyRate = 120 }, + new() { GenerationId = 4, ModelId = 2, Year = 2023, EngineVolume = 3.0, TransmissionType = TransmissionType.Automatic, HourlyRate = 150 }, + new() { GenerationId = 5, ModelId = 3, Year = 2021, EngineVolume = 1.5, TransmissionType = TransmissionType.Manual, HourlyRate = 30 }, + new() { GenerationId = 6, ModelId = 4, Year = 2020, EngineVolume = 2.0, TransmissionType = TransmissionType.Automatic, HourlyRate = 100 }, + new() { GenerationId = 7, ModelId = 5, Year = 2021, EngineVolume = 2.0, TransmissionType = TransmissionType.Automatic, HourlyRate = 110 } + }; - /// - /// Список всех автомобилей (экземпляров) - /// - public List Cars => - [ - new() { CarId = 1, GenerationId = 1, LicensePlate = "А001РС77", Color = "Белый" }, - new() { CarId = 2, GenerationId = 1, LicensePlate = "А002РС77", Color = "Черный" }, - new() { CarId = 3, GenerationId = 2, LicensePlate = "А003РС77", Color = "Серебристый" }, - new() { CarId = 4, GenerationId = 3, LicensePlate = "В001РС77", Color = "Черный" }, - new() { CarId = 5, GenerationId = 4, LicensePlate = "В002РС77", Color = "Белый" }, - new() { CarId = 6, GenerationId = 5, LicensePlate = "В003РС77", Color = "Синий" }, - new() { CarId = 7, GenerationId = 5, LicensePlate = "В004РС77", Color = "Красный" }, - new() { CarId = 8, GenerationId = 6, LicensePlate = "А004РС77", Color = "Серебристый" }, - new() { CarId = 9, GenerationId = 6, LicensePlate = "А005РС77", Color = "Черный" }, - new() { CarId = 10, GenerationId = 7, LicensePlate = "А006РС77", Color = "Серебристый" } - ]; + _cars = new List + { + new() { CarId = 1, GenerationId = 1, LicensePlate = "А001РС77", Color = "Белый" }, + new() { CarId = 2, GenerationId = 1, LicensePlate = "А002РС77", Color = "Черный" }, + new() { CarId = 3, GenerationId = 2, LicensePlate = "А003РС77", Color = "Серебристый" }, + new() { CarId = 4, GenerationId = 3, LicensePlate = "В001РС77", Color = "Черный" }, + new() { CarId = 5, GenerationId = 4, LicensePlate = "В002РС77", Color = "Белый" }, + new() { CarId = 6, GenerationId = 5, LicensePlate = "В003РС77", Color = "Синий" }, + new() { CarId = 7, GenerationId = 5, LicensePlate = "В004РС77", Color = "Красный" }, + new() { CarId = 8, GenerationId = 6, LicensePlate = "А004РС77", Color = "Серебристый" }, + new() { CarId = 9, GenerationId = 6, LicensePlate = "А005РС77", Color = "Черный" }, + new() { CarId = 10, GenerationId = 7, LicensePlate = "А006РС77", Color = "Серебристый" } + }; - /// - /// Список всех клиентов - /// - public List Customers => - [ - new() { CustomerId = 1, DriverLicenseNumber = "7701123456", FullName = "Иван Петров", DateOfBirth = new DateOnly(1990, 5, 15) }, - new() { CustomerId = 2, DriverLicenseNumber = "7701234567", FullName = "Мария Сидорова", DateOfBirth = new DateOnly(1985, 8, 22) }, - new() { CustomerId = 3, DriverLicenseNumber = "7701345678", FullName = "Алексей Смирнов", DateOfBirth = new DateOnly(1992, 3, 10) }, - new() { CustomerId = 4, DriverLicenseNumber = "7701456789", FullName = "Ольга Волкова", DateOfBirth = new DateOnly(1988, 11, 30) }, - new() { CustomerId = 5, DriverLicenseNumber = "7701567890", FullName = "Сергей Морозов", DateOfBirth = new DateOnly(1995, 6, 18) }, - new() { CustomerId = 6, DriverLicenseNumber = "7701678901", FullName = "Елена Николаева", DateOfBirth = new DateOnly(1989, 2, 25) }, - new() { CustomerId = 7, DriverLicenseNumber = "7701789012", FullName = "Дмитрий Соколов", DateOfBirth = new DateOnly(1987, 9, 12) }, - new() { CustomerId = 8, DriverLicenseNumber = "7701890123", FullName = "Анна Федорова", DateOfBirth = new DateOnly(1991, 4, 8) }, - new() { CustomerId = 9, DriverLicenseNumber = "7701901234", FullName = "Борис Козлов", DateOfBirth = new DateOnly(1993, 7, 20) }, - new() { CustomerId = 10, DriverLicenseNumber = "7702012345", FullName = "Валентина Романова", DateOfBirth = new DateOnly(1986, 12, 5) } - ]; + _customers = new List + { + new() { CustomerId = 1, DriverLicenseNumber = "7701123456", FullName = "Иван Петров", DateOfBirth = new DateOnly(1990, 5, 15) }, + new() { CustomerId = 2, DriverLicenseNumber = "7701234567", FullName = "Мария Сидорова", DateOfBirth = new DateOnly(1985, 8, 22) }, + new() { CustomerId = 3, DriverLicenseNumber = "7701345678", FullName = "Алексей Смирнов", DateOfBirth = new DateOnly(1992, 3, 10) }, + new() { CustomerId = 4, DriverLicenseNumber = "7701456789", FullName = "Ольга Волкова", DateOfBirth = new DateOnly(1988, 11, 30) }, + new() { CustomerId = 5, DriverLicenseNumber = "7701567890", FullName = "Сергей Морозов", DateOfBirth = new DateOnly(1995, 6, 18) }, + new() { CustomerId = 6, DriverLicenseNumber = "7701678901", FullName = "Елена Николаева", DateOfBirth = new DateOnly(1989, 2, 25) }, + new() { CustomerId = 7, DriverLicenseNumber = "7701789012", FullName = "Дмитрий Соколов", DateOfBirth = new DateOnly(1987, 9, 12) }, + new() { CustomerId = 8, DriverLicenseNumber = "7701890123", FullName = "Анна Федорова", DateOfBirth = new DateOnly(1991, 4, 8) }, + new() { CustomerId = 9, DriverLicenseNumber = "7701901234", FullName = "Борис Козлов", DateOfBirth = new DateOnly(1993, 7, 20) }, + new() { CustomerId = 10,DriverLicenseNumber = "7702012345", FullName = "Валентина Романова", DateOfBirth = new DateOnly(1986, 12, 5) } + }; - /// - /// Список всех контрактов аренды - /// - public List Contracts => - [ - new() { ContractId = 1, CarId = 1, CustomerId = 1, IssuanceTime = new DateTime(2025, 1, 5, 10, 0, 0), DurationHours = 24, ReturnTime = new DateTime(2025, 1, 6, 10, 0, 0) }, - new() { ContractId = 2, CarId = 1, CustomerId = 1, IssuanceTime = new DateTime(2025, 1, 20, 14, 0, 0), DurationHours = 8, ReturnTime = new DateTime(2025, 1, 20, 22, 0, 0) }, - new() { ContractId = 3, CarId = 1, CustomerId = 2, IssuanceTime = new DateTime(2025, 1, 10, 9, 0, 0), DurationHours = 48, ReturnTime = new DateTime(2025, 1, 12, 9, 0, 0) }, - new() { ContractId = 4, CarId = 2, CustomerId = 3, IssuanceTime = new DateTime(2025, 1, 8, 12, 0, 0), DurationHours = 16, ReturnTime = new DateTime(2025, 1, 9, 4, 0, 0) }, - new() { ContractId = 5, CarId = 4, CustomerId = 4, IssuanceTime = new DateTime(2025, 1, 3, 11, 0, 0), DurationHours = 72, ReturnTime = new DateTime(2025, 1, 6, 11, 0, 0) }, - new() { ContractId = 6, CarId = 5, CustomerId = 5, IssuanceTime = new DateTime(2025, 1, 15, 8, 0, 0), DurationHours = 24, ReturnTime = new DateTime(2025, 1, 16, 8, 0, 0) }, - new() { ContractId = 7, CarId = 6, CustomerId = 6, IssuanceTime = new DateTime(2025, 1, 12, 10, 0, 0), DurationHours = 6, ReturnTime = new DateTime(2025, 1, 12, 16, 0, 0) }, - new() { ContractId = 8, CarId = 7, CustomerId = 7, IssuanceTime = new DateTime(2025, 1, 18, 15, 0, 0), DurationHours = 12, ReturnTime = new DateTime(2025, 1, 19, 3, 0, 0) }, - new() { ContractId = 9, CarId = 8, CustomerId = 8, IssuanceTime = new DateTime(2025, 1, 7, 13, 0, 0), DurationHours = 20, ReturnTime = new DateTime(2025, 1, 8, 9, 0, 0) }, - new() { ContractId = 10, CarId = 10, CustomerId = 9, IssuanceTime = new DateTime(2025, 1, 11, 9, 0, 0), DurationHours = 36, ReturnTime = new DateTime(2025, 1, 12, 21, 0, 0) }, - new() { ContractId = 11, CarId = 3, CustomerId = 10, IssuanceTime = new DateTime(2025, 1, 14, 12, 0, 0), DurationHours = 8, ReturnTime = new DateTime(2025, 1, 14, 20, 0, 0) }, - new() { ContractId = 12, CarId = 4, CustomerId = 6, IssuanceTime = new DateTime(2025, 1, 21, 10, 0, 0), DurationHours = 24, ReturnTime = new DateTime(2025, 1, 22, 10, 0, 0) } - ]; -} + _contracts = new List + { + new() { ContractId = 1, CarId = 1, CustomerId = 1, IssuanceTime = new DateTime(2025, 1, 5, 10, 0, 0), DurationHours = 24, ReturnTime = new DateTime(2025, 1, 6, 10, 0, 0) }, + new() { ContractId = 2, CarId = 1, CustomerId = 1, IssuanceTime = new DateTime(2025, 1, 20, 14, 0, 0), DurationHours = 8, ReturnTime = new DateTime(2025, 1, 20, 22, 0, 0) }, + new() { ContractId = 3, CarId = 1, CustomerId = 2, IssuanceTime = new DateTime(2025, 1, 10, 9, 0, 0), DurationHours = 48, ReturnTime = new DateTime(2025, 1, 12, 9, 0, 0) }, + new() { ContractId = 4, CarId = 2, CustomerId = 3, IssuanceTime = new DateTime(2025, 1, 8, 12, 0, 0), DurationHours = 16, ReturnTime = new DateTime(2025, 1, 9, 4, 0, 0) }, + new() { ContractId = 5, CarId = 4, CustomerId = 4, IssuanceTime = new DateTime(2025, 1, 3, 11, 0, 0), DurationHours = 72, ReturnTime = new DateTime(2025, 1, 6, 11, 0, 0) }, + new() { ContractId = 6, CarId = 5, CustomerId = 5, IssuanceTime = new DateTime(2025, 1, 15, 8, 0, 0), DurationHours = 24, ReturnTime = new DateTime(2025, 1, 16, 8, 0, 0) }, + new() { ContractId = 7, CarId = 6, CustomerId = 6, IssuanceTime = new DateTime(2025, 1, 12, 10, 0, 0), DurationHours = 6, ReturnTime = new DateTime(2025, 1, 12, 16, 0, 0) }, + new() { ContractId = 8, CarId = 7, CustomerId = 7, IssuanceTime = new DateTime(2025, 1, 18, 15, 0, 0), DurationHours = 12, ReturnTime = new DateTime(2025, 1, 19, 3, 0, 0) }, + new() { ContractId = 9, CarId = 8, CustomerId = 8, IssuanceTime = new DateTime(2025, 1, 7, 13, 0, 0), DurationHours = 20, ReturnTime = new DateTime(2025, 1, 8, 9, 0, 0) }, + new() { ContractId = 10, CarId = 10, CustomerId = 9, IssuanceTime = new DateTime(2025, 1, 11, 9, 0, 0), DurationHours = 36, ReturnTime = new DateTime(2025, 1, 12, 21, 0, 0) }, + new() { ContractId = 11, CarId = 3, CustomerId = 10, IssuanceTime = new DateTime(2025, 1, 14, 12, 0, 0), DurationHours = 8, ReturnTime = new DateTime(2025, 1, 14, 20, 0, 0) }, + new() { ContractId = 12, CarId = 4, CustomerId = 6, IssuanceTime = new DateTime(2025, 1, 21, 10, 0, 0), DurationHours = 24, ReturnTime = new DateTime(2025, 1, 22, 10, 0, 0) } + }; + } + public List Models => _models; + public List Generations => _generations; + public List Cars => _cars; + public List Customers => _customers; + public List Contracts => _contracts; +} \ No newline at end of file diff --git a/CarRental/CarRental/CarRental.Tests/CarRentalTests.cs b/CarRental/CarRental/CarRental.Tests/CarRentalTests.cs index c936bb554..5c30f099a 100644 --- a/CarRental/CarRental/CarRental.Tests/CarRentalTests.cs +++ b/CarRental/CarRental/CarRental.Tests/CarRentalTests.cs @@ -2,7 +2,6 @@ namespace CarRental.Tests; using CarRental.Domain_.Data; using CarRental.Domain_.Models; -using CarRental.Domain_.Data; using Xunit; /// @@ -19,8 +18,8 @@ public class CarRentalTests(CarRentalFixture fixture) : IClassFixture { " ", " ", " " }; + const int expectedCount = 4; + var expectedNames = new List { " ", " ", " ", " "}; var targetModel = fixture.Models.First(m => m.Name == modelName); @@ -140,3 +139,4 @@ public void GetTop5CustomersByRentalCost_ShouldReturnTopCustomers() } } } + From 96915a7b37f9bdf9a4922c652945f0320ece56e8 Mon Sep 17 00:00:00 2001 From: ahewbu Date: Wed, 24 Dec 2025 10:06:41 +0400 Subject: [PATCH 06/20] git action + README --- .../.github/workflows/dotnet-tests.yml | 29 ++++ README.md | 158 ++++-------------- 2 files changed, 65 insertions(+), 122 deletions(-) create mode 100644 CarRental/CarRental/.github/workflows/dotnet-tests.yml diff --git a/CarRental/CarRental/.github/workflows/dotnet-tests.yml b/CarRental/CarRental/.github/workflows/dotnet-tests.yml new file mode 100644 index 000000000..b298f7583 --- /dev/null +++ b/CarRental/CarRental/.github/workflows/dotnet-tests.yml @@ -0,0 +1,29 @@ +name: .NET Tests + +on: + push: + branches: [ main, lab_1 ] + pull_request: + branches: [ main ] + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.0.x + + - name: Restore dependencies + run: dotnet restore ./CarRental/CarRental.sln + + - name: Build + run: dotnet build ./CarRental/CarRental.sln --no-restore --configuration Release + + - name: Test + run: dotnet test ./CarRental/CarRental.Tests/CarRental.Tests.csproj --configuration Release --verbosity normal diff --git a/README.md b/README.md index 39c9a8443..f1c8c8b69 100644 --- a/README.md +++ b/README.md @@ -1,136 +1,50 @@ -# Разработка корпоративных приложений -[Таблица с успеваемостью](https://docs.google.com/spreadsheets/d/1JD6aiOG6r7GrA79oJncjgUHWtfeW4g_YZ9ayNgxb_w0/edit?usp=sharing) +# Лабораторная работа 1: Классы и unit-тесты -## Задание -### Цель -Реализация проекта сервисно-ориентированного приложения. +## Описание предметной области -### Задачи -* Реализация объектно-ориентированной модели данных, -* Изучение реализации серверных приложений на базе WebAPI/OpenAPI, -* Изучение работы с брокерами сообщений, -* Изучение паттернов проектирования, -* Изучение работы со средствами оркестрации на примере .NET Aspire, -* Повторение основ работы с системами контроля версий, -* Unit-тестирование. +Реализована объектная модель для пункта проката автомобилей со следующими сущностями: -### Лабораторные работы -
-1. «Классы» - Реализация объектной модели данных и unit-тестов -
-В рамках первой лабораторной работы необходимо подготовить структуру классов, описывающих предметную область, определяемую в задании. В каждом из заданий присутствует часть, связанная с обработкой данных, представленная в разделе «Unit-тесты». Данную часть необходимо реализовать в виде unit-тестов: подготовить тестовые данные, выполнить запрос с использованием LINQ, проверить результаты. +- **`CarModel`** – модель автомобиля (справочник): тип привода, класс, тип кузова, количество мест. +- **`ModelGeneration`** – поколение модели: год выпуска, объём двигателя, коробка передач, стоимость часа аренды. +- **`Car`** – физический экземпляр автомобиля: госномер, цвет, поколение модели. +- **`Customer`** – клиент: номер водительского удостоверения, ФИО, дата рождения. +- **`RentalContract`** – договор аренды: клиент, автомобиль, время выдачи, длительность в часах, время возврата. -Хранение данных на этом этапе допускается осуществлять в памяти в виде коллекций. -Необходимо включить **как минимум 10** экземпляров каждого класса в датасид. - -
-
-2. «Сервер» - Реализация серверного приложения с использованием REST API -
-Во второй лабораторной работе необходимо реализовать серверное приложение, которое должно: -- Осуществлять базовые CRUD-операции с реализованными в первой лабораторной сущностями -- Предоставлять результаты аналитических запросов (раздел «Unit-тесты» задания) +Используются перечисления: -Хранение данных на этом этапе допускается осуществлять в памяти в виде коллекций. -
-
-
-3. «ORM» - Реализация объектно-реляционной модели. Подключение к базе данных и настройка оркестрации -
-В третьей лабораторной работе хранение должно быть переделано c инмемори коллекций на базу данных. -Должны быть созданы миграции для создания таблиц в бд и их первоначального заполнения. -
-Также необходимо настроить оркестратор Aspire на запуск сервера и базы данных. -
-
-
-4. «Инфраструктура» - Реализация сервиса генерации данных и его интеграция с сервером -
-В четвертой лабораторной работе необходимо имплементировать сервис, который генерировал бы контракты. Контракты далее передаются в сервер и сохраняются в бд. -Сервис должен представлять из себя отдельное приложение без референсов к серверным проектам за исключением библиотеки с контрактами. -Отправка контрактов при помощи gRPC должна выполняться в потоковом виде. -При использовании брокеров сообщений, необходимо предусмотреть ретраи при подключении к брокеру. +- **`CarClass`**: Economy, Middle, Premium. +- **`TransmissionType`**: Manual, Automatic, CVT, Robot. +- **`DriveType`**: FrontWheelDrive, RearWheelDrive, AllWheelDrive. +- **`BodyType`**: Sedan, Hatchback, Coupe, SUV, Wagon, Pickup. -Также необходимо добавить в конфигурацию Aspire запуск генератора и (если того требует вариант) брокера сообщений. -
-
-
-5. «Клиент» - Интеграция клиентского приложения с оркестратором -
-В пятой лабораторной необходимо добавить в конфигурацию Aspire запуск клиентского приложения для написанного ранее сервера. Клиент создается в рамках курса "Веб разработка". -
-
+## Структура решения -## Задание. Общая часть -**Обязательно**: -* Реализация серверной части на [.NET 8](https://learn.microsoft.com/ru-ru/dotnet/core/whats-new/dotnet-8/overview). -* Реализация серверной части на [ASP.NET](https://dotnet.microsoft.com/ru-ru/apps/aspnet). -* Реализация unit-тестов с использованием [xUnit](https://xunit.net/?tabs=cs). -* Использование хранения данных в базе данных согласно варианту задания. -* Оркестрация проектов при помощи [.NET Aspire](https://learn.microsoft.com/ru-ru/dotnet/aspire/get-started/aspire-overview) -* Реализация сервиса генерации данных при помощи [Bogus](https://github.com/bchavez/Bogus) и его взаимодейсвие с сервером согласно варианту задания. -* Автоматизация тестирования на уровне репозитория через [GitHub Actions](https://docs.github.com/en/actions/learn-github-actions/understanding-github-actions). -* Создание минимальной документации к проекту: страница на GitHub с информацией о задании, скриншоты приложения и прочая информация. +- `CarRental.Domain` – доменная модель (классы и enum-ы). +- `CarRental.Domain/Data/CarRentalFixture.cs` – тестовые данные (модели, поколения, машины, клиенты, контракты). +- `CarRental.Tests` – проект с unit-тестами (xUnit). +- `.github/workflows/dotnet-tests.yml` – GitHub Actions для сборки и запуска тестов. -**Факультативно**: -* Реализация авторизации/аутентификации. -* Реализация atomic batch publishing/atomic batch consumption для брокеров, поддерживающих такой функционал. -* Реализация интеграционных тестов при помощи .NET Aspire. -* Реализация клиента на Blazor WASM. +## Выполненные задачи -Внимательно прочитайте [дискуссии](https://github.com/itsecd/enterprise-development/discussions/1) о том, как работает автоматическое распределение на ревью. -Сразу корректно называйте свои pr, чтобы они попали на ревью нужному преподавателю. +### 1. Модель данных +Создана полная объектная модель с использованием enum-типов и классов предметной области. -По итогу работы в семестре должна получиться следующая информационная система: -
-C4 диаграмма +### 2. Тестовые данные +Создан класс `CarRentalFixture` содержащий: +- 5 моделей автомобилей +- 7 поколений моделей +- 10 физических экземпляров автомобилей +- 10 клиентов +- 12 контрактов аренды -image1 +### 3. Реализованные тесты (xUnit) -
+1. **`GetCustomersForModel_ShouldReturnCustomersOrderedByName`** – клиенты, бравшие в аренду указанную модель, отсортированные по ФИО. +2. **`GetCarsInRental_ShouldReturnCarsWithoutReturnTime`** – автомобили, которые сейчас находятся в аренде (нет даты возврата). +3. **`GetTop5MostRentedCars_ShouldReturnTopCars`**` – топ‑5 автомобилей по количеству аренд. +4. **`GetRentalCountPerCar_ShouldReturnCorrectCounts`** – количество аренд для каждого автомобиля. +5. **`GetTop5CustomersByRentalCost_ShouldReturnTopCustomers`** – топ‑5 клиентов по сумме стоимости аренды. -## Варианты заданий -Номер варианта задания присваивается в начале семестра. Изменить его нельзя. Каждый вариант имеет уникальную комбинацию из предметной области, базы данных и технологии для общения сервиса генерации данных и сервера апи. +## Результат -[Список вариантов](https://docs.google.com/document/d/1Wc8AvsKS_1JptpsxHO-cwfAxz2ghxvQRQ0fy4el2ZOc/edit?usp=sharing) -[Список предметных областей](https://docs.google.com/document/d/15jWhXMwd2K8giFMKku_yrY_s2uQNEu4ugJXLYPvYJAE/edit?usp=sharing) - -## Схема сдачи - -На каждую из лабораторных работ необходимо сделать отдельный [Pull Request (PR)](https://docs.github.com/en/pull-requests). - -Общая схема: -1. Сделать форк данного репозитория -2. Выполнить задание -3. Сделать PR в данный репозиторий -4. Исправить замечания после code review -5. Получить approve -6. Прийти на занятие и защитить работу - -## Критерии оценивания - -Конкурентный принцип. -Так как задания в первой лабораторной будут повторяться между студентами, то выделяются следующие показатели для оценки: -1. Скорость разработки -2. Качество разработки -3. Полнота выполнения задания - -Быстрее делаете PR - у вас преимущество. -Быстрее получаете Approve - у вас преимущество. -Выполните нечто немного выходящее за рамки проекта - у вас преимущество. - -### Шкала оценивания - -- **3 балла** за качество кода, из них: - - 2 балла - базовая оценка - - 1 балл (но не более) можно получить за выполнение любого из следующих пунктов: - - Реализация факультативного функционала - - Выполнение работы раньше других: первые 5 человек из каждой группы, которые сделали PR и получили approve, получают дополнительный балл -- **3 балла** за защиту: при сдаче лабораторной работы вам задается 3 вопроса, за каждый правильный ответ - 1 балл - -У вас 2 попытки пройти ревью (первичное ревью, ревью по результатам исправления). Если замечания по итогу не исправлены, то снимается один балл за код лабораторной работы. - -## Вопросы и обратная связь по курсу - -Чтобы задать вопрос по лабораторной, воспользуйтесь [соотвествующим разделом дискуссий](https://github.com/itsecd/enterprise-development/discussions/categories/questions) или заведите [ишью](https://github.com/itsecd/enterprise-development/issues/new). -Если у вас появились идеи/пожелания/прочие полезные мысли по преподаваемой дисциплине, их можно оставить [здесь](https://github.com/itsecd/enterprise-development/discussions/categories/ideas). +Создана полная объектная модель предметной области с комплексными unit-тестами, покрывающими все требования задания. From e591a7a9b61d4ce477d0e9bceb73fc419cc58aaa Mon Sep 17 00:00:00 2001 From: ahewbu Date: Thu, 25 Dec 2025 13:05:12 +0400 Subject: [PATCH 07/20] =?UTF-8?q?=D0=BD=D0=B5=D0=BA=D0=BE=D1=82=D0=BE?= =?UTF-8?q?=D1=80=D1=8B=D0=B5=20=D0=BF=D1=80=D0=B0=D0=B2=D0=BA=D0=B8,=20?= =?UTF-8?q?=D1=87=D0=B0=D1=81=D1=82=D1=8C=201?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CarRental.Domain/Data/CarRentalFixture.cs | 55 ++++++++----------- .../CarRental.Domain/Models/BodyType.cs | 10 +--- .../CarRental/CarRental.Domain/Models/Car.cs | 8 +-- .../CarRental.Domain/Models/CarClass.cs | 31 +++++------ .../CarRental.Domain/Models/CarModel.cs | 8 +-- .../CarRental.Domain/Models/Customer.cs | 8 +-- .../CarRental.Domain/Models/DriveType.cs | 8 +-- .../Models/ModelGeneration.cs | 9 +-- .../CarRental.Domain/Models/RentalContract.cs | 5 -- .../Models/TransmissionType.cs | 10 +--- .../CarRental.Tests/CarRentalTests.cs | 39 ++++++------- 11 files changed, 65 insertions(+), 126 deletions(-) diff --git a/CarRental/CarRental/CarRental.Domain/Data/CarRentalFixture.cs b/CarRental/CarRental/CarRental.Domain/Data/CarRentalFixture.cs index ada973149..f83e83fef 100644 --- a/CarRental/CarRental/CarRental.Domain/Data/CarRentalFixture.cs +++ b/CarRental/CarRental/CarRental.Domain/Data/CarRentalFixture.cs @@ -1,43 +1,34 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics.Contracts; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using CarRental.Domain_.Models; +using System.Runtime.ConstrainedExecution; namespace CarRental.Domain_.Data; -using CarRental.Domain_.Models; -using global::CarRental.Domain_.Models; -using System.Runtime.ConstrainedExecution; /// /// Fixture с тестовыми данными /// -/// -/// Fixture с тестовыми данными для ЛР1 -/// public class CarRentalFixture { - private readonly List _models; - private readonly List _generations; - private readonly List _cars; - private readonly List _customers; - private readonly List _contracts; + public List _models; + public List _generations; + public List _cars; + public List _customers; + public List _contracts; public CarRentalFixture() { - _models = new List - { + _models = + [ + new() { ModelId = 1, Name = "Toyota Camry", DriveType = DriveType.FrontWheelDrive, SeatsCount = 5, BodyType = BodyType.Sedan, CarClass = CarClass.Middle }, new() { ModelId = 2, Name = "BMW X5", DriveType = DriveType.AllWheelDrive, SeatsCount = 7, BodyType = BodyType.SUV, CarClass = CarClass.Premium }, new() { ModelId = 3, Name = "Volkswagen Golf", DriveType = DriveType.FrontWheelDrive, SeatsCount = 5, BodyType = BodyType.Hatchback, CarClass = CarClass.Economy }, new() { ModelId = 4, Name = "Audi A6", DriveType = DriveType.AllWheelDrive, SeatsCount = 5, BodyType = BodyType.Sedan, CarClass = CarClass.Premium }, new() { ModelId = 5, Name = "Mercedes C-Class", DriveType = DriveType.RearWheelDrive, SeatsCount = 5, BodyType = BodyType.Sedan, CarClass = CarClass.Premium } - }; + ]; - _generations = new List - { + _generations = + [ new() { GenerationId = 1, ModelId = 1, Year = 2020, EngineVolume = 2.5, TransmissionType = TransmissionType.Automatic, HourlyRate = 50 }, new() { GenerationId = 2, ModelId = 1, Year = 2022, EngineVolume = 2.5, TransmissionType = TransmissionType.Automatic, HourlyRate = 60 }, new() { GenerationId = 3, ModelId = 2, Year = 2019, EngineVolume = 3.0, TransmissionType = TransmissionType.Automatic, HourlyRate = 120 }, @@ -45,10 +36,10 @@ public CarRentalFixture() new() { GenerationId = 5, ModelId = 3, Year = 2021, EngineVolume = 1.5, TransmissionType = TransmissionType.Manual, HourlyRate = 30 }, new() { GenerationId = 6, ModelId = 4, Year = 2020, EngineVolume = 2.0, TransmissionType = TransmissionType.Automatic, HourlyRate = 100 }, new() { GenerationId = 7, ModelId = 5, Year = 2021, EngineVolume = 2.0, TransmissionType = TransmissionType.Automatic, HourlyRate = 110 } - }; + ]; - _cars = new List - { + _cars = + [ new() { CarId = 1, GenerationId = 1, LicensePlate = "А001РС77", Color = "Белый" }, new() { CarId = 2, GenerationId = 1, LicensePlate = "А002РС77", Color = "Черный" }, new() { CarId = 3, GenerationId = 2, LicensePlate = "А003РС77", Color = "Серебристый" }, @@ -59,10 +50,10 @@ public CarRentalFixture() new() { CarId = 8, GenerationId = 6, LicensePlate = "А004РС77", Color = "Серебристый" }, new() { CarId = 9, GenerationId = 6, LicensePlate = "А005РС77", Color = "Черный" }, new() { CarId = 10, GenerationId = 7, LicensePlate = "А006РС77", Color = "Серебристый" } - }; + ]; - _customers = new List - { + _customers = + [ new() { CustomerId = 1, DriverLicenseNumber = "7701123456", FullName = "Иван Петров", DateOfBirth = new DateOnly(1990, 5, 15) }, new() { CustomerId = 2, DriverLicenseNumber = "7701234567", FullName = "Мария Сидорова", DateOfBirth = new DateOnly(1985, 8, 22) }, new() { CustomerId = 3, DriverLicenseNumber = "7701345678", FullName = "Алексей Смирнов", DateOfBirth = new DateOnly(1992, 3, 10) }, @@ -73,10 +64,10 @@ public CarRentalFixture() new() { CustomerId = 8, DriverLicenseNumber = "7701890123", FullName = "Анна Федорова", DateOfBirth = new DateOnly(1991, 4, 8) }, new() { CustomerId = 9, DriverLicenseNumber = "7701901234", FullName = "Борис Козлов", DateOfBirth = new DateOnly(1993, 7, 20) }, new() { CustomerId = 10,DriverLicenseNumber = "7702012345", FullName = "Валентина Романова", DateOfBirth = new DateOnly(1986, 12, 5) } - }; + ]; - _contracts = new List - { + _contracts = + [ new() { ContractId = 1, CarId = 1, CustomerId = 1, IssuanceTime = new DateTime(2025, 1, 5, 10, 0, 0), DurationHours = 24, ReturnTime = new DateTime(2025, 1, 6, 10, 0, 0) }, new() { ContractId = 2, CarId = 1, CustomerId = 1, IssuanceTime = new DateTime(2025, 1, 20, 14, 0, 0), DurationHours = 8, ReturnTime = new DateTime(2025, 1, 20, 22, 0, 0) }, new() { ContractId = 3, CarId = 1, CustomerId = 2, IssuanceTime = new DateTime(2025, 1, 10, 9, 0, 0), DurationHours = 48, ReturnTime = new DateTime(2025, 1, 12, 9, 0, 0) }, @@ -89,7 +80,7 @@ public CarRentalFixture() new() { ContractId = 10, CarId = 10, CustomerId = 9, IssuanceTime = new DateTime(2025, 1, 11, 9, 0, 0), DurationHours = 36, ReturnTime = new DateTime(2025, 1, 12, 21, 0, 0) }, new() { ContractId = 11, CarId = 3, CustomerId = 10, IssuanceTime = new DateTime(2025, 1, 14, 12, 0, 0), DurationHours = 8, ReturnTime = new DateTime(2025, 1, 14, 20, 0, 0) }, new() { ContractId = 12, CarId = 4, CustomerId = 6, IssuanceTime = new DateTime(2025, 1, 21, 10, 0, 0), DurationHours = 24, ReturnTime = new DateTime(2025, 1, 22, 10, 0, 0) } - }; + ]; } public List Models => _models; public List Generations => _generations; diff --git a/CarRental/CarRental/CarRental.Domain/Models/BodyType.cs b/CarRental/CarRental/CarRental.Domain/Models/BodyType.cs index dae8c928a..5c5a20719 100644 --- a/CarRental/CarRental/CarRental.Domain/Models/BodyType.cs +++ b/CarRental/CarRental/CarRental.Domain/Models/BodyType.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace CarRental.Domain_.Models; +namespace CarRental.Domain_.Models; /// /// Перечисление типов кузова @@ -29,7 +23,7 @@ public enum BodyType /// /// Внедорожник (SUV, повышенная проходимость) /// - SUV, + Suv, /// /// Универсал (седан с большим багажником) diff --git a/CarRental/CarRental/CarRental.Domain/Models/Car.cs b/CarRental/CarRental/CarRental.Domain/Models/Car.cs index e3e73b557..e8e1d7fb9 100644 --- a/CarRental/CarRental/CarRental.Domain/Models/Car.cs +++ b/CarRental/CarRental/CarRental.Domain/Models/Car.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace CarRental.Domain_.Models; +namespace CarRental.Domain_.Models; /// /// Автомобиль в парке diff --git a/CarRental/CarRental/CarRental.Domain/Models/CarClass.cs b/CarRental/CarRental/CarRental.Domain/Models/CarClass.cs index fe6f9baed..c531d830e 100644 --- a/CarRental/CarRental/CarRental.Domain/Models/CarClass.cs +++ b/CarRental/CarRental/CarRental.Domain/Models/CarClass.cs @@ -1,28 +1,23 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - + namespace CarRental.Domain_.Models; -/// -/// Перечисление классов автомобилей -/// +// +// Перечисление классов автомобилей +// public enum CarClass { - /// - /// Эконом класс (дешевле, простые машины) - /// + // + // Эконом класс (дешевле, простые машины) + // Economy, - /// - /// Средний класс (среднее качество и цена) - /// + // + // Средний класс (среднее качество и цена) + // Middle, - /// - /// Премиум класс (люксовые машины) - /// + // + // Премиум класс (люксовые машины) + // Premium } diff --git a/CarRental/CarRental/CarRental.Domain/Models/CarModel.cs b/CarRental/CarRental/CarRental.Domain/Models/CarModel.cs index 26a81d011..f03b171bd 100644 --- a/CarRental/CarRental/CarRental.Domain/Models/CarModel.cs +++ b/CarRental/CarRental/CarRental.Domain/Models/CarModel.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace CarRental.Domain_.Models; +namespace CarRental.Domain_.Models; /// /// Модель автомобиля (справочник) diff --git a/CarRental/CarRental/CarRental.Domain/Models/Customer.cs b/CarRental/CarRental/CarRental.Domain/Models/Customer.cs index e43503de9..83a4dcc08 100644 --- a/CarRental/CarRental/CarRental.Domain/Models/Customer.cs +++ b/CarRental/CarRental/CarRental.Domain/Models/Customer.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace CarRental.Domain_.Models; +namespace CarRental.Domain_.Models; /// /// Клиент пункта проката diff --git a/CarRental/CarRental/CarRental.Domain/Models/DriveType.cs b/CarRental/CarRental/CarRental.Domain/Models/DriveType.cs index 6170ea4be..104db6840 100644 --- a/CarRental/CarRental/CarRental.Domain/Models/DriveType.cs +++ b/CarRental/CarRental/CarRental.Domain/Models/DriveType.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace CarRental.Domain_.Models; +namespace CarRental.Domain_.Models; /// diff --git a/CarRental/CarRental/CarRental.Domain/Models/ModelGeneration.cs b/CarRental/CarRental/CarRental.Domain/Models/ModelGeneration.cs index c7e7c1576..2297abc5b 100644 --- a/CarRental/CarRental/CarRental.Domain/Models/ModelGeneration.cs +++ b/CarRental/CarRental/CarRental.Domain/Models/ModelGeneration.cs @@ -1,11 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.ConstrainedExecution; -using System.Text; -using System.Threading.Tasks; - -namespace CarRental.Domain_.Models; +namespace CarRental.Domain_.Models; /// /// Поколение модели (справочник) diff --git a/CarRental/CarRental/CarRental.Domain/Models/RentalContract.cs b/CarRental/CarRental/CarRental.Domain/Models/RentalContract.cs index 8597a4d93..747fd6d9c 100644 --- a/CarRental/CarRental/CarRental.Domain/Models/RentalContract.cs +++ b/CarRental/CarRental/CarRental.Domain/Models/RentalContract.cs @@ -1,9 +1,4 @@ using CarRental.Domain_.Models; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace CarRental.Domain_.Models; diff --git a/CarRental/CarRental/CarRental.Domain/Models/TransmissionType.cs b/CarRental/CarRental/CarRental.Domain/Models/TransmissionType.cs index b8eba41dc..9a55e7594 100644 --- a/CarRental/CarRental/CarRental.Domain/Models/TransmissionType.cs +++ b/CarRental/CarRental/CarRental.Domain/Models/TransmissionType.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace CarRental.Domain_.Models; +namespace CarRental.Domain_.Models; /// /// Перечисление типов коробки передач @@ -24,7 +18,7 @@ public enum TransmissionType /// /// Вариатор (бесступенчатая трансмиссия) /// - CVT, + Cvt, /// /// Робот (автоматизированная механика) diff --git a/CarRental/CarRental/CarRental.Tests/CarRentalTests.cs b/CarRental/CarRental/CarRental.Tests/CarRentalTests.cs index 5c30f099a..abc510ed5 100644 --- a/CarRental/CarRental/CarRental.Tests/CarRentalTests.cs +++ b/CarRental/CarRental/CarRental.Tests/CarRentalTests.cs @@ -1,25 +1,26 @@ -namespace CarRental.Tests; - using CarRental.Domain_.Data; using CarRental.Domain_.Models; using Xunit; -/// -/// - -/// -/// Fixture +namespace CarRental.Tests; + + +// +// Юнит-тесты для пункта проката автомобилей +// +// Fixture с тестовыми данными public class CarRentalTests(CarRentalFixture fixture) : IClassFixture { - /// - /// 1: , - /// , . - /// + // + // ТЕСТ 1: Вывести информацию обо всех клиентах, + // которые брали в аренду автомобили указанной модели, упорядочить по ФИО. + // [Fact] public void GetCustomersForModel_ShouldReturnCustomersOrderedByName() { const string modelName = "Toyota Camry"; const int expectedCount = 4; - var expectedNames = new List { " ", " ", " ", " "}; + var expectedNames = new List { "Алексей Смирнов", "Валентина Романова", "Иван Петров", "Мария Сидорова"}; var targetModel = fixture.Models.First(m => m.Name == modelName); @@ -34,9 +35,9 @@ public void GetCustomersForModel_ShouldReturnCustomersOrderedByName() Assert.Equal(expectedNames, customersForModel.Select(c => c.FullName)); } - /// - /// 2: , . - /// + // + // ТЕСТ 2: Вывести информацию об автомобилях, находящихся в аренде. + // [Fact] public void GetCarsInRental_ShouldReturnCarsWithoutReturnTime() { @@ -63,9 +64,9 @@ public void GetCarsInRental_ShouldReturnCarsWithoutReturnTime() fixture.Contracts.Remove(contractWithoutReturn); } - /// - /// 3: 5 . - /// + // + // ТЕСТ 3: Вывести топ 5 наиболее часто арендуемых автомобилей. + // [Fact] public void GetTop5MostRentedCars_ShouldReturnTopCars() { @@ -86,7 +87,7 @@ public void GetTop5MostRentedCars_ShouldReturnTopCars() } /// - /// 4: . + /// ТЕСТ 4: Для каждого автомобиля вывести число аренд. /// [Fact] public void GetRentalCountPerCar_ShouldReturnCorrectCounts() @@ -110,7 +111,7 @@ public void GetRentalCountPerCar_ShouldReturnCorrectCounts() } /// - /// 5: 5 . + /// ТЕСТ 5: Вывести топ 5 клиентов по сумме аренды. /// [Fact] public void GetTop5CustomersByRentalCost_ShouldReturnTopCustomers() From 17f2efab218df29793416c79ab81ce1efaccb3b4 Mon Sep 17 00:00:00 2001 From: ahewbu Date: Fri, 26 Dec 2025 16:07:18 +0400 Subject: [PATCH 08/20] =?UTF-8?q?=D0=BD=D0=B5=D0=BA=D0=BE=D1=82=D0=BE?= =?UTF-8?q?=D1=80=D1=8B=D0=B5=20=D0=BF=D1=80=D0=B0=D0=B2=D0=BA=D0=B8,=20?= =?UTF-8?q?=D1=87=D0=B0=D1=81=D1=82=D1=8C=202?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CarRental.Domain/Data/CarRentalFixture.cs | 33 +++++++++++++++++-- .../CarRental.Domain/Models/BodyType.cs | 12 +++---- .../CarRental/CarRental.Domain/Models/Car.cs | 8 ++--- .../CarRental.Domain/Models/CarModel.cs | 4 +-- .../Models/{Customer.cs => Clients.cs} | 8 ++--- .../Models/ModelGeneration.cs | 8 ++--- .../CarRental.Domain/Models/RentalContract.cs | 10 +++--- .../Models/TransmissionType.cs | 8 ++--- .../CarRental.Tests/CarRentalTests.cs | 26 +++++---------- 9 files changed, 69 insertions(+), 48 deletions(-) rename CarRental/CarRental/CarRental.Domain/Models/{Customer.cs => Clients.cs} (75%) diff --git a/CarRental/CarRental/CarRental.Domain/Data/CarRentalFixture.cs b/CarRental/CarRental/CarRental.Domain/Data/CarRentalFixture.cs index f83e83fef..985d3f1d2 100644 --- a/CarRental/CarRental/CarRental.Domain/Data/CarRentalFixture.cs +++ b/CarRental/CarRental/CarRental.Domain/Data/CarRentalFixture.cs @@ -12,7 +12,7 @@ public class CarRentalFixture public List _models; public List _generations; public List _cars; - public List _customers; + public List _customers; public List _contracts; public CarRentalFixture() @@ -85,6 +85,35 @@ public CarRentalFixture() public List Models => _models; public List Generations => _generations; public List Cars => _cars; - public List Customers => _customers; + public List Customers => _customers; public List Contracts => _contracts; + + public CarRentalFixture() + { + Customers.AddRange( + [ + new() { CustomerId = 1, FullName = "Иван Петров" }, + new() { CustomerId = 2, FullName = "Мария Сидорова" }, + new() { CustomerId = 3, FullName = "Алексей Смирнов" }, + new() { CustomerId = 4, FullName = "Валентина Романова" } + ]); + + Cars.AddRange( + [ + new() { CarId = 1, ModelId = 1, GenerationId = 1, LicensePlate = "A111AA", Color = "Белый" }, + new() { CarId = 2, ModelId = 1, GenerationId = 2, LicensePlate = "A222AA", Color = "Черный" }, + new() { CarId = 3, ModelId = 2, GenerationId = 3, LicensePlate = "B333BB", Color = "Серый" }, + new() { CarId = 4, ModelId = 3, GenerationId = 4, LicensePlate = "C444CC", Color = "Красный" }, + new() { CarId = 5, ModelId = 4, GenerationId = 5, LicensePlate = "D555DD", Color = "Синий" }, + new() { CarId = 6, ModelId = 5, GenerationId = 6, LicensePlate = "E666EE", Color = "Черный" } + ]); + + Contracts.AddRange( + [ + new() { ContractId = 1, CarId = 1, CustomerId = 1, IssuanceTime = new DateTime(2024, 1, 1, 10, 0, 0), DurationHours = 5, ReturnTime = new DateTime(2024, 1, 1, 15, 0, 0) }, + new() { ContractId = 2, CarId = 1, CustomerId = 2, IssuanceTime = new DateTime(2024, 2, 1, 9, 0, 0), DurationHours = 24, ReturnTime = new DateTime(2024, 2, 2, 9, 0, 0) }, + new() { ContractId = 3, CarId = 2, CustomerId = 3, IssuanceTime = new DateTime(2024, 3, 1, 12, 0, 0), DurationHours = 10, ReturnTime = new DateTime(2024, 3, 1, 22, 0, 0) }, + new() { ContractId = 999, CarId = 6, CustomerId = 1, IssuanceTime = new DateTime(2024, 4, 1, 10, 0, 0), DurationHours = 24, ReturnTime = null } + ]); + } } \ No newline at end of file diff --git a/CarRental/CarRental/CarRental.Domain/Models/BodyType.cs b/CarRental/CarRental/CarRental.Domain/Models/BodyType.cs index 5c5a20719..69ed0b0ff 100644 --- a/CarRental/CarRental/CarRental.Domain/Models/BodyType.cs +++ b/CarRental/CarRental/CarRental.Domain/Models/BodyType.cs @@ -8,30 +8,30 @@ public enum BodyType /// /// Седан (4-5 мест, закрытый) /// - Sedan, + Sedan = 0, /// /// Хэтчбек (компактный, спортивный) /// - Hatchback, + Hatchback = 1, /// /// Купе (спортивный, 2-3 места) /// - Coupe, + Coupe = 2, /// /// Внедорожник (SUV, повышенная проходимость) /// - Suv, + Suv = 3, /// /// Универсал (седан с большим багажником) /// - Wagon, + Wagon = 4, /// /// Пикап (с открытым кузовом сзади) /// - Pickup + Pickup = 5 } diff --git a/CarRental/CarRental/CarRental.Domain/Models/Car.cs b/CarRental/CarRental/CarRental.Domain/Models/Car.cs index e8e1d7fb9..05c7fd16e 100644 --- a/CarRental/CarRental/CarRental.Domain/Models/Car.cs +++ b/CarRental/CarRental/CarRental.Domain/Models/Car.cs @@ -8,12 +8,12 @@ public class Car /// /// Уникальный ID автомобиля в парке /// - public required int CarId { get; set; } + public required int Id { get; set; } /// /// Внешний ключ на поколение модели /// - public required int GenerationId { get; set; } + public required int ModelGenerationId { get; set; } /// /// Государственный номер @@ -28,11 +28,11 @@ public class Car /// /// Ссылка на поколение модели этого автомобиля /// - public ModelGeneration? Generation { get; set; } + public ModelGeneration? ModelGeneration { get; set; } /// /// История аренд этого автомобиля /// - public ICollection RentalContracts { get; set; } = new List(); + public List RentalContracts { get; set; } = new (); } diff --git a/CarRental/CarRental/CarRental.Domain/Models/CarModel.cs b/CarRental/CarRental/CarRental.Domain/Models/CarModel.cs index f03b171bd..7f81346b9 100644 --- a/CarRental/CarRental/CarRental.Domain/Models/CarModel.cs +++ b/CarRental/CarRental/CarRental.Domain/Models/CarModel.cs @@ -8,7 +8,7 @@ public class CarModel /// /// Уникальный идентификатор модели /// - public required int ModelId { get; set; } + public required int Id { get; set; } /// /// Название модели (например, "Toyota Camry") @@ -38,6 +38,6 @@ public class CarModel /// /// Коллекция поколений этой модели /// - public ICollection Generations { get; set; } = new List(); + public List Generations { get; set; } = new (); } diff --git a/CarRental/CarRental/CarRental.Domain/Models/Customer.cs b/CarRental/CarRental/CarRental.Domain/Models/Clients.cs similarity index 75% rename from CarRental/CarRental/CarRental.Domain/Models/Customer.cs rename to CarRental/CarRental/CarRental.Domain/Models/Clients.cs index 83a4dcc08..e3385bca1 100644 --- a/CarRental/CarRental/CarRental.Domain/Models/Customer.cs +++ b/CarRental/CarRental/CarRental.Domain/Models/Clients.cs @@ -3,17 +3,17 @@ /// /// Клиент пункта проката /// -public class Customer +public class Client { /// /// Уникальный ID клиента /// - public required int CustomerId { get; set; } + public required int Id { get; set; } /// /// Номер водительского удостоверения (уникален) /// - public required string DriverLicenseNumber { get; set; } + public required string LicenseNumber { get; set; } /// /// Полное имя клиента (ФИО) @@ -28,5 +28,5 @@ public class Customer /// /// История аренд этого клиента /// - public ICollection RentalContracts { get; set; } = new List(); + public List RentalContracts { get; set; } = new (); } diff --git a/CarRental/CarRental/CarRental.Domain/Models/ModelGeneration.cs b/CarRental/CarRental/CarRental.Domain/Models/ModelGeneration.cs index 2297abc5b..194effdb9 100644 --- a/CarRental/CarRental/CarRental.Domain/Models/ModelGeneration.cs +++ b/CarRental/CarRental/CarRental.Domain/Models/ModelGeneration.cs @@ -9,7 +9,7 @@ public class ModelGeneration /// /// Уникальный идентификатор поколения /// - public required int GenerationId { get; set; } + public required int Id { get; set; } /// /// Внешний ключ на модель @@ -29,12 +29,12 @@ public class ModelGeneration /// /// Тип коробки передач /// - public required TransmissionType TransmissionType { get; set; } + public required TransmissionType Transmission { get; set; } /// /// Стоимость аренды в час /// - public required decimal HourlyRate { get; set; } + public required decimal RentalPricePerHour { get; set; } /// /// Ссылка на модель (навигационное свойство) @@ -44,5 +44,5 @@ public class ModelGeneration /// /// Коллекция автомобилей этого поколения /// - public ICollection Cars { get; set; } = new List(); + public List Cars { get; set; } = new (); } diff --git a/CarRental/CarRental/CarRental.Domain/Models/RentalContract.cs b/CarRental/CarRental/CarRental.Domain/Models/RentalContract.cs index 747fd6d9c..81ed8bbf1 100644 --- a/CarRental/CarRental/CarRental.Domain/Models/RentalContract.cs +++ b/CarRental/CarRental/CarRental.Domain/Models/RentalContract.cs @@ -11,7 +11,7 @@ public class RentalContract /// /// Уникальный ID контракта /// - public required int ContractId { get; set; } + public required int Id { get; set; } /// /// Внешний ключ на автомобиль @@ -21,17 +21,17 @@ public class RentalContract /// /// Внешний ключ на клиента /// - public required int CustomerId { get; set; } + public required int ClientId { get; set; } /// /// Дата и время выдачи автомобиля клиенту /// - public required DateTime IssuanceTime { get; set; } + public required DateTime RentalDate { get; set; } /// /// Длительность аренды в часах /// - public required int DurationHours { get; set; } + public required int RentalHours { get; set; } /// /// Дата и время возврата (может быть null, если машина ещё в аренде) @@ -46,5 +46,5 @@ public class RentalContract /// /// Ссылка на клиента, арендовавшего машину /// - public Customer? Customer { get; set; } + public Client? Client { get; set; } } diff --git a/CarRental/CarRental/CarRental.Domain/Models/TransmissionType.cs b/CarRental/CarRental/CarRental.Domain/Models/TransmissionType.cs index 9a55e7594..ff1680a99 100644 --- a/CarRental/CarRental/CarRental.Domain/Models/TransmissionType.cs +++ b/CarRental/CarRental/CarRental.Domain/Models/TransmissionType.cs @@ -8,20 +8,20 @@ public enum TransmissionType /// /// Механическая коробка передач (МКПП) /// - Manual, + Manual = 0, /// /// Автоматическая коробка передач (АКПП) /// - Automatic, + Automatic = 1, /// /// Вариатор (бесступенчатая трансмиссия) /// - Cvt, + Cvt = 2, /// /// Робот (автоматизированная механика) /// - Robot + Robot = 3 } diff --git a/CarRental/CarRental/CarRental.Tests/CarRentalTests.cs b/CarRental/CarRental/CarRental.Tests/CarRentalTests.cs index abc510ed5..17404ae52 100644 --- a/CarRental/CarRental/CarRental.Tests/CarRentalTests.cs +++ b/CarRental/CarRental/CarRental.Tests/CarRentalTests.cs @@ -22,11 +22,15 @@ public void GetCustomersForModel_ShouldReturnCustomersOrderedByName() const int expectedCount = 4; var expectedNames = new List { "Алексей Смирнов", "Валентина Романова", "Иван Петров", "Мария Сидорова"}; - var targetModel = fixture.Models.First(m => m.Name == modelName); - - var customersForModel = fixture.Contracts - .Where(c => fixture.Cars.First(car => car.CarId == c.CarId).GenerationId is 1 or 2) - .Select(c => fixture.Customers.First(cust => cust.CustomerId == c.CustomerId)) + //var targetModel = fixture.Models.First(m => m.Name == modelName); + + var customersForModel = + (from contract in fixture.Contracts + join car in fixture.Cars on contract.CarId equals car.CarId + join model in fixture.Models on car.ModelId equals model.ModelId + join customer in fixture.Customers on contract.CustomerId equals customer.CustomerId + where model.Name == modelName && car.GenerationId is 1 or 2 + select customer) .Distinct() .OrderBy(c => c.FullName) .ToList(); @@ -41,17 +45,6 @@ public void GetCustomersForModel_ShouldReturnCustomersOrderedByName() [Fact] public void GetCarsInRental_ShouldReturnCarsWithoutReturnTime() { - var contractWithoutReturn = new RentalContract - { - ContractId = 999, - CarId = 6, - CustomerId = 1, - IssuanceTime = DateTime.Now.AddHours(-5), - DurationHours = 24, - ReturnTime = null - }; - fixture.Contracts.Add(contractWithoutReturn); - var carsInRental = fixture.Contracts .Where(c => c.ReturnTime == null) .Select(c => fixture.Cars.First(car => car.CarId == c.CarId)) @@ -61,7 +54,6 @@ public void GetCarsInRental_ShouldReturnCarsWithoutReturnTime() Assert.NotEmpty(carsInRental); Assert.Contains(carsInRental, c => c.CarId == 6); - fixture.Contracts.Remove(contractWithoutReturn); } // From aa1837f88d6d2d2194da8c6bc7c172c171310baa Mon Sep 17 00:00:00 2001 From: ahewbu Date: Fri, 13 Feb 2026 18:55:43 +0400 Subject: [PATCH 09/20] =?UTF-8?q?=D1=83=D0=BF=D1=80=D0=BE=D1=89=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D0=BC=D0=BE=D0=B4=D0=B5=D0=BB=D0=B5=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CarRental.Domain/Data/CarRentalFixture.cs | 14 +++---- .../CarRental.Domain/Models/BodyType.cs | 37 ------------------- .../CarRental/CarRental.Domain/Models/Car.cs | 9 +---- .../CarRental.Domain/Models/CarClass.cs | 23 ------------ .../CarRental.Domain/Models/CarModel.cs | 13 ++----- .../Models/{Clients.cs => Client.cs} | 9 +---- .../CarRental.Domain/Models/DriveType.cs | 23 ------------ .../Models/ModelGeneration.cs | 10 +---- .../Models/{RentalContract.cs => Rental.cs} | 10 ++--- .../Models/TransmissionType.cs | 27 -------------- 10 files changed, 22 insertions(+), 153 deletions(-) delete mode 100644 CarRental/CarRental/CarRental.Domain/Models/BodyType.cs delete mode 100644 CarRental/CarRental/CarRental.Domain/Models/CarClass.cs rename CarRental/CarRental/CarRental.Domain/Models/{Clients.cs => Client.cs} (69%) delete mode 100644 CarRental/CarRental/CarRental.Domain/Models/DriveType.cs rename CarRental/CarRental/CarRental.Domain/Models/{RentalContract.cs => Rental.cs} (86%) delete mode 100644 CarRental/CarRental/CarRental.Domain/Models/TransmissionType.cs diff --git a/CarRental/CarRental/CarRental.Domain/Data/CarRentalFixture.cs b/CarRental/CarRental/CarRental.Domain/Data/CarRentalFixture.cs index 985d3f1d2..f72d5b9a1 100644 --- a/CarRental/CarRental/CarRental.Domain/Data/CarRentalFixture.cs +++ b/CarRental/CarRental/CarRental.Domain/Data/CarRentalFixture.cs @@ -13,18 +13,18 @@ public class CarRentalFixture public List _generations; public List _cars; public List _customers; - public List _contracts; + public List _contracts; public CarRentalFixture() { _models = [ - new() { ModelId = 1, Name = "Toyota Camry", DriveType = DriveType.FrontWheelDrive, SeatsCount = 5, BodyType = BodyType.Sedan, CarClass = CarClass.Middle }, - new() { ModelId = 2, Name = "BMW X5", DriveType = DriveType.AllWheelDrive, SeatsCount = 7, BodyType = BodyType.SUV, CarClass = CarClass.Premium }, - new() { ModelId = 3, Name = "Volkswagen Golf", DriveType = DriveType.FrontWheelDrive, SeatsCount = 5, BodyType = BodyType.Hatchback, CarClass = CarClass.Economy }, - new() { ModelId = 4, Name = "Audi A6", DriveType = DriveType.AllWheelDrive, SeatsCount = 5, BodyType = BodyType.Sedan, CarClass = CarClass.Premium }, - new() { ModelId = 5, Name = "Mercedes C-Class", DriveType = DriveType.RearWheelDrive, SeatsCount = 5, BodyType = BodyType.Sedan, CarClass = CarClass.Premium } + new() { ModelId = 1, Name = "Toyota Camry", DriveType = DriveType.FrontWheelDrive, SeatsCount = 5, BodyType = BodyType.Sedan, Class = CarClass.Middle }, + new() { ModelId = 2, Name = "BMW X5", DriveType = DriveType.AllWheelDrive, SeatsCount = 7, BodyType = BodyType.SUV, Class = CarClass.Premium }, + new() { ModelId = 3, Name = "Volkswagen Golf", DriveType = DriveType.FrontWheelDrive, SeatsCount = 5, BodyType = BodyType.Hatchback, Class = CarClass.Economy }, + new() { ModelId = 4, Name = "Audi A6", DriveType = DriveType.AllWheelDrive, SeatsCount = 5, BodyType = BodyType.Sedan, Class = CarClass.Premium }, + new() { ModelId = 5, Name = "Mercedes C-Class", DriveType = DriveType.RearWheelDrive, SeatsCount = 5, BodyType = BodyType.Sedan, Class = CarClass.Premium } ]; _generations = @@ -86,7 +86,7 @@ public CarRentalFixture() public List Generations => _generations; public List Cars => _cars; public List Customers => _customers; - public List Contracts => _contracts; + public List Contracts => _contracts; public CarRentalFixture() { diff --git a/CarRental/CarRental/CarRental.Domain/Models/BodyType.cs b/CarRental/CarRental/CarRental.Domain/Models/BodyType.cs deleted file mode 100644 index 69ed0b0ff..000000000 --- a/CarRental/CarRental/CarRental.Domain/Models/BodyType.cs +++ /dev/null @@ -1,37 +0,0 @@ -namespace CarRental.Domain_.Models; - -/// -/// Перечисление типов кузова -/// -public enum BodyType -{ - /// - /// Седан (4-5 мест, закрытый) - /// - Sedan = 0, - - /// - /// Хэтчбек (компактный, спортивный) - /// - Hatchback = 1, - - /// - /// Купе (спортивный, 2-3 места) - /// - Coupe = 2, - - /// - /// Внедорожник (SUV, повышенная проходимость) - /// - Suv = 3, - - /// - /// Универсал (седан с большим багажником) - /// - Wagon = 4, - - /// - /// Пикап (с открытым кузовом сзади) - /// - Pickup = 5 -} diff --git a/CarRental/CarRental/CarRental.Domain/Models/Car.cs b/CarRental/CarRental/CarRental.Domain/Models/Car.cs index 05c7fd16e..d0f240e6f 100644 --- a/CarRental/CarRental/CarRental.Domain/Models/Car.cs +++ b/CarRental/CarRental/CarRental.Domain/Models/Car.cs @@ -8,7 +8,7 @@ public class Car /// /// Уникальный ID автомобиля в парке /// - public required int Id { get; set; } + public int Id { get; set; } /// /// Внешний ключ на поколение модели @@ -28,11 +28,6 @@ public class Car /// /// Ссылка на поколение модели этого автомобиля /// - public ModelGeneration? ModelGeneration { get; set; } - - /// - /// История аренд этого автомобиля - /// - public List RentalContracts { get; set; } = new (); + public required ModelGeneration ModelGeneration { get; set; } } diff --git a/CarRental/CarRental/CarRental.Domain/Models/CarClass.cs b/CarRental/CarRental/CarRental.Domain/Models/CarClass.cs deleted file mode 100644 index c531d830e..000000000 --- a/CarRental/CarRental/CarRental.Domain/Models/CarClass.cs +++ /dev/null @@ -1,23 +0,0 @@ - -namespace CarRental.Domain_.Models; - -// -// Перечисление классов автомобилей -// -public enum CarClass -{ - // - // Эконом класс (дешевле, простые машины) - // - Economy, - - // - // Средний класс (среднее качество и цена) - // - Middle, - - // - // Премиум класс (люксовые машины) - // - Premium -} diff --git a/CarRental/CarRental/CarRental.Domain/Models/CarModel.cs b/CarRental/CarRental/CarRental.Domain/Models/CarModel.cs index 7f81346b9..9900f39a4 100644 --- a/CarRental/CarRental/CarRental.Domain/Models/CarModel.cs +++ b/CarRental/CarRental/CarRental.Domain/Models/CarModel.cs @@ -8,7 +8,7 @@ public class CarModel /// /// Уникальный идентификатор модели /// - public required int Id { get; set; } + public int Id { get; set; } /// /// Название модели (например, "Toyota Camry") @@ -18,7 +18,7 @@ public class CarModel /// /// Тип привода /// - public required DriveType DriveType { get; set; } + public required string DriveType { get; set; } /// /// Количество посадочных мест @@ -28,16 +28,11 @@ public class CarModel /// /// Тип кузова /// - public required BodyType BodyType { get; set; } + public required string BodyType { get; set; } /// /// Класс автомобиля /// - public required CarClass CarClass { get; set; } - - /// - /// Коллекция поколений этой модели - /// - public List Generations { get; set; } = new (); + public required string Class { get; set; } } diff --git a/CarRental/CarRental/CarRental.Domain/Models/Clients.cs b/CarRental/CarRental/CarRental.Domain/Models/Client.cs similarity index 69% rename from CarRental/CarRental/CarRental.Domain/Models/Clients.cs rename to CarRental/CarRental/CarRental.Domain/Models/Client.cs index e3385bca1..d89e6f7d8 100644 --- a/CarRental/CarRental/CarRental.Domain/Models/Clients.cs +++ b/CarRental/CarRental/CarRental.Domain/Models/Client.cs @@ -8,7 +8,7 @@ public class Client /// /// Уникальный ID клиента /// - public required int Id { get; set; } + public int Id { get; set; } /// /// Номер водительского удостоверения (уникален) @@ -23,10 +23,5 @@ public class Client /// /// Дата рождения /// - public required DateOnly DateOfBirth { get; set; } - - /// - /// История аренд этого клиента - /// - public List RentalContracts { get; set; } = new (); + public required DateOnly BirthDate { get; set; } } diff --git a/CarRental/CarRental/CarRental.Domain/Models/DriveType.cs b/CarRental/CarRental/CarRental.Domain/Models/DriveType.cs deleted file mode 100644 index 104db6840..000000000 --- a/CarRental/CarRental/CarRental.Domain/Models/DriveType.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace CarRental.Domain_.Models; - - -/// -/// Перечисление типов привода -/// -public enum DriveType -{ - /// - /// Передний привод (FWD) - /// - FrontWheelDrive, - - /// - /// Задний привод (RWD) - /// - RearWheelDrive, - - /// - /// Полный привод (AWD) - /// - AllWheelDrive -} diff --git a/CarRental/CarRental/CarRental.Domain/Models/ModelGeneration.cs b/CarRental/CarRental/CarRental.Domain/Models/ModelGeneration.cs index 194effdb9..0a87d7683 100644 --- a/CarRental/CarRental/CarRental.Domain/Models/ModelGeneration.cs +++ b/CarRental/CarRental/CarRental.Domain/Models/ModelGeneration.cs @@ -2,7 +2,6 @@ /// /// Поколение модели (справочник) -/// Пример: Toyota Camry 2020, Toyota Camry 2022 /// public class ModelGeneration { @@ -29,7 +28,7 @@ public class ModelGeneration /// /// Тип коробки передач /// - public required TransmissionType Transmission { get; set; } + public required string Transmission { get; set; } /// /// Стоимость аренды в час @@ -39,10 +38,5 @@ public class ModelGeneration /// /// Ссылка на модель (навигационное свойство) /// - public CarModel? Model { get; set; } - - /// - /// Коллекция автомобилей этого поколения - /// - public List Cars { get; set; } = new (); + public required CarModel Model { get; set; } } diff --git a/CarRental/CarRental/CarRental.Domain/Models/RentalContract.cs b/CarRental/CarRental/CarRental.Domain/Models/Rental.cs similarity index 86% rename from CarRental/CarRental/CarRental.Domain/Models/RentalContract.cs rename to CarRental/CarRental/CarRental.Domain/Models/Rental.cs index 81ed8bbf1..91b87782e 100644 --- a/CarRental/CarRental/CarRental.Domain/Models/RentalContract.cs +++ b/CarRental/CarRental/CarRental.Domain/Models/Rental.cs @@ -6,12 +6,12 @@ namespace CarRental.Domain_.Models; /// Договор аренды (контракт) /// Фиксирует факт выдачи автомобиля клиенту /// -public class RentalContract +public class Rental { /// /// Уникальный ID контракта /// - public required int Id { get; set; } + public int Id { get; set; } /// /// Внешний ключ на автомобиль @@ -36,15 +36,15 @@ public class RentalContract /// /// Дата и время возврата (может быть null, если машина ещё в аренде) /// - public DateTime? ReturnTime { get; set; } + public required DateTime ReturnTime { get; set; } /// /// Ссылка на арендуемый автомобиль /// - public Car? Car { get; set; } + public required Car Car { get; set; } /// /// Ссылка на клиента, арендовавшего машину /// - public Client? Client { get; set; } + public required Client Client { get; set; } } diff --git a/CarRental/CarRental/CarRental.Domain/Models/TransmissionType.cs b/CarRental/CarRental/CarRental.Domain/Models/TransmissionType.cs deleted file mode 100644 index ff1680a99..000000000 --- a/CarRental/CarRental/CarRental.Domain/Models/TransmissionType.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace CarRental.Domain_.Models; - -/// -/// Перечисление типов коробки передач -/// -public enum TransmissionType -{ - /// - /// Механическая коробка передач (МКПП) - /// - Manual = 0, - - /// - /// Автоматическая коробка передач (АКПП) - /// - Automatic = 1, - - /// - /// Вариатор (бесступенчатая трансмиссия) - /// - Cvt = 2, - - /// - /// Робот (автоматизированная механика) - /// - Robot = 3 -} From 4b2cf5cb56e7892c1ce971404b18a853c3222209 Mon Sep 17 00:00:00 2001 From: ahewbu Date: Fri, 13 Feb 2026 20:19:25 +0400 Subject: [PATCH 10/20] =?UTF-8?q?=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=BD=D1=8B=D0=B5=20=D1=82=D0=B5=D1=81=D1=82?= =?UTF-8?q?=D0=BE=D0=B2=D1=8B=D0=B5=20=D0=B4=D0=B0=D0=BD=D0=BD=D1=8B=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CarRental.Domain/Data/CarRentalFixture.cs | 181 +++++++++--------- .../CarRental.Domain/Models/Rental.cs | 5 - CarRental/CarRental/CarRental.slnx | 1 - 3 files changed, 91 insertions(+), 96 deletions(-) diff --git a/CarRental/CarRental/CarRental.Domain/Data/CarRentalFixture.cs b/CarRental/CarRental/CarRental.Domain/Data/CarRentalFixture.cs index f72d5b9a1..f11598f32 100644 --- a/CarRental/CarRental/CarRental.Domain/Data/CarRentalFixture.cs +++ b/CarRental/CarRental/CarRental.Domain/Data/CarRentalFixture.cs @@ -1,5 +1,4 @@ using CarRental.Domain_.Models; -using System.Runtime.ConstrainedExecution; namespace CarRental.Domain_.Data; @@ -9,111 +8,113 @@ namespace CarRental.Domain_.Data; /// public class CarRentalFixture { - public List _models; - public List _generations; - public List _cars; - public List _customers; - public List _contracts; + public List CarModels { get; } + public List ModelGenerations { get; } + public List Cars { get; } + public List Clients { get; } + public List Rentals { get; } public CarRentalFixture() { - _models = + CarModels = [ - - new() { ModelId = 1, Name = "Toyota Camry", DriveType = DriveType.FrontWheelDrive, SeatsCount = 5, BodyType = BodyType.Sedan, Class = CarClass.Middle }, - new() { ModelId = 2, Name = "BMW X5", DriveType = DriveType.AllWheelDrive, SeatsCount = 7, BodyType = BodyType.SUV, Class = CarClass.Premium }, - new() { ModelId = 3, Name = "Volkswagen Golf", DriveType = DriveType.FrontWheelDrive, SeatsCount = 5, BodyType = BodyType.Hatchback, Class = CarClass.Economy }, - new() { ModelId = 4, Name = "Audi A6", DriveType = DriveType.AllWheelDrive, SeatsCount = 5, BodyType = BodyType.Sedan, Class = CarClass.Premium }, - new() { ModelId = 5, Name = "Mercedes C-Class", DriveType = DriveType.RearWheelDrive, SeatsCount = 5, BodyType = BodyType.Sedan, Class = CarClass.Premium } + new() { Id = 1, Name = "BMW 3 Series", DriveType = "RWD", SeatsCount = 5, BodyType = "Sedan", Class = "Premium" }, + new() { Id = 2, Name = "Ford Mustang", DriveType = "RWD", SeatsCount = 4, BodyType = "Coupe", Class = "Sports" }, + new() { Id = 3, Name = "Honda Civic", DriveType = "FWD", SeatsCount = 5, BodyType = "Sedan", Class = "Compact" }, + new() { Id = 4, Name = "Jeep Wrangler", DriveType = "4WD", SeatsCount = 5, BodyType = "SUV", Class = "Off-road" }, + new() { Id = 5, Name = "Porsche 911", DriveType = "RWD", SeatsCount = 4, BodyType = "Coupe", Class = "Luxury" }, + new() { Id = 6, Name = "Chevrolet Tahoe", DriveType = "AWD", SeatsCount = 8, BodyType = "SUV", Class = "Full-size" }, + new() { Id = 7, Name = "Lada Vesta", DriveType = "FWD", SeatsCount = 5, BodyType = "Sedan", Class = "Economy" }, + new() { Id = 8, Name = "Subaru Outback", DriveType = "AWD", SeatsCount = 5, BodyType = "SUV", Class = "Mid-size" }, + new() { Id = 9, Name = "GAZ Gazelle Next", DriveType = "RWD", SeatsCount = 3, BodyType = "Van", Class = "Commercial" }, + new() { Id = 10, Name = "Toyota Prius", DriveType = "FWD", SeatsCount = 5, BodyType = "Hatchback", Class = "Hybrid" }, + new() { Id = 11, Name = "UAZ Patriot", DriveType = "4WD", SeatsCount = 5, BodyType = "SUV", Class = "Off-road" }, + new() { Id = 12, Name = "Lexus RX", DriveType = "AWD", SeatsCount = 5, BodyType = "SUV", Class = "Premium" }, + new() { Id = 13, Name = "Range Rover Sport", DriveType = "AWD", SeatsCount = 5, BodyType = "SUV", Class = "Luxury" }, + new() { Id = 14, Name = "Audi A4", DriveType = "AWD", SeatsCount = 5, BodyType = "Sedan", Class = "Premium" }, + new() { Id = 15, Name = "Lada Niva Travel", DriveType = "4WD", SeatsCount = 5, BodyType = "SUV", Class = "Off-road" } ]; - _generations = - [ - new() { GenerationId = 1, ModelId = 1, Year = 2020, EngineVolume = 2.5, TransmissionType = TransmissionType.Automatic, HourlyRate = 50 }, - new() { GenerationId = 2, ModelId = 1, Year = 2022, EngineVolume = 2.5, TransmissionType = TransmissionType.Automatic, HourlyRate = 60 }, - new() { GenerationId = 3, ModelId = 2, Year = 2019, EngineVolume = 3.0, TransmissionType = TransmissionType.Automatic, HourlyRate = 120 }, - new() { GenerationId = 4, ModelId = 2, Year = 2023, EngineVolume = 3.0, TransmissionType = TransmissionType.Automatic, HourlyRate = 150 }, - new() { GenerationId = 5, ModelId = 3, Year = 2021, EngineVolume = 1.5, TransmissionType = TransmissionType.Manual, HourlyRate = 30 }, - new() { GenerationId = 6, ModelId = 4, Year = 2020, EngineVolume = 2.0, TransmissionType = TransmissionType.Automatic, HourlyRate = 100 }, - new() { GenerationId = 7, ModelId = 5, Year = 2021, EngineVolume = 2.0, TransmissionType = TransmissionType.Automatic, HourlyRate = 110 } - ]; - _cars = + ModelGenerations = [ - new() { CarId = 1, GenerationId = 1, LicensePlate = "А001РС77", Color = "Белый" }, - new() { CarId = 2, GenerationId = 1, LicensePlate = "А002РС77", Color = "Черный" }, - new() { CarId = 3, GenerationId = 2, LicensePlate = "А003РС77", Color = "Серебристый" }, - new() { CarId = 4, GenerationId = 3, LicensePlate = "В001РС77", Color = "Черный" }, - new() { CarId = 5, GenerationId = 4, LicensePlate = "В002РС77", Color = "Белый" }, - new() { CarId = 6, GenerationId = 5, LicensePlate = "В003РС77", Color = "Синий" }, - new() { CarId = 7, GenerationId = 5, LicensePlate = "В004РС77", Color = "Красный" }, - new() { CarId = 8, GenerationId = 6, LicensePlate = "А004РС77", Color = "Серебристый" }, - new() { CarId = 9, GenerationId = 6, LicensePlate = "А005РС77", Color = "Черный" }, - new() { CarId = 10, GenerationId = 7, LicensePlate = "А006РС77", Color = "Серебристый" } + new() { Id = 1, Year = 2023, EngineVolume = 2.0, Transmission = "AT", RentalPricePerHour = 2200, ModelId = 1, Model = CarModels[0] }, + new() { Id = 2, Year = 2022, EngineVolume = 5.0, Transmission = "AT", RentalPricePerHour = 5000, ModelId = 2, Model = CarModels[1] }, + new() { Id = 3, Year = 2024, EngineVolume = 1.5, Transmission = "CVT", RentalPricePerHour = 1200, ModelId = 3, Model = CarModels[2] }, + new() { Id = 4, Year = 2023, EngineVolume = 3.6, Transmission = "AT", RentalPricePerHour = 2800, ModelId = 4, Model = CarModels[3] }, + new() { Id = 5, Year = 2024, EngineVolume = 3.0, Transmission = "AT", RentalPricePerHour = 8000, ModelId = 5, Model = CarModels[4] }, + new() { Id = 6, Year = 2022, EngineVolume = 5.3, Transmission = "AT", RentalPricePerHour = 3500, ModelId = 6, Model = CarModels[5] }, + new() { Id = 7, Year = 2023, EngineVolume = 1.6, Transmission = "MT", RentalPricePerHour = 700, ModelId = 7, Model = CarModels[6] }, + new() { Id = 8, Year = 2024, EngineVolume = 2.5, Transmission = "AT", RentalPricePerHour = 1800, ModelId = 8, Model = CarModels[7] }, + new() { Id = 9, Year = 2022, EngineVolume = 2.7, Transmission = "MT", RentalPricePerHour = 1500, ModelId = 9, Model = CarModels[8] }, + new() { Id = 10, Year = 2023, EngineVolume = 1.8, Transmission = "CVT", RentalPricePerHour = 1600, ModelId = 10, Model = CarModels[9] }, + new() { Id = 11, Year = 2022, EngineVolume = 2.7, Transmission = "MT", RentalPricePerHour = 1400, ModelId = 11, Model = CarModels[10] }, + new() { Id = 12, Year = 2024, EngineVolume = 3.5, Transmission = "AT", RentalPricePerHour = 3200, ModelId = 12, Model = CarModels[11] }, + new() { Id = 13, Year = 2023, EngineVolume = 3.0, Transmission = "AT", RentalPricePerHour = 6000, ModelId = 13, Model = CarModels[12] }, + new() { Id = 14, Year = 2024, EngineVolume = 2.0, Transmission = "AT", RentalPricePerHour = 2800, ModelId = 14, Model = CarModels[13] }, + new() { Id = 15, Year = 2023, EngineVolume = 1.7, Transmission = "MT", RentalPricePerHour = 900, ModelId = 15, Model = CarModels[14] } ]; - _customers = + Cars = [ - new() { CustomerId = 1, DriverLicenseNumber = "7701123456", FullName = "Иван Петров", DateOfBirth = new DateOnly(1990, 5, 15) }, - new() { CustomerId = 2, DriverLicenseNumber = "7701234567", FullName = "Мария Сидорова", DateOfBirth = new DateOnly(1985, 8, 22) }, - new() { CustomerId = 3, DriverLicenseNumber = "7701345678", FullName = "Алексей Смирнов", DateOfBirth = new DateOnly(1992, 3, 10) }, - new() { CustomerId = 4, DriverLicenseNumber = "7701456789", FullName = "Ольга Волкова", DateOfBirth = new DateOnly(1988, 11, 30) }, - new() { CustomerId = 5, DriverLicenseNumber = "7701567890", FullName = "Сергей Морозов", DateOfBirth = new DateOnly(1995, 6, 18) }, - new() { CustomerId = 6, DriverLicenseNumber = "7701678901", FullName = "Елена Николаева", DateOfBirth = new DateOnly(1989, 2, 25) }, - new() { CustomerId = 7, DriverLicenseNumber = "7701789012", FullName = "Дмитрий Соколов", DateOfBirth = new DateOnly(1987, 9, 12) }, - new() { CustomerId = 8, DriverLicenseNumber = "7701890123", FullName = "Анна Федорова", DateOfBirth = new DateOnly(1991, 4, 8) }, - new() { CustomerId = 9, DriverLicenseNumber = "7701901234", FullName = "Борис Козлов", DateOfBirth = new DateOnly(1993, 7, 20) }, - new() { CustomerId = 10,DriverLicenseNumber = "7702012345", FullName = "Валентина Романова", DateOfBirth = new DateOnly(1986, 12, 5) } + new() { Id = 1, LicensePlate = "A001AA163", Color = "Black", ModelGenerationId = 1, ModelGeneration = ModelGenerations[0] }, + new() { Id = 2, LicensePlate = "B777BC163", Color = "Red", ModelGenerationId = 2, ModelGeneration = ModelGenerations[1] }, + new() { Id = 3, LicensePlate = "C123ET163", Color = "White", ModelGenerationId = 3, ModelGeneration = ModelGenerations[2] }, + new() { Id = 4, LicensePlate = "E555KH163", Color = "Green", ModelGenerationId = 4, ModelGeneration = ModelGenerations[3] }, + new() { Id = 5, LicensePlate = "K234MR163", Color = "Silver", ModelGenerationId = 5, ModelGeneration = ModelGenerations[4] }, + new() { Id = 6, LicensePlate = "M888OA163", Color = "Gray", ModelGenerationId = 6, ModelGeneration = ModelGenerations[5] }, + new() { Id = 7, LicensePlate = "N456RS163", Color = "Blue", ModelGenerationId = 7, ModelGeneration = ModelGenerations[6] }, + new() { Id = 8, LicensePlate = "O789TU163", Color = "Brown", ModelGenerationId = 8, ModelGeneration = ModelGenerations[7] }, + new() { Id = 9, LicensePlate = "P321XO163", Color = "White", ModelGenerationId = 9, ModelGeneration = ModelGenerations[8] }, + new() { Id = 10, LicensePlate = "S654AM163", Color = "Black", ModelGenerationId = 10, ModelGeneration = ModelGenerations[9] }, + new() { Id = 11, LicensePlate = "T987RE163", Color = "Orange", ModelGenerationId = 11, ModelGeneration = ModelGenerations[10] }, + new() { Id = 12, LicensePlate = "U246KN163", Color = "White", ModelGenerationId = 12, ModelGeneration = ModelGenerations[11] }, + new() { Id = 13, LicensePlate = "H135VT163", Color = "Black", ModelGenerationId = 13, ModelGeneration = ModelGenerations[12] }, + new() { Id = 14, LicensePlate = "SH579SA163", Color = "Gray", ModelGenerationId = 14, ModelGeneration = ModelGenerations[13] }, + new() { Id = 15, LicensePlate = "SCH864RO163", Color = "Blue", ModelGenerationId = 15, ModelGeneration = ModelGenerations[14] } ]; - _contracts = - [ - new() { ContractId = 1, CarId = 1, CustomerId = 1, IssuanceTime = new DateTime(2025, 1, 5, 10, 0, 0), DurationHours = 24, ReturnTime = new DateTime(2025, 1, 6, 10, 0, 0) }, - new() { ContractId = 2, CarId = 1, CustomerId = 1, IssuanceTime = new DateTime(2025, 1, 20, 14, 0, 0), DurationHours = 8, ReturnTime = new DateTime(2025, 1, 20, 22, 0, 0) }, - new() { ContractId = 3, CarId = 1, CustomerId = 2, IssuanceTime = new DateTime(2025, 1, 10, 9, 0, 0), DurationHours = 48, ReturnTime = new DateTime(2025, 1, 12, 9, 0, 0) }, - new() { ContractId = 4, CarId = 2, CustomerId = 3, IssuanceTime = new DateTime(2025, 1, 8, 12, 0, 0), DurationHours = 16, ReturnTime = new DateTime(2025, 1, 9, 4, 0, 0) }, - new() { ContractId = 5, CarId = 4, CustomerId = 4, IssuanceTime = new DateTime(2025, 1, 3, 11, 0, 0), DurationHours = 72, ReturnTime = new DateTime(2025, 1, 6, 11, 0, 0) }, - new() { ContractId = 6, CarId = 5, CustomerId = 5, IssuanceTime = new DateTime(2025, 1, 15, 8, 0, 0), DurationHours = 24, ReturnTime = new DateTime(2025, 1, 16, 8, 0, 0) }, - new() { ContractId = 7, CarId = 6, CustomerId = 6, IssuanceTime = new DateTime(2025, 1, 12, 10, 0, 0), DurationHours = 6, ReturnTime = new DateTime(2025, 1, 12, 16, 0, 0) }, - new() { ContractId = 8, CarId = 7, CustomerId = 7, IssuanceTime = new DateTime(2025, 1, 18, 15, 0, 0), DurationHours = 12, ReturnTime = new DateTime(2025, 1, 19, 3, 0, 0) }, - new() { ContractId = 9, CarId = 8, CustomerId = 8, IssuanceTime = new DateTime(2025, 1, 7, 13, 0, 0), DurationHours = 20, ReturnTime = new DateTime(2025, 1, 8, 9, 0, 0) }, - new() { ContractId = 10, CarId = 10, CustomerId = 9, IssuanceTime = new DateTime(2025, 1, 11, 9, 0, 0), DurationHours = 36, ReturnTime = new DateTime(2025, 1, 12, 21, 0, 0) }, - new() { ContractId = 11, CarId = 3, CustomerId = 10, IssuanceTime = new DateTime(2025, 1, 14, 12, 0, 0), DurationHours = 8, ReturnTime = new DateTime(2025, 1, 14, 20, 0, 0) }, - new() { ContractId = 12, CarId = 4, CustomerId = 6, IssuanceTime = new DateTime(2025, 1, 21, 10, 0, 0), DurationHours = 24, ReturnTime = new DateTime(2025, 1, 22, 10, 0, 0) } - ]; - } - public List Models => _models; - public List Generations => _generations; - public List Cars => _cars; - public List Customers => _customers; - public List Contracts => _contracts; - public CarRentalFixture() - { - Customers.AddRange( + Clients = [ - new() { CustomerId = 1, FullName = "Иван Петров" }, - new() { CustomerId = 2, FullName = "Мария Сидорова" }, - new() { CustomerId = 3, FullName = "Алексей Смирнов" }, - new() { CustomerId = 4, FullName = "Валентина Романова" } - ]); - - Cars.AddRange( - [ - new() { CarId = 1, ModelId = 1, GenerationId = 1, LicensePlate = "A111AA", Color = "Белый" }, - new() { CarId = 2, ModelId = 1, GenerationId = 2, LicensePlate = "A222AA", Color = "Черный" }, - new() { CarId = 3, ModelId = 2, GenerationId = 3, LicensePlate = "B333BB", Color = "Серый" }, - new() { CarId = 4, ModelId = 3, GenerationId = 4, LicensePlate = "C444CC", Color = "Красный" }, - new() { CarId = 5, ModelId = 4, GenerationId = 5, LicensePlate = "D555DD", Color = "Синий" }, - new() { CarId = 6, ModelId = 5, GenerationId = 6, LicensePlate = "E666EE", Color = "Черный" } - ]); + new() { Id = 1, LicenseNumber = "2023-001", FullName = "Alexander Smirnov", BirthDate = new DateOnly(1988, 3, 15) }, + new() { Id = 2, LicenseNumber = "2022-045", FullName = "Marina Kovalenko", BirthDate = new DateOnly(1992, 7, 22) }, + new() { Id = 3, LicenseNumber = "2024-012", FullName = "Denis Popov", BirthDate = new DateOnly(1995, 11, 10) }, + new() { Id = 4, LicenseNumber = "2021-078", FullName = "Elena Vasnetsova", BirthDate = new DateOnly(1985, 5, 3) }, + new() { Id = 5, LicenseNumber = "2023-056", FullName = "Igor Kozlovsky",BirthDate = new DateOnly(1990, 9, 30) }, + new() { Id = 6, LicenseNumber = "2022-123", FullName = "Anna Orlova", BirthDate = new DateOnly(1993, 2, 14) }, + new() { Id = 7, LicenseNumber = "2024-034", FullName = "Artem Belov", BirthDate = new DateOnly(1987, 8, 18) }, + new() { Id = 8, LicenseNumber = "2021-099", FullName = "Sofia Grigorieva", BirthDate = new DateOnly(1994, 12, 25) }, + new() { Id = 9, LicenseNumber = "2023-087", FullName = "Pavel Melnikov", BirthDate = new DateOnly(1991, 6, 7) }, + new() { Id = 10, LicenseNumber = "2022-067", FullName = "Olga Zakharova", BirthDate = new DateOnly(1989, 4, 12) }, + new() { Id = 11, LicenseNumber = "2024-005", FullName = "Mikhail Tikhonov", BirthDate = new DateOnly(1996, 10, 28) }, + new() { Id = 12, LicenseNumber = "2021-112", FullName = "Ksenia Fedorova", BirthDate = new DateOnly(1986, 1, 19) }, + new() { Id = 13, LicenseNumber = "2023-092", FullName = "Roman Sokolov", BirthDate = new DateOnly(1997, 7, 3) }, + new() { Id = 14, LicenseNumber = "2022-031", FullName = "Tatiana Krylova", BirthDate = new DateOnly(1984, 3, 22) }, + new() { Id = 15, LicenseNumber = "2024-021", FullName = "Andrey Davydov", BirthDate = new DateOnly(1998, 11, 15) } + ]; - Contracts.AddRange( + Rentals = [ - new() { ContractId = 1, CarId = 1, CustomerId = 1, IssuanceTime = new DateTime(2024, 1, 1, 10, 0, 0), DurationHours = 5, ReturnTime = new DateTime(2024, 1, 1, 15, 0, 0) }, - new() { ContractId = 2, CarId = 1, CustomerId = 2, IssuanceTime = new DateTime(2024, 2, 1, 9, 0, 0), DurationHours = 24, ReturnTime = new DateTime(2024, 2, 2, 9, 0, 0) }, - new() { ContractId = 3, CarId = 2, CustomerId = 3, IssuanceTime = new DateTime(2024, 3, 1, 12, 0, 0), DurationHours = 10, ReturnTime = new DateTime(2024, 3, 1, 22, 0, 0) }, - new() { ContractId = 999, CarId = 6, CustomerId = 1, IssuanceTime = new DateTime(2024, 4, 1, 10, 0, 0), DurationHours = 24, ReturnTime = null } - ]); + new() { Id = 1, CarId = 7, ClientId = 1, RentalDate = new DateTime(2024, 3, 1, 10, 0, 0), RentalHours = 48, Car = Cars[6], Client = Clients[0] }, + new() { Id = 2, CarId = 7, ClientId = 3, RentalDate = new DateTime(2024, 2, 25, 14, 30, 0), RentalHours = 72, Car = Cars[6], Client = Clients[2] }, + new() { Id = 3, CarId = 7, ClientId = 5, RentalDate = new DateTime(2024, 2, 20, 9, 15, 0), RentalHours = 24, Car = Cars[6], Client = Clients[4] }, + new() { Id = 4, CarId = 1, ClientId = 2, RentalDate = new DateTime(2024, 2, 27, 11, 45, 0), RentalHours = 96, Car = Cars[0], Client = Clients[1] }, + new() { Id = 5, CarId = 1, ClientId = 4, RentalDate = new DateTime(2024, 2, 25, 16, 0, 0), RentalHours = 120, Car = Cars[0], Client = Clients[3] }, + new() { Id = 6, CarId = 2, ClientId = 6, RentalDate = new DateTime(2024, 2, 23, 13, 20, 0), RentalHours = 72, Car = Cars[1], Client = Clients[5] }, + new() { Id = 7, CarId = 2, ClientId = 8, RentalDate = new DateTime(2024, 2, 18, 10, 10, 0), RentalHours = 48, Car = Cars[1], Client = Clients[7] }, + new() { Id = 8, CarId = 3, ClientId = 7, RentalDate = new DateTime(2024, 2, 28, 8, 30, 0), RentalHours = 36, Car = Cars[2], Client = Clients[6] }, + new() { Id = 9, CarId = 4, ClientId = 9, RentalDate = new DateTime(2024, 2, 15, 12, 0, 0), RentalHours = 96, Car = Cars[3], Client = Clients[8] }, + new() { Id = 10, CarId = 5, ClientId = 10, RentalDate = new DateTime(2024, 2, 28, 7, 0, 0), RentalHours = 168, Car = Cars[4], Client = Clients[9] }, + new() { Id = 11, CarId = 6, ClientId = 11, RentalDate = new DateTime(2024, 2, 22, 15, 45, 0), RentalHours = 72, Car = Cars[5], Client = Clients[10] }, + new() { Id = 12, CarId = 8, ClientId = 12, RentalDate = new DateTime(2024, 2, 26, 9, 20, 0), RentalHours = 48, Car = Cars[7], Client = Clients[11] }, + new() { Id = 13, CarId = 9, ClientId = 13, RentalDate = new DateTime(2024, 2, 29, 22, 0, 0), RentalHours = 60, Car = Cars[8], Client = Clients[12] }, + new() { Id = 14, CarId = 10, ClientId = 14, RentalDate = new DateTime(2024, 2, 24, 11, 30, 0), RentalHours = 96, Car = Cars[9], Client = Clients[13] }, + new() { Id = 15, CarId = 11, ClientId = 15, RentalDate = new DateTime(2024, 2, 10, 14, 15, 0), RentalHours = 120, Car = Cars[10], Client = Clients[14] }, + new() { Id = 16, CarId = 12, ClientId = 1, RentalDate = new DateTime(2024, 2, 29, 14, 0, 0), RentalHours = 48, Car = Cars[11], Client = Clients[0] }, + new() { Id = 17, CarId = 13, ClientId = 2, RentalDate = new DateTime(2024, 2, 5, 16, 45, 0), RentalHours = 72, Car = Cars[12], Client = Clients[1] }, + new() { Id = 18, CarId = 14, ClientId = 3, RentalDate = new DateTime(2024, 2, 12, 10, 10, 0), RentalHours = 36, Car = Cars[13], Client = Clients[2] }, + new() { Id = 19, CarId = 15, ClientId = 4, RentalDate = new DateTime(2024, 2, 16, 13, 30, 0), RentalHours = 84, Car = Cars[14], Client = Clients[3] } + ]; } } \ No newline at end of file diff --git a/CarRental/CarRental/CarRental.Domain/Models/Rental.cs b/CarRental/CarRental/CarRental.Domain/Models/Rental.cs index 91b87782e..0a568074d 100644 --- a/CarRental/CarRental/CarRental.Domain/Models/Rental.cs +++ b/CarRental/CarRental/CarRental.Domain/Models/Rental.cs @@ -33,11 +33,6 @@ public class Rental /// public required int RentalHours { get; set; } - /// - /// Дата и время возврата (может быть null, если машина ещё в аренде) - /// - public required DateTime ReturnTime { get; set; } - /// /// Ссылка на арендуемый автомобиль /// diff --git a/CarRental/CarRental/CarRental.slnx b/CarRental/CarRental/CarRental.slnx index 98e0684aa..4c0efb589 100644 --- a/CarRental/CarRental/CarRental.slnx +++ b/CarRental/CarRental/CarRental.slnx @@ -1,5 +1,4 @@ - From de2283e7a34c66c50ffd1d5e6e473df54c4de079 Mon Sep 17 00:00:00 2001 From: ahewbu Date: Fri, 13 Feb 2026 20:59:56 +0400 Subject: [PATCH 11/20] =?UTF-8?q?=D0=B8=D0=B7=D0=BC=D0=B5=D0=BD=D0=B5?= =?UTF-8?q?=D0=BD=D0=BD=D1=8B=D0=B5=20=D1=82=D0=B5=D1=81=D1=82=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CarRental.Tests/CarRentalTests.cs | 164 +++++++++--------- 1 file changed, 80 insertions(+), 84 deletions(-) diff --git a/CarRental/CarRental/CarRental.Tests/CarRentalTests.cs b/CarRental/CarRental/CarRental.Tests/CarRentalTests.cs index 17404ae52..10d4d8b94 100644 --- a/CarRental/CarRental/CarRental.Tests/CarRentalTests.cs +++ b/CarRental/CarRental/CarRental.Tests/CarRentalTests.cs @@ -1,135 +1,131 @@ using CarRental.Domain_.Data; -using CarRental.Domain_.Models; -using Xunit; namespace CarRental.Tests; -// -// Юнит-тесты для пункта проката автомобилей -// -// Fixture с тестовыми данными +/// +/// Юнит-тесты для пункта проката автомобилей +/// public class CarRentalTests(CarRentalFixture fixture) : IClassFixture { - // - // ТЕСТ 1: Вывести информацию обо всех клиентах, - // которые брали в аренду автомобили указанной модели, упорядочить по ФИО. - // + /// + /// ТЕСТ 1: Вывести информацию обо всех клиентах, + /// которые брали в аренду автомобили указанной модели, упорядочить по ФИО. + /// [Fact] - public void GetCustomersForModel_ShouldReturnCustomersOrderedByName() + public void GetClientsByModelSortedByName() { - const string modelName = "Toyota Camry"; - const int expectedCount = 4; - var expectedNames = new List { "Алексей Смирнов", "Валентина Романова", "Иван Петров", "Мария Сидорова"}; - - //var targetModel = fixture.Models.First(m => m.Name == modelName); - - var customersForModel = - (from contract in fixture.Contracts - join car in fixture.Cars on contract.CarId equals car.CarId - join model in fixture.Models on car.ModelId equals model.ModelId - join customer in fixture.Customers on contract.CustomerId equals customer.CustomerId - where model.Name == modelName && car.GenerationId is 1 or 2 - select customer) + const string targetModel = "Lada Vesta"; + const int expectedCount = 3; + const string expectedFirstName = "Alexander Smirnov"; + const string expectedSecondName = "Denis Popov"; + const string expectedThirdName = "Igor Kozlovsky"; + + var clients = fixture.Rentals + .Where(r => r.Car.ModelGeneration.Model.Name == targetModel) + .Select(r => r.Client) .Distinct() .OrderBy(c => c.FullName) .ToList(); - Assert.Equal(expectedCount, customersForModel.Count); - Assert.Equal(expectedNames, customersForModel.Select(c => c.FullName)); + Assert.Equal(expectedCount, clients.Count); + Assert.Equal(expectedFirstName, clients[0].FullName); + Assert.Equal(expectedSecondName, clients[1].FullName); + Assert.Equal(expectedThirdName, clients[2].FullName); } - // - // ТЕСТ 2: Вывести информацию об автомобилях, находящихся в аренде. - // + /// + /// ТЕСТ 2: Вывести информацию об автомобилях, находящихся в аренде. + /// [Fact] - public void GetCarsInRental_ShouldReturnCarsWithoutReturnTime() + public void GetCurrentlyRentedCars() { - var carsInRental = fixture.Contracts - .Where(c => c.ReturnTime == null) - .Select(c => fixture.Cars.First(car => car.CarId == c.CarId)) + var testDate = new DateTime(2024, 3, 5, 12, 0, 0); + var expectedPlate = "K234MR163"; + + var rentedCars = fixture.Rentals + .Where(r => r.RentalDate.AddHours(r.RentalHours) > testDate) + .Select(r => r.Car) .Distinct() .ToList(); - Assert.NotEmpty(carsInRental); - Assert.Contains(carsInRental, c => c.CarId == 6); - + Assert.Contains(rentedCars, c => c.LicensePlate == expectedPlate); } - // - // ТЕСТ 3: Вывести топ 5 наиболее часто арендуемых автомобилей. - // + /// + /// ТЕСТ 3: Вывести топ 5 наиболее часто арендуемых автомобилей. + /// [Fact] - public void GetTop5MostRentedCars_ShouldReturnTopCars() + public void GetTop5MostRentedCars() { - var top5Cars = fixture.Contracts - .GroupBy(c => c.CarId) - .OrderByDescending(g => g.Count()) + const int expectedCount = 5; + const string expectedTopCarPlate = "N456RS163"; + const int expectedTopCarRentalCount = 3; + + var topCars = fixture.Rentals + .GroupBy(r => r.Car) + .Select(g => new { Car = g.Key, RentalCount = g.Count() }) + .OrderByDescending(x => x.RentalCount) .Take(5) - .Select(g => new - { - Car = fixture.Cars.First(c => c.CarId == g.Key), - RentalCount = g.Count() - }) .ToList(); - Assert.NotEmpty(top5Cars); - Assert.True(top5Cars.Count <= 5); - Assert.Contains(top5Cars, x => x.Car.CarId == 1); + Assert.Equal(expectedCount, topCars.Count); + Assert.Equal(expectedTopCarPlate, topCars[0].Car.LicensePlate); + Assert.Equal(expectedTopCarRentalCount, topCars[0].RentalCount); } /// /// ТЕСТ 4: Для каждого автомобиля вывести число аренд. /// [Fact] - public void GetRentalCountPerCar_ShouldReturnCorrectCounts() + public void GetRentalCountPerCar() { - var rentalCountPerCar = fixture.Contracts - .GroupBy(c => c.CarId) - .Select(g => new + const int expectedTotalCars = 15; + const int expectedLadaVestaRentalCount = 3; + const int expectedBmwRentalCount = 2; + const int ladaVestaCarId = 7; + const int bmwCarId = 1; + + var carsWithRentalCount = fixture.Cars + .Select(car => new { - Car = fixture.Cars.First(c => c.CarId == g.Key), - RentalCount = g.Count() + Car = car, + RentalCount = fixture.Rentals.Count(r => r.CarId == car.Id) }) - .OrderBy(x => x.Car.CarId) .ToList(); - Assert.NotEmpty(rentalCountPerCar); - var car1Rentals = rentalCountPerCar.First(x => x.Car.CarId == 1); - Assert.Equal(3, car1Rentals.RentalCount); + Assert.Equal(expectedTotalCars, carsWithRentalCount.Count); + + var ladaVesta = carsWithRentalCount.First(c => c.Car.Id == ladaVestaCarId); + var bmw = carsWithRentalCount.First(c => c.Car.Id == bmwCarId); - var car4Rentals = rentalCountPerCar.First(x => x.Car.CarId == 4); - Assert.Equal(2, car4Rentals.RentalCount); + Assert.Equal(expectedLadaVestaRentalCount, ladaVesta.RentalCount); + Assert.Equal(expectedBmwRentalCount, bmw.RentalCount); + Assert.True(carsWithRentalCount.All(x => x.RentalCount >= 0)); } - /// - /// ТЕСТ 5: Вывести топ 5 клиентов по сумме аренды. - /// - [Fact] - public void GetTop5CustomersByRentalCost_ShouldReturnTopCustomers() + /// + /// ТЕСТ 5: Вывести топ 5 клиентов по сумме аренды. + /// + [Fact] + public void GetTop5ClientsByRentalAmount() { - var top5CustomersBySpent = fixture.Contracts - .GroupBy(c => c.CustomerId) + const int expectedCount = 5; + const string expectedTopClientName = "Olga Zakharova"; + + var topClients = fixture.Rentals + .GroupBy(r => r.Client) .Select(g => new { - Customer = fixture.Customers.First(cust => cust.CustomerId == g.Key), - TotalSpent = g.Sum(c => - { - var car = fixture.Cars.First(car => car.CarId == c.CarId); - var generation = fixture.Generations.First(gen => gen.GenerationId == car.GenerationId); - return (decimal)(c.DurationHours * (double)generation.HourlyRate); - }) + Client = g.Key, + TotalAmount = g.Sum(r => r.RentalHours * r.Car.ModelGeneration.RentalPricePerHour) }) - .OrderByDescending(x => x.TotalSpent) + .OrderByDescending(x => x.TotalAmount) .Take(5) .ToList(); - Assert.NotEmpty(top5CustomersBySpent); - Assert.True(top5CustomersBySpent.Count <= 5); - if (top5CustomersBySpent.Count > 1) - { - Assert.True(top5CustomersBySpent[0].TotalSpent >= top5CustomersBySpent[1].TotalSpent); - } + Assert.Equal(expectedCount, topClients.Count); + Assert.Equal(expectedTopClientName, topClients[0].Client.FullName); } } From 58dfcd029abd735dbeef33bc40967819882b818a Mon Sep 17 00:00:00 2001 From: ahewbu Date: Mon, 16 Feb 2026 13:02:58 +0400 Subject: [PATCH 12/20] =?UTF-8?q?=D0=B8=D0=B7=D0=BC=D0=B5=D0=BD=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D0=BD=D0=B0=D0=B7=D0=B2=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F=20=D0=BF=D1=80=D0=BE=D0=B5=D0=BA=D1=82=D0=B0,=20=D0=B2?= =?UTF-8?q?=D1=8B=D1=80=D0=B0=D0=B2=D0=BD=D0=B8=D0=B2=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5,=20cleanup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...arRental.Domain..csproj => CarRental.Domain.csproj} | 2 +- .../CarRental.Domain/Data/CarRentalFixture.cs | 4 ++-- CarRental/CarRental/CarRental.Domain/Models/Car.cs | 2 +- .../CarRental/CarRental.Domain/Models/CarModel.cs | 2 +- CarRental/CarRental/CarRental.Domain/Models/Client.cs | 2 +- .../CarRental.Domain/Models/ModelGeneration.cs | 2 +- CarRental/CarRental/CarRental.Domain/Models/Rental.cs | 4 +--- .../CarRental/CarRental.Tests/CarRental.Tests.csproj | 2 +- CarRental/CarRental/CarRental.Tests/CarRentalTests.cs | 10 +++++----- CarRental/CarRental/CarRental.slnx | 2 +- 10 files changed, 15 insertions(+), 17 deletions(-) rename CarRental/CarRental/CarRental.Domain/{CarRental.Domain..csproj => CarRental.Domain.csproj} (84%) diff --git a/CarRental/CarRental/CarRental.Domain/CarRental.Domain..csproj b/CarRental/CarRental/CarRental.Domain/CarRental.Domain.csproj similarity index 84% rename from CarRental/CarRental/CarRental.Domain/CarRental.Domain..csproj rename to CarRental/CarRental/CarRental.Domain/CarRental.Domain.csproj index 0786a09f7..4b9cc15f8 100644 --- a/CarRental/CarRental/CarRental.Domain/CarRental.Domain..csproj +++ b/CarRental/CarRental/CarRental.Domain/CarRental.Domain.csproj @@ -2,7 +2,7 @@ net8.0 - CarRental.Domain_ + CarRental.Domain enable enable diff --git a/CarRental/CarRental/CarRental.Domain/Data/CarRentalFixture.cs b/CarRental/CarRental/CarRental.Domain/Data/CarRentalFixture.cs index f11598f32..fdda097d1 100644 --- a/CarRental/CarRental/CarRental.Domain/Data/CarRentalFixture.cs +++ b/CarRental/CarRental/CarRental.Domain/Data/CarRentalFixture.cs @@ -1,6 +1,6 @@ -using CarRental.Domain_.Models; +using CarRental.Domain.Models; -namespace CarRental.Domain_.Data; +namespace CarRental.Domain.Data; /// diff --git a/CarRental/CarRental/CarRental.Domain/Models/Car.cs b/CarRental/CarRental/CarRental.Domain/Models/Car.cs index d0f240e6f..1843c7dfa 100644 --- a/CarRental/CarRental/CarRental.Domain/Models/Car.cs +++ b/CarRental/CarRental/CarRental.Domain/Models/Car.cs @@ -1,4 +1,4 @@ -namespace CarRental.Domain_.Models; +namespace CarRental.Domain.Models; /// /// Автомобиль в парке diff --git a/CarRental/CarRental/CarRental.Domain/Models/CarModel.cs b/CarRental/CarRental/CarRental.Domain/Models/CarModel.cs index 9900f39a4..a03a07a7f 100644 --- a/CarRental/CarRental/CarRental.Domain/Models/CarModel.cs +++ b/CarRental/CarRental/CarRental.Domain/Models/CarModel.cs @@ -1,4 +1,4 @@ -namespace CarRental.Domain_.Models; +namespace CarRental.Domain.Models; /// /// Модель автомобиля (справочник) diff --git a/CarRental/CarRental/CarRental.Domain/Models/Client.cs b/CarRental/CarRental/CarRental.Domain/Models/Client.cs index d89e6f7d8..6bb963714 100644 --- a/CarRental/CarRental/CarRental.Domain/Models/Client.cs +++ b/CarRental/CarRental/CarRental.Domain/Models/Client.cs @@ -1,4 +1,4 @@ -namespace CarRental.Domain_.Models; +namespace CarRental.Domain.Models; /// /// Клиент пункта проката diff --git a/CarRental/CarRental/CarRental.Domain/Models/ModelGeneration.cs b/CarRental/CarRental/CarRental.Domain/Models/ModelGeneration.cs index 0a87d7683..eea0a5d9a 100644 --- a/CarRental/CarRental/CarRental.Domain/Models/ModelGeneration.cs +++ b/CarRental/CarRental/CarRental.Domain/Models/ModelGeneration.cs @@ -1,4 +1,4 @@ -namespace CarRental.Domain_.Models; +namespace CarRental.Domain.Models; /// /// Поколение модели (справочник) diff --git a/CarRental/CarRental/CarRental.Domain/Models/Rental.cs b/CarRental/CarRental/CarRental.Domain/Models/Rental.cs index 0a568074d..3052003ca 100644 --- a/CarRental/CarRental/CarRental.Domain/Models/Rental.cs +++ b/CarRental/CarRental/CarRental.Domain/Models/Rental.cs @@ -1,6 +1,4 @@ -using CarRental.Domain_.Models; - -namespace CarRental.Domain_.Models; +namespace CarRental.Domain.Models; /// /// Договор аренды (контракт) diff --git a/CarRental/CarRental/CarRental.Tests/CarRental.Tests.csproj b/CarRental/CarRental/CarRental.Tests/CarRental.Tests.csproj index d842ec838..7361aa8b2 100644 --- a/CarRental/CarRental/CarRental.Tests/CarRental.Tests.csproj +++ b/CarRental/CarRental/CarRental.Tests/CarRental.Tests.csproj @@ -17,7 +17,7 @@ - + diff --git a/CarRental/CarRental/CarRental.Tests/CarRentalTests.cs b/CarRental/CarRental/CarRental.Tests/CarRentalTests.cs index 10d4d8b94..9cba886e3 100644 --- a/CarRental/CarRental/CarRental.Tests/CarRentalTests.cs +++ b/CarRental/CarRental/CarRental.Tests/CarRentalTests.cs @@ -1,4 +1,4 @@ -using CarRental.Domain_.Data; +using CarRental.Domain.Data; namespace CarRental.Tests; @@ -104,10 +104,10 @@ public void GetRentalCountPerCar() Assert.True(carsWithRentalCount.All(x => x.RentalCount >= 0)); } - /// - /// ТЕСТ 5: Вывести топ 5 клиентов по сумме аренды. - /// - [Fact] + /// + /// ТЕСТ 5: Вывести топ 5 клиентов по сумме аренды. + /// + [Fact] public void GetTop5ClientsByRentalAmount() { const int expectedCount = 5; diff --git a/CarRental/CarRental/CarRental.slnx b/CarRental/CarRental/CarRental.slnx index 4c0efb589..a8febc9ef 100644 --- a/CarRental/CarRental/CarRental.slnx +++ b/CarRental/CarRental/CarRental.slnx @@ -1,4 +1,4 @@ - + From cae8e05b433792ad9c802a2cc980474ba8f1da1c Mon Sep 17 00:00:00 2001 From: ahewbu Date: Mon, 16 Feb 2026 13:14:56 +0400 Subject: [PATCH 13/20] =?UTF-8?q?=D0=B8=D0=B7=D0=BC=D0=B5=D0=BD=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20dotnet-tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CarRental/CarRental/.github/workflows/dotnet-tests.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CarRental/CarRental/.github/workflows/dotnet-tests.yml b/CarRental/CarRental/.github/workflows/dotnet-tests.yml index b298f7583..36c06b335 100644 --- a/CarRental/CarRental/.github/workflows/dotnet-tests.yml +++ b/CarRental/CarRental/.github/workflows/dotnet-tests.yml @@ -4,7 +4,7 @@ on: push: branches: [ main, lab_1 ] pull_request: - branches: [ main ] + branches: [ main, lab_1 ] jobs: test: @@ -20,10 +20,10 @@ jobs: dotnet-version: 8.0.x - name: Restore dependencies - run: dotnet restore ./CarRental/CarRental.sln + run: dotnet restore ./CarRental/CarRental.slnx - name: Build - run: dotnet build ./CarRental/CarRental.sln --no-restore --configuration Release + run: dotnet build ./CarRental/CarRental.slnx --no-restore --configuration Release - name: Test run: dotnet test ./CarRental/CarRental.Tests/CarRental.Tests.csproj --configuration Release --verbosity normal From 98aebdb6c5d5b3d9b96529a27860ecfbf9d55a45 Mon Sep 17 00:00:00 2001 From: ahewbu Date: Mon, 16 Feb 2026 15:34:22 +0400 Subject: [PATCH 14/20] =?UTF-8?q?=D0=B8=D0=B7=D0=BC=D0=B5=D0=BD=D0=B5?= =?UTF-8?q?=D0=BD=D0=BD=D1=8B=D0=B9=20readme,=20=D1=83=D0=B4=D0=B0=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=20=D0=BD=D0=B5=D0=BD=D1=83=D0=B6=D0=BD=D1=8B=D0=B9?= =?UTF-8?q?=20=D0=BF=D1=80=D0=BE=D0=B5=D0=BA=D1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CarRental/CarRental/CarRental.csproj | 10 ------ CarRental/CarRental/CarRental/Program.cs | 2 -- README.md | 33 ++++++++----------- 3 files changed, 13 insertions(+), 32 deletions(-) delete mode 100644 CarRental/CarRental/CarRental/CarRental.csproj delete mode 100644 CarRental/CarRental/CarRental/Program.cs diff --git a/CarRental/CarRental/CarRental/CarRental.csproj b/CarRental/CarRental/CarRental/CarRental.csproj deleted file mode 100644 index e7874f6f9..000000000 --- a/CarRental/CarRental/CarRental/CarRental.csproj +++ /dev/null @@ -1,10 +0,0 @@ - - - - Exe - net8.0 - enable - enable - - - \ No newline at end of file diff --git a/CarRental/CarRental/CarRental/Program.cs b/CarRental/CarRental/CarRental/Program.cs deleted file mode 100644 index 3751555cb..000000000 --- a/CarRental/CarRental/CarRental/Program.cs +++ /dev/null @@ -1,2 +0,0 @@ -// See https://aka.ms/new-console-template for more information -Console.WriteLine("Hello, World!"); diff --git a/README.md b/README.md index f1c8c8b69..2596fcabc 100644 --- a/README.md +++ b/README.md @@ -7,15 +7,8 @@ - **`CarModel`** – модель автомобиля (справочник): тип привода, класс, тип кузова, количество мест. - **`ModelGeneration`** – поколение модели: год выпуска, объём двигателя, коробка передач, стоимость часа аренды. - **`Car`** – физический экземпляр автомобиля: госномер, цвет, поколение модели. -- **`Customer`** – клиент: номер водительского удостоверения, ФИО, дата рождения. -- **`RentalContract`** – договор аренды: клиент, автомобиль, время выдачи, длительность в часах, время возврата. - -Используются перечисления: - -- **`CarClass`**: Economy, Middle, Premium. -- **`TransmissionType`**: Manual, Automatic, CVT, Robot. -- **`DriveType`**: FrontWheelDrive, RearWheelDrive, AllWheelDrive. -- **`BodyType`**: Sedan, Hatchback, Coupe, SUV, Wagon, Pickup. +- **`Client`** – клиент: номер водительского удостоверения, ФИО, дата рождения. +- **`Rental`** – договор аренды: клиент, автомобиль, время выдачи, длительность в часах. ## Структура решения @@ -27,23 +20,23 @@ ## Выполненные задачи ### 1. Модель данных -Создана полная объектная модель с использованием enum-типов и классов предметной области. +Создана полная объектная модель с использованием классов предметной области. ### 2. Тестовые данные Создан класс `CarRentalFixture` содержащий: -- 5 моделей автомобилей -- 7 поколений моделей -- 10 физических экземпляров автомобилей -- 10 клиентов -- 12 контрактов аренды +- 15 моделей автомобилей +- 15 поколений моделей +- 15 физических экземпляров автомобилей +- 15 клиентов +- 19 контрактов аренды ### 3. Реализованные тесты (xUnit) -1. **`GetCustomersForModel_ShouldReturnCustomersOrderedByName`** – клиенты, бравшие в аренду указанную модель, отсортированные по ФИО. -2. **`GetCarsInRental_ShouldReturnCarsWithoutReturnTime`** – автомобили, которые сейчас находятся в аренде (нет даты возврата). -3. **`GetTop5MostRentedCars_ShouldReturnTopCars`**` – топ‑5 автомобилей по количеству аренд. -4. **`GetRentalCountPerCar_ShouldReturnCorrectCounts`** – количество аренд для каждого автомобиля. -5. **`GetTop5CustomersByRentalCost_ShouldReturnTopCustomers`** – топ‑5 клиентов по сумме стоимости аренды. +1. **`GetClientsByModelSortedByName`** – клиенты, бравшие в аренду указанную модель, отсортированные по ФИО. +2. **`GetCurrentlyRentedCars`** – автомобили, которые сейчас находятся в аренде (нет даты возврата). +3. **`GetTop5MostRentedCars`**` – топ‑5 автомобилей по количеству аренд. +4. **`GetRentalCountPerCar`** – количество аренд для каждого автомобиля. +5. **`GetTop5ClientsByRentalAmount`** – топ‑5 клиентов по сумме стоимости аренды. ## Результат From a1306d4e16f383f9ef3b65c9a2ac17a6e0d678a0 Mon Sep 17 00:00:00 2001 From: ahewbu Date: Tue, 17 Feb 2026 01:02:03 +0400 Subject: [PATCH 15/20] =?UTF-8?q?=D0=B8=D0=B7=D0=BC=D0=B5=D0=BD=D0=B5?= =?UTF-8?q?=D0=BD=20domain?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CarRental.Domain/Data/CarRentalFixture.cs | 2 +- .../{Models => Entities}/Car.cs | 27 ++++++++++----- .../{Models => Entities}/CarModel.cs | 19 +++++++++-- .../{Models => Entities}/Client.cs | 14 ++++++-- .../{Models => Entities}/ModelGeneration.cs | 29 +++++++++++----- .../{Models => Entities}/Rental.cs | 33 +++++++++++------- .../Interfaces/IRepository.cs | 16 +++++++++ .../CarRental.Tests/CarRentalTests.cs | 34 ++++++++++++------- 8 files changed, 125 insertions(+), 49 deletions(-) rename CarRental/CarRental/CarRental.Domain/{Models => Entities}/Car.cs (68%) rename CarRental/CarRental/CarRental.Domain/{Models => Entities}/CarModel.cs (70%) rename CarRental/CarRental/CarRental.Domain/{Models => Entities}/Client.cs (68%) rename CarRental/CarRental/CarRental.Domain/{Models => Entities}/ModelGeneration.cs (70%) rename CarRental/CarRental/CarRental.Domain/{Models => Entities}/Rental.cs (75%) create mode 100644 CarRental/CarRental/CarRental.Domain/Interfaces/IRepository.cs diff --git a/CarRental/CarRental/CarRental.Domain/Data/CarRentalFixture.cs b/CarRental/CarRental/CarRental.Domain/Data/CarRentalFixture.cs index fdda097d1..e94ef7cf1 100644 --- a/CarRental/CarRental/CarRental.Domain/Data/CarRentalFixture.cs +++ b/CarRental/CarRental/CarRental.Domain/Data/CarRentalFixture.cs @@ -1,4 +1,4 @@ -using CarRental.Domain.Models; +using CarRental.Domain.Entities; namespace CarRental.Domain.Data; diff --git a/CarRental/CarRental/CarRental.Domain/Models/Car.cs b/CarRental/CarRental/CarRental.Domain/Entities/Car.cs similarity index 68% rename from CarRental/CarRental/CarRental.Domain/Models/Car.cs rename to CarRental/CarRental/CarRental.Domain/Entities/Car.cs index 1843c7dfa..225757a16 100644 --- a/CarRental/CarRental/CarRental.Domain/Models/Car.cs +++ b/CarRental/CarRental/CarRental.Domain/Entities/Car.cs @@ -1,33 +1,42 @@ -namespace CarRental.Domain.Models; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace CarRental.Domain.Entities; /// /// Автомобиль в парке /// +[Table("cars")] public class Car { /// /// Уникальный ID автомобиля в парке /// + [Column("id")] public int Id { get; set; } - /// - /// Внешний ключ на поколение модели - /// - public required int ModelGenerationId { get; set; } - /// /// Государственный номер /// + [Column("license_plate")] + [MaxLength(20)] public required string LicensePlate { get; set; } /// /// Цвет кузова /// + [Column("color")] + [MaxLength(30)] public required string Color { get; set; } /// - /// Ссылка на поколение модели этого автомобиля + /// Внешний ключ на поколение модели /// - public required ModelGeneration ModelGeneration { get; set; } -} + [Column("model_generation_id")] + public required int ModelGenerationId { get; set; } + /// + /// Ссылка на поколение модели этого автомобиля + /// + public ModelGeneration? ModelGeneration { get; set; } +} \ No newline at end of file diff --git a/CarRental/CarRental/CarRental.Domain/Models/CarModel.cs b/CarRental/CarRental/CarRental.Domain/Entities/CarModel.cs similarity index 70% rename from CarRental/CarRental/CarRental.Domain/Models/CarModel.cs rename to CarRental/CarRental/CarRental.Domain/Entities/CarModel.cs index a03a07a7f..67883f208 100644 --- a/CarRental/CarRental/CarRental.Domain/Models/CarModel.cs +++ b/CarRental/CarRental/CarRental.Domain/Entities/CarModel.cs @@ -1,38 +1,51 @@ -namespace CarRental.Domain.Models; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace CarRental.Domain.Entities; /// /// Модель автомобиля (справочник) /// +[Table("car_models")] public class CarModel { /// /// Уникальный идентификатор модели /// + [Column("id")] public int Id { get; set; } /// /// Название модели (например, "Toyota Camry") /// + [Column("name")] + [MaxLength(50)] public required string Name { get; set; } /// /// Тип привода /// + [Column("drive_type")] + [MaxLength(10)] public required string DriveType { get; set; } /// /// Количество посадочных мест /// + [Column("seats_count")] public required int SeatsCount { get; set; } /// /// Тип кузова /// + [Column("body_type")] + [MaxLength(20)] public required string BodyType { get; set; } /// /// Класс автомобиля /// + [Column("class")] + [MaxLength(20)] public required string Class { get; set; } -} - +} \ No newline at end of file diff --git a/CarRental/CarRental/CarRental.Domain/Models/Client.cs b/CarRental/CarRental/CarRental.Domain/Entities/Client.cs similarity index 68% rename from CarRental/CarRental/CarRental.Domain/Models/Client.cs rename to CarRental/CarRental/CarRental.Domain/Entities/Client.cs index 6bb963714..831cf2e4b 100644 --- a/CarRental/CarRental/CarRental.Domain/Models/Client.cs +++ b/CarRental/CarRental/CarRental.Domain/Entities/Client.cs @@ -1,27 +1,37 @@ -namespace CarRental.Domain.Models; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace CarRental.Domain.Entities; /// /// Клиент пункта проката /// +[Table("clients")] public class Client { /// /// Уникальный ID клиента /// + [Column("id")] public int Id { get; set; } /// /// Номер водительского удостоверения (уникален) /// + [Column("license_number")] + [MaxLength(20)] public required string LicenseNumber { get; set; } /// /// Полное имя клиента (ФИО) /// + [Column("full_name")] + [MaxLength(100)] public required string FullName { get; set; } /// /// Дата рождения /// + [Column("birth_date")] public required DateOnly BirthDate { get; set; } -} +} \ No newline at end of file diff --git a/CarRental/CarRental/CarRental.Domain/Models/ModelGeneration.cs b/CarRental/CarRental/CarRental.Domain/Entities/ModelGeneration.cs similarity index 70% rename from CarRental/CarRental/CarRental.Domain/Models/ModelGeneration.cs rename to CarRental/CarRental/CarRental.Domain/Entities/ModelGeneration.cs index eea0a5d9a..0cbdd814b 100644 --- a/CarRental/CarRental/CarRental.Domain/Models/ModelGeneration.cs +++ b/CarRental/CarRental/CarRental.Domain/Entities/ModelGeneration.cs @@ -1,42 +1,53 @@ -namespace CarRental.Domain.Models; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace CarRental.Domain.Entities; /// /// Поколение модели (справочник) /// +[Table("model_generations")] public class ModelGeneration { /// /// Уникальный идентификатор поколения /// - public required int Id { get; set; } - - /// - /// Внешний ключ на модель - /// - public required int ModelId { get; set; } + [Column("id")] + public int Id { get; set; } /// /// Год выпуска /// + [Column("year")] public required int Year { get; set; } /// /// Объем двигателя в литрах /// + [Column("engine_volume")] public required double EngineVolume { get; set; } /// /// Тип коробки передач /// + [Column("transmission")] + [MaxLength(10)] public required string Transmission { get; set; } /// /// Стоимость аренды в час /// + [Column("rental_price_per_hour")] public required decimal RentalPricePerHour { get; set; } + /// + /// Внешний ключ на модель + /// + [Column("model_id")] + public required int ModelId { get; set; } + /// /// Ссылка на модель (навигационное свойство) /// - public required CarModel Model { get; set; } -} + public CarModel? Model { get; set; } +} \ No newline at end of file diff --git a/CarRental/CarRental/CarRental.Domain/Models/Rental.cs b/CarRental/CarRental/CarRental.Domain/Entities/Rental.cs similarity index 75% rename from CarRental/CarRental/CarRental.Domain/Models/Rental.cs rename to CarRental/CarRental/CarRental.Domain/Entities/Rental.cs index 3052003ca..2cfafc7b7 100644 --- a/CarRental/CarRental/CarRental.Domain/Models/Rental.cs +++ b/CarRental/CarRental/CarRental.Domain/Entities/Rental.cs @@ -1,43 +1,52 @@ -namespace CarRental.Domain.Models; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace CarRental.Domain.Entities; /// /// Договор аренды (контракт) /// Фиксирует факт выдачи автомобиля клиенту /// +[Table("rentals")] public class Rental { /// /// Уникальный ID контракта /// + [Column("id")] public int Id { get; set; } /// - /// Внешний ключ на автомобиль + /// Дата и время выдачи автомобиля клиенту /// - public required int CarId { get; set; } + [Column("rental_date")] + public required DateTime RentalDate { get; set; } /// - /// Внешний ключ на клиента + /// Длительность аренды в часах /// - public required int ClientId { get; set; } + [Column("rental_hours")] + public required int RentalHours { get; set; } /// - /// Дата и время выдачи автомобиля клиенту + /// Внешний ключ на автомобиль /// - public required DateTime RentalDate { get; set; } + [Column("car_id")] + public required int CarId { get; set; } /// - /// Длительность аренды в часах + /// Внешний ключ на клиента /// - public required int RentalHours { get; set; } + [Column("client_id")] + public required int ClientId { get; set; } /// /// Ссылка на арендуемый автомобиль /// - public required Car Car { get; set; } + public Car? Car { get; set; } /// /// Ссылка на клиента, арендовавшего машину /// - public required Client Client { get; set; } -} + public Client? Client { get; set; } +} \ No newline at end of file diff --git a/CarRental/CarRental/CarRental.Domain/Interfaces/IRepository.cs b/CarRental/CarRental/CarRental.Domain/Interfaces/IRepository.cs new file mode 100644 index 000000000..26c2c675c --- /dev/null +++ b/CarRental/CarRental/CarRental.Domain/Interfaces/IRepository.cs @@ -0,0 +1,16 @@ +using System.Linq.Expressions; + +namespace CarRental.Domain.Interfaces; + +public interface IRepository where T : class +{ + public Task GetByIdAsync(int id); + public Task> GetAllAsync(); + public Task AddAsync(T entity); + public Task UpdateAsync(T entity); + public Task DeleteAsync(int id); + + public Task GetByIdAsync(int id, Func, IQueryable>? include = null); + public Task> GetAllAsync(Func, IQueryable>? include = null); + public IQueryable GetQueryable(Func, IQueryable>? include = null); +} \ No newline at end of file diff --git a/CarRental/CarRental/CarRental.Tests/CarRentalTests.cs b/CarRental/CarRental/CarRental.Tests/CarRentalTests.cs index 9cba886e3..30ef04730 100644 --- a/CarRental/CarRental/CarRental.Tests/CarRentalTests.cs +++ b/CarRental/CarRental/CarRental.Tests/CarRentalTests.cs @@ -22,16 +22,17 @@ public void GetClientsByModelSortedByName() const string expectedThirdName = "Igor Kozlovsky"; var clients = fixture.Rentals - .Where(r => r.Car.ModelGeneration.Model.Name == targetModel) + .Where(r => r.Car?.ModelGeneration?.Model?.Name == targetModel) .Select(r => r.Client) + .Where(client => client != null) .Distinct() - .OrderBy(c => c.FullName) + .OrderBy(c => c!.FullName) .ToList(); Assert.Equal(expectedCount, clients.Count); - Assert.Equal(expectedFirstName, clients[0].FullName); - Assert.Equal(expectedSecondName, clients[1].FullName); - Assert.Equal(expectedThirdName, clients[2].FullName); + Assert.Equal(expectedFirstName, clients[0]?.FullName); + Assert.Equal(expectedSecondName, clients[1]?.FullName); + Assert.Equal(expectedThirdName, clients[2]?.FullName); } /// @@ -44,12 +45,13 @@ public void GetCurrentlyRentedCars() var expectedPlate = "K234MR163"; var rentedCars = fixture.Rentals - .Where(r => r.RentalDate.AddHours(r.RentalHours) > testDate) + .Where(r => r.Car != null && r.RentalDate.AddHours(r.RentalHours) > testDate) .Select(r => r.Car) + .Where(car => car != null) .Distinct() .ToList(); - Assert.Contains(rentedCars, c => c.LicensePlate == expectedPlate); + Assert.Contains(rentedCars, c => c?.LicensePlate == expectedPlate); } /// @@ -63,14 +65,16 @@ public void GetTop5MostRentedCars() const int expectedTopCarRentalCount = 3; var topCars = fixture.Rentals + .Where(r => r.Car != null) .GroupBy(r => r.Car) .Select(g => new { Car = g.Key, RentalCount = g.Count() }) + .Where(x => x.Car != null) .OrderByDescending(x => x.RentalCount) .Take(5) .ToList(); Assert.Equal(expectedCount, topCars.Count); - Assert.Equal(expectedTopCarPlate, topCars[0].Car.LicensePlate); + Assert.Equal(expectedTopCarPlate, topCars[0].Car?.LicensePlate); Assert.Equal(expectedTopCarRentalCount, topCars[0].RentalCount); } @@ -87,6 +91,7 @@ public void GetRentalCountPerCar() const int bmwCarId = 1; var carsWithRentalCount = fixture.Cars + .Where(car => car != null) .Select(car => new { Car = car, @@ -114,18 +119,21 @@ public void GetTop5ClientsByRentalAmount() const string expectedTopClientName = "Olga Zakharova"; var topClients = fixture.Rentals - .GroupBy(r => r.Client) - .Select(g => new + .Where(r => r.Client != null && r.Car?.ModelGeneration != null) + .Select(r => new { - Client = g.Key, - TotalAmount = g.Sum(r => r.RentalHours * r.Car.ModelGeneration.RentalPricePerHour) + Client = r.Client, + Amount = r.RentalHours * r.Car!.ModelGeneration!.RentalPricePerHour }) + .GroupBy(x => x.Client) + .Select(g => new { Client = g.Key, TotalAmount = g.Sum(x => x.Amount) }) + .Where(x => x.Client != null) .OrderByDescending(x => x.TotalAmount) .Take(5) .ToList(); Assert.Equal(expectedCount, topClients.Count); - Assert.Equal(expectedTopClientName, topClients[0].Client.FullName); + Assert.Equal(expectedTopClientName, topClients[0].Client?.FullName); } } From cf8f1fc454ca3dddc654d4036c0e024ee4618ce0 Mon Sep 17 00:00:00 2001 From: ahewbu Date: Tue, 17 Feb 2026 02:19:32 +0400 Subject: [PATCH 16/20] =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D1=8B=20DTO=20+=20mapping?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CarRental.Application.Contracts.csproj | 14 +++++++ .../Dto/AnalyticsDto.cs | 8 ++++ .../Dto/CarEditDto.cs | 13 +++++++ .../Dto/CarGetDto.cs | 15 ++++++++ .../Dto/CarModelEditDto.cs | 17 +++++++++ .../Dto/CarModelGetDto.cs | 19 ++++++++++ .../Dto/ClientAnalyticsDto.cs | 8 ++++ .../Dto/ClientEditDto.cs | 13 +++++++ .../Dto/ClientGetDto.cs | 15 ++++++++ .../Dto/ModelGenerationEditDto.cs | 17 +++++++++ .../Dto/ModelGenerationGetDto.cs | 19 ++++++++++ .../Dto/RentalEditDto.cs | 15 ++++++++ .../Dto/RentalGetDto.cs | 17 +++++++++ .../MappingProfile.cs | 37 +++++++++++++++++++ CarRental/CarRental/CarRental.slnx | 1 + 15 files changed, 228 insertions(+) create mode 100644 CarRental/CarRental/CarRental.Application.Contracts/CarRental.Application.Contracts.csproj create mode 100644 CarRental/CarRental/CarRental.Application.Contracts/Dto/AnalyticsDto.cs create mode 100644 CarRental/CarRental/CarRental.Application.Contracts/Dto/CarEditDto.cs create mode 100644 CarRental/CarRental/CarRental.Application.Contracts/Dto/CarGetDto.cs create mode 100644 CarRental/CarRental/CarRental.Application.Contracts/Dto/CarModelEditDto.cs create mode 100644 CarRental/CarRental/CarRental.Application.Contracts/Dto/CarModelGetDto.cs create mode 100644 CarRental/CarRental/CarRental.Application.Contracts/Dto/ClientAnalyticsDto.cs create mode 100644 CarRental/CarRental/CarRental.Application.Contracts/Dto/ClientEditDto.cs create mode 100644 CarRental/CarRental/CarRental.Application.Contracts/Dto/ClientGetDto.cs create mode 100644 CarRental/CarRental/CarRental.Application.Contracts/Dto/ModelGenerationEditDto.cs create mode 100644 CarRental/CarRental/CarRental.Application.Contracts/Dto/ModelGenerationGetDto.cs create mode 100644 CarRental/CarRental/CarRental.Application.Contracts/Dto/RentalEditDto.cs create mode 100644 CarRental/CarRental/CarRental.Application.Contracts/Dto/RentalGetDto.cs create mode 100644 CarRental/CarRental/CarRental.Application.Contracts/MappingProfile.cs diff --git a/CarRental/CarRental/CarRental.Application.Contracts/CarRental.Application.Contracts.csproj b/CarRental/CarRental/CarRental.Application.Contracts/CarRental.Application.Contracts.csproj new file mode 100644 index 000000000..1e76dcbfc --- /dev/null +++ b/CarRental/CarRental/CarRental.Application.Contracts/CarRental.Application.Contracts.csproj @@ -0,0 +1,14 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + diff --git a/CarRental/CarRental/CarRental.Application.Contracts/Dto/AnalyticsDto.cs b/CarRental/CarRental/CarRental.Application.Contracts/Dto/AnalyticsDto.cs new file mode 100644 index 000000000..6be43e18d --- /dev/null +++ b/CarRental/CarRental/CarRental.Application.Contracts/Dto/AnalyticsDto.cs @@ -0,0 +1,8 @@ +namespace CarRental.Application.Contracts.Dto; + +/// +/// DTO для отображения количества арендованных автомобилей +/// +/// Информация об автомобиле +/// Количество прокатов этого автомобиля +public record CarRentalCountDto(CarGetDto Car, int RentalCount); \ No newline at end of file diff --git a/CarRental/CarRental/CarRental.Application.Contracts/Dto/CarEditDto.cs b/CarRental/CarRental/CarRental.Application.Contracts/Dto/CarEditDto.cs new file mode 100644 index 000000000..693139711 --- /dev/null +++ b/CarRental/CarRental/CarRental.Application.Contracts/Dto/CarEditDto.cs @@ -0,0 +1,13 @@ +namespace CarRental.Application.Contracts.Dto; + +/// +/// DTO для создания и обновления автомобилей +/// +/// Номерной знак автомобиля +/// Цвет автомобиля +/// Идентификатор поколения модели +public record CarEditDto( + string LicensePlate, + string Color, + int ModelGenerationId +); \ No newline at end of file diff --git a/CarRental/CarRental/CarRental.Application.Contracts/Dto/CarGetDto.cs b/CarRental/CarRental/CarRental.Application.Contracts/Dto/CarGetDto.cs new file mode 100644 index 000000000..61745bfbe --- /dev/null +++ b/CarRental/CarRental/CarRental.Application.Contracts/Dto/CarGetDto.cs @@ -0,0 +1,15 @@ +namespace CarRental.Application.Contracts.Dto; + +/// +/// DTO для получения информации об автомобиле +/// +/// Уникальный идентификатор автомобиля +/// Номерной знак автомобиля +/// Цвет автомобиля +/// Информация о создании модели, включая подробные сведения о модели +public record CarGetDto( + int Id, + string LicensePlate, + string Color, + ModelGenerationGetDto ModelGeneration +); \ No newline at end of file diff --git a/CarRental/CarRental/CarRental.Application.Contracts/Dto/CarModelEditDto.cs b/CarRental/CarRental/CarRental.Application.Contracts/Dto/CarModelEditDto.cs new file mode 100644 index 000000000..182a33ff5 --- /dev/null +++ b/CarRental/CarRental/CarRental.Application.Contracts/Dto/CarModelEditDto.cs @@ -0,0 +1,17 @@ +namespace CarRental.Application.Contracts.Dto; + +/// +/// DTO для создания и обновления моделей автомобилей +/// +/// Название модели автомобиля (например, "BMW 3 Series") +/// Тип привода (FWD, RWD, AWD, 4WD) +/// Количество посадочных мест в автомобиле +/// Тип кузова (Sedan, SUV, Coupe, и т.д.) +/// Класс автомобиля (Economy, Premium, Luxury, и т.д.) +public record CarModelEditDto( + string Name, + string DriveType, + int SeatsCount, + string BodyType, + string Class +); \ No newline at end of file diff --git a/CarRental/CarRental/CarRental.Application.Contracts/Dto/CarModelGetDto.cs b/CarRental/CarRental/CarRental.Application.Contracts/Dto/CarModelGetDto.cs new file mode 100644 index 000000000..bba44edb7 --- /dev/null +++ b/CarRental/CarRental/CarRental.Application.Contracts/Dto/CarModelGetDto.cs @@ -0,0 +1,19 @@ +namespace CarRental.Application.Contracts.Dto; + +/// +/// DTO для получения информации о модели автомобиля +/// +/// Уникальный идентификатор модели автомобиля +/// Название модели автомобиля (например, "BMW 3 Series") +/// Тип привода (FWD, RWD, AWD, 4WD) +/// Количество посадочных мест в автомобиле +/// Тип кузова (Sedan, SUV, Coupe, и т.д.) +/// Класс автомобиля (Economy, Premium, Luxury, и т.д.) +public record CarModelGetDto( + int Id, + string Name, + string DriveType, + int SeatsCount, + string BodyType, + string Class +); \ No newline at end of file diff --git a/CarRental/CarRental/CarRental.Application.Contracts/Dto/ClientAnalyticsDto.cs b/CarRental/CarRental/CarRental.Application.Contracts/Dto/ClientAnalyticsDto.cs new file mode 100644 index 000000000..4ae25b474 --- /dev/null +++ b/CarRental/CarRental/CarRental.Application.Contracts/Dto/ClientAnalyticsDto.cs @@ -0,0 +1,8 @@ +namespace CarRental.Application.Contracts.Dto; + +/// +/// DTO для отображения сумм арендной платы клиентов +/// +/// Информация о клиенте +/// Общая сумма арендной платы для данного клиента +public record ClientRentalAmountDto(ClientGetDto Client, decimal TotalAmount); \ No newline at end of file diff --git a/CarRental/CarRental/CarRental.Application.Contracts/Dto/ClientEditDto.cs b/CarRental/CarRental/CarRental.Application.Contracts/Dto/ClientEditDto.cs new file mode 100644 index 000000000..769d9deb4 --- /dev/null +++ b/CarRental/CarRental/CarRental.Application.Contracts/Dto/ClientEditDto.cs @@ -0,0 +1,13 @@ +namespace CarRental.Application.Contracts.Dto; + +/// +/// DTO для создания и обновления клиентов +/// +/// Номер водительского удостоверения +/// Полное имя клиента +/// Дата рождения клиента +public record ClientEditDto( + string LicenseNumber, + string FullName, + DateOnly BirthDate +); \ No newline at end of file diff --git a/CarRental/CarRental/CarRental.Application.Contracts/Dto/ClientGetDto.cs b/CarRental/CarRental/CarRental.Application.Contracts/Dto/ClientGetDto.cs new file mode 100644 index 000000000..3f8e1c9a2 --- /dev/null +++ b/CarRental/CarRental/CarRental.Application.Contracts/Dto/ClientGetDto.cs @@ -0,0 +1,15 @@ +namespace CarRental.Application.Contracts.Dto; + +/// +/// DTO для получения информации о клиенте +/// +/// Уникальный идентификатор клиента +/// Номер водительского удостоверения +/// Полное имя клиента +/// Дата рождения клиента +public record ClientGetDto( + int Id, + string LicenseNumber, + string FullName, + DateOnly BirthDate +); \ No newline at end of file diff --git a/CarRental/CarRental/CarRental.Application.Contracts/Dto/ModelGenerationEditDto.cs b/CarRental/CarRental/CarRental.Application.Contracts/Dto/ModelGenerationEditDto.cs new file mode 100644 index 000000000..67260c51f --- /dev/null +++ b/CarRental/CarRental/CarRental.Application.Contracts/Dto/ModelGenerationEditDto.cs @@ -0,0 +1,17 @@ +namespace CarRental.Application.Contracts.Dto; + +/// +/// DTO для создания и обновления поколений моделей +/// +/// Год выпуска поколения модели +/// Объем двигателя в литрах +/// Тип трансмиссии (MT, AT, CVT) +/// Стоимость аренды в час +/// Идентификатор модели автомобиля +public record ModelGenerationEditDto( + int Year, + double EngineVolume, + string Transmission, + decimal RentalPricePerHour, + int ModelId +); \ No newline at end of file diff --git a/CarRental/CarRental/CarRental.Application.Contracts/Dto/ModelGenerationGetDto.cs b/CarRental/CarRental/CarRental.Application.Contracts/Dto/ModelGenerationGetDto.cs new file mode 100644 index 000000000..db938281b --- /dev/null +++ b/CarRental/CarRental/CarRental.Application.Contracts/Dto/ModelGenerationGetDto.cs @@ -0,0 +1,19 @@ +namespace CarRental.Application.Contracts.Dto; + +/// +/// DTO для получения информации о генерации модели +/// +/// Уникальный идентификатор генерации модели +/// Год выпуска поколения модели +/// Объем двигателя в литрах +/// Тип трансмиссии (MT, AT, вариатор) +/// Стоимость аренды в час +/// Информация о модели автомобиля +public record ModelGenerationGetDto( + int Id, + int Year, + double EngineVolume, + string Transmission, + decimal RentalPricePerHour, + CarModelGetDto Model +); \ No newline at end of file diff --git a/CarRental/CarRental/CarRental.Application.Contracts/Dto/RentalEditDto.cs b/CarRental/CarRental/CarRental.Application.Contracts/Dto/RentalEditDto.cs new file mode 100644 index 000000000..f8a168404 --- /dev/null +++ b/CarRental/CarRental/CarRental.Application.Contracts/Dto/RentalEditDto.cs @@ -0,0 +1,15 @@ +namespace CarRental.Application.Contracts.Dto; + +/// +/// DTO для создания и обновления проката +/// +/// Дата и время начала аренды +/// Продолжительность аренды в часах +/// Идентификатор арендованного автомобиля +/// Идентификатор клиента +public record RentalEditDto( + DateTime RentalDate, + int RentalHours, + int CarId, + int ClientId +); \ No newline at end of file diff --git a/CarRental/CarRental/CarRental.Application.Contracts/Dto/RentalGetDto.cs b/CarRental/CarRental/CarRental.Application.Contracts/Dto/RentalGetDto.cs new file mode 100644 index 000000000..eb379969b --- /dev/null +++ b/CarRental/CarRental/CarRental.Application.Contracts/Dto/RentalGetDto.cs @@ -0,0 +1,17 @@ +namespace CarRental.Application.Contracts.Dto; + +/// +/// DTO для получения информации об аренде +/// +/// Уникальный идентификатор объекта аренды +/// Дата и время начала аренды +/// Продолжительность аренды в часах +/// Информация об арендованном автомобиле +/// Информация о клиенте +public record RentalGetDto( + int Id, + DateTime RentalDate, + int RentalHours, + CarGetDto Car, + ClientGetDto Client +); \ No newline at end of file diff --git a/CarRental/CarRental/CarRental.Application.Contracts/MappingProfile.cs b/CarRental/CarRental/CarRental.Application.Contracts/MappingProfile.cs new file mode 100644 index 000000000..e396443e3 --- /dev/null +++ b/CarRental/CarRental/CarRental.Application.Contracts/MappingProfile.cs @@ -0,0 +1,37 @@ +using AutoMapper; +using CarRental.Application.Contracts.Dto; +using CarRental.Domain.Entities; +using System.Runtime.ConstrainedExecution; +using static System.Runtime.InteropServices.JavaScript.JSType; + +namespace CarRental.Application.Contracts; + +public class MappingProfile : Profile +{ + public MappingProfile() + { + CreateMap() + .ForMember(dest => dest.ModelGeneration, + opt => opt.MapFrom(src => src.ModelGeneration)); + + CreateMap(); + + CreateMap(); + CreateMap(); + + CreateMap(); + CreateMap(); + + CreateMap() + .ForMember(dest => dest.Model, + opt => opt.MapFrom(src => src.Model)); + CreateMap(); + + CreateMap() + .ForMember(dest => dest.Car, + opt => opt.MapFrom(src => src.Car)) + .ForMember(dest => dest.Client, + opt => opt.MapFrom(src => src.Client)); + CreateMap(); + } +} \ No newline at end of file diff --git a/CarRental/CarRental/CarRental.slnx b/CarRental/CarRental/CarRental.slnx index a8febc9ef..306c1bc48 100644 --- a/CarRental/CarRental/CarRental.slnx +++ b/CarRental/CarRental/CarRental.slnx @@ -1,4 +1,5 @@ + From 76e4218a2c93e9bb24b11350c460d99171d587eb Mon Sep 17 00:00:00 2001 From: ahewbu Date: Tue, 17 Feb 2026 05:22:59 +0400 Subject: [PATCH 17/20] =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D1=8B=20=D1=81=D0=B5=D1=80=D0=B2=D0=B8=D1=81=D1=8B?= =?UTF-8?q?,=20=D0=BA=D0=BE=D0=BD=D1=82=D1=80=D0=BE=D0=BB=D0=BB=D0=B5?= =?UTF-8?q?=D1=80=D1=8B=20=D0=B8=20=D0=BA=D0=BE=D0=BD=D1=84=D0=B8=D0=B3?= =?UTF-8?q?=D0=B8=20AppHost?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CarRental.API/CarRental.API.csproj | 25 +++ .../Controllers/AnalyticsController.cs | 151 ++++++++++++++++++ .../Controllers/CarModelsController.cs | 92 +++++++++++ .../Controllers/CarsController.cs | 124 ++++++++++++++ .../Controllers/ClientsController.cs | 92 +++++++++++ .../Controllers/ModelGenerationsController.cs | 117 ++++++++++++++ .../Controllers/RentalsController.cs | 140 ++++++++++++++++ CarRental/CarRental/CarRental.API/Program.cs | 85 ++++++++++ .../Properties/launchSettings.json | 41 +++++ .../appsettings.Development.json | 8 + .../CarRental/CarRental.API/appsettings.json | 9 ++ .../CarRental.AppHost.csproj | 21 +++ .../CarRental/CarRental.AppHost/Program.cs | 16 ++ .../Properties/launchSettings.json | 17 ++ .../appsettings.Development.json | 19 +++ .../CarRental.AppHost/appsettings.json | 12 ++ .../CarRental.Application.Contracts.csproj | 11 +- .../CarRental.ServiceDefaults.csproj | 18 +++ .../CarRental.ServiceDefaults/Extensions.cs | 88 ++++++++++ CarRental/CarRental/CarRental.slnx | 9 +- 20 files changed, 1087 insertions(+), 8 deletions(-) create mode 100644 CarRental/CarRental/CarRental.API/CarRental.API.csproj create mode 100644 CarRental/CarRental/CarRental.API/Controllers/AnalyticsController.cs create mode 100644 CarRental/CarRental/CarRental.API/Controllers/CarModelsController.cs create mode 100644 CarRental/CarRental/CarRental.API/Controllers/CarsController.cs create mode 100644 CarRental/CarRental/CarRental.API/Controllers/ClientsController.cs create mode 100644 CarRental/CarRental/CarRental.API/Controllers/ModelGenerationsController.cs create mode 100644 CarRental/CarRental/CarRental.API/Controllers/RentalsController.cs create mode 100644 CarRental/CarRental/CarRental.API/Program.cs create mode 100644 CarRental/CarRental/CarRental.API/Properties/launchSettings.json create mode 100644 CarRental/CarRental/CarRental.API/appsettings.Development.json create mode 100644 CarRental/CarRental/CarRental.API/appsettings.json create mode 100644 CarRental/CarRental/CarRental.AppHost/CarRental.AppHost.csproj create mode 100644 CarRental/CarRental/CarRental.AppHost/Program.cs create mode 100644 CarRental/CarRental/CarRental.AppHost/Properties/launchSettings.json create mode 100644 CarRental/CarRental/CarRental.AppHost/appsettings.Development.json create mode 100644 CarRental/CarRental/CarRental.AppHost/appsettings.json create mode 100644 CarRental/CarRental/CarRental.ServiceDefaults/CarRental.ServiceDefaults.csproj create mode 100644 CarRental/CarRental/CarRental.ServiceDefaults/Extensions.cs diff --git a/CarRental/CarRental/CarRental.API/CarRental.API.csproj b/CarRental/CarRental/CarRental.API/CarRental.API.csproj new file mode 100644 index 000000000..2686cf64b --- /dev/null +++ b/CarRental/CarRental/CarRental.API/CarRental.API.csproj @@ -0,0 +1,25 @@ + + + net8.0 + enable + enable + true + $(NoWarn);1591 + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + \ No newline at end of file diff --git a/CarRental/CarRental/CarRental.API/Controllers/AnalyticsController.cs b/CarRental/CarRental/CarRental.API/Controllers/AnalyticsController.cs new file mode 100644 index 000000000..acfb8b662 --- /dev/null +++ b/CarRental/CarRental/CarRental.API/Controllers/AnalyticsController.cs @@ -0,0 +1,151 @@ +using AutoMapper; +using CarRental.Application.Contracts.Dto; +using CarRental.Domain.Entities; +using CarRental.Domain.Interfaces; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; + +namespace CarRental.Api.Controllers; + +/// +/// Контроллер для аналитических запросов и отчетов +/// +[ApiController] +[Route("api/analytics")] +public class AnalyticsController( + IRepository rentalsRepo, + IRepository carsRepo, + IMapper mapper) : ControllerBase +{ + /// + /// Получает список клиентов, арендовавших автомобили указанной модели, отсортированный по названию + /// + /// Название модели автомобиля + /// Список клиентов + [HttpGet("clients-by-model")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task>> GetClientsByModelSortedByName( + [FromQuery] string modelName) + { + var rentalsQuery = rentalsRepo.GetQueryable( + include: query => query + .Include(r => r.Car) + .ThenInclude(c => c.ModelGeneration) + .ThenInclude(mg => mg.Model) + .Include(r => r.Client)); + + var clients = await rentalsQuery + .Where(r => r.Car.ModelGeneration.Model.Name == modelName) + .Select(r => r.Client) + .Distinct() + .OrderBy(c => c.FullName) + .ToListAsync(); + + var result = clients + .Select(mapper.Map) + .ToList(); + + return Ok(result); + } + + /// + /// Получает арендованные в данный момент автомобили + /// + /// Текущая дата проверки + /// Список арендованных автомобилей + [HttpGet("currently-rented-cars")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task>> GetCurrentlyRentedCars( + [FromQuery] DateTime currentDate) + { + var rentals = await rentalsRepo.GetAllAsync( + include: query => query.Include(r => r.Car)); + + var rentedCars = rentals + .Where(r => r.RentalDate.AddHours(r.RentalHours) > currentDate) + .Select(r => r.Car) + .Distinct() + .Select(mapper.Map) + .ToList(); + + return Ok(rentedCars); + } + + /// + /// Получает топ 5 самых популярных арендованных автомобилей + /// + /// Список автомобилей, которые можно взять напрокат + [HttpGet("top-5-most-rented-cars")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task>> GetTop5MostRentedCars() + { + var rentals = await rentalsRepo.GetAllAsync( + include: query => query.Include(r => r.Car)); + + var topCars = rentals + .GroupBy(r => r.Car) + .Select(g => new { Car = g.Key, RentalCount = g.Count() }) + .OrderByDescending(x => x.RentalCount) + .Take(5) + .Select(x => new CarRentalCountDto( + mapper.Map(x.Car), + x.RentalCount)) + .ToList(); + + return Ok(topCars); + } + + /// + /// Получает количество арендованных автомобилей для каждого автомобиля + /// + /// Список всех автомобилей, которые были взяты в аренду + [HttpGet("rental-count-per-car")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task>> GetRentalCountPerCar() + { + var rentals = await rentalsRepo.GetAllAsync(); + var cars = await carsRepo.GetAllAsync( + include: query => query.Include(c => c.ModelGeneration)); + + var carsWithRentalCount = cars + .Select(car => new CarRentalCountDto( + mapper.Map(car), + rentals.Count(r => r.CarId == car.Id))) + .OrderByDescending(x => x.RentalCount) + .ToList(); + + return Ok(carsWithRentalCount); + } + + /// + /// Получает топ 5 клиентов по общей сумме аренды + /// + /// Список клиентов с общей суммой арендной платы + [HttpGet("top-5-clients-by-rental-amount")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task>> GetTop5ClientsByRentalAmount() + { + var rentals = await rentalsRepo.GetAllAsync( + include: query => query + .Include(r => r.Car) + .ThenInclude(c => c.ModelGeneration) + .Include(r => r.Client)); + + var topClients = rentals + .Select(r => new + { + Client = r.Client, + Amount = r.RentalHours * r.Car.ModelGeneration.RentalPricePerHour + }) + .GroupBy(x => x.Client) + .Select(g => new { Client = g.Key, TotalAmount = g.Sum(x => x.Amount) }) + .OrderByDescending(x => x.TotalAmount) + .Take(5) + .Select(x => new ClientRentalAmountDto( + mapper.Map(x.Client), + x.TotalAmount)) + .ToList(); + + return Ok(topClients); + } +} \ No newline at end of file diff --git a/CarRental/CarRental/CarRental.API/Controllers/CarModelsController.cs b/CarRental/CarRental/CarRental.API/Controllers/CarModelsController.cs new file mode 100644 index 000000000..faeb8d19d --- /dev/null +++ b/CarRental/CarRental/CarRental.API/Controllers/CarModelsController.cs @@ -0,0 +1,92 @@ +using AutoMapper; +using CarRental.Application.Contracts.Dto; +using CarRental.Domain.Entities; +using CarRental.Domain.Interfaces; +using Microsoft.AspNetCore.Mvc; + +namespace CarRental.Api.Controllers; + +/// +/// Контроллер для управления моделями автомобилей +/// +[ApiController] +[Route("api/car-models")] +public class CarModelsController( + IRepository repo, + IMapper mapper) : ControllerBase +{ + /// + /// Получает доступ ко всем моделям автомобилей + /// + /// Список всех моделей автомобилей + [HttpGet] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task>> GetAll() + { + var entities = await repo.GetAllAsync(); + var dtos = mapper.Map>(entities); + return Ok(dtos); + } + + /// + /// Получает модель автомобиля по идентификатору + /// + /// Идентификатор модели + /// Модель автомобиля + [HttpGet("{id}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task> Get(int id) + { + var entity = await repo.GetByIdAsync(id); + if (entity == null) return NotFound(); + var dto = mapper.Map(entity); + return Ok(dto); + } + + /// + /// Создает новую модель автомобиля + /// + /// Данные для создания модели + /// Созданная модель автомобиля + [HttpPost] + [ProducesResponseType(StatusCodes.Status201Created)] + public async Task> Create([FromBody] CarModelEditDto dto) + { + var entity = mapper.Map(dto); + var created = await repo.AddAsync(entity); + var resultDto = mapper.Map(created); + return CreatedAtAction(nameof(Get), new { id = resultDto.Id }, resultDto); + } + + /// + /// Обновляет существующую модель автомобиля + /// + /// Идентификатор модели + /// Обновленные данные модели + /// Обновленная модель автомобиля + [HttpPut("{id}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task> Update(int id, [FromBody] CarModelEditDto dto) + { + var entity = await repo.GetByIdAsync(id); + if (entity == null) return NotFound(); + mapper.Map(dto, entity); + await repo.UpdateAsync(entity); + var resultDto = mapper.Map(entity); + return Ok(resultDto); + } + + /// + /// Удалить модель автомобиля + /// + /// Идентификатор модели + [HttpDelete("{id}")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public async Task Delete(int id) + { + await repo.DeleteAsync(id); + return NoContent(); + } +} \ No newline at end of file diff --git a/CarRental/CarRental/CarRental.API/Controllers/CarsController.cs b/CarRental/CarRental/CarRental.API/Controllers/CarsController.cs new file mode 100644 index 000000000..7f99fbd4a --- /dev/null +++ b/CarRental/CarRental/CarRental.API/Controllers/CarsController.cs @@ -0,0 +1,124 @@ +using AutoMapper; +using CarRental.Application.Contracts.Dto; +using CarRental.Domain.Entities; +using CarRental.Domain.Interfaces; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; + +namespace CarRental.Api.Controllers; + +/// +/// +/// +[ApiController] +[Route("api/cars")] +public class CarsController( + IRepository repo, + IRepository modelGenerationRepo, + IMapper mapper) : ControllerBase +{ + /// + /// + /// + /// + [HttpGet] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task>> GetAll() + { + var entities = await repo.GetAllAsync( + include: query => query + .Include(c => c.ModelGeneration) + .ThenInclude(mg => mg.Model)); + var dtos = mapper.Map>(entities); + return Ok(dtos); + } + + /// + /// ID + /// + /// + /// + [HttpGet("{id}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task> Get(int id) + { + var entity = await repo.GetByIdAsync(id, + include: query => query + .Include(c => c.ModelGeneration) + .ThenInclude(mg => mg.Model)); + if (entity == null) return NotFound(); + var dto = mapper.Map(entity); + return Ok(dto); + } + + /// + /// + /// + /// + /// + [HttpPost] + [ProducesResponseType(StatusCodes.Status201Created)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public async Task> Create([FromBody] CarEditDto dto) + { + var modelGeneration = await modelGenerationRepo.GetByIdAsync(dto.ModelGenerationId); + if (modelGeneration == null) + return BadRequest($"Model generation with Id {dto.ModelGenerationId} does not exist."); + + var entity = mapper.Map(dto); + var created = await repo.AddAsync(entity); + + // DTO + var carWithIncludes = await repo.GetByIdAsync(created.Id, + include: query => query + .Include(c => c.ModelGeneration) + .ThenInclude(mg => mg.Model)); + var resultDto = mapper.Map(carWithIncludes); + + return CreatedAtAction(nameof(Get), new { id = resultDto.Id }, resultDto); + } + + /// + /// + /// + /// + /// + /// + [HttpPut("{id}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public async Task> Update(int id, [FromBody] CarEditDto dto) + { + var entity = await repo.GetByIdAsync(id); + if (entity == null) return NotFound(); + + var modelGeneration = await modelGenerationRepo.GetByIdAsync(dto.ModelGenerationId); + if (modelGeneration == null) + return BadRequest($"Model generation with Id {dto.ModelGenerationId} does not exist."); + + mapper.Map(dto, entity); + await repo.UpdateAsync(entity); + + var updatedWithIncludes = await repo.GetByIdAsync(entity.Id, + include: query => query + .Include(c => c.ModelGeneration) + .ThenInclude(mg => mg.Model)); + var resultDto = mapper.Map(updatedWithIncludes); + + return Ok(resultDto); + } + + /// + /// + /// + /// + [HttpDelete("{id}")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public async Task Delete(int id) + { + await repo.DeleteAsync(id); + return NoContent(); + } +} \ No newline at end of file diff --git a/CarRental/CarRental/CarRental.API/Controllers/ClientsController.cs b/CarRental/CarRental/CarRental.API/Controllers/ClientsController.cs new file mode 100644 index 000000000..40d2cf804 --- /dev/null +++ b/CarRental/CarRental/CarRental.API/Controllers/ClientsController.cs @@ -0,0 +1,92 @@ +using AutoMapper; +using CarRental.Application.Contracts.Dto; +using CarRental.Domain.Entities; +using CarRental.Domain.Interfaces; +using Microsoft.AspNetCore.Mvc; + +namespace CarRental.Api.Controllers; + +/// +/// Контроллер для управления клиентами +/// +[ApiController] +[Route("api/clients")] +public class ClientsController( + IRepository repo, + IMapper mapper) : ControllerBase +{ + /// + /// Получает всех клиентов + /// + /// Список всех клиентов + [HttpGet] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task>> GetAll() + { + var entities = await repo.GetAllAsync(); + var dtos = mapper.Map>(entities); + return Ok(dtos); + } + + /// + /// Получает клиента по ID + /// + /// Идентификатор клиента + /// Клиент + [HttpGet("{id}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task> Get(int id) + { + var entity = await repo.GetByIdAsync(id); + if (entity == null) return NotFound(); + var dto = mapper.Map(entity); + return Ok(dto); + } + + /// + /// Создает нового клиента + /// + /// Данные для создания клиента + /// Созданный клиент + [HttpPost] + [ProducesResponseType(StatusCodes.Status201Created)] + public async Task> Create([FromBody] ClientEditDto dto) + { + var entity = mapper.Map(dto); + var created = await repo.AddAsync(entity); + var resultDto = mapper.Map(created); + return CreatedAtAction(nameof(Get), new { id = resultDto.Id }, resultDto); + } + + /// + /// Обновляет существующий клиент + /// + /// Идентификатор клиента + /// Обновленные данные о клиентах + /// Обновленный клиент + [HttpPut("{id}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task> Update(int id, [FromBody] ClientEditDto dto) + { + var entity = await repo.GetByIdAsync(id); + if (entity == null) return NotFound(); + mapper.Map(dto, entity); + await repo.UpdateAsync(entity); + var resultDto = mapper.Map(entity); + return Ok(resultDto); + } + + /// + /// Удаляет клиента + /// + /// Идентификатор клиента + [HttpDelete("{id}")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public async Task Delete(int id) + { + await repo.DeleteAsync(id); + return NoContent(); + } +} \ No newline at end of file diff --git a/CarRental/CarRental/CarRental.API/Controllers/ModelGenerationsController.cs b/CarRental/CarRental/CarRental.API/Controllers/ModelGenerationsController.cs new file mode 100644 index 000000000..1a4d77932 --- /dev/null +++ b/CarRental/CarRental/CarRental.API/Controllers/ModelGenerationsController.cs @@ -0,0 +1,117 @@ +using AutoMapper; +using CarRental.Application.Contracts.Dto; +using CarRental.Domain.Entities; +using CarRental.Domain.Interfaces; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; + +namespace CarRental.Api.Controllers; + +/// +/// +/// +[ApiController] +[Route("api/model-generations")] +public class ModelGenerationsController( + IRepository repo, + IRepository carModelRepo, + IMapper mapper) : ControllerBase +{ + /// + /// + /// + /// + [HttpGet] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task>> GetAll() + { + var entities = await repo.GetAllAsync( + include: query => query.Include(mg => mg.Model)); + var dtos = mapper.Map>(entities); + return Ok(dtos); + } + + /// + /// ID + /// + /// + /// + [HttpGet("{id}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task> Get(int id) + { + var entity = await repo.GetByIdAsync(id, + include: query => query.Include(mg => mg.Model)); + if (entity == null) return NotFound(); + var dto = mapper.Map(entity); + return Ok(dto); + } + + /// + /// + /// + /// + /// + [HttpPost] + [ProducesResponseType(StatusCodes.Status201Created)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public async Task> Create([FromBody] ModelGenerationEditDto dto) + { + var carModel = await carModelRepo.GetByIdAsync(dto.ModelId); + if (carModel == null) + return BadRequest($"Car model with Id {dto.ModelId} does not exist."); + + var entity = mapper.Map(dto); + var created = await repo.AddAsync(entity); + + // DTO + var generationWithIncludes = await repo.GetByIdAsync(created.Id, + include: query => query.Include(mg => mg.Model)); + var resultDto = mapper.Map(generationWithIncludes); + + return CreatedAtAction(nameof(Get), new { id = resultDto.Id }, resultDto); + } + + /// + /// + /// + /// + /// + /// + [HttpPut("{id}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public async Task> Update(int id, [FromBody] ModelGenerationEditDto dto) + { + var entity = await repo.GetByIdAsync(id); + if (entity == null) return NotFound(); + + var carModel = await carModelRepo.GetByIdAsync(dto.ModelId); + if (carModel == null) + return BadRequest($"Car model with Id {dto.ModelId} does not exist."); + + mapper.Map(dto, entity); + await repo.UpdateAsync(entity); + + // DTO + var updatedWithIncludes = await repo.GetByIdAsync(entity.Id, + include: query => query.Include(mg => mg.Model)); + var resultDto = mapper.Map(updatedWithIncludes); + + return Ok(resultDto); + } + + /// + /// + /// + /// + [HttpDelete("{id}")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public async Task Delete(int id) + { + await repo.DeleteAsync(id); + return NoContent(); + } +} \ No newline at end of file diff --git a/CarRental/CarRental/CarRental.API/Controllers/RentalsController.cs b/CarRental/CarRental/CarRental.API/Controllers/RentalsController.cs new file mode 100644 index 000000000..f02632935 --- /dev/null +++ b/CarRental/CarRental/CarRental.API/Controllers/RentalsController.cs @@ -0,0 +1,140 @@ +using AutoMapper; +using CarRental.Application.Contracts.Dto; +using CarRental.Domain.Entities; +using CarRental.Domain.Interfaces; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; + +namespace CarRental.Api.Controllers; + +/// +/// Контроллер для управления арендой +/// +[ApiController] +[Route("api/rentals")] +public class RentalsController( + IRepository repo, + IRepository carRepo, + IRepository clientRepo, + IMapper mapper) : ControllerBase +{ + /// + /// Получает все аренды + /// + /// Список всех объектов аренды + [HttpGet] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task>> GetAll() + { + var entities = await repo.GetAllAsync( + include: query => query + .Include(r => r.Car) + .ThenInclude(c => c.ModelGeneration) + .ThenInclude(mg => mg.Model) + .Include(r => r.Client)); + var dtos = mapper.Map>(entities); + return Ok(dtos); + } + + /// + /// Получает аренду по ID + /// + /// Идентификатор аренды + /// Аренда + [HttpGet("{id}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task> Get(int id) + { + var entity = await repo.GetByIdAsync(id, + include: query => query + .Include(r => r.Car) + .ThenInclude(c => c.ModelGeneration) + .ThenInclude(mg => mg.Model) + .Include(r => r.Client)); + if (entity == null) return NotFound(); + var dto = mapper.Map(entity); + return Ok(dto); + } + + /// + /// Создает новую аренду + /// + /// Данные о создании аренды + /// Созданная аренда + [HttpPost] + [ProducesResponseType(StatusCodes.Status201Created)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public async Task> Create([FromBody] RentalEditDto dto) + { + var car = await carRepo.GetByIdAsync(dto.CarId); + if (car == null) + return BadRequest($"Car with Id {dto.CarId} does not exist."); + + var client = await clientRepo.GetByIdAsync(dto.ClientId); + if (client == null) + return BadRequest($"Client with Id {dto.ClientId} does not exist."); + + var entity = mapper.Map(dto); + var created = await repo.AddAsync(entity); + + var rentalWithIncludes = await repo.GetByIdAsync(created.Id, + include: query => query + .Include(r => r.Car) + .ThenInclude(c => c.ModelGeneration) + .ThenInclude(mg => mg.Model) + .Include(r => r.Client)); + var resultDto = mapper.Map(rentalWithIncludes); + + return CreatedAtAction(nameof(Get), new { id = resultDto.Id }, resultDto); + } + + /// + /// Обновляет существующую аренду + /// + /// Идентификатор аренды + /// Обновленные данные об аренде + /// Обновленная аренда + [HttpPut("{id}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public async Task> Update(int id, [FromBody] RentalEditDto dto) + { + var entity = await repo.GetByIdAsync(id); + if (entity == null) return NotFound(); + + var car = await carRepo.GetByIdAsync(dto.CarId); + if (car == null) + return BadRequest($"Car with Id {dto.CarId} does not exist."); + + var client = await clientRepo.GetByIdAsync(dto.ClientId); + if (client == null) + return BadRequest($"Client with Id {dto.ClientId} does not exist."); + + mapper.Map(dto, entity); + await repo.UpdateAsync(entity); + + var updatedWithIncludes = await repo.GetByIdAsync(entity.Id, + include: query => query + .Include(r => r.Car) + .ThenInclude(c => c.ModelGeneration) + .ThenInclude(mg => mg.Model) + .Include(r => r.Client)); + var resultDto = mapper.Map(updatedWithIncludes); + + return Ok(resultDto); + } + + /// + /// Удаляет аренду + /// + /// Идентификатор аренды + [HttpDelete("{id}")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public async Task Delete(int id) + { + await repo.DeleteAsync(id); + return NoContent(); + } +} \ No newline at end of file diff --git a/CarRental/CarRental/CarRental.API/Program.cs b/CarRental/CarRental/CarRental.API/Program.cs new file mode 100644 index 000000000..0185d1bdb --- /dev/null +++ b/CarRental/CarRental/CarRental.API/Program.cs @@ -0,0 +1,85 @@ +using CarRental.Application.Contracts; +using CarRental.Domain.Entities; +using CarRental.Domain.Interfaces; +using CarRental.Infrastructure.Persistence; +using CarRental.Infrastructure.Repositories; +using Microsoft.EntityFrameworkCore; +using System.Reflection; +using System.Runtime.ConstrainedExecution; +using System.Text.Json.Serialization; + +var builder = WebApplication.CreateBuilder(args); + +builder.AddServiceDefaults(); + +builder.Services.AddControllers().AddJsonOptions(options => +{ + options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); +}); + +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(c => +{ + var basePath = AppContext.BaseDirectory; + + var xmlApiPath = Path.Combine(basePath, "CarRental.Api.xml"); + if (File.Exists(xmlApiPath)) + { + c.IncludeXmlComments(xmlApiPath, includeControllerXmlComments: true); + } + + var xmlContractsPath = Path.Combine(basePath, "CarRental.Application.Contracts.xml"); + if (File.Exists(xmlContractsPath)) + { + c.IncludeXmlComments(xmlContractsPath); + } + + var xmlDomainPath = Path.Combine(basePath, "CarRental.Domain.xml"); + if (File.Exists(xmlDomainPath)) + { + c.IncludeXmlComments(xmlDomainPath); + } +}); + +builder.Services.AddAutoMapper(cfg => cfg.AddProfile()); + +builder.Services.AddDbContext(options => + options.UseSqlServer( + builder.Configuration.GetConnectionString("DefaultConnection"))); + +builder.Services.AddScoped, DbRepository>(); +builder.Services.AddScoped, DbRepository>(); +builder.Services.AddScoped, DbRepository>(); +builder.Services.AddScoped, DbRepository>(); +builder.Services.AddScoped, DbRepository>(); + +var app = builder.Build(); + +using (var scope = app.Services.CreateScope()) +{ + var db = scope.ServiceProvider.GetRequiredService(); + db.Database.Migrate(); + + if (!db.CarModels.Any()) + { + Console.WriteLine("Seeding database..."); + var testData = new CarRental.Domain.Data.TestData(); + + db.CarModels.AddRange(testData.CarModels); + db.Clients.AddRange(testData.Clients); + db.ModelGenerations.AddRange(testData.ModelGenerations); + db.Cars.AddRange(testData.Cars); + db.Rentals.AddRange(testData.Rentals); + + db.SaveChanges(); + Console.WriteLine("Database seeded successfully!"); + } +} + +app.UseSwagger(); +app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "Car Rental API")); +app.UseHttpsRedirection(); +app.UseAuthorization(); +app.MapDefaultEndpoints(); +app.MapControllers(); +app.Run(); \ No newline at end of file diff --git a/CarRental/CarRental/CarRental.API/Properties/launchSettings.json b/CarRental/CarRental/CarRental.API/Properties/launchSettings.json new file mode 100644 index 000000000..8abf301bf --- /dev/null +++ b/CarRental/CarRental/CarRental.API/Properties/launchSettings.json @@ -0,0 +1,41 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:22448", + "sslPort": 44345 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5208", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7197;http://localhost:5208", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/CarRental/CarRental/CarRental.API/appsettings.Development.json b/CarRental/CarRental/CarRental.API/appsettings.Development.json new file mode 100644 index 000000000..0c208ae91 --- /dev/null +++ b/CarRental/CarRental/CarRental.API/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/CarRental/CarRental/CarRental.API/appsettings.json b/CarRental/CarRental/CarRental.API/appsettings.json new file mode 100644 index 000000000..10f68b8c8 --- /dev/null +++ b/CarRental/CarRental/CarRental.API/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/CarRental/CarRental/CarRental.AppHost/CarRental.AppHost.csproj b/CarRental/CarRental/CarRental.AppHost/CarRental.AppHost.csproj new file mode 100644 index 000000000..3b3d5007d --- /dev/null +++ b/CarRental/CarRental/CarRental.AppHost/CarRental.AppHost.csproj @@ -0,0 +1,21 @@ + + + + + Exe + net8.0 + enable + enable + true + 191c3bb9-6290-42d5-81ef-3f797aee0fdb + + + + + + + + + + + \ No newline at end of file diff --git a/CarRental/CarRental/CarRental.AppHost/Program.cs b/CarRental/CarRental/CarRental.AppHost/Program.cs new file mode 100644 index 000000000..9b564201c --- /dev/null +++ b/CarRental/CarRental/CarRental.AppHost/Program.cs @@ -0,0 +1,16 @@ +using Aspire.Hosting; +using Aspire.Hosting.SqlServer; + +var builder = DistributedApplication.CreateBuilder(args); + + +var sqlServer = builder.AddSqlServer("bikerental-sql-server") + .AddDatabase("BikeRentalDb"); + +var api = builder.AddProject("carrental-api") + .WithReference(sqlServer, "DefaultConnection") + .WaitFor(sqlServer); + +builder.Configuration["ASPIRE_DASHBOARD_UNSECURED_ALLOW_ANONYMOUS"] = "true"; + +builder.Build().Run(); \ No newline at end of file diff --git a/CarRental/CarRental/CarRental.AppHost/Properties/launchSettings.json b/CarRental/CarRental/CarRental.AppHost/Properties/launchSettings.json new file mode 100644 index 000000000..a37731897 --- /dev/null +++ b/CarRental/CarRental/CarRental.AppHost/Properties/launchSettings.json @@ -0,0 +1,17 @@ +{ + "profiles": { + "CarRental.AppHost": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://localhost:15058", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19141", + "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20144", + "ASPIRE_ALLOW_UNSECURED_TRANSPORT": "true" + } + } + } +} \ No newline at end of file diff --git a/CarRental/CarRental/CarRental.AppHost/appsettings.Development.json b/CarRental/CarRental/CarRental.AppHost/appsettings.Development.json new file mode 100644 index 000000000..b09d7ac12 --- /dev/null +++ b/CarRental/CarRental/CarRental.AppHost/appsettings.Development.json @@ -0,0 +1,19 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug", + "Microsoft.AspNetCore": "Warning", + "Aspire.Hosting.Dcp": "None" + } + }, + "Aspire": { + "Dashboard": { + "Frontend": { + "BrowserAuthMode": "Unsecured" + }, + "ResourceServiceClient": { + "AuthMode": "Unsecured" + } + } + } +} \ No newline at end of file diff --git a/CarRental/CarRental/CarRental.AppHost/appsettings.json b/CarRental/CarRental/CarRental.AppHost/appsettings.json new file mode 100644 index 000000000..c093f65d5 --- /dev/null +++ b/CarRental/CarRental/CarRental.AppHost/appsettings.json @@ -0,0 +1,12 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "ConnectionStrings": { + "DefaultConnection": "" + } +} \ No newline at end of file diff --git a/CarRental/CarRental/CarRental.Application.Contracts/CarRental.Application.Contracts.csproj b/CarRental/CarRental/CarRental.Application.Contracts/CarRental.Application.Contracts.csproj index 1e76dcbfc..12163a6ec 100644 --- a/CarRental/CarRental/CarRental.Application.Contracts/CarRental.Application.Contracts.csproj +++ b/CarRental/CarRental/CarRental.Application.Contracts/CarRental.Application.Contracts.csproj @@ -1,14 +1,15 @@  - - Exe net8.0 enable enable + true + $(NoWarn);1591 - - + + + + - diff --git a/CarRental/CarRental/CarRental.ServiceDefaults/CarRental.ServiceDefaults.csproj b/CarRental/CarRental/CarRental.ServiceDefaults/CarRental.ServiceDefaults.csproj new file mode 100644 index 000000000..2afa7684e --- /dev/null +++ b/CarRental/CarRental/CarRental.ServiceDefaults/CarRental.ServiceDefaults.csproj @@ -0,0 +1,18 @@ + + + net8.0 + enable + enable + true + + + + + + + + + + + + \ No newline at end of file diff --git a/CarRental/CarRental/CarRental.ServiceDefaults/Extensions.cs b/CarRental/CarRental/CarRental.ServiceDefaults/Extensions.cs new file mode 100644 index 000000000..87618230d --- /dev/null +++ b/CarRental/CarRental/CarRental.ServiceDefaults/Extensions.cs @@ -0,0 +1,88 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Diagnostics.HealthChecks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Diagnostics.HealthChecks; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.ServiceDiscovery; +using OpenTelemetry; +using OpenTelemetry.Metrics; +using OpenTelemetry.Trace; + +namespace Microsoft.Extensions.Hosting; + +public static class Extensions +{ + public static TBuilder AddServiceDefaults(this TBuilder builder) where TBuilder : IHostApplicationBuilder + { + builder.ConfigureOpenTelemetry(); + builder.AddDefaultHealthChecks(); + builder.Services.AddServiceDiscovery(); + + builder.Services.ConfigureHttpClientDefaults(http => + { + http.AddStandardResilienceHandler(); + http.AddServiceDiscovery(); + }); + + return builder; + } + + public static TBuilder ConfigureOpenTelemetry(this TBuilder builder) where TBuilder : IHostApplicationBuilder + { + builder.Logging.AddOpenTelemetry(logging => + { + logging.IncludeFormattedMessage = true; + logging.IncludeScopes = true; + }); + + builder.Services.AddOpenTelemetry() + .WithMetrics(metrics => + { + metrics.AddAspNetCoreInstrumentation() + .AddHttpClientInstrumentation() + .AddRuntimeInstrumentation(); + }) + .WithTracing(tracing => + { + tracing.AddSource(builder.Environment.ApplicationName) + .AddAspNetCoreInstrumentation() + .AddHttpClientInstrumentation(); + }); + + builder.AddOpenTelemetryExporters(); + + return builder; + } + + private static TBuilder AddOpenTelemetryExporters(this TBuilder builder) where TBuilder : IHostApplicationBuilder + { + var useOtlpExporter = !string.IsNullOrWhiteSpace(builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]); + if (useOtlpExporter) + { + builder.Services.AddOpenTelemetry().UseOtlpExporter(); + } + + return builder; + } + + public static TBuilder AddDefaultHealthChecks(this TBuilder builder) where TBuilder : IHostApplicationBuilder + { + builder.Services.AddHealthChecks() + .AddCheck("self", () => HealthCheckResult.Healthy(), ["live"]); + return builder; + } + + public static WebApplication MapDefaultEndpoints(this WebApplication app) + { + if (app.Environment.IsDevelopment()) + { + app.MapHealthChecks("/health"); + app.MapHealthChecks("/alive", new HealthCheckOptions + { + Predicate = r => r.Tags.Contains("live") + }); + } + + return app; + } +} \ No newline at end of file diff --git a/CarRental/CarRental/CarRental.slnx b/CarRental/CarRental/CarRental.slnx index 306c1bc48..9ab00b431 100644 --- a/CarRental/CarRental/CarRental.slnx +++ b/CarRental/CarRental/CarRental.slnx @@ -1,5 +1,8 @@ - - - + + + + + + From 1becd0e778d2468ee752f2676d185d9b149f5c50 Mon Sep 17 00:00:00 2001 From: ahewbu Date: Thu, 19 Feb 2026 22:36:17 +0400 Subject: [PATCH 18/20] API + Apphost --- CarRental/CarRental/CarRental.API/Program.cs | 2 +- .../CarRental.Infrastructure.csproj | 18 ++++++ .../Persistence/AppDbContext.cs | 55 ++++++++++++++++ .../Repositories/DbRepository.cs | 63 +++++++++++++++++++ CarRental/CarRental/CarRental.slnx | 13 ++-- README.md | 42 ++++++------- 6 files changed, 162 insertions(+), 31 deletions(-) create mode 100644 CarRental/CarRental/CarRental.Infrastructure/CarRental.Infrastructure.csproj create mode 100644 CarRental/CarRental/CarRental.Infrastructure/Persistence/AppDbContext.cs create mode 100644 CarRental/CarRental/CarRental.Infrastructure/Repositories/DbRepository.cs diff --git a/CarRental/CarRental/CarRental.API/Program.cs b/CarRental/CarRental/CarRental.API/Program.cs index 0185d1bdb..1ad73711d 100644 --- a/CarRental/CarRental/CarRental.API/Program.cs +++ b/CarRental/CarRental/CarRental.API/Program.cs @@ -63,7 +63,7 @@ if (!db.CarModels.Any()) { Console.WriteLine("Seeding database..."); - var testData = new CarRental.Domain.Data.TestData(); + var testData = new CarRental.Domain.Data.CarRentalFixture(); db.CarModels.AddRange(testData.CarModels); db.Clients.AddRange(testData.Clients); diff --git a/CarRental/CarRental/CarRental.Infrastructure/CarRental.Infrastructure.csproj b/CarRental/CarRental/CarRental.Infrastructure/CarRental.Infrastructure.csproj new file mode 100644 index 000000000..877c43686 --- /dev/null +++ b/CarRental/CarRental/CarRental.Infrastructure/CarRental.Infrastructure.csproj @@ -0,0 +1,18 @@ + + + net8.0 + enable + enable + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + \ No newline at end of file diff --git a/CarRental/CarRental/CarRental.Infrastructure/Persistence/AppDbContext.cs b/CarRental/CarRental/CarRental.Infrastructure/Persistence/AppDbContext.cs new file mode 100644 index 000000000..ba7ae018d --- /dev/null +++ b/CarRental/CarRental/CarRental.Infrastructure/Persistence/AppDbContext.cs @@ -0,0 +1,55 @@ +using CarRental.Domain.Entities; +using Microsoft.EntityFrameworkCore; + +namespace CarRental.Infrastructure.Persistence; + +public class AppDbContext(DbContextOptions options) : DbContext(options) +{ + public DbSet Cars { get; set; } + public DbSet Clients { get; set; } + public DbSet CarModels { get; set; } + public DbSet ModelGenerations { get; set; } + public DbSet Rentals { get; set; } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.Entity(entity => + { + entity.HasKey(c => c.Id); + entity.HasOne(c => c.ModelGeneration) + .WithMany() + .HasForeignKey(c => c.ModelGenerationId) + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(mg => mg.Id); + entity.HasOne(mg => mg.Model) + .WithMany() + .HasForeignKey(mg => mg.ModelId) + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(r => r.Id); + entity.HasOne(r => r.Car) + .WithMany() + .HasForeignKey(r => r.CarId) + .OnDelete(DeleteBehavior.Cascade); + entity.HasOne(r => r.Client) + .WithMany() + .HasForeignKey(r => r.ClientId) + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity() + .HasKey(c => c.Id); + + modelBuilder.Entity() + .HasKey(cm => cm.Id); + } +} \ No newline at end of file diff --git a/CarRental/CarRental/CarRental.Infrastructure/Repositories/DbRepository.cs b/CarRental/CarRental/CarRental.Infrastructure/Repositories/DbRepository.cs new file mode 100644 index 000000000..ce920f0e0 --- /dev/null +++ b/CarRental/CarRental/CarRental.Infrastructure/Repositories/DbRepository.cs @@ -0,0 +1,63 @@ +using CarRental.Domain.Interfaces; +using CarRental.Infrastructure.Persistence; +using Microsoft.EntityFrameworkCore; +using System.Linq.Expressions; + +namespace CarRental.Infrastructure.Repositories; + +public class DbRepository(AppDbContext context) : IRepository where T : class +{ + protected readonly DbSet _set = context.Set(); + + public async Task GetByIdAsync(int id) => await _set.FindAsync(id); + + public async Task> GetAllAsync() => await _set.ToListAsync(); + + public async Task AddAsync(T entity) + { + await _set.AddAsync(entity); + await context.SaveChangesAsync(); + return entity; + } + + public async Task UpdateAsync(T entity) + { + _set.Update(entity); + await context.SaveChangesAsync(); + return entity; + } + + public async Task DeleteAsync(int id) + { + var entity = await _set.FindAsync(id); + if (entity == null) return false; + _set.Remove(entity); + await context.SaveChangesAsync(); + return true; + } + + public async Task GetByIdAsync(int id, Func, IQueryable>? include = null) + { + var query = _set.AsQueryable(); + if (include != null) + query = include(query); + + return await query.FirstOrDefaultAsync(e => EF.Property(e, "Id") == id); + } + + public async Task> GetAllAsync(Func, IQueryable>? include = null) + { + var query = _set.AsQueryable(); + if (include != null) + query = include(query); + return await query.ToListAsync(); + } + + public IQueryable GetQueryable(Func, IQueryable>? include = null) + { + var query = _set.AsQueryable(); + if (include != null) + query = include(query); + return query; + } +} \ No newline at end of file diff --git a/CarRental/CarRental/CarRental.slnx b/CarRental/CarRental/CarRental.slnx index 9ab00b431..d2918441c 100644 --- a/CarRental/CarRental/CarRental.slnx +++ b/CarRental/CarRental/CarRental.slnx @@ -1,8 +1,9 @@ - - - - - - + + + + + + + diff --git a/README.md b/README.md index 2596fcabc..5cce7c175 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,16 @@ -# Лабораторная работа 1: Классы и unit-тесты +# Лабораторная работа 2 и 3: Сервер - Реализация серверного приложения с использованием REST API, +# ORM - Реализация объектно-реляционной модели. Подключение к базе данных и настройка оркестрации + + +## Вариант 80 + +* **Platform**: .NET 8 (C# 12) +* **Database**: SqlServer +* **ORM**: Entity Framework Core 8 +* **Messaging**: Apache Kafka +* **Mapping**: AutoMapper +* **Testing**: xUnit, Bogus (Fake Data) + ## Описание предметной области @@ -10,27 +22,7 @@ - **`Client`** – клиент: номер водительского удостоверения, ФИО, дата рождения. - **`Rental`** – договор аренды: клиент, автомобиль, время выдачи, длительность в часах. -## Структура решения - -- `CarRental.Domain` – доменная модель (классы и enum-ы). -- `CarRental.Domain/Data/CarRentalFixture.cs` – тестовые данные (модели, поколения, машины, клиенты, контракты). -- `CarRental.Tests` – проект с unit-тестами (xUnit). -- `.github/workflows/dotnet-tests.yml` – GitHub Actions для сборки и запуска тестов. - -## Выполненные задачи - -### 1. Модель данных -Создана полная объектная модель с использованием классов предметной области. - -### 2. Тестовые данные -Создан класс `CarRentalFixture` содержащий: -- 15 моделей автомобилей -- 15 поколений моделей -- 15 физических экземпляров автомобилей -- 15 клиентов -- 19 контрактов аренды - -### 3. Реализованные тесты (xUnit) +## Реализованные тесты (xUnit) 1. **`GetClientsByModelSortedByName`** – клиенты, бравшие в аренду указанную модель, отсортированные по ФИО. 2. **`GetCurrentlyRentedCars`** – автомобили, которые сейчас находятся в аренде (нет даты возврата). @@ -38,6 +30,8 @@ 4. **`GetRentalCountPerCar`** – количество аренд для каждого автомобиля. 5. **`GetTop5ClientsByRentalAmount`** – топ‑5 клиентов по сумме стоимости аренды. -## Результат +## Результат 2 и 3 лабораторной работы -Создана полная объектная модель предметной области с комплексными unit-тестами, покрывающими все требования задания. +- Реализовано серверное приложение, которое осуществляет базовые CRUD-операции с реализованными в первой лабораторной сущностями и предоставляет результаты аналитических запросов +- Созданы миграции для создания таблиц в бд и их первоначального заполнения. +- Также был создан оркестратор Aspire на запуск сервера и базы данных. From 427594a9cb1a67f05d893672ee4f35087359be14 Mon Sep 17 00:00:00 2001 From: ahewbu Date: Fri, 20 Feb 2026 10:24:01 +0400 Subject: [PATCH 19/20] =?UTF-8?q?=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=20AnalyticsController,=20=D0=B4=D0=BE=D0=B1?= =?UTF-8?q?=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D1=8B=20=D0=BC=D0=B8=D0=B3=D1=80?= =?UTF-8?q?=D0=B0=D1=86=D0=B8=D0=B8,=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D0=B5=D0=BD=D1=8B=20=D0=BF=D0=B0=D0=BA=D0=B5=D1=82?= =?UTF-8?q?=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/AnalyticsController.cs | 112 ++- CarRental/CarRental/CarRental.API/Program.cs | 20 +- .../CarRental/CarRental.AppHost/Program.cs | 11 +- .../CarRental.Domain/Data/CarRentalFixture.cs | 99 +- .../CarRental.Infrastructure.csproj | 7 +- .../20260220055307_InitialCreate.Designer.cs | 889 ++++++++++++++++++ .../20260220055307_InitialCreate.cs | 274 ++++++ .../Migrations/AppDbContextModelSnapshot.cs | 886 +++++++++++++++++ .../Persistence/AppDbContext.cs | 9 +- .../Repositories/DbRepository.cs | 1 - CarRental/CarRental/CarRental.slnx | 2 +- 11 files changed, 2194 insertions(+), 116 deletions(-) create mode 100644 CarRental/CarRental/CarRental.Infrastructure/Migrations/20260220055307_InitialCreate.Designer.cs create mode 100644 CarRental/CarRental/CarRental.Infrastructure/Migrations/20260220055307_InitialCreate.cs create mode 100644 CarRental/CarRental/CarRental.Infrastructure/Migrations/AppDbContextModelSnapshot.cs diff --git a/CarRental/CarRental/CarRental.API/Controllers/AnalyticsController.cs b/CarRental/CarRental/CarRental.API/Controllers/AnalyticsController.cs index acfb8b662..03e53cbba 100644 --- a/CarRental/CarRental/CarRental.API/Controllers/AnalyticsController.cs +++ b/CarRental/CarRental/CarRental.API/Controllers/AnalyticsController.cs @@ -15,6 +15,8 @@ namespace CarRental.Api.Controllers; public class AnalyticsController( IRepository rentalsRepo, IRepository carsRepo, + IRepository clientsRepo, + IRepository generationsRepo, IMapper mapper) : ControllerBase { /// @@ -35,7 +37,7 @@ public async Task>> GetClientsByModelSort .Include(r => r.Client)); var clients = await rentalsQuery - .Where(r => r.Car.ModelGeneration.Model.Name == modelName) + .Where(r => r.Car!.ModelGeneration!.Model!.Name == modelName) .Select(r => r.Client) .Distinct() .OrderBy(c => c.FullName) @@ -58,17 +60,23 @@ public async Task>> GetClientsByModelSort public async Task>> GetCurrentlyRentedCars( [FromQuery] DateTime currentDate) { - var rentals = await rentalsRepo.GetAllAsync( - include: query => query.Include(r => r.Car)); - - var rentedCars = rentals + var rentedCarIds = await rentalsRepo.GetQueryable() .Where(r => r.RentalDate.AddHours(r.RentalHours) > currentDate) - .Select(r => r.Car) + .Select(r => r.CarId) .Distinct() + .ToListAsync(); + + var rentedCars = await carsRepo.GetQueryable() + .Where(c => rentedCarIds.Contains(c.Id)) + .Include(c => c.ModelGeneration) + .ThenInclude(m => m!.Model) + .ToListAsync(); + + var result = rentedCars .Select(mapper.Map) .ToList(); - return Ok(rentedCars); + return Ok(result); } /// @@ -79,20 +87,31 @@ public async Task>> GetCurrentlyRentedCars( [ProducesResponseType(StatusCodes.Status200OK)] public async Task>> GetTop5MostRentedCars() { - var rentals = await rentalsRepo.GetAllAsync( - include: query => query.Include(r => r.Car)); - - var topCars = rentals - .GroupBy(r => r.Car) - .Select(g => new { Car = g.Key, RentalCount = g.Count() }) + var topCarStats = await rentalsRepo.GetQueryable() + .GroupBy(r => r.CarId) + .Select(g => new { CarId = g.Key, RentalCount = g.Count() }) .OrderByDescending(x => x.RentalCount) .Take(5) + .ToListAsync(); + + var topCarIds = topCarStats.Select(x => x.CarId).ToList(); + + var cars = await carsRepo.GetQueryable() + .Where(c => topCarIds.Contains(c.Id)) + .Include(c => c.ModelGeneration) + .ThenInclude(m => m!.Model) + .ToListAsync(); + + var carsDict = cars.ToDictionary(c => c.Id); + + var topCarsResult = topCarStats + .Where(x => carsDict.ContainsKey(x.CarId)) .Select(x => new CarRentalCountDto( - mapper.Map(x.Car), + mapper.Map(carsDict[x.CarId]), x.RentalCount)) .ToList(); - return Ok(topCars); + return Ok(topCarsResult); } /// @@ -103,14 +122,20 @@ public async Task>> GetTop5MostRente [ProducesResponseType(StatusCodes.Status200OK)] public async Task>> GetRentalCountPerCar() { - var rentals = await rentalsRepo.GetAllAsync(); - var cars = await carsRepo.GetAllAsync( - include: query => query.Include(c => c.ModelGeneration)); + var rentalCounts = await rentalsRepo.GetQueryable() + .GroupBy(r => r.CarId) + .Select(g => new { CarId = g.Key, Count = g.Count() }) + .ToDictionaryAsync(x => x.CarId, x => x.Count); + + var cars = await carsRepo.GetQueryable() + .Include(c => c.ModelGeneration) + .ThenInclude(m => m!.Model) + .ToListAsync(); var carsWithRentalCount = cars .Select(car => new CarRentalCountDto( mapper.Map(car), - rentals.Count(r => r.CarId == car.Id))) + rentalCounts.GetValueOrDefault(car.Id, 0))) .OrderByDescending(x => x.RentalCount) .ToList(); @@ -125,27 +150,50 @@ public async Task>> GetRentalCountPe [ProducesResponseType(StatusCodes.Status200OK)] public async Task>> GetTop5ClientsByRentalAmount() { - var rentals = await rentalsRepo.GetAllAsync( - include: query => query - .Include(r => r.Car) - .ThenInclude(c => c.ModelGeneration) - .Include(r => r.Client)); + var rentals = await rentalsRepo.GetQueryable() + .Select(r => new { r.ClientId, r.CarId, r.RentalHours }) + .ToListAsync(); + + var cars = await carsRepo.GetQueryable() + .Select(c => new { c.Id, c.ModelGenerationId }) + .ToListAsync(); + + var generations = await generationsRepo.GetQueryable() + .Select(g => new { g.Id, g.RentalPricePerHour }) + .ToListAsync(); + + var carPrices = cars.Join(generations, + c => c.ModelGenerationId, + g => g.Id, + (c, g) => new { CarId = c.Id, Price = g.RentalPricePerHour }) + .ToDictionary(x => x.CarId, x => x.Price); - var topClients = rentals - .Select(r => new + var topClientStats = rentals + .GroupBy(r => r.ClientId) + .Select(g => new { - Client = r.Client, - Amount = r.RentalHours * r.Car.ModelGeneration.RentalPricePerHour + ClientId = g.Key, + TotalAmount = g.Sum(r => r.RentalHours * carPrices.GetValueOrDefault(r.CarId, 0)) }) - .GroupBy(x => x.Client) - .Select(g => new { Client = g.Key, TotalAmount = g.Sum(x => x.Amount) }) .OrderByDescending(x => x.TotalAmount) .Take(5) + .ToList(); + + var topClientIds = topClientStats.Select(x => x.ClientId).ToList(); + + var clients = await clientsRepo.GetQueryable() + .Where(c => topClientIds.Contains(c.Id)) + .ToListAsync(); + + var clientsDict = clients.ToDictionary(c => c.Id); + + var result = topClientStats + .Where(x => clientsDict.ContainsKey(x.ClientId)) .Select(x => new ClientRentalAmountDto( - mapper.Map(x.Client), + mapper.Map(clientsDict[x.ClientId]), x.TotalAmount)) .ToList(); - return Ok(topClients); + return Ok(result); } } \ No newline at end of file diff --git a/CarRental/CarRental/CarRental.API/Program.cs b/CarRental/CarRental/CarRental.API/Program.cs index 1ad73711d..6f20b751c 100644 --- a/CarRental/CarRental/CarRental.API/Program.cs +++ b/CarRental/CarRental/CarRental.API/Program.cs @@ -1,17 +1,18 @@ using CarRental.Application.Contracts; +using CarRental.Domain.Data; using CarRental.Domain.Entities; using CarRental.Domain.Interfaces; using CarRental.Infrastructure.Persistence; using CarRental.Infrastructure.Repositories; using Microsoft.EntityFrameworkCore; -using System.Reflection; -using System.Runtime.ConstrainedExecution; using System.Text.Json.Serialization; var builder = WebApplication.CreateBuilder(args); builder.AddServiceDefaults(); +builder.Services.AddSingleton(); + builder.Services.AddControllers().AddJsonOptions(options => { options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); @@ -59,21 +60,6 @@ { var db = scope.ServiceProvider.GetRequiredService(); db.Database.Migrate(); - - if (!db.CarModels.Any()) - { - Console.WriteLine("Seeding database..."); - var testData = new CarRental.Domain.Data.CarRentalFixture(); - - db.CarModels.AddRange(testData.CarModels); - db.Clients.AddRange(testData.Clients); - db.ModelGenerations.AddRange(testData.ModelGenerations); - db.Cars.AddRange(testData.Cars); - db.Rentals.AddRange(testData.Rentals); - - db.SaveChanges(); - Console.WriteLine("Database seeded successfully!"); - } } app.UseSwagger(); diff --git a/CarRental/CarRental/CarRental.AppHost/Program.cs b/CarRental/CarRental/CarRental.AppHost/Program.cs index 9b564201c..f0a289646 100644 --- a/CarRental/CarRental/CarRental.AppHost/Program.cs +++ b/CarRental/CarRental/CarRental.AppHost/Program.cs @@ -1,16 +1,11 @@ -using Aspire.Hosting; -using Aspire.Hosting.SqlServer; +var builder = DistributedApplication.CreateBuilder(args); -var builder = DistributedApplication.CreateBuilder(args); - -var sqlServer = builder.AddSqlServer("bikerental-sql-server") - .AddDatabase("BikeRentalDb"); +var sqlServer = builder.AddSqlServer("carrental-sql-server") + .AddDatabase("CarRentalDb"); var api = builder.AddProject("carrental-api") .WithReference(sqlServer, "DefaultConnection") .WaitFor(sqlServer); -builder.Configuration["ASPIRE_DASHBOARD_UNSECURED_ALLOW_ANONYMOUS"] = "true"; - builder.Build().Run(); \ No newline at end of file diff --git a/CarRental/CarRental/CarRental.Domain/Data/CarRentalFixture.cs b/CarRental/CarRental/CarRental.Domain/Data/CarRentalFixture.cs index e94ef7cf1..49117dea4 100644 --- a/CarRental/CarRental/CarRental.Domain/Data/CarRentalFixture.cs +++ b/CarRental/CarRental/CarRental.Domain/Data/CarRentalFixture.cs @@ -2,7 +2,6 @@ namespace CarRental.Domain.Data; - /// /// Fixture с тестовыми данными /// @@ -38,40 +37,40 @@ public CarRentalFixture() ModelGenerations = [ - new() { Id = 1, Year = 2023, EngineVolume = 2.0, Transmission = "AT", RentalPricePerHour = 2200, ModelId = 1, Model = CarModels[0] }, - new() { Id = 2, Year = 2022, EngineVolume = 5.0, Transmission = "AT", RentalPricePerHour = 5000, ModelId = 2, Model = CarModels[1] }, - new() { Id = 3, Year = 2024, EngineVolume = 1.5, Transmission = "CVT", RentalPricePerHour = 1200, ModelId = 3, Model = CarModels[2] }, - new() { Id = 4, Year = 2023, EngineVolume = 3.6, Transmission = "AT", RentalPricePerHour = 2800, ModelId = 4, Model = CarModels[3] }, - new() { Id = 5, Year = 2024, EngineVolume = 3.0, Transmission = "AT", RentalPricePerHour = 8000, ModelId = 5, Model = CarModels[4] }, - new() { Id = 6, Year = 2022, EngineVolume = 5.3, Transmission = "AT", RentalPricePerHour = 3500, ModelId = 6, Model = CarModels[5] }, - new() { Id = 7, Year = 2023, EngineVolume = 1.6, Transmission = "MT", RentalPricePerHour = 700, ModelId = 7, Model = CarModels[6] }, - new() { Id = 8, Year = 2024, EngineVolume = 2.5, Transmission = "AT", RentalPricePerHour = 1800, ModelId = 8, Model = CarModels[7] }, - new() { Id = 9, Year = 2022, EngineVolume = 2.7, Transmission = "MT", RentalPricePerHour = 1500, ModelId = 9, Model = CarModels[8] }, - new() { Id = 10, Year = 2023, EngineVolume = 1.8, Transmission = "CVT", RentalPricePerHour = 1600, ModelId = 10, Model = CarModels[9] }, - new() { Id = 11, Year = 2022, EngineVolume = 2.7, Transmission = "MT", RentalPricePerHour = 1400, ModelId = 11, Model = CarModels[10] }, - new() { Id = 12, Year = 2024, EngineVolume = 3.5, Transmission = "AT", RentalPricePerHour = 3200, ModelId = 12, Model = CarModels[11] }, - new() { Id = 13, Year = 2023, EngineVolume = 3.0, Transmission = "AT", RentalPricePerHour = 6000, ModelId = 13, Model = CarModels[12] }, - new() { Id = 14, Year = 2024, EngineVolume = 2.0, Transmission = "AT", RentalPricePerHour = 2800, ModelId = 14, Model = CarModels[13] }, - new() { Id = 15, Year = 2023, EngineVolume = 1.7, Transmission = "MT", RentalPricePerHour = 900, ModelId = 15, Model = CarModels[14] } + new() { Id = 1, Year = 2023, EngineVolume = 2.0, Transmission = "AT", RentalPricePerHour = 2200, ModelId = 1 }, + new() { Id = 2, Year = 2022, EngineVolume = 5.0, Transmission = "AT", RentalPricePerHour = 5000, ModelId = 2 }, + new() { Id = 3, Year = 2024, EngineVolume = 1.5, Transmission = "CVT", RentalPricePerHour = 1200, ModelId = 3 }, + new() { Id = 4, Year = 2023, EngineVolume = 3.6, Transmission = "AT", RentalPricePerHour = 2800, ModelId = 4 }, + new() { Id = 5, Year = 2024, EngineVolume = 3.0, Transmission = "AT", RentalPricePerHour = 8000, ModelId = 5 }, + new() { Id = 6, Year = 2022, EngineVolume = 5.3, Transmission = "AT", RentalPricePerHour = 3500, ModelId = 6 }, + new() { Id = 7, Year = 2023, EngineVolume = 1.6, Transmission = "MT", RentalPricePerHour = 700, ModelId = 7 }, + new() { Id = 8, Year = 2024, EngineVolume = 2.5, Transmission = "AT", RentalPricePerHour = 1800, ModelId = 8 }, + new() { Id = 9, Year = 2022, EngineVolume = 2.7, Transmission = "MT", RentalPricePerHour = 1500, ModelId = 9 }, + new() { Id = 10, Year = 2023, EngineVolume = 1.8, Transmission = "CVT", RentalPricePerHour = 1600, ModelId = 10 }, + new() { Id = 11, Year = 2022, EngineVolume = 2.7, Transmission = "MT", RentalPricePerHour = 1400, ModelId = 11 }, + new() { Id = 12, Year = 2024, EngineVolume = 3.5, Transmission = "AT", RentalPricePerHour = 3200, ModelId = 12 }, + new() { Id = 13, Year = 2023, EngineVolume = 3.0, Transmission = "AT", RentalPricePerHour = 6000, ModelId = 13 }, + new() { Id = 14, Year = 2024, EngineVolume = 2.0, Transmission = "AT", RentalPricePerHour = 2800, ModelId = 14 }, + new() { Id = 15, Year = 2023, EngineVolume = 1.7, Transmission = "MT", RentalPricePerHour = 900, ModelId = 15 } ]; Cars = [ - new() { Id = 1, LicensePlate = "A001AA163", Color = "Black", ModelGenerationId = 1, ModelGeneration = ModelGenerations[0] }, - new() { Id = 2, LicensePlate = "B777BC163", Color = "Red", ModelGenerationId = 2, ModelGeneration = ModelGenerations[1] }, - new() { Id = 3, LicensePlate = "C123ET163", Color = "White", ModelGenerationId = 3, ModelGeneration = ModelGenerations[2] }, - new() { Id = 4, LicensePlate = "E555KH163", Color = "Green", ModelGenerationId = 4, ModelGeneration = ModelGenerations[3] }, - new() { Id = 5, LicensePlate = "K234MR163", Color = "Silver", ModelGenerationId = 5, ModelGeneration = ModelGenerations[4] }, - new() { Id = 6, LicensePlate = "M888OA163", Color = "Gray", ModelGenerationId = 6, ModelGeneration = ModelGenerations[5] }, - new() { Id = 7, LicensePlate = "N456RS163", Color = "Blue", ModelGenerationId = 7, ModelGeneration = ModelGenerations[6] }, - new() { Id = 8, LicensePlate = "O789TU163", Color = "Brown", ModelGenerationId = 8, ModelGeneration = ModelGenerations[7] }, - new() { Id = 9, LicensePlate = "P321XO163", Color = "White", ModelGenerationId = 9, ModelGeneration = ModelGenerations[8] }, - new() { Id = 10, LicensePlate = "S654AM163", Color = "Black", ModelGenerationId = 10, ModelGeneration = ModelGenerations[9] }, - new() { Id = 11, LicensePlate = "T987RE163", Color = "Orange", ModelGenerationId = 11, ModelGeneration = ModelGenerations[10] }, - new() { Id = 12, LicensePlate = "U246KN163", Color = "White", ModelGenerationId = 12, ModelGeneration = ModelGenerations[11] }, - new() { Id = 13, LicensePlate = "H135VT163", Color = "Black", ModelGenerationId = 13, ModelGeneration = ModelGenerations[12] }, - new() { Id = 14, LicensePlate = "SH579SA163", Color = "Gray", ModelGenerationId = 14, ModelGeneration = ModelGenerations[13] }, - new() { Id = 15, LicensePlate = "SCH864RO163", Color = "Blue", ModelGenerationId = 15, ModelGeneration = ModelGenerations[14] } + new() { Id = 1, LicensePlate = "A001AA163", Color = "Black", ModelGenerationId = 1 }, + new() { Id = 2, LicensePlate = "B777BC163", Color = "Red", ModelGenerationId = 2 }, + new() { Id = 3, LicensePlate = "C123ET163", Color = "White", ModelGenerationId = 3 }, + new() { Id = 4, LicensePlate = "E555KH163", Color = "Green", ModelGenerationId = 4 }, + new() { Id = 5, LicensePlate = "K234MR163", Color = "Silver", ModelGenerationId = 5 }, + new() { Id = 6, LicensePlate = "M888OA163", Color = "Gray", ModelGenerationId = 6 }, + new() { Id = 7, LicensePlate = "N456RS163", Color = "Blue", ModelGenerationId = 7 }, + new() { Id = 8, LicensePlate = "O789TU163", Color = "Brown", ModelGenerationId = 8 }, + new() { Id = 9, LicensePlate = "P321XO163", Color = "White", ModelGenerationId = 9 }, + new() { Id = 10, LicensePlate = "S654AM163", Color = "Black", ModelGenerationId = 10 }, + new() { Id = 11, LicensePlate = "T987RE163", Color = "Orange", ModelGenerationId = 11 }, + new() { Id = 12, LicensePlate = "U246KN163", Color = "White", ModelGenerationId = 12 }, + new() { Id = 13, LicensePlate = "H135VT163", Color = "Black", ModelGenerationId = 13 }, + new() { Id = 14, LicensePlate = "SH579SA163", Color = "Gray", ModelGenerationId = 14 }, + new() { Id = 15, LicensePlate = "SCH864RO163", Color = "Blue", ModelGenerationId = 15 } ]; @@ -96,25 +95,25 @@ public CarRentalFixture() Rentals = [ - new() { Id = 1, CarId = 7, ClientId = 1, RentalDate = new DateTime(2024, 3, 1, 10, 0, 0), RentalHours = 48, Car = Cars[6], Client = Clients[0] }, - new() { Id = 2, CarId = 7, ClientId = 3, RentalDate = new DateTime(2024, 2, 25, 14, 30, 0), RentalHours = 72, Car = Cars[6], Client = Clients[2] }, - new() { Id = 3, CarId = 7, ClientId = 5, RentalDate = new DateTime(2024, 2, 20, 9, 15, 0), RentalHours = 24, Car = Cars[6], Client = Clients[4] }, - new() { Id = 4, CarId = 1, ClientId = 2, RentalDate = new DateTime(2024, 2, 27, 11, 45, 0), RentalHours = 96, Car = Cars[0], Client = Clients[1] }, - new() { Id = 5, CarId = 1, ClientId = 4, RentalDate = new DateTime(2024, 2, 25, 16, 0, 0), RentalHours = 120, Car = Cars[0], Client = Clients[3] }, - new() { Id = 6, CarId = 2, ClientId = 6, RentalDate = new DateTime(2024, 2, 23, 13, 20, 0), RentalHours = 72, Car = Cars[1], Client = Clients[5] }, - new() { Id = 7, CarId = 2, ClientId = 8, RentalDate = new DateTime(2024, 2, 18, 10, 10, 0), RentalHours = 48, Car = Cars[1], Client = Clients[7] }, - new() { Id = 8, CarId = 3, ClientId = 7, RentalDate = new DateTime(2024, 2, 28, 8, 30, 0), RentalHours = 36, Car = Cars[2], Client = Clients[6] }, - new() { Id = 9, CarId = 4, ClientId = 9, RentalDate = new DateTime(2024, 2, 15, 12, 0, 0), RentalHours = 96, Car = Cars[3], Client = Clients[8] }, - new() { Id = 10, CarId = 5, ClientId = 10, RentalDate = new DateTime(2024, 2, 28, 7, 0, 0), RentalHours = 168, Car = Cars[4], Client = Clients[9] }, - new() { Id = 11, CarId = 6, ClientId = 11, RentalDate = new DateTime(2024, 2, 22, 15, 45, 0), RentalHours = 72, Car = Cars[5], Client = Clients[10] }, - new() { Id = 12, CarId = 8, ClientId = 12, RentalDate = new DateTime(2024, 2, 26, 9, 20, 0), RentalHours = 48, Car = Cars[7], Client = Clients[11] }, - new() { Id = 13, CarId = 9, ClientId = 13, RentalDate = new DateTime(2024, 2, 29, 22, 0, 0), RentalHours = 60, Car = Cars[8], Client = Clients[12] }, - new() { Id = 14, CarId = 10, ClientId = 14, RentalDate = new DateTime(2024, 2, 24, 11, 30, 0), RentalHours = 96, Car = Cars[9], Client = Clients[13] }, - new() { Id = 15, CarId = 11, ClientId = 15, RentalDate = new DateTime(2024, 2, 10, 14, 15, 0), RentalHours = 120, Car = Cars[10], Client = Clients[14] }, - new() { Id = 16, CarId = 12, ClientId = 1, RentalDate = new DateTime(2024, 2, 29, 14, 0, 0), RentalHours = 48, Car = Cars[11], Client = Clients[0] }, - new() { Id = 17, CarId = 13, ClientId = 2, RentalDate = new DateTime(2024, 2, 5, 16, 45, 0), RentalHours = 72, Car = Cars[12], Client = Clients[1] }, - new() { Id = 18, CarId = 14, ClientId = 3, RentalDate = new DateTime(2024, 2, 12, 10, 10, 0), RentalHours = 36, Car = Cars[13], Client = Clients[2] }, - new() { Id = 19, CarId = 15, ClientId = 4, RentalDate = new DateTime(2024, 2, 16, 13, 30, 0), RentalHours = 84, Car = Cars[14], Client = Clients[3] } + new() { Id = 1, CarId = 7, ClientId = 1, RentalDate = new DateTime(2024, 3, 1, 10, 0, 0), RentalHours = 48 }, + new() { Id = 2, CarId = 7, ClientId = 3, RentalDate = new DateTime(2024, 2, 25, 14, 30, 0), RentalHours = 72 }, + new() { Id = 3, CarId = 7, ClientId = 5, RentalDate = new DateTime(2024, 2, 20, 9, 15, 0), RentalHours = 24}, + new() { Id = 4, CarId = 1, ClientId = 2, RentalDate = new DateTime(2024, 2, 27, 11, 45, 0), RentalHours = 96 }, + new() { Id = 5, CarId = 1, ClientId = 4, RentalDate = new DateTime(2024, 2, 25, 16, 0, 0), RentalHours = 120}, + new() { Id = 6, CarId = 2, ClientId = 6, RentalDate = new DateTime(2024, 2, 23, 13, 20, 0), RentalHours = 72}, + new() { Id = 7, CarId = 2, ClientId = 8, RentalDate = new DateTime(2024, 2, 18, 10, 10, 0), RentalHours = 48 }, + new() { Id = 8, CarId = 3, ClientId = 7, RentalDate = new DateTime(2024, 2, 28, 8, 30, 0), RentalHours = 36 }, + new() { Id = 9, CarId = 4, ClientId = 9, RentalDate = new DateTime(2024, 2, 15, 12, 0, 0), RentalHours = 96 }, + new() { Id = 10, CarId = 5, ClientId = 10, RentalDate = new DateTime(2024, 2, 28, 7, 0, 0), RentalHours = 168 }, + new() { Id = 11, CarId = 6, ClientId = 11, RentalDate = new DateTime(2024, 2, 22, 15, 45, 0), RentalHours = 72 }, + new() { Id = 12, CarId = 8, ClientId = 12, RentalDate = new DateTime(2024, 2, 26, 9, 20, 0), RentalHours = 48 }, + new() { Id = 13, CarId = 9, ClientId = 13, RentalDate = new DateTime(2024, 2, 29, 22, 0, 0), RentalHours = 60 }, + new() { Id = 14, CarId = 10, ClientId = 14, RentalDate = new DateTime(2024, 2, 24, 11, 30, 0), RentalHours = 96 }, + new() { Id = 15, CarId = 11, ClientId = 15, RentalDate = new DateTime(2024, 2, 10, 14, 15, 0), RentalHours = 120 }, + new() { Id = 16, CarId = 12, ClientId = 1, RentalDate = new DateTime(2024, 2, 29, 14, 0, 0), RentalHours = 48 }, + new() { Id = 17, CarId = 13, ClientId = 2, RentalDate = new DateTime(2024, 2, 5, 16, 45, 0), RentalHours = 72 }, + new() { Id = 18, CarId = 14, ClientId = 3, RentalDate = new DateTime(2024, 2, 12, 10, 10, 0), RentalHours = 36 }, + new() { Id = 19, CarId = 15, ClientId = 4, RentalDate = new DateTime(2024, 2, 16, 13, 30, 0), RentalHours = 84 } ]; } } \ No newline at end of file diff --git a/CarRental/CarRental/CarRental.Infrastructure/CarRental.Infrastructure.csproj b/CarRental/CarRental/CarRental.Infrastructure/CarRental.Infrastructure.csproj index 877c43686..0513ff5aa 100644 --- a/CarRental/CarRental/CarRental.Infrastructure/CarRental.Infrastructure.csproj +++ b/CarRental/CarRental/CarRental.Infrastructure/CarRental.Infrastructure.csproj @@ -5,12 +5,7 @@ enable - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - + diff --git a/CarRental/CarRental/CarRental.Infrastructure/Migrations/20260220055307_InitialCreate.Designer.cs b/CarRental/CarRental/CarRental.Infrastructure/Migrations/20260220055307_InitialCreate.Designer.cs new file mode 100644 index 000000000..52321e4f6 --- /dev/null +++ b/CarRental/CarRental/CarRental.Infrastructure/Migrations/20260220055307_InitialCreate.Designer.cs @@ -0,0 +1,889 @@ +// +using System; +using CarRental.Infrastructure.Persistence; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace CarRental.Infrastructure.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20260220055307_InitialCreate")] + partial class InitialCreate + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.13") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("CarRental.Domain.Entities.Car", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Color") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)") + .HasColumnName("color"); + + b.Property("LicensePlate") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)") + .HasColumnName("license_plate"); + + b.Property("ModelGenerationId") + .HasColumnType("int") + .HasColumnName("model_generation_id"); + + b.HasKey("Id"); + + b.HasIndex("ModelGenerationId"); + + b.ToTable("cars"); + + b.HasData( + new + { + Id = 1, + Color = "Black", + LicensePlate = "A001AA163", + ModelGenerationId = 1 + }, + new + { + Id = 2, + Color = "Red", + LicensePlate = "B777BC163", + ModelGenerationId = 2 + }, + new + { + Id = 3, + Color = "White", + LicensePlate = "C123ET163", + ModelGenerationId = 3 + }, + new + { + Id = 4, + Color = "Green", + LicensePlate = "E555KH163", + ModelGenerationId = 4 + }, + new + { + Id = 5, + Color = "Silver", + LicensePlate = "K234MR163", + ModelGenerationId = 5 + }, + new + { + Id = 6, + Color = "Gray", + LicensePlate = "M888OA163", + ModelGenerationId = 6 + }, + new + { + Id = 7, + Color = "Blue", + LicensePlate = "N456RS163", + ModelGenerationId = 7 + }, + new + { + Id = 8, + Color = "Brown", + LicensePlate = "O789TU163", + ModelGenerationId = 8 + }, + new + { + Id = 9, + Color = "White", + LicensePlate = "P321XO163", + ModelGenerationId = 9 + }, + new + { + Id = 10, + Color = "Black", + LicensePlate = "S654AM163", + ModelGenerationId = 10 + }, + new + { + Id = 11, + Color = "Orange", + LicensePlate = "T987RE163", + ModelGenerationId = 11 + }, + new + { + Id = 12, + Color = "White", + LicensePlate = "U246KN163", + ModelGenerationId = 12 + }, + new + { + Id = 13, + Color = "Black", + LicensePlate = "H135VT163", + ModelGenerationId = 13 + }, + new + { + Id = 14, + Color = "Gray", + LicensePlate = "SH579SA163", + ModelGenerationId = 14 + }, + new + { + Id = 15, + Color = "Blue", + LicensePlate = "SCH864RO163", + ModelGenerationId = 15 + }); + }); + + modelBuilder.Entity("CarRental.Domain.Entities.CarModel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("BodyType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)") + .HasColumnName("body_type"); + + b.Property("Class") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)") + .HasColumnName("class"); + + b.Property("DriveType") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)") + .HasColumnName("drive_type"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)") + .HasColumnName("name"); + + b.Property("SeatsCount") + .HasColumnType("int") + .HasColumnName("seats_count"); + + b.HasKey("Id"); + + b.ToTable("car_models"); + + b.HasData( + new + { + Id = 1, + BodyType = "Sedan", + Class = "Premium", + DriveType = "RWD", + Name = "BMW 3 Series", + SeatsCount = 5 + }, + new + { + Id = 2, + BodyType = "Coupe", + Class = "Sports", + DriveType = "RWD", + Name = "Ford Mustang", + SeatsCount = 4 + }, + new + { + Id = 3, + BodyType = "Sedan", + Class = "Compact", + DriveType = "FWD", + Name = "Honda Civic", + SeatsCount = 5 + }, + new + { + Id = 4, + BodyType = "SUV", + Class = "Off-road", + DriveType = "4WD", + Name = "Jeep Wrangler", + SeatsCount = 5 + }, + new + { + Id = 5, + BodyType = "Coupe", + Class = "Luxury", + DriveType = "RWD", + Name = "Porsche 911", + SeatsCount = 4 + }, + new + { + Id = 6, + BodyType = "SUV", + Class = "Full-size", + DriveType = "AWD", + Name = "Chevrolet Tahoe", + SeatsCount = 8 + }, + new + { + Id = 7, + BodyType = "Sedan", + Class = "Economy", + DriveType = "FWD", + Name = "Lada Vesta", + SeatsCount = 5 + }, + new + { + Id = 8, + BodyType = "SUV", + Class = "Mid-size", + DriveType = "AWD", + Name = "Subaru Outback", + SeatsCount = 5 + }, + new + { + Id = 9, + BodyType = "Van", + Class = "Commercial", + DriveType = "RWD", + Name = "GAZ Gazelle Next", + SeatsCount = 3 + }, + new + { + Id = 10, + BodyType = "Hatchback", + Class = "Hybrid", + DriveType = "FWD", + Name = "Toyota Prius", + SeatsCount = 5 + }, + new + { + Id = 11, + BodyType = "SUV", + Class = "Off-road", + DriveType = "4WD", + Name = "UAZ Patriot", + SeatsCount = 5 + }, + new + { + Id = 12, + BodyType = "SUV", + Class = "Premium", + DriveType = "AWD", + Name = "Lexus RX", + SeatsCount = 5 + }, + new + { + Id = 13, + BodyType = "SUV", + Class = "Luxury", + DriveType = "AWD", + Name = "Range Rover Sport", + SeatsCount = 5 + }, + new + { + Id = 14, + BodyType = "Sedan", + Class = "Premium", + DriveType = "AWD", + Name = "Audi A4", + SeatsCount = 5 + }, + new + { + Id = 15, + BodyType = "SUV", + Class = "Off-road", + DriveType = "4WD", + Name = "Lada Niva Travel", + SeatsCount = 5 + }); + }); + + modelBuilder.Entity("CarRental.Domain.Entities.Client", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("BirthDate") + .HasColumnType("date") + .HasColumnName("birth_date"); + + b.Property("FullName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)") + .HasColumnName("full_name"); + + b.Property("LicenseNumber") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)") + .HasColumnName("license_number"); + + b.HasKey("Id"); + + b.ToTable("clients"); + + b.HasData( + new + { + Id = 1, + BirthDate = new DateOnly(1988, 3, 15), + FullName = "Alexander Smirnov", + LicenseNumber = "2023-001" + }, + new + { + Id = 2, + BirthDate = new DateOnly(1992, 7, 22), + FullName = "Marina Kovalenko", + LicenseNumber = "2022-045" + }, + new + { + Id = 3, + BirthDate = new DateOnly(1995, 11, 10), + FullName = "Denis Popov", + LicenseNumber = "2024-012" + }, + new + { + Id = 4, + BirthDate = new DateOnly(1985, 5, 3), + FullName = "Elena Vasnetsova", + LicenseNumber = "2021-078" + }, + new + { + Id = 5, + BirthDate = new DateOnly(1990, 9, 30), + FullName = "Igor Kozlovsky", + LicenseNumber = "2023-056" + }, + new + { + Id = 6, + BirthDate = new DateOnly(1993, 2, 14), + FullName = "Anna Orlova", + LicenseNumber = "2022-123" + }, + new + { + Id = 7, + BirthDate = new DateOnly(1987, 8, 18), + FullName = "Artem Belov", + LicenseNumber = "2024-034" + }, + new + { + Id = 8, + BirthDate = new DateOnly(1994, 12, 25), + FullName = "Sofia Grigorieva", + LicenseNumber = "2021-099" + }, + new + { + Id = 9, + BirthDate = new DateOnly(1991, 6, 7), + FullName = "Pavel Melnikov", + LicenseNumber = "2023-087" + }, + new + { + Id = 10, + BirthDate = new DateOnly(1989, 4, 12), + FullName = "Olga Zakharova", + LicenseNumber = "2022-067" + }, + new + { + Id = 11, + BirthDate = new DateOnly(1996, 10, 28), + FullName = "Mikhail Tikhonov", + LicenseNumber = "2024-005" + }, + new + { + Id = 12, + BirthDate = new DateOnly(1986, 1, 19), + FullName = "Ksenia Fedorova", + LicenseNumber = "2021-112" + }, + new + { + Id = 13, + BirthDate = new DateOnly(1997, 7, 3), + FullName = "Roman Sokolov", + LicenseNumber = "2023-092" + }, + new + { + Id = 14, + BirthDate = new DateOnly(1984, 3, 22), + FullName = "Tatiana Krylova", + LicenseNumber = "2022-031" + }, + new + { + Id = 15, + BirthDate = new DateOnly(1998, 11, 15), + FullName = "Andrey Davydov", + LicenseNumber = "2024-021" + }); + }); + + modelBuilder.Entity("CarRental.Domain.Entities.ModelGeneration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("EngineVolume") + .HasColumnType("float") + .HasColumnName("engine_volume"); + + b.Property("ModelId") + .HasColumnType("int") + .HasColumnName("model_id"); + + b.Property("RentalPricePerHour") + .HasColumnType("decimal(18,2)") + .HasColumnName("rental_price_per_hour"); + + b.Property("Transmission") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)") + .HasColumnName("transmission"); + + b.Property("Year") + .HasColumnType("int") + .HasColumnName("year"); + + b.HasKey("Id"); + + b.HasIndex("ModelId"); + + b.ToTable("model_generations"); + + b.HasData( + new + { + Id = 1, + EngineVolume = 2.0, + ModelId = 1, + RentalPricePerHour = 2200m, + Transmission = "AT", + Year = 2023 + }, + new + { + Id = 2, + EngineVolume = 5.0, + ModelId = 2, + RentalPricePerHour = 5000m, + Transmission = "AT", + Year = 2022 + }, + new + { + Id = 3, + EngineVolume = 1.5, + ModelId = 3, + RentalPricePerHour = 1200m, + Transmission = "CVT", + Year = 2024 + }, + new + { + Id = 4, + EngineVolume = 3.6000000000000001, + ModelId = 4, + RentalPricePerHour = 2800m, + Transmission = "AT", + Year = 2023 + }, + new + { + Id = 5, + EngineVolume = 3.0, + ModelId = 5, + RentalPricePerHour = 8000m, + Transmission = "AT", + Year = 2024 + }, + new + { + Id = 6, + EngineVolume = 5.2999999999999998, + ModelId = 6, + RentalPricePerHour = 3500m, + Transmission = "AT", + Year = 2022 + }, + new + { + Id = 7, + EngineVolume = 1.6000000000000001, + ModelId = 7, + RentalPricePerHour = 700m, + Transmission = "MT", + Year = 2023 + }, + new + { + Id = 8, + EngineVolume = 2.5, + ModelId = 8, + RentalPricePerHour = 1800m, + Transmission = "AT", + Year = 2024 + }, + new + { + Id = 9, + EngineVolume = 2.7000000000000002, + ModelId = 9, + RentalPricePerHour = 1500m, + Transmission = "MT", + Year = 2022 + }, + new + { + Id = 10, + EngineVolume = 1.8, + ModelId = 10, + RentalPricePerHour = 1600m, + Transmission = "CVT", + Year = 2023 + }, + new + { + Id = 11, + EngineVolume = 2.7000000000000002, + ModelId = 11, + RentalPricePerHour = 1400m, + Transmission = "MT", + Year = 2022 + }, + new + { + Id = 12, + EngineVolume = 3.5, + ModelId = 12, + RentalPricePerHour = 3200m, + Transmission = "AT", + Year = 2024 + }, + new + { + Id = 13, + EngineVolume = 3.0, + ModelId = 13, + RentalPricePerHour = 6000m, + Transmission = "AT", + Year = 2023 + }, + new + { + Id = 14, + EngineVolume = 2.0, + ModelId = 14, + RentalPricePerHour = 2800m, + Transmission = "AT", + Year = 2024 + }, + new + { + Id = 15, + EngineVolume = 1.7, + ModelId = 15, + RentalPricePerHour = 900m, + Transmission = "MT", + Year = 2023 + }); + }); + + modelBuilder.Entity("CarRental.Domain.Entities.Rental", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CarId") + .HasColumnType("int") + .HasColumnName("car_id"); + + b.Property("ClientId") + .HasColumnType("int") + .HasColumnName("client_id"); + + b.Property("RentalDate") + .HasColumnType("datetime2") + .HasColumnName("rental_date"); + + b.Property("RentalHours") + .HasColumnType("int") + .HasColumnName("rental_hours"); + + b.HasKey("Id"); + + b.HasIndex("CarId"); + + b.HasIndex("ClientId"); + + b.ToTable("rentals"); + + b.HasData( + new + { + Id = 1, + CarId = 7, + ClientId = 1, + RentalDate = new DateTime(2024, 3, 1, 10, 0, 0, 0, DateTimeKind.Unspecified), + RentalHours = 48 + }, + new + { + Id = 2, + CarId = 7, + ClientId = 3, + RentalDate = new DateTime(2024, 2, 25, 14, 30, 0, 0, DateTimeKind.Unspecified), + RentalHours = 72 + }, + new + { + Id = 3, + CarId = 7, + ClientId = 5, + RentalDate = new DateTime(2024, 2, 20, 9, 15, 0, 0, DateTimeKind.Unspecified), + RentalHours = 24 + }, + new + { + Id = 4, + CarId = 1, + ClientId = 2, + RentalDate = new DateTime(2024, 2, 27, 11, 45, 0, 0, DateTimeKind.Unspecified), + RentalHours = 96 + }, + new + { + Id = 5, + CarId = 1, + ClientId = 4, + RentalDate = new DateTime(2024, 2, 25, 16, 0, 0, 0, DateTimeKind.Unspecified), + RentalHours = 120 + }, + new + { + Id = 6, + CarId = 2, + ClientId = 6, + RentalDate = new DateTime(2024, 2, 23, 13, 20, 0, 0, DateTimeKind.Unspecified), + RentalHours = 72 + }, + new + { + Id = 7, + CarId = 2, + ClientId = 8, + RentalDate = new DateTime(2024, 2, 18, 10, 10, 0, 0, DateTimeKind.Unspecified), + RentalHours = 48 + }, + new + { + Id = 8, + CarId = 3, + ClientId = 7, + RentalDate = new DateTime(2024, 2, 28, 8, 30, 0, 0, DateTimeKind.Unspecified), + RentalHours = 36 + }, + new + { + Id = 9, + CarId = 4, + ClientId = 9, + RentalDate = new DateTime(2024, 2, 15, 12, 0, 0, 0, DateTimeKind.Unspecified), + RentalHours = 96 + }, + new + { + Id = 10, + CarId = 5, + ClientId = 10, + RentalDate = new DateTime(2024, 2, 28, 7, 0, 0, 0, DateTimeKind.Unspecified), + RentalHours = 168 + }, + new + { + Id = 11, + CarId = 6, + ClientId = 11, + RentalDate = new DateTime(2024, 2, 22, 15, 45, 0, 0, DateTimeKind.Unspecified), + RentalHours = 72 + }, + new + { + Id = 12, + CarId = 8, + ClientId = 12, + RentalDate = new DateTime(2024, 2, 26, 9, 20, 0, 0, DateTimeKind.Unspecified), + RentalHours = 48 + }, + new + { + Id = 13, + CarId = 9, + ClientId = 13, + RentalDate = new DateTime(2024, 2, 29, 22, 0, 0, 0, DateTimeKind.Unspecified), + RentalHours = 60 + }, + new + { + Id = 14, + CarId = 10, + ClientId = 14, + RentalDate = new DateTime(2024, 2, 24, 11, 30, 0, 0, DateTimeKind.Unspecified), + RentalHours = 96 + }, + new + { + Id = 15, + CarId = 11, + ClientId = 15, + RentalDate = new DateTime(2024, 2, 10, 14, 15, 0, 0, DateTimeKind.Unspecified), + RentalHours = 120 + }, + new + { + Id = 16, + CarId = 12, + ClientId = 1, + RentalDate = new DateTime(2024, 2, 29, 14, 0, 0, 0, DateTimeKind.Unspecified), + RentalHours = 48 + }, + new + { + Id = 17, + CarId = 13, + ClientId = 2, + RentalDate = new DateTime(2024, 2, 5, 16, 45, 0, 0, DateTimeKind.Unspecified), + RentalHours = 72 + }, + new + { + Id = 18, + CarId = 14, + ClientId = 3, + RentalDate = new DateTime(2024, 2, 12, 10, 10, 0, 0, DateTimeKind.Unspecified), + RentalHours = 36 + }, + new + { + Id = 19, + CarId = 15, + ClientId = 4, + RentalDate = new DateTime(2024, 2, 16, 13, 30, 0, 0, DateTimeKind.Unspecified), + RentalHours = 84 + }); + }); + + modelBuilder.Entity("CarRental.Domain.Entities.Car", b => + { + b.HasOne("CarRental.Domain.Entities.ModelGeneration", "ModelGeneration") + .WithMany() + .HasForeignKey("ModelGenerationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ModelGeneration"); + }); + + modelBuilder.Entity("CarRental.Domain.Entities.ModelGeneration", b => + { + b.HasOne("CarRental.Domain.Entities.CarModel", "Model") + .WithMany() + .HasForeignKey("ModelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Model"); + }); + + modelBuilder.Entity("CarRental.Domain.Entities.Rental", b => + { + b.HasOne("CarRental.Domain.Entities.Car", "Car") + .WithMany() + .HasForeignKey("CarId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CarRental.Domain.Entities.Client", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Car"); + + b.Navigation("Client"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/CarRental/CarRental/CarRental.Infrastructure/Migrations/20260220055307_InitialCreate.cs b/CarRental/CarRental/CarRental.Infrastructure/Migrations/20260220055307_InitialCreate.cs new file mode 100644 index 000000000..5c5f1c8d0 --- /dev/null +++ b/CarRental/CarRental/CarRental.Infrastructure/Migrations/20260220055307_InitialCreate.cs @@ -0,0 +1,274 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional + +namespace CarRental.Infrastructure.Migrations +{ + /// + public partial class InitialCreate : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "car_models", + columns: table => new + { + id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + name = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + drive_type = table.Column(type: "nvarchar(10)", maxLength: 10, nullable: false), + seats_count = table.Column(type: "int", nullable: false), + body_type = table.Column(type: "nvarchar(20)", maxLength: 20, nullable: false), + @class = table.Column(name: "class", type: "nvarchar(20)", maxLength: 20, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_car_models", x => x.id); + }); + + migrationBuilder.CreateTable( + name: "clients", + columns: table => new + { + id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + license_number = table.Column(type: "nvarchar(20)", maxLength: 20, nullable: false), + full_name = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), + birth_date = table.Column(type: "date", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_clients", x => x.id); + }); + + migrationBuilder.CreateTable( + name: "model_generations", + columns: table => new + { + id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + year = table.Column(type: "int", nullable: false), + engine_volume = table.Column(type: "float", nullable: false), + transmission = table.Column(type: "nvarchar(10)", maxLength: 10, nullable: false), + rental_price_per_hour = table.Column(type: "decimal(18,2)", nullable: false), + model_id = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_model_generations", x => x.id); + table.ForeignKey( + name: "FK_model_generations_car_models_model_id", + column: x => x.model_id, + principalTable: "car_models", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "cars", + columns: table => new + { + id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + license_plate = table.Column(type: "nvarchar(20)", maxLength: 20, nullable: false), + color = table.Column(type: "nvarchar(30)", maxLength: 30, nullable: false), + model_generation_id = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_cars", x => x.id); + table.ForeignKey( + name: "FK_cars_model_generations_model_generation_id", + column: x => x.model_generation_id, + principalTable: "model_generations", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "rentals", + columns: table => new + { + id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + rental_date = table.Column(type: "datetime2", nullable: false), + rental_hours = table.Column(type: "int", nullable: false), + car_id = table.Column(type: "int", nullable: false), + client_id = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_rentals", x => x.id); + table.ForeignKey( + name: "FK_rentals_cars_car_id", + column: x => x.car_id, + principalTable: "cars", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_rentals_clients_client_id", + column: x => x.client_id, + principalTable: "clients", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.InsertData( + table: "car_models", + columns: new[] { "id", "body_type", "class", "drive_type", "name", "seats_count" }, + values: new object[,] + { + { 1, "Sedan", "Premium", "RWD", "BMW 3 Series", 5 }, + { 2, "Coupe", "Sports", "RWD", "Ford Mustang", 4 }, + { 3, "Sedan", "Compact", "FWD", "Honda Civic", 5 }, + { 4, "SUV", "Off-road", "4WD", "Jeep Wrangler", 5 }, + { 5, "Coupe", "Luxury", "RWD", "Porsche 911", 4 }, + { 6, "SUV", "Full-size", "AWD", "Chevrolet Tahoe", 8 }, + { 7, "Sedan", "Economy", "FWD", "Lada Vesta", 5 }, + { 8, "SUV", "Mid-size", "AWD", "Subaru Outback", 5 }, + { 9, "Van", "Commercial", "RWD", "GAZ Gazelle Next", 3 }, + { 10, "Hatchback", "Hybrid", "FWD", "Toyota Prius", 5 }, + { 11, "SUV", "Off-road", "4WD", "UAZ Patriot", 5 }, + { 12, "SUV", "Premium", "AWD", "Lexus RX", 5 }, + { 13, "SUV", "Luxury", "AWD", "Range Rover Sport", 5 }, + { 14, "Sedan", "Premium", "AWD", "Audi A4", 5 }, + { 15, "SUV", "Off-road", "4WD", "Lada Niva Travel", 5 } + }); + + migrationBuilder.InsertData( + table: "clients", + columns: new[] { "id", "birth_date", "full_name", "license_number" }, + values: new object[,] + { + { 1, new DateOnly(1988, 3, 15), "Alexander Smirnov", "2023-001" }, + { 2, new DateOnly(1992, 7, 22), "Marina Kovalenko", "2022-045" }, + { 3, new DateOnly(1995, 11, 10), "Denis Popov", "2024-012" }, + { 4, new DateOnly(1985, 5, 3), "Elena Vasnetsova", "2021-078" }, + { 5, new DateOnly(1990, 9, 30), "Igor Kozlovsky", "2023-056" }, + { 6, new DateOnly(1993, 2, 14), "Anna Orlova", "2022-123" }, + { 7, new DateOnly(1987, 8, 18), "Artem Belov", "2024-034" }, + { 8, new DateOnly(1994, 12, 25), "Sofia Grigorieva", "2021-099" }, + { 9, new DateOnly(1991, 6, 7), "Pavel Melnikov", "2023-087" }, + { 10, new DateOnly(1989, 4, 12), "Olga Zakharova", "2022-067" }, + { 11, new DateOnly(1996, 10, 28), "Mikhail Tikhonov", "2024-005" }, + { 12, new DateOnly(1986, 1, 19), "Ksenia Fedorova", "2021-112" }, + { 13, new DateOnly(1997, 7, 3), "Roman Sokolov", "2023-092" }, + { 14, new DateOnly(1984, 3, 22), "Tatiana Krylova", "2022-031" }, + { 15, new DateOnly(1998, 11, 15), "Andrey Davydov", "2024-021" } + }); + + migrationBuilder.InsertData( + table: "model_generations", + columns: new[] { "id", "engine_volume", "model_id", "rental_price_per_hour", "transmission", "year" }, + values: new object[,] + { + { 1, 2.0, 1, 2200m, "AT", 2023 }, + { 2, 5.0, 2, 5000m, "AT", 2022 }, + { 3, 1.5, 3, 1200m, "CVT", 2024 }, + { 4, 3.6000000000000001, 4, 2800m, "AT", 2023 }, + { 5, 3.0, 5, 8000m, "AT", 2024 }, + { 6, 5.2999999999999998, 6, 3500m, "AT", 2022 }, + { 7, 1.6000000000000001, 7, 700m, "MT", 2023 }, + { 8, 2.5, 8, 1800m, "AT", 2024 }, + { 9, 2.7000000000000002, 9, 1500m, "MT", 2022 }, + { 10, 1.8, 10, 1600m, "CVT", 2023 }, + { 11, 2.7000000000000002, 11, 1400m, "MT", 2022 }, + { 12, 3.5, 12, 3200m, "AT", 2024 }, + { 13, 3.0, 13, 6000m, "AT", 2023 }, + { 14, 2.0, 14, 2800m, "AT", 2024 }, + { 15, 1.7, 15, 900m, "MT", 2023 } + }); + + migrationBuilder.InsertData( + table: "cars", + columns: new[] { "id", "color", "license_plate", "model_generation_id" }, + values: new object[,] + { + { 1, "Black", "A001AA163", 1 }, + { 2, "Red", "B777BC163", 2 }, + { 3, "White", "C123ET163", 3 }, + { 4, "Green", "E555KH163", 4 }, + { 5, "Silver", "K234MR163", 5 }, + { 6, "Gray", "M888OA163", 6 }, + { 7, "Blue", "N456RS163", 7 }, + { 8, "Brown", "O789TU163", 8 }, + { 9, "White", "P321XO163", 9 }, + { 10, "Black", "S654AM163", 10 }, + { 11, "Orange", "T987RE163", 11 }, + { 12, "White", "U246KN163", 12 }, + { 13, "Black", "H135VT163", 13 }, + { 14, "Gray", "SH579SA163", 14 }, + { 15, "Blue", "SCH864RO163", 15 } + }); + + migrationBuilder.InsertData( + table: "rentals", + columns: new[] { "id", "car_id", "client_id", "rental_date", "rental_hours" }, + values: new object[,] + { + { 1, 7, 1, new DateTime(2024, 3, 1, 10, 0, 0, 0, DateTimeKind.Unspecified), 48 }, + { 2, 7, 3, new DateTime(2024, 2, 25, 14, 30, 0, 0, DateTimeKind.Unspecified), 72 }, + { 3, 7, 5, new DateTime(2024, 2, 20, 9, 15, 0, 0, DateTimeKind.Unspecified), 24 }, + { 4, 1, 2, new DateTime(2024, 2, 27, 11, 45, 0, 0, DateTimeKind.Unspecified), 96 }, + { 5, 1, 4, new DateTime(2024, 2, 25, 16, 0, 0, 0, DateTimeKind.Unspecified), 120 }, + { 6, 2, 6, new DateTime(2024, 2, 23, 13, 20, 0, 0, DateTimeKind.Unspecified), 72 }, + { 7, 2, 8, new DateTime(2024, 2, 18, 10, 10, 0, 0, DateTimeKind.Unspecified), 48 }, + { 8, 3, 7, new DateTime(2024, 2, 28, 8, 30, 0, 0, DateTimeKind.Unspecified), 36 }, + { 9, 4, 9, new DateTime(2024, 2, 15, 12, 0, 0, 0, DateTimeKind.Unspecified), 96 }, + { 10, 5, 10, new DateTime(2024, 2, 28, 7, 0, 0, 0, DateTimeKind.Unspecified), 168 }, + { 11, 6, 11, new DateTime(2024, 2, 22, 15, 45, 0, 0, DateTimeKind.Unspecified), 72 }, + { 12, 8, 12, new DateTime(2024, 2, 26, 9, 20, 0, 0, DateTimeKind.Unspecified), 48 }, + { 13, 9, 13, new DateTime(2024, 2, 29, 22, 0, 0, 0, DateTimeKind.Unspecified), 60 }, + { 14, 10, 14, new DateTime(2024, 2, 24, 11, 30, 0, 0, DateTimeKind.Unspecified), 96 }, + { 15, 11, 15, new DateTime(2024, 2, 10, 14, 15, 0, 0, DateTimeKind.Unspecified), 120 }, + { 16, 12, 1, new DateTime(2024, 2, 29, 14, 0, 0, 0, DateTimeKind.Unspecified), 48 }, + { 17, 13, 2, new DateTime(2024, 2, 5, 16, 45, 0, 0, DateTimeKind.Unspecified), 72 }, + { 18, 14, 3, new DateTime(2024, 2, 12, 10, 10, 0, 0, DateTimeKind.Unspecified), 36 }, + { 19, 15, 4, new DateTime(2024, 2, 16, 13, 30, 0, 0, DateTimeKind.Unspecified), 84 } + }); + + migrationBuilder.CreateIndex( + name: "IX_cars_model_generation_id", + table: "cars", + column: "model_generation_id"); + + migrationBuilder.CreateIndex( + name: "IX_model_generations_model_id", + table: "model_generations", + column: "model_id"); + + migrationBuilder.CreateIndex( + name: "IX_rentals_car_id", + table: "rentals", + column: "car_id"); + + migrationBuilder.CreateIndex( + name: "IX_rentals_client_id", + table: "rentals", + column: "client_id"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "rentals"); + + migrationBuilder.DropTable( + name: "cars"); + + migrationBuilder.DropTable( + name: "clients"); + + migrationBuilder.DropTable( + name: "model_generations"); + + migrationBuilder.DropTable( + name: "car_models"); + } + } +} diff --git a/CarRental/CarRental/CarRental.Infrastructure/Migrations/AppDbContextModelSnapshot.cs b/CarRental/CarRental/CarRental.Infrastructure/Migrations/AppDbContextModelSnapshot.cs new file mode 100644 index 000000000..47bd37913 --- /dev/null +++ b/CarRental/CarRental/CarRental.Infrastructure/Migrations/AppDbContextModelSnapshot.cs @@ -0,0 +1,886 @@ +// +using System; +using CarRental.Infrastructure.Persistence; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace CarRental.Infrastructure.Migrations +{ + [DbContext(typeof(AppDbContext))] + partial class AppDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.13") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("CarRental.Domain.Entities.Car", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Color") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)") + .HasColumnName("color"); + + b.Property("LicensePlate") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)") + .HasColumnName("license_plate"); + + b.Property("ModelGenerationId") + .HasColumnType("int") + .HasColumnName("model_generation_id"); + + b.HasKey("Id"); + + b.HasIndex("ModelGenerationId"); + + b.ToTable("cars"); + + b.HasData( + new + { + Id = 1, + Color = "Black", + LicensePlate = "A001AA163", + ModelGenerationId = 1 + }, + new + { + Id = 2, + Color = "Red", + LicensePlate = "B777BC163", + ModelGenerationId = 2 + }, + new + { + Id = 3, + Color = "White", + LicensePlate = "C123ET163", + ModelGenerationId = 3 + }, + new + { + Id = 4, + Color = "Green", + LicensePlate = "E555KH163", + ModelGenerationId = 4 + }, + new + { + Id = 5, + Color = "Silver", + LicensePlate = "K234MR163", + ModelGenerationId = 5 + }, + new + { + Id = 6, + Color = "Gray", + LicensePlate = "M888OA163", + ModelGenerationId = 6 + }, + new + { + Id = 7, + Color = "Blue", + LicensePlate = "N456RS163", + ModelGenerationId = 7 + }, + new + { + Id = 8, + Color = "Brown", + LicensePlate = "O789TU163", + ModelGenerationId = 8 + }, + new + { + Id = 9, + Color = "White", + LicensePlate = "P321XO163", + ModelGenerationId = 9 + }, + new + { + Id = 10, + Color = "Black", + LicensePlate = "S654AM163", + ModelGenerationId = 10 + }, + new + { + Id = 11, + Color = "Orange", + LicensePlate = "T987RE163", + ModelGenerationId = 11 + }, + new + { + Id = 12, + Color = "White", + LicensePlate = "U246KN163", + ModelGenerationId = 12 + }, + new + { + Id = 13, + Color = "Black", + LicensePlate = "H135VT163", + ModelGenerationId = 13 + }, + new + { + Id = 14, + Color = "Gray", + LicensePlate = "SH579SA163", + ModelGenerationId = 14 + }, + new + { + Id = 15, + Color = "Blue", + LicensePlate = "SCH864RO163", + ModelGenerationId = 15 + }); + }); + + modelBuilder.Entity("CarRental.Domain.Entities.CarModel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("BodyType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)") + .HasColumnName("body_type"); + + b.Property("Class") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)") + .HasColumnName("class"); + + b.Property("DriveType") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)") + .HasColumnName("drive_type"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)") + .HasColumnName("name"); + + b.Property("SeatsCount") + .HasColumnType("int") + .HasColumnName("seats_count"); + + b.HasKey("Id"); + + b.ToTable("car_models"); + + b.HasData( + new + { + Id = 1, + BodyType = "Sedan", + Class = "Premium", + DriveType = "RWD", + Name = "BMW 3 Series", + SeatsCount = 5 + }, + new + { + Id = 2, + BodyType = "Coupe", + Class = "Sports", + DriveType = "RWD", + Name = "Ford Mustang", + SeatsCount = 4 + }, + new + { + Id = 3, + BodyType = "Sedan", + Class = "Compact", + DriveType = "FWD", + Name = "Honda Civic", + SeatsCount = 5 + }, + new + { + Id = 4, + BodyType = "SUV", + Class = "Off-road", + DriveType = "4WD", + Name = "Jeep Wrangler", + SeatsCount = 5 + }, + new + { + Id = 5, + BodyType = "Coupe", + Class = "Luxury", + DriveType = "RWD", + Name = "Porsche 911", + SeatsCount = 4 + }, + new + { + Id = 6, + BodyType = "SUV", + Class = "Full-size", + DriveType = "AWD", + Name = "Chevrolet Tahoe", + SeatsCount = 8 + }, + new + { + Id = 7, + BodyType = "Sedan", + Class = "Economy", + DriveType = "FWD", + Name = "Lada Vesta", + SeatsCount = 5 + }, + new + { + Id = 8, + BodyType = "SUV", + Class = "Mid-size", + DriveType = "AWD", + Name = "Subaru Outback", + SeatsCount = 5 + }, + new + { + Id = 9, + BodyType = "Van", + Class = "Commercial", + DriveType = "RWD", + Name = "GAZ Gazelle Next", + SeatsCount = 3 + }, + new + { + Id = 10, + BodyType = "Hatchback", + Class = "Hybrid", + DriveType = "FWD", + Name = "Toyota Prius", + SeatsCount = 5 + }, + new + { + Id = 11, + BodyType = "SUV", + Class = "Off-road", + DriveType = "4WD", + Name = "UAZ Patriot", + SeatsCount = 5 + }, + new + { + Id = 12, + BodyType = "SUV", + Class = "Premium", + DriveType = "AWD", + Name = "Lexus RX", + SeatsCount = 5 + }, + new + { + Id = 13, + BodyType = "SUV", + Class = "Luxury", + DriveType = "AWD", + Name = "Range Rover Sport", + SeatsCount = 5 + }, + new + { + Id = 14, + BodyType = "Sedan", + Class = "Premium", + DriveType = "AWD", + Name = "Audi A4", + SeatsCount = 5 + }, + new + { + Id = 15, + BodyType = "SUV", + Class = "Off-road", + DriveType = "4WD", + Name = "Lada Niva Travel", + SeatsCount = 5 + }); + }); + + modelBuilder.Entity("CarRental.Domain.Entities.Client", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("BirthDate") + .HasColumnType("date") + .HasColumnName("birth_date"); + + b.Property("FullName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)") + .HasColumnName("full_name"); + + b.Property("LicenseNumber") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)") + .HasColumnName("license_number"); + + b.HasKey("Id"); + + b.ToTable("clients"); + + b.HasData( + new + { + Id = 1, + BirthDate = new DateOnly(1988, 3, 15), + FullName = "Alexander Smirnov", + LicenseNumber = "2023-001" + }, + new + { + Id = 2, + BirthDate = new DateOnly(1992, 7, 22), + FullName = "Marina Kovalenko", + LicenseNumber = "2022-045" + }, + new + { + Id = 3, + BirthDate = new DateOnly(1995, 11, 10), + FullName = "Denis Popov", + LicenseNumber = "2024-012" + }, + new + { + Id = 4, + BirthDate = new DateOnly(1985, 5, 3), + FullName = "Elena Vasnetsova", + LicenseNumber = "2021-078" + }, + new + { + Id = 5, + BirthDate = new DateOnly(1990, 9, 30), + FullName = "Igor Kozlovsky", + LicenseNumber = "2023-056" + }, + new + { + Id = 6, + BirthDate = new DateOnly(1993, 2, 14), + FullName = "Anna Orlova", + LicenseNumber = "2022-123" + }, + new + { + Id = 7, + BirthDate = new DateOnly(1987, 8, 18), + FullName = "Artem Belov", + LicenseNumber = "2024-034" + }, + new + { + Id = 8, + BirthDate = new DateOnly(1994, 12, 25), + FullName = "Sofia Grigorieva", + LicenseNumber = "2021-099" + }, + new + { + Id = 9, + BirthDate = new DateOnly(1991, 6, 7), + FullName = "Pavel Melnikov", + LicenseNumber = "2023-087" + }, + new + { + Id = 10, + BirthDate = new DateOnly(1989, 4, 12), + FullName = "Olga Zakharova", + LicenseNumber = "2022-067" + }, + new + { + Id = 11, + BirthDate = new DateOnly(1996, 10, 28), + FullName = "Mikhail Tikhonov", + LicenseNumber = "2024-005" + }, + new + { + Id = 12, + BirthDate = new DateOnly(1986, 1, 19), + FullName = "Ksenia Fedorova", + LicenseNumber = "2021-112" + }, + new + { + Id = 13, + BirthDate = new DateOnly(1997, 7, 3), + FullName = "Roman Sokolov", + LicenseNumber = "2023-092" + }, + new + { + Id = 14, + BirthDate = new DateOnly(1984, 3, 22), + FullName = "Tatiana Krylova", + LicenseNumber = "2022-031" + }, + new + { + Id = 15, + BirthDate = new DateOnly(1998, 11, 15), + FullName = "Andrey Davydov", + LicenseNumber = "2024-021" + }); + }); + + modelBuilder.Entity("CarRental.Domain.Entities.ModelGeneration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("EngineVolume") + .HasColumnType("float") + .HasColumnName("engine_volume"); + + b.Property("ModelId") + .HasColumnType("int") + .HasColumnName("model_id"); + + b.Property("RentalPricePerHour") + .HasColumnType("decimal(18,2)") + .HasColumnName("rental_price_per_hour"); + + b.Property("Transmission") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)") + .HasColumnName("transmission"); + + b.Property("Year") + .HasColumnType("int") + .HasColumnName("year"); + + b.HasKey("Id"); + + b.HasIndex("ModelId"); + + b.ToTable("model_generations"); + + b.HasData( + new + { + Id = 1, + EngineVolume = 2.0, + ModelId = 1, + RentalPricePerHour = 2200m, + Transmission = "AT", + Year = 2023 + }, + new + { + Id = 2, + EngineVolume = 5.0, + ModelId = 2, + RentalPricePerHour = 5000m, + Transmission = "AT", + Year = 2022 + }, + new + { + Id = 3, + EngineVolume = 1.5, + ModelId = 3, + RentalPricePerHour = 1200m, + Transmission = "CVT", + Year = 2024 + }, + new + { + Id = 4, + EngineVolume = 3.6000000000000001, + ModelId = 4, + RentalPricePerHour = 2800m, + Transmission = "AT", + Year = 2023 + }, + new + { + Id = 5, + EngineVolume = 3.0, + ModelId = 5, + RentalPricePerHour = 8000m, + Transmission = "AT", + Year = 2024 + }, + new + { + Id = 6, + EngineVolume = 5.2999999999999998, + ModelId = 6, + RentalPricePerHour = 3500m, + Transmission = "AT", + Year = 2022 + }, + new + { + Id = 7, + EngineVolume = 1.6000000000000001, + ModelId = 7, + RentalPricePerHour = 700m, + Transmission = "MT", + Year = 2023 + }, + new + { + Id = 8, + EngineVolume = 2.5, + ModelId = 8, + RentalPricePerHour = 1800m, + Transmission = "AT", + Year = 2024 + }, + new + { + Id = 9, + EngineVolume = 2.7000000000000002, + ModelId = 9, + RentalPricePerHour = 1500m, + Transmission = "MT", + Year = 2022 + }, + new + { + Id = 10, + EngineVolume = 1.8, + ModelId = 10, + RentalPricePerHour = 1600m, + Transmission = "CVT", + Year = 2023 + }, + new + { + Id = 11, + EngineVolume = 2.7000000000000002, + ModelId = 11, + RentalPricePerHour = 1400m, + Transmission = "MT", + Year = 2022 + }, + new + { + Id = 12, + EngineVolume = 3.5, + ModelId = 12, + RentalPricePerHour = 3200m, + Transmission = "AT", + Year = 2024 + }, + new + { + Id = 13, + EngineVolume = 3.0, + ModelId = 13, + RentalPricePerHour = 6000m, + Transmission = "AT", + Year = 2023 + }, + new + { + Id = 14, + EngineVolume = 2.0, + ModelId = 14, + RentalPricePerHour = 2800m, + Transmission = "AT", + Year = 2024 + }, + new + { + Id = 15, + EngineVolume = 1.7, + ModelId = 15, + RentalPricePerHour = 900m, + Transmission = "MT", + Year = 2023 + }); + }); + + modelBuilder.Entity("CarRental.Domain.Entities.Rental", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CarId") + .HasColumnType("int") + .HasColumnName("car_id"); + + b.Property("ClientId") + .HasColumnType("int") + .HasColumnName("client_id"); + + b.Property("RentalDate") + .HasColumnType("datetime2") + .HasColumnName("rental_date"); + + b.Property("RentalHours") + .HasColumnType("int") + .HasColumnName("rental_hours"); + + b.HasKey("Id"); + + b.HasIndex("CarId"); + + b.HasIndex("ClientId"); + + b.ToTable("rentals"); + + b.HasData( + new + { + Id = 1, + CarId = 7, + ClientId = 1, + RentalDate = new DateTime(2024, 3, 1, 10, 0, 0, 0, DateTimeKind.Unspecified), + RentalHours = 48 + }, + new + { + Id = 2, + CarId = 7, + ClientId = 3, + RentalDate = new DateTime(2024, 2, 25, 14, 30, 0, 0, DateTimeKind.Unspecified), + RentalHours = 72 + }, + new + { + Id = 3, + CarId = 7, + ClientId = 5, + RentalDate = new DateTime(2024, 2, 20, 9, 15, 0, 0, DateTimeKind.Unspecified), + RentalHours = 24 + }, + new + { + Id = 4, + CarId = 1, + ClientId = 2, + RentalDate = new DateTime(2024, 2, 27, 11, 45, 0, 0, DateTimeKind.Unspecified), + RentalHours = 96 + }, + new + { + Id = 5, + CarId = 1, + ClientId = 4, + RentalDate = new DateTime(2024, 2, 25, 16, 0, 0, 0, DateTimeKind.Unspecified), + RentalHours = 120 + }, + new + { + Id = 6, + CarId = 2, + ClientId = 6, + RentalDate = new DateTime(2024, 2, 23, 13, 20, 0, 0, DateTimeKind.Unspecified), + RentalHours = 72 + }, + new + { + Id = 7, + CarId = 2, + ClientId = 8, + RentalDate = new DateTime(2024, 2, 18, 10, 10, 0, 0, DateTimeKind.Unspecified), + RentalHours = 48 + }, + new + { + Id = 8, + CarId = 3, + ClientId = 7, + RentalDate = new DateTime(2024, 2, 28, 8, 30, 0, 0, DateTimeKind.Unspecified), + RentalHours = 36 + }, + new + { + Id = 9, + CarId = 4, + ClientId = 9, + RentalDate = new DateTime(2024, 2, 15, 12, 0, 0, 0, DateTimeKind.Unspecified), + RentalHours = 96 + }, + new + { + Id = 10, + CarId = 5, + ClientId = 10, + RentalDate = new DateTime(2024, 2, 28, 7, 0, 0, 0, DateTimeKind.Unspecified), + RentalHours = 168 + }, + new + { + Id = 11, + CarId = 6, + ClientId = 11, + RentalDate = new DateTime(2024, 2, 22, 15, 45, 0, 0, DateTimeKind.Unspecified), + RentalHours = 72 + }, + new + { + Id = 12, + CarId = 8, + ClientId = 12, + RentalDate = new DateTime(2024, 2, 26, 9, 20, 0, 0, DateTimeKind.Unspecified), + RentalHours = 48 + }, + new + { + Id = 13, + CarId = 9, + ClientId = 13, + RentalDate = new DateTime(2024, 2, 29, 22, 0, 0, 0, DateTimeKind.Unspecified), + RentalHours = 60 + }, + new + { + Id = 14, + CarId = 10, + ClientId = 14, + RentalDate = new DateTime(2024, 2, 24, 11, 30, 0, 0, DateTimeKind.Unspecified), + RentalHours = 96 + }, + new + { + Id = 15, + CarId = 11, + ClientId = 15, + RentalDate = new DateTime(2024, 2, 10, 14, 15, 0, 0, DateTimeKind.Unspecified), + RentalHours = 120 + }, + new + { + Id = 16, + CarId = 12, + ClientId = 1, + RentalDate = new DateTime(2024, 2, 29, 14, 0, 0, 0, DateTimeKind.Unspecified), + RentalHours = 48 + }, + new + { + Id = 17, + CarId = 13, + ClientId = 2, + RentalDate = new DateTime(2024, 2, 5, 16, 45, 0, 0, DateTimeKind.Unspecified), + RentalHours = 72 + }, + new + { + Id = 18, + CarId = 14, + ClientId = 3, + RentalDate = new DateTime(2024, 2, 12, 10, 10, 0, 0, DateTimeKind.Unspecified), + RentalHours = 36 + }, + new + { + Id = 19, + CarId = 15, + ClientId = 4, + RentalDate = new DateTime(2024, 2, 16, 13, 30, 0, 0, DateTimeKind.Unspecified), + RentalHours = 84 + }); + }); + + modelBuilder.Entity("CarRental.Domain.Entities.Car", b => + { + b.HasOne("CarRental.Domain.Entities.ModelGeneration", "ModelGeneration") + .WithMany() + .HasForeignKey("ModelGenerationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ModelGeneration"); + }); + + modelBuilder.Entity("CarRental.Domain.Entities.ModelGeneration", b => + { + b.HasOne("CarRental.Domain.Entities.CarModel", "Model") + .WithMany() + .HasForeignKey("ModelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Model"); + }); + + modelBuilder.Entity("CarRental.Domain.Entities.Rental", b => + { + b.HasOne("CarRental.Domain.Entities.Car", "Car") + .WithMany() + .HasForeignKey("CarId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CarRental.Domain.Entities.Client", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Car"); + + b.Navigation("Client"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/CarRental/CarRental/CarRental.Infrastructure/Persistence/AppDbContext.cs b/CarRental/CarRental/CarRental.Infrastructure/Persistence/AppDbContext.cs index ba7ae018d..d19c310c7 100644 --- a/CarRental/CarRental/CarRental.Infrastructure/Persistence/AppDbContext.cs +++ b/CarRental/CarRental/CarRental.Infrastructure/Persistence/AppDbContext.cs @@ -1,9 +1,10 @@ +using CarRental.Domain.Data; using CarRental.Domain.Entities; using Microsoft.EntityFrameworkCore; namespace CarRental.Infrastructure.Persistence; -public class AppDbContext(DbContextOptions options) : DbContext(options) +public class AppDbContext(DbContextOptions options, CarRentalFixture fixture) : DbContext(options) { public DbSet Cars { get; set; } public DbSet Clients { get; set; } @@ -51,5 +52,11 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.Entity() .HasKey(cm => cm.Id); + + modelBuilder.Entity().HasData(fixture.Cars); + modelBuilder.Entity().HasData(fixture.CarModels); + modelBuilder.Entity().HasData(fixture.Clients); + modelBuilder.Entity().HasData(fixture.ModelGenerations); + modelBuilder.Entity().HasData(fixture.Rentals); } } \ No newline at end of file diff --git a/CarRental/CarRental/CarRental.Infrastructure/Repositories/DbRepository.cs b/CarRental/CarRental/CarRental.Infrastructure/Repositories/DbRepository.cs index ce920f0e0..02620b4e0 100644 --- a/CarRental/CarRental/CarRental.Infrastructure/Repositories/DbRepository.cs +++ b/CarRental/CarRental/CarRental.Infrastructure/Repositories/DbRepository.cs @@ -1,7 +1,6 @@ using CarRental.Domain.Interfaces; using CarRental.Infrastructure.Persistence; using Microsoft.EntityFrameworkCore; -using System.Linq.Expressions; namespace CarRental.Infrastructure.Repositories; diff --git a/CarRental/CarRental/CarRental.slnx b/CarRental/CarRental/CarRental.slnx index d2918441c..933fe8c5a 100644 --- a/CarRental/CarRental/CarRental.slnx +++ b/CarRental/CarRental/CarRental.slnx @@ -1,5 +1,5 @@ - + From 579ec27f227c646ef36ef7854ac4786388dec762 Mon Sep 17 00:00:00 2001 From: ahewbu Date: Sat, 21 Feb 2026 16:46:17 +0400 Subject: [PATCH 20/20] =?UTF-8?q?=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D1=8B=20=D1=82=D0=B5=D1=81=D1=82=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CarRental.Tests/CarRentalTests.cs | 107 +++++++++++++----- 1 file changed, 79 insertions(+), 28 deletions(-) diff --git a/CarRental/CarRental/CarRental.Tests/CarRentalTests.cs b/CarRental/CarRental/CarRental.Tests/CarRentalTests.cs index 30ef04730..7788d9bd7 100644 --- a/CarRental/CarRental/CarRental.Tests/CarRentalTests.cs +++ b/CarRental/CarRental/CarRental.Tests/CarRentalTests.cs @@ -21,18 +21,33 @@ public void GetClientsByModelSortedByName() const string expectedSecondName = "Denis Popov"; const string expectedThirdName = "Igor Kozlovsky"; - var clients = fixture.Rentals - .Where(r => r.Car?.ModelGeneration?.Model?.Name == targetModel) - .Select(r => r.Client) - .Where(client => client != null) + var modelId = fixture.CarModels.FirstOrDefault(m => m.Name == targetModel)?.Id; + + var generationIds = fixture.ModelGenerations + .Where(mg => mg.ModelId == modelId) + .Select(mg => mg.Id) + .ToList(); + + var carIds = fixture.Cars + .Where(c => generationIds.Contains(c.ModelGenerationId)) + .Select(c => c.Id) + .ToList(); + + var clientIds = fixture.Rentals + .Where(r => carIds.Contains(r.CarId)) + .Select(r => r.ClientId) .Distinct() - .OrderBy(c => c!.FullName) + .ToList(); + + var clients = fixture.Clients + .Where(c => clientIds.Contains(c.Id)) + .OrderBy(c => c.FullName) .ToList(); Assert.Equal(expectedCount, clients.Count); - Assert.Equal(expectedFirstName, clients[0]?.FullName); - Assert.Equal(expectedSecondName, clients[1]?.FullName); - Assert.Equal(expectedThirdName, clients[2]?.FullName); + Assert.Equal(expectedFirstName, clients[0].FullName); + Assert.Equal(expectedSecondName, clients[1].FullName); + Assert.Equal(expectedThirdName, clients[2].FullName); } /// @@ -44,14 +59,17 @@ public void GetCurrentlyRentedCars() var testDate = new DateTime(2024, 3, 5, 12, 0, 0); var expectedPlate = "K234MR163"; - var rentedCars = fixture.Rentals - .Where(r => r.Car != null && r.RentalDate.AddHours(r.RentalHours) > testDate) - .Select(r => r.Car) - .Where(car => car != null) + var rentedCarIds = fixture.Rentals + .Where(r => r.RentalDate.AddHours(r.RentalHours) > testDate) + .Select(r => r.CarId) .Distinct() .ToList(); - Assert.Contains(rentedCars, c => c?.LicensePlate == expectedPlate); + var rentedCars = fixture.Cars + .Where(c => rentedCarIds.Contains(c.Id)) + .ToList(); + + Assert.Contains(rentedCars, c => c.LicensePlate == expectedPlate); } /// @@ -64,15 +82,27 @@ public void GetTop5MostRentedCars() const string expectedTopCarPlate = "N456RS163"; const int expectedTopCarRentalCount = 3; - var topCars = fixture.Rentals - .Where(r => r.Car != null) - .GroupBy(r => r.Car) - .Select(g => new { Car = g.Key, RentalCount = g.Count() }) - .Where(x => x.Car != null) + var topCarStats = fixture.Rentals + .GroupBy(r => r.CarId) + .Select(g => new { CarId = g.Key, RentalCount = g.Count() }) .OrderByDescending(x => x.RentalCount) .Take(5) .ToList(); + var topCarIds = topCarStats.Select(x => x.CarId).ToList(); + var carsDict = fixture.Cars + .Where(c => topCarIds.Contains(c.Id)) + .ToDictionary(c => c.Id); + + var topCars = topCarStats + .Select(x => new + { + Car = carsDict.GetValueOrDefault(x.CarId), + x.RentalCount + }) + .Where(x => x.Car != null) + .ToList(); + Assert.Equal(expectedCount, topCars.Count); Assert.Equal(expectedTopCarPlate, topCars[0].Car?.LicensePlate); Assert.Equal(expectedTopCarRentalCount, topCars[0].RentalCount); @@ -90,12 +120,15 @@ public void GetRentalCountPerCar() const int ladaVestaCarId = 7; const int bmwCarId = 1; + var rentalCounts = fixture.Rentals + .GroupBy(r => r.CarId) + .ToDictionary(g => g.Key, g => g.Count()); + var carsWithRentalCount = fixture.Cars - .Where(car => car != null) .Select(car => new { Car = car, - RentalCount = fixture.Rentals.Count(r => r.CarId == car.Id) + RentalCount = rentalCounts.GetValueOrDefault(car.Id, 0) }) .ToList(); @@ -118,20 +151,38 @@ public void GetTop5ClientsByRentalAmount() const int expectedCount = 5; const string expectedTopClientName = "Olga Zakharova"; - var topClients = fixture.Rentals - .Where(r => r.Client != null && r.Car?.ModelGeneration != null) - .Select(r => new + var carPrices = fixture.Cars + .Join(fixture.ModelGenerations, + c => c.ModelGenerationId, + g => g.Id, + (c, g) => new { CarId = c.Id, Price = g.RentalPricePerHour }) + .ToDictionary(x => x.CarId, x => x.Price); + + var topClientStats = fixture.Rentals + .GroupBy(r => r.ClientId) + .Select(g => new { - Client = r.Client, - Amount = r.RentalHours * r.Car!.ModelGeneration!.RentalPricePerHour + ClientId = g.Key, + TotalAmount = g.Sum(r => r.RentalHours * carPrices.GetValueOrDefault(r.CarId, 0)) }) - .GroupBy(x => x.Client) - .Select(g => new { Client = g.Key, TotalAmount = g.Sum(x => x.Amount) }) - .Where(x => x.Client != null) .OrderByDescending(x => x.TotalAmount) .Take(5) .ToList(); + var topClientIds = topClientStats.Select(x => x.ClientId).ToList(); + var clientsDict = fixture.Clients + .Where(c => topClientIds.Contains(c.Id)) + .ToDictionary(c => c.Id); + + var topClients = topClientStats + .Select(x => new + { + Client = clientsDict.GetValueOrDefault(x.ClientId), + x.TotalAmount + }) + .Where(x => x.Client != null) + .ToList(); + Assert.Equal(expectedCount, topClients.Count); Assert.Equal(expectedTopClientName, topClients[0].Client?.FullName); }