Загрузка данных


Вот **полный, готовый к копированию и сдаче вариант** для темы «Автосервис».

Код написан в стиле студента (просто, без сложных архитектур), компактно, и учитывает все требования (БД, интерфейс, 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, запусти. Код полностью соответствует критериям и стилю студенческой работы. Удачи на экзамене!