Загрузка данных
Вот **полный, готовый к копированию и сдаче вариант** для темы «Автосервис».
Код написан в стиле студента (просто, без сложных архитектур), компактно, и учитывает все требования (БД, интерфейс, CRUD, поиск, сортировка, отчет, тест-кейсы).
### ВАЖНО ПЕРЕД НАЧАЛОМ:
1. Создай проект **WPF App (.NET Framework)**.
2. Установи пакет **Npgsql** через NuGet (Правая кнопка по проекту -> Manage NuGet Packages -> Browse -> Npgsql -> Install).
3. Выполни SQL скрипт в pgAdmin.
---
### ЧАСТЬ 1: SQL (Выполнить в pgAdmin)
*Этот скрипт создаст базу данных, таблицы и заполнит их тестовыми данными.*
```sql
-- 1. Создаем базу
CREATE DATABASE autoservice_db;
-- 2. Подключаемся к ней и создаем таблицы
-- (В pgAdmin кликни по базе autoservice_db правой кнопкой -> Query Tool -> вставь и выполни)
CREATE TABLE clients (
client_id SERIAL PRIMARY KEY,
last_name VARCHAR(50),
phone VARCHAR(20)
);
CREATE TABLE cars (
car_id SERIAL PRIMARY KEY,
client_id INT REFERENCES clients(client_id),
model VARCHAR(50),
plate_number VARCHAR(10)
);
CREATE TABLE services (
service_id SERIAL PRIMARY KEY,
service_name VARCHAR(100),
price NUMERIC(10,2)
);
-- Главная таблица: Заказ-наряд
CREATE TABLE orders (
order_id SERIAL PRIMARY KEY,
car_id INT REFERENCES cars(car_id),
service_id INT REFERENCES services(service_id),
order_date DATE,
status VARCHAR(30),
cost NUMERIC(10,2),
completion_date DATE -- Дата выполнения (может быть пустой)
);
-- 3. Добавляем по 5 записей в каждую таблицу
INSERT INTO clients (last_name, phone) VALUES
('Иванов', '89001112233'), ('Петров', '89004445566'), ('Сидоров', '89007778899'),
('Кузнецов', '89000001122'), ('Смирнов', '89003334455');
INSERT INTO cars (client_id, model, plate_number) VALUES
(1, 'Toyota Camry', 'А111АА'), (2, 'BMW X5', 'В222ВВ'), (3, 'Kia Rio', 'Е333ЕЕ'),
(4, 'Hyundai Solaris', 'К444КК'), (5, 'Ford Focus', 'М555ММ');
INSERT INTO services (service_name, price) VALUES
('Замена масла', 1500), ('Диагностика ходовой', 2000), ('Замена колодок', 3000),
('Шиномонтаж', 2500), ('Ремонт двигателя', 5000);
INSERT INTO orders (car_id, service_id, order_date, status, cost, completion_date) VALUES
(1, 1, '2026-04-10', 'Выполнен', 1500, '2026-04-11'),
(2, 2, '2026-04-12', 'В работе', 2000, NULL),
(3, 3, '2026-04-13', 'Принят', 3000, NULL),
(4, 4, '2026-04-14', 'Выполнен', 2500, '2026-04-14'),
(5, 5, '2026-04-15', 'Отменен', 0, NULL);
```
---
### ЧАСТЬ 2: Интерфейс (MainWindow.xaml)
*Полностью замени содержимое файла `MainWindow.xaml` на этот код.*
```xml
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Автосервис - Учет заказов" Height="600" Width="900">
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- Панель кнопок -->
<StackPanel Grid.Row="0" Orientation="Horizontal" Margin="0,0,0,10">
<Button Content="Добавить" Click="BtnAdd_Click" Width="80" Margin="5" Background="LightGreen"/>
<Button Content="Редактировать" Click="BtnEdit_Click" Width="90" Margin="5" Background="LightBlue"/>
<Button Content="Удалить" Click="BtnDelete_Click" Width="70" Margin="5" Background="LightPink"/>
<Button Content="Обновить" Click="BtnRefresh_Click" Width="70" Margin="5"/>
<Button Content="Отчет" Click="BtnReport_Click" Width="70" Margin="5" Background="Yellow"/>
</StackPanel>
<!-- Панель поиска и сортировки -->
<StackPanel Grid.Row="1" Orientation="Horizontal" Margin="0,0,0,10">
<TextBlock Text="Поиск по статусу:" VerticalAlignment="Center" Margin="0,0,5,0"/>
<TextBox x:Name="TxtSearch" Width="150" Margin="0,0,5,0"/>
<Button Content="Найти" Click="BtnSearch_Click" Width="60"/>
<TextBlock Text="Сортировка:" VerticalAlignment="Center" Margin="20,0,5,0"/>
<ComboBox x:Name="ComboSort" Width="150" Margin="0,0,5,0">
<ComboBoxItem Content="По дате (возрастание)" IsSelected="True"/>
<ComboBoxItem Content="По стоимости (убывание)"/>
</ComboBox>
<Button Content="Применить" Click="BtnSort_Click" Width="80"/>
</StackPanel>
<!-- Таблица данных -->
<DataGrid x:Name="DataGridOrders" Grid.Row="2" AutoGenerateColumns="True" IsReadOnly="True" SelectionMode="Single" Margin="0,0,0,10"/>
<!-- Форма добавления/редактирования (скрыта по умолчанию) -->
<Border Grid.Row="3" x:Name="FormPanel" Visibility="Collapsed" BorderBrush="Gray" BorderThickness="1" Padding="10" Background="#F0F0F0" Margin="0,0,0,10">
<StackPanel>
<TextBlock Text="Заполните данные заказа (ID должны быть числами!)" FontWeight="Bold" Foreground="Red" Margin="0,0,0,5"/>
<StackPanel Orientation="Horizontal" Margin="0,2">
<TextBlock Text="ID Авто:" Width="70"/><TextBox x:Name="FldCarId" Width="80" Margin="5,0"/>
<TextBlock Text="ID Услуги:" Width="70" Margin="10,0,0,0"/><TextBox x:Name="FldServiceId" Width="80" Margin="5,0"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,2">
<TextBlock Text="Дата:" Width="70"/><TextBox x:Name="FldDate" Width="100" Margin="5,0" Text="2026-04-20"/>
<TextBlock Text="Статус:" Width="70" Margin="10,0,0,0"/><TextBox x:Name="FldStatus" Width="120" Margin="5,0"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,2">
<TextBlock Text="Сумма:" Width="70"/><TextBox x:Name="FldCost" Width="80" Margin="5,0"/>
<TextBlock Text="Дата вып.:" Width="70" Margin="10,0,0,0"/><TextBox x:Name="FldCompDate" Width="100" Margin="5,0"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,10,0,0">
<Button Content="Сохранить" Click="BtnSave_Click" Width="100" Background="LightGreen"/>
<Button Content="Отмена" Click="BtnCancel_Click" Width="100" Margin="10,0,0,0" Background="LightGray"/>
</StackPanel>
</StackPanel>
</Border>
<StatusBar Grid.Row="4">
<TextBlock x:Name="StatusText" Text="Готово к работе"/>
</StatusBar>
</Grid>
</Window>
```
---
### ЧАСТЬ 3: Логика (MainWindow.xaml.cs)
*Полностью замени содержимое файла `MainWindow.xaml.cs` на этот код.*
```csharp
using System;
using System.Data;
using System.Windows;
using System.Windows.Controls;
using Npgsql; // Библиотека для работы с PostgreSQL
namespace WpfApp1
{
public partial class MainWindow : Window
{
// Строка подключения к базе (проверь пароль, если у тебя другой)
string connString = "Host=localhost;Database=autoservice_db;Username=postgres;Password=12345";
int currentEditId = -1; // -1 значит мы добавляем, иначе редактируем
public MainWindow()
{
InitializeComponent();
LoadData();
}
// Функция загрузки данных из БД в таблицу
void LoadData(string sql = null)
{
try
{
using (NpgsqlConnection conn = new NpgsqlConnection(connString))
{
conn.Open();
// Если SQL не передан, грузим всё с сортировкой по умолчанию
string query = sql ?? "SELECT * FROM orders ORDER BY order_date ASC";
NpgsqlDataAdapter da = new NpgsqlDataAdapter(query, conn);
DataTable dt = new DataTable();
da.Fill(dt);
DataGridOrders.ItemsSource = dt.DefaultView;
StatusText.Text = "Загружено записей: " + dt.Rows.Count;
}
}
catch (Exception ex)
{
MessageBox.Show("Ошибка подключения к базе: " + ex.Message);
}
}
// --- ОБРАБОТЧИКИ КНОПОК ---
// Показать форму для добавления
private void BtnAdd_Click(object sender, RoutedEventArgs e)
{
currentEditId = -1;
FormPanel.Visibility = Visibility.Visible;
// Очистка полей
FldCarId.Text = "";
FldServiceId.Text = "";
FldDate.Text = DateTime.Today.ToString("yyyy-MM-dd");
FldStatus.Text = "Принят";
FldCost.Text = "";
FldCompDate.Text = "";
}
// Показать форму для редактирования
private void BtnEdit_Click(object sender, RoutedEventArgs e)
{
if (DataGridOrders.SelectedItem == null) return;
DataRowView row = (DataRowView)DataGridOrders.SelectedItem;
currentEditId = Convert.ToInt32(row["order_id"]);
FormPanel.Visibility = Visibility.Visible;
FldCarId.Text = row["car_id"].ToString();
FldServiceId.Text = row["service_id"].ToString();
// Форматируем даты в строку YYYY-MM-DD
FldDate.Text = Convert.ToDateTime(row["order_date"]).ToString("yyyy-MM-dd");
FldStatus.Text = row["status"].ToString();
FldCost.Text = row["cost"].ToString();
// Проверяем, есть ли дата выполнения (может быть NULL)
if (row["completion_date"] != DBNull.Value)
FldCompDate.Text = Convert.ToDateTime(row["completion_date"]).ToString("yyyy-MM-dd");
else
FldCompDate.Text = "";
}
// Сохранение (Добавление или Изменение)
private void BtnSave_Click(object sender, RoutedEventArgs e)
{
try
{
// Простая валидация
if (FldCarId.Text == "" || FldServiceId.Text == "")
{
MessageBox.Show("Введите ID Авто и ID Услуги!");
return;
}
int carId = int.Parse(FldCarId.Text);
int serviceId = int.Parse(FldServiceId.Text);
string date = FldDate.Text; // Оставляем как строку YYYY-MM-DD
string status = FldStatus.Text;
decimal cost = string.IsNullOrEmpty(FldCost.Text) ? 0 : decimal.Parse(FldCost.Text);
// Работа с датой выполнения (если пусто, ставим NULL)
string compDateSql = "NULL";
if (FldCompDate.Text != "")
compDateSql = "'" + FldCompDate.Text + "'";
using (NpgsqlConnection conn = new NpgsqlConnection(connString))
{
conn.Open();
string query;
if (currentEditId == -1)
{
// INSERT (Добавление)
query = $"INSERT INTO orders (car_id, service_id, order_date, status, cost, completion_date) " +
$"VALUES ({carId}, {serviceId}, '{date}', '{status}', {cost}, {compDateSql})";
}
else
{
// UPDATE (Редактирование)
query = $"UPDATE orders SET car_id={carId}, service_id={serviceId}, order_date='{date}', " +
$"status='{status}', cost={cost}, completion_date={compDateSql} WHERE order_id={currentEditId}";
}
NpgsqlCommand cmd = new NpgsqlCommand(query, conn);
cmd.ExecuteNonQuery();
}
FormPanel.Visibility = Visibility.Collapsed;
LoadData(); // Обновляем таблицу
}
catch (Exception ex)
{
MessageBox.Show("Ошибка сохранения: " + ex.Message);
}
}
// Отмена редактирования
private void BtnCancel_Click(object sender, RoutedEventArgs e)
{
FormPanel.Visibility = Visibility.Collapsed;
}
// Удаление
private void BtnDelete_Click(object sender, RoutedEventArgs e)
{
if (DataGridOrders.SelectedItem == null) return;
if (MessageBox.Show("Удалить запись?", "Внимание", MessageBoxButton.YesNo) == MessageBoxResult.Yes)
{
try
{
DataRowView row = (DataRowView)DataGridOrders.SelectedItem;
int id = Convert.ToInt32(row["order_id"]);
using (NpgsqlConnection conn = new NpgsqlConnection(connString))
{
conn.Open();
NpgsqlCommand cmd = new NpgsqlCommand("DELETE FROM orders WHERE order_id = @id", conn);
cmd.Parameters.AddWithValue("@id", id);
cmd.ExecuteNonQuery();
}
LoadData();
}
catch (Exception ex) { MessageBox.Show("Ошибка удаления: " + ex.Message); }
}
}
// Обновить таблицу
private void BtnRefresh_Click(object sender, RoutedEventArgs e)
{
LoadData();
}
// Поиск
private void BtnSearch_Click(object sender, RoutedEventArgs e)
{
string text = TxtSearch.Text;
if (text == "") return;
// ILIKE - поиск без учета регистра
LoadData("SELECT * FROM orders WHERE status ILIKE '%" + text + "%'");
}
// Сортировка
private void BtnSort_Click(object sender, RoutedEventArgs e)
{
if (ComboSort.SelectedIndex == 0)
LoadData("SELECT * FROM orders ORDER BY order_date ASC");
else
LoadData("SELECT * FROM orders ORDER BY cost DESC");
}
// Отчет
private void BtnReport_Click(object sender, RoutedEventArgs e)
{
string reportText = "ОТЧЕТ ПО ЗАКАЗАМ АВТОСЕРВИСА\nДата: " + DateTime.Now.ToShortDateString() + "\n\n";
// Проходим по строкам таблицы
DataView view = (DataView)DataGridOrders.ItemsSource;
if (view != null)
{
foreach (DataRowView row in view)
{
reportText += $"Заказ #{row["order_id"]} | Авто: {row["car_id"]} | Сумма: {row["cost"]} руб. | Статус: {row["status"]}\n";
}
}
// Показываем в новом окне
Window win = new Window();
win.Title = "Отчет";
win.Width = 500;
win.Height = 400;
TextBox txt = new TextBox();
txt.Text = reportText;
txt.IsReadOnly = true;
txt.TextWrapping = TextWrapping.Wrap;
txt.VerticalScrollBarVisibility = ScrollBarVisibility.Auto;
txt.FontFamily = new System.Windows.Media.FontFamily("Consolas");
win.Content = txt;
win.ShowDialog();
}
}
}
```
---
### ЧАСТЬ 4: Тест-кейсы (Задание 4)
*Скопируй это в текстовый документ для сдачи экзамена.*
**Позитивные тест-кейсы (Ожидаемый результат: Успешно):**
1. **Добавление заказа:** Нажать «Добавить», ввести ID Авто (1), ID Услуги (1), Дату, Статус, Нажать «Сохранить». *Результат:* Новая запись появилась в таблице.
2. **Поиск:** Ввести в поиск «Принят», нажать «Найти». *Результат:* В таблице остались только заказы со статусом «Принят».
3. **Сортировка:** Выбрать «По стоимости (убывание)», нажать «Применить». *Результат:* Заказы выстроились от дорогих к дешевым.
**Негативные тест-кейсы (Ожидаемый результат: Ошибка/Предупреждение):**
1. **Пустые обязательные поля:** Нажать «Добавить», оставить ID пустыми, нажать «Сохранить». *Результат:* Появилось сообщение «Введите ID Авто и ID Услуги!».
2. **Некорректный тип данных:** Ввести буквы в поле ID (например «abc»), нажать «Сохранить». *Результат:* Появилось сообщение об ошибке преобразования формата.
3. **Удаление без подтверждения:** Нажать «Удалить», выбрать «Нет». *Результат:* Запись не удалилась.
Вот полный, готовый к копированию вариант для темы **«Гостиница»**. Код компактный, без лишней архитектуры, написан в студенческом стиле и закрывает все критерии экзамена.
---
### ЧАСТЬ 1: SQL (Выполнить в pgAdmin)
```sql
CREATE DATABASE hotel_db;
-- (Выполни внутри созданной базы hotel_db)
CREATE TABLE guests (
guest_id SERIAL PRIMARY KEY,
last_name VARCHAR(50),
phone VARCHAR(20)
);
CREATE TABLE rooms (
room_id SERIAL PRIMARY KEY,
room_number VARCHAR(10),
room_type VARCHAR(30),
price_per_night NUMERIC(10,2)
);
CREATE TABLE employees (
emp_id SERIAL PRIMARY KEY,
last_name VARCHAR(50),
position VARCHAR(50)
);
-- Главная таблица: Бронирования
CREATE TABLE bookings (
booking_id SERIAL PRIMARY KEY,
guest_id INT REFERENCES guests(guest_id),
room_id INT REFERENCES rooms(room_id),
emp_id INT REFERENCES employees(emp_id),
check_in DATE NOT NULL,
check_out DATE, -- Может быть NULL, если гость еще не выехал
status VARCHAR(30),
total_price NUMERIC(10,2)
);
-- По 5 записей в каждую таблицу
INSERT INTO guests (last_name, phone) VALUES
('Иванов', '89001112233'), ('Петрова', '89004445566'), ('Сидоров', '89007778899'),
('Кузнецова', '89000001122'), ('Смирнов', '89003334455');
INSERT INTO rooms (room_number, room_type, price_per_night) VALUES
('101', 'Стандарт', 2500), ('102', 'Стандарт', 2500), ('201', 'Люкс', 5000),
('202', 'Люкс', 5000), ('301', 'Сьют', 8000);
INSERT INTO employees (last_name, position) VALUES
('Волкова', 'Администратор'), ('Зайцев', 'Портье'), ('Морозов', 'Менеджер'),
('Лебедева', 'Администратор'), ('Павлов', 'Управляющий');
INSERT INTO bookings (guest_id, room_id, emp_id, check_in, check_out, status, total_price) VALUES
(1, 1, 1, '2026-04-10', '2026-04-13', 'Выселен', 7500),
(2, 3, 2, '2026-04-11', NULL, 'Проживает', 10000),
(3, 2, 1, '2026-04-12', '2026-04-14', 'Выселен', 5000),
(4, 5, 3, '2026-04-13', NULL, 'Бронь', 16000),
(5, 4, 2, '2026-04-14', '2026-04-15', 'Выселен', 5000);
```
---
### ЧАСТЬ 2: Интерфейс (MainWindow.xaml)
```xml
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Гостиница - Учет бронирований" Height="620" Width="920">
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/><RowDefinition Height="Auto"/><RowDefinition Height="*"/><RowDefinition Height="Auto"/><RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- Кнопки -->
<StackPanel Grid.Row="0" Orientation="Horizontal" Margin="0,0,0,8">
<Button Content="Добавить" Click="BtnAdd_Click" Width="80" Margin="3" Background="LightGreen"/>
<Button Content="Редактировать" Click="BtnEdit_Click" Width="90" Margin="3" Background="LightBlue"/>
<Button Content="Удалить" Click="BtnDelete_Click" Width="70" Margin="3" Background="LightPink"/>
<Button Content="Обновить" Click="BtnRefresh_Click" Width="70" Margin="3"/>
<Button Content="Отчет" Click="BtnReport_Click" Width="70" Margin="3" Background="Yellow"/>
</StackPanel>
<!-- Поиск и сортировка -->
<StackPanel Grid.Row="1" Orientation="Horizontal" Margin="0,0,0,8">
<TextBlock Text="Поиск по статусу:" VerticalAlignment="Center" Margin="0,0,5,0"/>
<TextBox x:Name="TxtSearch" Width="150" Margin="0,0,5,0"/>
<Button Content="Найти" Click="BtnSearch_Click" Width="60"/>
<TextBlock Text="Сортировка:" VerticalAlignment="Center" Margin="20,0,5,0"/>
<ComboBox x:Name="ComboSort" Width="160" Margin="0,0,5,0">
<ComboBoxItem Content="По сумме (убывание)" IsSelected="True"/>
<ComboBoxItem Content="По дате заезда (возрастание)"/>
</ComboBox>
<Button Content="Применить" Click="BtnSort_Click" Width="80"/>
</StackPanel>
<!-- Таблица -->
<DataGrid x:Name="DataGridBookings" Grid.Row="2" AutoGenerateColumns="True" IsReadOnly="True" SelectionMode="Single" Margin="0,0,0,8"/>
<!-- Форма ввода -->
<Border Grid.Row="3" x:Name="FormPanel" Visibility="Collapsed" BorderBrush="Gray" BorderThickness="1" Padding="10" Background="#F5F5F5" Margin="0,0,0,8">
<StackPanel>
<TextBlock Text="Бронирование (ID - числа из таблиц!)" FontWeight="Bold" Foreground="Red" Margin="0,0,0,5"/>
<StackPanel Orientation="Horizontal" Margin="0,2">
<TextBlock Text="ID Гостя:" Width="70"/><TextBox x:Name="FldGuestId" Width="80" Margin="5,0"/>
<TextBlock Text="ID Номера:" Width="70" Margin="10,0,0,0"/><TextBox x:Name="FldRoomId" Width="80" Margin="5,0"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,2">
<TextBlock Text="ID Сотр.:" Width="70"/><TextBox x:Name="FldEmpId" Width="80" Margin="5,0"/>
<TextBlock Text="Заезд:" Width="70" Margin="10,0,0,0"/><TextBox x:Name="FldCheckIn" Width="100" Margin="5,0"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,2">
<TextBlock Text="Статус:" Width="70"/><TextBox x:Name="FldStatus" Width="100" Margin="5,0"/>
<TextBlock Text="Сумма:" Width="70" Margin="10,0,0,0"/><TextBox x:Name="FldTotal" Width="80" Margin="5,0"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,2">
<TextBlock Text="Выезд:" Width="70"/><TextBox x:Name="FldCheckOut" Width="100" Margin="5,0"/>
<TextBlock Text="(пусто если еще тут)" Margin="5,0,0,0" Foreground="Gray"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,10,0,0">
<Button Content="Сохранить" Click="BtnSave_Click" Width="100" Background="LightGreen"/>
<Button Content="Отмена" Click="BtnCancel_Click" Width="100" Margin="10,0,0,0" Background="LightGray"/>
</StackPanel>
</StackPanel>
</Border>
<StatusBar Grid.Row="4"><TextBlock x:Name="StatusText" Text="Готово к работе"/></StatusBar>
</Grid>
</Window>
```
---
### ЧАСТЬ 3: Логика (MainWindow.xaml.cs)
```csharp
using System;
using System.Data;
using System.Windows;
using System.Windows.Controls;
using Npgsql;
namespace WpfApp1
{
public partial class MainWindow : Window
{
// Строка подключения (проверь пароль от PostgreSQL)
string conn = "Host=localhost;Database=hotel_db;Username=postgres;Password=12345";
int editId = -1;
public MainWindow()
{
InitializeComponent();
Load();
}
void Load(string sql = null)
{
try
{
using (var c = new NpgsqlConnection(conn))
{
c.Open();
var da = new NpgsqlDataAdapter(sql ?? "SELECT * FROM bookings ORDER BY total_price DESC", c);
var dt = new DataTable();
da.Fill(dt);
DataGridBookings.ItemsSource = dt.DefaultView;
StatusText.Text = "Записей: " + dt.Rows.Count;
}
}
catch (Exception ex) { MessageBox.Show("Ошибка БД: " + ex.Message); }
}
void ShowForm(bool show, int id = -1)
{
FormPanel.Visibility = show ? Visibility.Visible : Visibility.Collapsed;
editId = id;
if (show && id > 0)
{
var r = (DataRowView)DataGridBookings.SelectedItem;
FldGuestId.Text = r["guest_id"].ToString();
FldRoomId.Text = r["room_id"].ToString();
FldEmpId.Text = r["emp_id"].ToString();
FldCheckIn.Text = Convert.ToDateTime(r["check_in"]).ToString("yyyy-MM-dd");
FldStatus.Text = r["status"].ToString();
FldTotal.Text = r["total_price"].ToString();
FldCheckOut.Text = r["check_out"] != DBNull.Value ? Convert.ToDateTime(r["check_out"]).ToString("yyyy-MM-dd") : "";
}
if (show && id < 0)
{
FldGuestId.Clear(); FldRoomId.Clear(); FldEmpId.Clear();
FldCheckIn.Text = DateTime.Today.ToString("yyyy-MM-dd");
FldStatus.Text = "Бронь"; FldTotal.Text = ""; FldCheckOut.Text = "";
}
}
private void BtnAdd_Click(object s, RoutedEventArgs e) { ShowForm(true); }
private void BtnEdit_Click(object s, RoutedEventArgs e)
{
if (DataGridBookings.SelectedItem == null) return;
ShowForm(true, Convert.ToInt32(((DataRowView)DataGridBookings.SelectedItem)["booking_id"]));
}
private void BtnDelete_Click(object s, RoutedEventArgs e)
{
if (DataGridBookings.SelectedItem == null) return;
if (MessageBox.Show("Удалить бронирование?", "Внимание", MessageBoxButton.YesNo) == MessageBoxResult.Yes)
{
try
{
using (var c = new NpgsqlConnection(conn))
{
c.Open();
var cmd = new NpgsqlCommand("DELETE FROM bookings WHERE booking_id = @id", c);
cmd.Parameters.AddWithValue("@id", Convert.ToInt32(((DataRowView)DataGridBookings.SelectedItem)["booking_id"]));
cmd.ExecuteNonQuery();
}
Load();
}
catch (Exception ex) { MessageBox.Show("Ошибка: " + ex.Message); }
}
}
private void BtnSave_Click(object s, RoutedEventArgs e)
{
try
{
if (FldGuestId.Text == "" || FldRoomId.Text == "" || FldCheckIn.Text == "")
{ MessageBox.Show("Заполните ID гостей/номеров и дату заезда!"); return; }
int g = int.Parse(FldGuestId.Text);
int r = int.Parse(FldRoomId.Text);
int e_id = string.IsNullOrEmpty(FldEmpId.Text) ? 0 : int.Parse(FldEmpId.Text);
string ci = FldCheckIn.Text;
string st = FldStatus.Text;
string tp = string.IsNullOrEmpty(FldTotal.Text) ? "0" : FldTotal.Text;
string co = string.IsNullOrEmpty(FldCheckOut.Text) ? "NULL" : "'" + FldCheckOut.Text + "'";
using (var c = new NpgsqlConnection(conn))
{
c.Open();
string q;
if (editId > 0)
q = $"UPDATE bookings SET guest_id={g},room_id={r},emp_id={e_id},check_in='{ci}',status='{st}',total_price={tp},check_out={co} WHERE booking_id={editId}";
else
q = $"INSERT INTO bookings (guest_id,room_id,emp_id,check_in,status,total_price,check_out) VALUES ({g},{r},{e_id},'{ci}','{st}',{tp},{co})";
new NpgsqlCommand(q, c).ExecuteNonQuery();
}
Load();
ShowForm(false);
}
catch (Exception ex) { MessageBox.Show("Ошибка сохранения: " + ex.Message); }
}
private void BtnCancel_Click(object s, RoutedEventArgs e) { ShowForm(false); }
private void BtnRefresh_Click(object s, RoutedEventArgs e) { Load(); }
private void BtnSearch_Click(object s, RoutedEventArgs e)
{
if (TxtSearch.Text == "") return;
Load($"SELECT * FROM bookings WHERE status ILIKE '%{TxtSearch.Text}%'");
}
private void BtnSort_Click(object s, RoutedEventArgs e)
{
string o = ComboSort.SelectedIndex == 1 ? "ASC" : "DESC";
Load($"SELECT * FROM bookings ORDER BY total_price {o}");
}
private void BtnReport_Click(object s, RoutedEventArgs e)
{
string rep = "ОТЧЕТ: Гостиница\nДата: " + DateTime.Now.ToString("dd.MM.yyyy") + "\n\n";
var dv = (DataView)DataGridBookings.ItemsSource;
if (dv != null)
foreach (DataRowView row in dv)
rep += $"Бронь #{row["booking_id"]} | Гость:{row["guest_id"]} | Номер:{row["room_id"]} | Сумма:{row["total_price"]}р. | Статус:{row["status"]}\n";
new Window { Title = "Отчет", Width = 500, Height = 400, Content = new TextBox { Text = rep, IsReadOnly = true, TextWrapping = TextWrapping.Wrap, VerticalScrollBarVisibility = ScrollBarVisibility.Auto, FontFamily = new System.Windows.Media.FontFamily("Consolas") } }.ShowDialog();
}
}
}
```
---
### ЧАСТЬ 4: Тест-кейсы (Задание 4)
*Скопируй в документ на экзамене.*
**Позитивные (Ожидаемый результат: Успешно):**
1. **Добавление брони:** Нажать «Добавить», ввести ID Гостя (1), ID Номера (2), ID Сотрудника (1), Дату заезда, Статус «Бронь», Сумму, нажать «Сохранить». *Результат:* Запись появилась в таблице.
2. **Поиск по статусу:** Ввести «Проживает», нажать «Найти». *Результат:* Таблица отфильтрована, видны только активные проживания.
3. **Сортировка:** Выбрать «По сумме (убывание)», нажать «Применить». *Результат:* Заказы выстроены от дорогих к дешевым.
**Негативные (Ожидаемый результат: Ошибка/Предупреждение):**
1. **Пустые обязательные поля:** Оставить ID пустыми, нажать «Сохранить». *Результат:* Сообщение «Заполните ID гостей/номеров и дату заезда!», запись не добавляется.
2. **Некорректный формат ID:** Ввести буквы в поле ID, нажать «Сохранить». *Результат:* Сообщение об ошибке преобразования, запись не сохраняется.
3. **Отмена удаления:** Выделить запись, нажать «Удалить», выбрать «Нет». *Результат:* Запись остается в таблице.
---
**Готово.** Просто замени `Password=12345` на свой пароль от PostgreSQL, установи `Npgsql` и запускай. Все критерии закрыты.
Вот полный, готовый к сдаче вариант для темы **«Ателье»**. Код компактный, без лишних архитектурных наворотов, написан так, как обычно делают на экзаменах, и полностью закрывает все критерии.
---
### ЧАСТЬ 1: SQL (Выполнить в pgAdmin)
```sql
CREATE DATABASE atelier_db;
-- (Выполни внутри созданной базы atelier_db)
CREATE TABLE clients (
client_id SERIAL PRIMARY KEY,
full_name VARCHAR(100),
phone VARCHAR(20)
);
CREATE TABLE garment_types (
type_id SERIAL PRIMARY KEY,
name VARCHAR(50),
base_price NUMERIC(10,2)
);
CREATE TABLE tailors (
tailor_id SERIAL PRIMARY KEY,
full_name VARCHAR(100),
specialization VARCHAR(50)
);
-- Главная таблица: Заказы
CREATE TABLE orders (
order_id SERIAL PRIMARY KEY,
client_id INT REFERENCES clients(client_id),
type_id INT REFERENCES garment_types(type_id),
tailor_id INT REFERENCES tailors(tailor_id),
order_date DATE NOT NULL,
deadline_date DATE, -- Срок сдачи (может быть NULL)
status VARCHAR(30),
final_price NUMERIC(10,2)
);
-- По 5 записей в каждую таблицу
INSERT INTO clients (full_name, phone) VALUES
('Иванова Мария', '89001112233'), ('Петров Сергей', '89004445566'), ('Сидорова Анна', '89007778899'),
('Кузнецов Олег', '89000001122'), ('Смирнова Елена', '89003334455');
INSERT INTO garment_types (name, base_price) VALUES
('Пошив платья', 5000), ('Ремонт молнии', 800), ('Укоротить брюки', 1200),
('Пошив костюма', 15000), ('Подгонка по фигуре', 3000);
INSERT INTO tailors (full_name, specialization) VALUES
('Волкова Н.И.', 'Женская одежда'), ('Зайцев А.П.', 'Мужская одежда'), ('Морозова Т.С.', 'Ремонт'),
('Лебедев Д.В.', 'Верхняя одежда'), ('Павлова К.М.', 'Универсал');
INSERT INTO orders (client_id, type_id, tailor_id, order_date, deadline_date, status, final_price) VALUES
(1, 1, 1, '2026-04-10', '2026-04-20', 'Выдан', 6500),
(2, 4, 2, '2026-04-11', '2026-04-30', 'В работе', 18000),
(3, 3, 3, '2026-04-12', '2026-04-15', 'Готов', 1200),
(4, 2, 3, '2026-04-13', '2026-04-14', 'Выдан', 800),
(5, 5, 4, '2026-04-14', NULL, 'Принят', 3500);
```
---
### ЧАСТЬ 2: Интерфейс (MainWindow.xaml)
```xml
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Ателье - Учет заказов" Height="640" Width="900">
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/><RowDefinition Height="Auto"/><RowDefinition Height="*"/><RowDefinition Height="Auto"/><RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- Кнопки управления -->
<StackPanel Grid.Row="0" Orientation="Horizontal" Margin="0,0,0,8">
<Button Content="Добавить" Click="BtnAdd_Click" Width="80" Margin="3" Background="LightGreen"/>
<Button Content="Редактировать" Click="BtnEdit_Click" Width="90" Margin="3" Background="LightBlue"/>
<Button Content="Удалить" Click="BtnDelete_Click" Width="70" Margin="3" Background="LightPink"/>
<Button Content="Обновить" Click="BtnRefresh_Click" Width="70" Margin="3"/>
<Button Content="Отчет" Click="BtnReport_Click" Width="70" Margin="3" Background="Yellow"/>
</StackPanel>
<!-- Поиск и сортировка -->
<StackPanel Grid.Row="1" Orientation="Horizontal" Margin="0,0,0,8">
<TextBlock Text="Поиск по статусу:" VerticalAlignment="Center" Margin="0,0,5,0"/>
<TextBox x:Name="TxtSearch" Width="150" Margin="0,0,5,0"/>
<Button Content="Найти" Click="BtnSearch_Click" Width="60"/>
<TextBlock Text="Сортировка:" VerticalAlignment="Center" Margin="20,0,5,0"/>
<ComboBox x:Name="ComboSort" Width="160" Margin="0,0,5,0">
<ComboBoxItem Content="По сроку сдачи (возрастание)" IsSelected="True"/>
<ComboBoxItem Content="По стоимости (убывание)"/>
</ComboBox>
<Button Content="Применить" Click="BtnSort_Click" Width="80"/>
</StackPanel>
<!-- Таблица данных -->
<DataGrid x:Name="DataGridOrders" Grid.Row="2" AutoGenerateColumns="True" IsReadOnly="True" SelectionMode="Single" Margin="0,0,0,8"/>
<!-- Форма ввода -->
<Border Grid.Row="3" x:Name="FormPanel" Visibility="Collapsed" BorderBrush="Gray" BorderThickness="1" Padding="10" Background="#F5F5F5" Margin="0,0,0,8">
<StackPanel>
<TextBlock Text="Новый/Редактирование заказа (ID - числа!)" FontWeight="Bold" Foreground="Red" Margin="0,0,0,5"/>
<StackPanel Orientation="Horizontal" Margin="0,2">
<TextBlock Text="ID Клиента:" Width="80"/><TextBox x:Name="FldClientId" Width="80" Margin="5,0"/>
<TextBlock Text="ID Типа:" Width="70" Margin="10,0,0,0"/><TextBox x:Name="FldTypeId" Width="80" Margin="5,0"/>
<TextBlock Text="ID Портного:" Width="90" Margin="10,0,0,0"/><TextBox x:Name="FldTailorId" Width="80" Margin="5,0"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,2">
<TextBlock Text="Дата приема:" Width="80"/><TextBox x:Name="FldOrderDate" Width="100" Margin="5,0"/>
<TextBlock Text="Срок сдачи:" Width="80" Margin="10,0,0,0"/><TextBox x:Name="FldDeadline" Width="100" Margin="5,0"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,2">
<TextBlock Text="Статус:" Width="80"/><TextBox x:Name="FldStatus" Width="120" Margin="5,0"/>
<TextBlock Text="Итого:" Width="70" Margin="10,0,0,0"/><TextBox x:Name="FldPrice" Width="80" Margin="5,0"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,10,0,0">
<Button Content="Сохранить" Click="BtnSave_Click" Width="100" Background="LightGreen"/>
<Button Content="Отмена" Click="BtnCancel_Click" Width="100" Margin="10,0,0,0" Background="LightGray"/>
</StackPanel>
</StackPanel>
</Border>
<StatusBar Grid.Row="4"><TextBlock x:Name="StatusText" Text="Готово к работе"/></StatusBar>
</Grid>
</Window>
```
---
### ЧАСТЬ 3: Логика (MainWindow.xaml.cs)
```csharp
using System;
using System.Data;
using System.Windows;
using System.Windows.Controls;
using Npgsql;
namespace WpfApp1
{
public partial class MainWindow : Window
{
// Строка подключения (проверь пароль от PostgreSQL)
string conn = "Host=localhost;Database=atelier_db;Username=postgres;Password=12345";
int editId = -1;
public MainWindow()
{
InitializeComponent();
Load();
}
void Load(string sql = null)
{
try
{
using (var c = new NpgsqlConnection(conn))
{
c.Open();
var da = new NpgsqlDataAdapter(sql ?? "SELECT * FROM orders ORDER BY deadline_date ASC", c);
var dt = new DataTable();
da.Fill(dt);
DataGridOrders.ItemsSource = dt.DefaultView;
StatusText.Text = "Записей: " + dt.Rows.Count;
}
}
catch (Exception ex) { MessageBox.Show("Ошибка БД: " + ex.Message); }
}
void ShowForm(bool show, int id = -1)
{
FormPanel.Visibility = show ? Visibility.Visible : Visibility.Collapsed;
editId = id;
if (show && id > 0)
{
var r = (DataRowView)DataGridOrders.SelectedItem;
FldClientId.Text = r["client_id"].ToString();
FldTypeId.Text = r["type_id"].ToString();
FldTailorId.Text = r["tailor_id"].ToString();
FldOrderDate.Text = Convert.ToDateTime(r["order_date"]).ToString("yyyy-MM-dd");
FldStatus.Text = r["status"].ToString();
FldPrice.Text = r["final_price"].ToString();
// Срок сдачи может быть пустым
FldDeadline.Text = r["deadline_date"] != DBNull.Value ? Convert.ToDateTime(r["deadline_date"]).ToString("yyyy-MM-dd") : "";
}
if (show && id < 0)
{
FldClientId.Clear(); FldTypeId.Clear(); FldTailorId.Clear();
FldOrderDate.Text = DateTime.Today.ToString("yyyy-MM-dd");
FldStatus.Text = "Принят"; FldPrice.Text = ""; FldDeadline.Text = "";
}
}
private void BtnAdd_Click(object s, RoutedEventArgs e) { ShowForm(true); }
private void BtnEdit_Click(object s, RoutedEventArgs e)
{
if (DataGridOrders.SelectedItem == null) return;
ShowForm(true, Convert.ToInt32(((DataRowView)DataGridOrders.SelectedItem)["order_id"]));
}
private void BtnDelete_Click(object s, RoutedEventArgs e)
{
if (DataGridOrders.SelectedItem == null) return;
if (MessageBox.Show("Удалить заказ?", "Внимание", MessageBoxButton.YesNo) == MessageBoxResult.Yes)
{
try
{
using (var c = new NpgsqlConnection(conn))
{
c.Open();
var cmd = new NpgsqlCommand("DELETE FROM orders WHERE order_id = @id", c);
cmd.Parameters.AddWithValue("@id", Convert.ToInt32(((DataRowView)DataGridOrders.SelectedItem)["order_id"]));
cmd.ExecuteNonQuery();
}
Load();
}
catch (Exception ex) { MessageBox.Show("Ошибка: " + ex.Message); }
}
}
private void BtnSave_Click(object s, RoutedEventArgs e)
{
try
{
if (FldClientId.Text == "" || FldTypeId.Text == "" || FldOrderDate.Text == "")
{ MessageBox.Show("Заполните ID и дату приема!"); return; }
int cl = int.Parse(FldClientId.Text);
int tp = int.Parse(FldTypeId.Text);
int tl = string.IsNullOrEmpty(FldTailorId.Text) ? 0 : int.Parse(FldTailorId.Text);
string od = FldOrderDate.Text;
string st = FldStatus.Text;
string pr = string.IsNullOrEmpty(FldPrice.Text) ? "0" : FldPrice.Text;
string dl = string.IsNullOrEmpty(FldDeadline.Text) ? "NULL" : "'" + FldDeadline.Text + "'";
using (var c = new NpgsqlConnection(conn))
{
c.Open();
string q;
if (editId > 0)
q = $"UPDATE orders SET client_id={cl},type_id={tp},tailor_id={tl},order_date='{od}',deadline_date={dl},status='{st}',final_price={pr} WHERE order_id={editId}";
else
q = $"INSERT INTO orders (client_id,type_id,tailor_id,order_date,deadline_date,status,final_price) VALUES ({cl},{tp},{tl},'{od}',{dl},'{st}',{pr})";
new NpgsqlCommand(q, c).ExecuteNonQuery();
}
Load();
ShowForm(false);
}
catch (Exception ex) { MessageBox.Show("Ошибка сохранения: " + ex.Message); }
}
private void BtnCancel_Click(object s, RoutedEventArgs e) { ShowForm(false); }
private void BtnRefresh_Click(object s, RoutedEventArgs e) { Load(); }
private void BtnSearch_Click(object s, RoutedEventArgs e)
{
if (TxtSearch.Text == "") return;
Load($"SELECT * FROM orders WHERE status ILIKE '%{TxtSearch.Text}%'");
}
private void BtnSort_Click(object s, RoutedEventArgs e)
{
string o = ComboSort.SelectedIndex == 1 ? "DESC" : "ASC";
Load($"SELECT * FROM orders ORDER BY final_price {o}");
}
private void BtnReport_Click(object s, RoutedEventArgs e)
{
string rep = "ОТЧЕТ: Ателье\nДата: " + DateTime.Now.ToString("dd.MM.yyyy") + "\n\n";
var dv = (DataView)DataGridOrders.ItemsSource;
if (dv != null)
foreach (DataRowView row in dv)
rep += $"Заказ #{row["order_id"]} | Клиент:{row["client_id"]} | Тип:{row["type_id"]} | Сумма:{row["final_price"]}р. | Статус:{row["status"]}\n";
new Window { Title = "Отчет", Width = 500, Height = 400, Content = new TextBox { Text = rep, IsReadOnly = true, TextWrapping = TextWrapping.Wrap, VerticalScrollBarVisibility = ScrollBarVisibility.Auto, FontFamily = new System.Windows.Media.FontFamily("Consolas") } }.ShowDialog();
}
}
}
```
---
### ЧАСТЬ 4: Тест-кейсы (Задание 4)
*Скопируй в документ на экзамене.*
**Позитивные (Ожидаемый результат: Успешно):**
1. **Добавление заказа:** Нажать «Добавить», ввести ID Клиента (1), ID Типа (2), ID Портного (3), Дату приема, Срок сдачи, Статус «Принят», Сумму, нажать «Сохранить». *Результат:* Запись появилась в таблице.
2. **Поиск по статусу:** Ввести «Готов», нажать «Найти». *Результат:* Таблица отфильтрована, видны только готовые заказы.
3. **Сортировка:** Выбрать «По стоимости (убывание)», нажать «Применить». *Результат:* Заказы выстроены от дорогих к дешевым.
**Негативные (Ожидаемый результат: Ошибка/Предупреждение):**
1. **Пустые обязательные поля:** Оставить ID пустыми, нажать «Сохранить». *Результат:* Сообщение «Заполните ID и дату приема!», запись не добавляется.
2. **Некорректный формат ID:** Ввести буквы в поле ID, нажать «Сохранить». *Результат:* Сообщение об ошибке преобразования, запись не сохраняется.
3. **Отмена удаления:** Выделить запись, нажать «Удалить», выбрать «Нет». *Результат:* Запись остается в таблице.
---
**Всё готово.** Замени `Password=12345` на свой, установи `Npgsql` через NuGet, запусти. Код полностью соответствует критериям и стилю студенческой работы. Удачи на экзамене!