Kaynağa Gözat

Добавление README

icp0 2 hafta önce
ebeveyn
işleme
b3bf6b7f68

+ 9 - 0
.gitignore

@@ -1,7 +1,16 @@
 bin/
 obj/
+.vs/
+publish/
+dist/
+WindowsFormsApp3_ready/
+installer/app.zip
+
 *.db
 *.db-shm
 *.db-wal
 Reports/
 dbsettings.json
+*.user
+*.suo
+*.tmp

+ 8 - 0
AutorentWinForms.csproj

@@ -7,6 +7,14 @@
     <ImplicitUsings>enable</ImplicitUsings>
     <ApplicationIcon />
   </PropertyGroup>
+  <ItemGroup>
+    <Compile Remove="WindowsFormsApp3_ready\**\*.cs" />
+    <Compile Remove="installer\**\*.cs" />
+    <None Remove="WindowsFormsApp3_ready\**\*" />
+    <None Remove="installer\**\*" />
+    <Content Remove="WindowsFormsApp3_ready\**\*" />
+    <Content Remove="installer\**\*" />
+  </ItemGroup>
   <ItemGroup>
     <Content Include="dbsettings.json">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>

+ 147 - 37
README.md

@@ -1,54 +1,164 @@
-# Автопрокат: Windows Forms приложение и защита БД
+# Аннотация
 
-Проект реализует настольное приложение на C# Windows Forms для предметной области «Автопрокат».
+Информационная система «Автопрокат» представляет собой настольное Windows Forms приложение на C# для учета клиентов, автомобилей, марок и договоров аренды. Программа подключается к PostgreSQL, инициализирует учебную схему данных, разграничивает права пользователей по ролям и позволяет формировать отчеты.
 
-## Запуск
+# Введение
+
+Основные возможности системы:
+
+- авторизация пользователей с ролями `admin`, `manager`, `viewer`;
+- просмотр, поиск, добавление, изменение и удаление клиентов;
+- ведение справочника автомобилей и марок;
+- оформление и сопровождение договоров аренды;
+- экспорт таблиц в CSV;
+- формирование отчетов в HTML и TXT;
+- просмотр журнала операций и событий аудита;
+- проверка работоспособности через режим `--smoke-test`;
+- разворачивание приложения через графический установщик `AutorentSetup.exe`.
+
+# Назначение и условия применения
+
+Программа предназначена для автоматизации работы учебного пункта автопроката: хранения сведений о клиентах, автопарке и арендах, контроля доступности автомобилей, подготовки отчетов и фиксации действий пользователей.
+
+Условия применения:
+
+- операционная система Windows 10 или новее;
+- установленный .NET SDK 9.0 для сборки проекта;
+- доступ к серверу PostgreSQL;
+- учетная запись БД с правом подключения к рабочей базе и схеме;
+- сетевой доступ к серверу `edu-pg.itiscaf.ru` или к другому PostgreSQL-серверу, указанному в `dbsettings.json`.
+
+# Установка
+
+## Шаги установки для пользователя
+
+1. [ ] Запустите файл `AutorentSetup.exe`.
+2. [ ] В окне мастера установки выберите папку для установки.
+
+   По умолчанию используется:
+
+   ```text
+   C:\Users\<пользователь>\AppData\Local\AutorentWinForms
+   ```
+
+   Папку можно изменить кнопкой `Обзор...`.
+
+3. [ ] При необходимости оставьте отметку `Создать ярлык на рабочем столе`.
+4. [ ] Нажмите `Установить` и дождитесь завершения копирования файлов.
+5. [ ] Нажмите `Готово`.
+6. [x] Установка завершена. Приложение можно запустить через ярлык на рабочем столе или через файл:
+
+   ```text
+   C:\Users\<пользователь>\AppData\Local\AutorentWinForms\AutorentWinForms.exe
+   ```
+
+## Что устанавливается в систему
+
+- главный исполняемый файл приложения `AutorentWinForms.exe`;
+- файл настроек подключения `dbsettings.json`;
+- зависимости .NET и библиотеки PostgreSQL-драйвера;
+- папка `Reports` создается при сохранении отчетов и CSV-файлов;
+- ярлык на рабочем столе, если пользователь выбрал соответствующий пункт.
+
+## Создание установщика из исходного кода
+
+Для разработчика установщик собирается командой:
 
 ```powershell
-dotnet run
+powershell -ExecutionPolicy Bypass -File .\scripts\build_installer.ps1
 ```
 
-Приложение подключается к PostgreSQL через файл `dbsettings.json`. Сейчас оно настроено на учебный сервер `edu-pg.itiscaf.ru`, базу `dbwork`, схему `garanin_gi`.
осле выполнения команды создается файл:
 
-Строку подключения также можно передать через переменную окружения `AUTORENT_CONNECTION_STRING`.
+```text
+dist\AutorentSetup.exe
+```
 
-Пользователь для входа:
роверить работоспособность установленной версии можно командой:
 
-| Логин | Пароль | Роль | Обязанности |
-|---|---|---|---|
-| `garanin_gi` | пароль от PostgreSQL | Администратор | Полный доступ к данным, отчетам и журналу аудита |
-| `manager` | `Manager123!` | Менеджер | Ввод и редактирование рабочих данных, формирование отчетов |
-| `shagavaliev_iv` | пароль viewer-пользователя | Аналитик | Просмотр данных и отчетов без права изменения |
+```powershell
+.\publish\AutorentWinForms\AutorentWinForms.exe --smoke-test
+```
 
-## Реализованные функции
+Успешная проверка завершается без сообщения об ошибке. В консольной сборке режим также выводит строку вида `OK: clients=..., audit=..., adminAuth=True`.
 
-- ввод, поиск, редактирование и удаление клиентов;
-- ввод, поиск, редактирование и удаление автомобилей;
-- управление справочником марок, доступно администратору;
-- оформление и сопровождение договоров аренды;
-- CSV-экспорт текущих таблиц;
-- формирование отчетных документов в `HTML` и `TXT`;
-- просмотр журнала операций.
+Файл подключения можно изменить после установки:
+
+```text
+<папка установки>\dbsettings.json
+```
+
+Дополнительно можно передать строку подключения через переменную окружения `AUTORENT_CONNECTION_STRING`. В этом случае файл `dbsettings.json` не используется.
+
+# Описание операций
 
-## Защита на уровне приложения
+Пользователь входит в программу через форму авторизации. После успешного входа открывается главное окно с вкладками данных. Набор доступных операций зависит от роли пользователя.
 
-- авторизация по логину и паролю;
-- пароли хранятся не в открытом виде, а как `PBKDF2-SHA256` хеш с индивидуальной солью;
-- разграничение прав по ролям `admin`, `manager`, `viewer`;
-- запрет операций изменения для аналитика;
-- параметризованные запросы к БД;
-- пользовательские сообщения об ошибках целостности без раскрытия лишних внутренних деталей.
+Основные операции:
+
+- `Клиенты`: поиск, просмотр, добавление, редактирование, удаление и экспорт списка клиентов;
+- `Автомобили`: учет регистрационного номера, марки, года выпуска, стоимости аренды и статуса;
+- `Марки`: ведение справочника марок автомобилей, доступно администратору;
+- `Аренда`: создание договора аренды, изменение периода, стоимости и статуса возврата;
+- `Отчеты`: формирование аналитической сводки и сохранение ее в HTML или TXT;
+- `Аудит`: просмотр журнала действий пользователей;
+- `CSV`: выгрузка текущей таблицы в папку `Reports`.
+
+Тестовые учетные записи для учебного стенда:
+
+| Логин | Пароль | Роль |
+| - | - | - |
+| `garanin_gi` | пароль PostgreSQL из учебного стенда | Администратор |
+| `manager` | `Manager123!` | Менеджер |
+| `shagavaliev_iv` | пароль пользователя просмотра | Аналитик |
+
+# Диаграмма развертывания системы
+
+```mermaid
+flowchart LR
+    subgraph Client["Рабочая станция оператора"]
+        User["Оператор"]
+        App["AutorentWinForms.exe"]
+        Settings["dbsettings.json или AUTORENT_CONNECTION_STRING"]
+        Reports["Папка Reports"]
+    end
+
+    subgraph Build["Среда сборки"]
+        Installer["AutorentSetup.exe"]
+        Publish["publish/AutorentWinForms"]
+    end
+
+    subgraph Server["Сервер PostgreSQL"]
+        Db["База dbwork"]
+        Schema["Схема автопроката"]
+        Audit["audit_log"]
+    end
+
+    User --> App
+    Installer --> Publish
+    Publish --> App
+    App --> Settings
+    App --> Reports
+    App -- "Npgsql, TCP 5432" --> Db
+    Db --> Schema
+    Schema --> Audit
+```
 
-## Защита на уровне БД
+# Руководство оператора
 
-В локальной демонстрационной БД реализованы:
+Подробное руководство оператора находится в файле [docs/operator_manual.md](docs/operator_manual.md).
 
-- первичные и внешние ключи;
-- ограничения `UNIQUE` для госномеров, телефонов, водительских удостоверений и названий марок;
-- ограничения `CHECK` для статусов, года выпуска, цены и дат аренды;
-- `ON DELETE RESTRICT` для защиты связанных данных;
-- представления `v_rentals_full`, `v_available_cars`, `v_client_stats`;
-- триггеры аудита `INSERT`, `UPDATE`, `DELETE` для таблиц `brand`, `car`, `client`, `rental`;
-- журнал `audit_log`.
+# Термины и сокращения
 
-Файл [database_security_postgresql.sql](database_security_postgresql.sql) содержит вариант DDL для PostgreSQL с ролями БД, правами доступа, Row Level Security и триггерами логирования.
+| Термин | Расшифровка |
+| - | - |
+| ИС | Информационная система |
+| БД | База данных |
+| PostgreSQL | Система управления реляционными базами данных |
+| WinForms | Технология создания настольных приложений Windows на .NET |
+| Оператор | Пользователь, выполняющий работу в программе |
+| Администратор | Пользователь с полными правами на данные и справочники |
+| Менеджер | Пользователь, ведущий клиентов, автомобили и аренды |
+| Аналитик | Пользователь с правом просмотра данных и отчетов |
+| CSV | Текстовый формат табличной выгрузки |
+| Npgsql | .NET-драйвер для подключения к PostgreSQL |

+ 3 - 0
dbsettings.example.json

@@ -0,0 +1,3 @@
+{
+  "ConnectionString": "Host=edu-pg.itiscaf.ru;Port=5432;Database=dbwork;Username=YOUR_LOGIN;Password=YOUR_PASSWORD;Search Path=YOUR_SCHEMA,public;Pooling=true;Timeout=15;Command Timeout=30"
+}

+ 131 - 0
docs/operator_manual.md

@@ -0,0 +1,131 @@
+# Аннотация
+
+Руководство оператора описывает порядок установки, проверки и использования информационной системы «Автопрокат». Документ предназначен для пользователя, который запускает программу, вводит данные об аренде автомобилей, формирует отчеты и выполняет контроль корректности работы.
+
+# Введение
+
+Область применения системы: учет деятельности учебного автопроката, включая клиентов, автомобили, марки, договоры аренды, отчеты и журнал операций.
+
+Основные возможности:
+
+- вход в систему по логину и паролю;
+- работа с данными клиентов, автомобилей, марок и договоров аренды;
+- разграничение доступа по ролям;
+- экспорт списков в CSV;
+- формирование отчетов в HTML и TXT;
+- просмотр аудита выполненных операций;
+- проверка состояния подключения и базовых функций.
+
+# Подготовка к работе
+
+Порядок установки:
+
+1. Убедиться, что на компьютере установлена Windows 10 или новее.
+2. Запустить файл `AutorentSetup.exe`.
+3. В окне мастера установки выбрать папку установки.
+4. По желанию отметить создание ярлыка на рабочем столе.
+5. Нажать `Установить`.
+6. Дождаться завершения копирования файлов.
+7. Нажать `Готово`.
+8. Открыть файл `dbsettings.json` в папке установки и указать параметры подключения к PostgreSQL, если они отличаются от учебного стенда.
+9. Запустить приложение через ярлык или файл `AutorentWinForms.exe`.
+
+Порядок создания установщика из исходного кода:
+
+1. Установить .NET SDK 9.0.
+2. Получить исходный код из репозитория Gogs.
+3. Выполнить команду:
+
+   ```powershell
+   powershell -ExecutionPolicy Bypass -File .\scripts\build_installer.ps1
+   ```
+
+4. Передать пользователю файл `dist\AutorentSetup.exe`.
+
+Порядок проверки работоспособности:
+
+1. Выполнить smoke-тест:
+
+   ```powershell
+   .\AutorentWinForms.exe --smoke-test
+   ```
+
+2. Убедиться, что команда завершилась без сообщения об ошибке. В консольной сборке может появиться строка `OK`.
+3. Запустить программу в обычном режиме.
+4. Войти под тестовой учетной записью менеджера.
+5. Открыть вкладку `Клиенты` и убедиться, что таблица загрузилась.
+6. Выполнить поиск по части ФИО или телефона.
+7. Сформировать отчет и проверить появление файла в папке `Reports`.
+
+# Описание операций
+
+Авторизация:
+
+1. Запустить `AutorentWinForms.exe`.
+2. Ввести логин и пароль.
+3. Нажать кнопку входа.
+4. При неверных данных повторить ввод или обратиться к администратору.
+
+Работа с клиентами:
+
+1. Открыть вкладку `Клиенты`.
+2. Для поиска ввести часть ФИО, телефона или другого признака в поле поиска.
+3. Для добавления нажать `Добавить`, заполнить поля и сохранить запись.
+4. Для изменения выбрать строку, нажать `Редактировать`, внести изменения и сохранить.
+5. Для удаления выбрать строку и подтвердить удаление.
+6. Для выгрузки нажать `CSV`.
+
+Работа с автомобилями:
+
+1. Открыть вкладку `Автомобили`.
+2. Проверить регистрационный номер, марку, год выпуска, стоимость аренды и статус.
+3. Добавить или изменить автомобиль при наличии прав менеджера или администратора.
+4. Использовать статус автомобиля для контроля доступности.
+
+Работа с арендами:
+
+1. Открыть вкладку `Аренда`.
+2. Создать новую запись аренды, выбрав клиента и автомобиль.
+3. Указать даты начала и окончания аренды.
+4. Указать стоимость и статус договора.
+5. При возврате автомобиля обновить статус аренды.
+
+Формирование отчетов:
+
+1. Открыть вкладку `Отчеты`.
+2. Нажать кнопку формирования отчета.
+3. Выбрать сохранение в HTML или TXT.
+4. Открыть созданный файл из папки `Reports`.
+
+Просмотр аудита:
+
+1. Открыть вкладку `Аудит`.
+2. Просмотреть записи о добавлении, изменении и удалении данных.
+3. При необходимости использовать сведения аудита для восстановления хода операций.
+
+# Аварийные ситуации
+
+| Ситуация | Причина | Действие оператора |
+| - | - | - |
+| Программа не запускается | Повреждены файлы установки или отсутствует исполняемый файл | Повторно запустить `AutorentSetup.exe` |
+| Ошибка подключения к БД | Неверная строка подключения или нет сети | Проверить `dbsettings.json`, логин, пароль и доступность PostgreSQL |
+| Не выполняется вход | Неверные учетные данные | Проверить логин и пароль, обратиться к администратору |
+| Таблица не загружается | Недоступна схема БД или нарушены права | Выполнить `--smoke-test`, проверить права пользователя БД |
+| Не сохраняется запись | Нарушено ограничение данных | Проверить обязательные поля, уникальность телефона, номера автомобиля и корректность дат |
+| Не создается отчет | Нет прав записи в папку программы | Запустить из доступной пользователю папки или выдать права на папку `Reports` |
+| Нет доступа к редактированию | Роль пользователя не позволяет изменять данные | Войти под менеджером или администратором |
+
+# Термины и сокращения
+
+| Термин | Значение |
+| - | - |
+| ИС | Информационная система |
+| БД | База данных |
+| Оператор | Пользователь, выполняющий операции в программе |
+| Администратор | Пользователь с полными правами |
+| Менеджер | Пользователь, который ведет рабочие данные автопроката |
+| Аналитик | Пользователь, который просматривает данные и отчеты |
+| Аренда | Договор временного использования автомобиля клиентом |
+| Аудит | Журнал действий пользователей |
+| CSV | Формат выгрузки табличных данных |
+| Smoke-тест | Быстрая проверка запуска, подключения и базовых функций |

+ 360 - 0
generate_report.py

@@ -0,0 +1,360 @@
+from pathlib import Path
+
+from docx import Document
+from docx.enum.section import WD_SECTION
+from docx.enum.text import WD_ALIGN_PARAGRAPH
+from docx.enum.table import WD_TABLE_ALIGNMENT, WD_CELL_VERTICAL_ALIGNMENT
+from docx.oxml import OxmlElement
+from docx.oxml.ns import qn
+from docx.shared import Cm, Pt, RGBColor
+
+
+OUT = Path(r"C:\Users\sdfgjnfjhkgbjkhdfg\Documents\autoprokat\Отчет_ПМ11_Автопрокат.docx")
+
+
+def set_cell_shading(cell, fill):
+    tc_pr = cell._tc.get_or_add_tcPr()
+    shd = OxmlElement("w:shd")
+    shd.set(qn("w:fill"), fill)
+    tc_pr.append(shd)
+
+
+def set_cell_text(cell, text, bold=False):
+    cell.text = ""
+    p = cell.paragraphs[0]
+    run = p.add_run(str(text))
+    run.bold = bold
+    run.font.name = "Times New Roman"
+    run.font.size = Pt(12)
+    cell.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER
+
+
+def add_table(doc, title, headers, rows):
+    p = doc.add_paragraph(title)
+    p.alignment = WD_ALIGN_PARAGRAPH.CENTER
+    p.runs[0].font.name = "Times New Roman"
+    p.runs[0].font.size = Pt(12)
+
+    table = doc.add_table(rows=1, cols=len(headers))
+    table.alignment = WD_TABLE_ALIGNMENT.CENTER
+    table.style = "Table Grid"
+    for i, h in enumerate(headers):
+        set_cell_text(table.rows[0].cells[i], h, True)
+        set_cell_shading(table.rows[0].cells[i], "D9EAF7")
+    for row in rows:
+        cells = table.add_row().cells
+        for i, value in enumerate(row):
+            set_cell_text(cells[i], value)
+    doc.add_paragraph()
+    return table
+
+
+def add_heading(doc, text, level=1):
+    p = doc.add_heading(text, level=level)
+    for run in p.runs:
+        run.font.name = "Times New Roman"
+        run.font.color.rgb = RGBColor(0, 0, 0)
+    return p
+
+
+def add_paragraph(doc, text="", first_line=True):
+    p = doc.add_paragraph()
+    p.alignment = WD_ALIGN_PARAGRAPH.JUSTIFY
+    p.paragraph_format.line_spacing = 1.5
+    if first_line:
+        p.paragraph_format.first_line_indent = Cm(1.25)
+    run = p.add_run(text)
+    run.font.name = "Times New Roman"
+    run.font.size = Pt(14)
+    return p
+
+
+def add_bullets(doc, items):
+    for item in items:
+        p = doc.add_paragraph(style=None)
+        p.paragraph_format.left_indent = Cm(1.25)
+        p.paragraph_format.line_spacing = 1.5
+        run = p.add_run(item)
+        run.font.name = "Times New Roman"
+        run.font.size = Pt(14)
+
+
+def add_code(doc, text):
+    p = doc.add_paragraph()
+    p.paragraph_format.line_spacing = 1.0
+    run = p.add_run(text)
+    run.font.name = "Courier New"
+    run.font.size = Pt(9)
+
+
+def set_document_style(doc):
+    section = doc.sections[0]
+    section.top_margin = Cm(2)
+    section.bottom_margin = Cm(2)
+    section.left_margin = Cm(3)
+    section.right_margin = Cm(1.5)
+
+    styles = doc.styles
+    styles["Normal"].font.name = "Times New Roman"
+    styles["Normal"].font.size = Pt(14)
+    for style_name in ["Heading 1", "Heading 2", "Heading 3"]:
+        style = styles[style_name]
+        style.font.name = "Times New Roman"
+        style.font.color.rgb = RGBColor(0, 0, 0)
+
+
+def title_page(doc):
+    lines = [
+        "Министерство науки и высшего образования Российской Федерации",
+        "Федеральное государственное бюджетное образовательное учреждение высшего образования",
+        "«Новгородский государственный университет имени Ярослава Мудрого»",
+        "ИНСТИТУТ ЭЛЕКТРОННЫХ И ИНФОРМАЦИОННЫХ СИСТЕМ",
+        "ПОЛИТЕХНИЧЕСКИЙ КОЛЛЕДЖ",
+    ]
+    for line in lines:
+        p = doc.add_paragraph()
+        p.alignment = WD_ALIGN_PARAGRAPH.CENTER
+        r = p.add_run(line)
+        r.font.name = "Times New Roman"
+        r.font.size = Pt(12)
+        if line.isupper():
+            r.bold = True
+
+    doc.add_paragraph("\n\n")
+    p = doc.add_paragraph("Утверждаю:\n\nЗам. директора по УМ и ВР\n\n____________________\n\n«___» __________ 202_ г.")
+    p.alignment = WD_ALIGN_PARAGRAPH.RIGHT
+
+    doc.add_paragraph("\n\n")
+    for line, size, bold in [
+        ("ПРОИЗВОДСТВЕННАЯ ПРАКТИКА", 16, True),
+        ("ПМ.11 РАЗРАБОТКА И АДМИНИСТРИРОВАНИЕ БАЗ ДАННЫХ", 14, True),
+        ("09.02.07 Информационные системы и программирование", 12, False),
+        ("код и наименование специальности", 10, False),
+        ("ОТЧЁТ", 18, True),
+        ("по теме: разработка базы данных и приложения «Автопрокат»", 14, True),
+    ]:
+        p = doc.add_paragraph()
+        p.alignment = WD_ALIGN_PARAGRAPH.CENTER
+        r = p.add_run(line)
+        r.font.name = "Times New Roman"
+        r.font.size = Pt(size)
+        r.bold = bold
+
+    doc.add_paragraph("\n\n")
+    p = doc.add_paragraph("Руководитель от организации:\n____________________\n«___» __________ 202_ г.\n")
+    p.alignment = WD_ALIGN_PARAGRAPH.LEFT
+    p = doc.add_paragraph("Руководитель от колледжа:\n____________________\n«___» __________ 202_ г.\n")
+    p.alignment = WD_ALIGN_PARAGRAPH.LEFT
+    p = doc.add_paragraph("Выполнил:\nСтудент гр. ________\n____________________\n«___» __________ 202_ г.")
+    p.alignment = WD_ALIGN_PARAGRAPH.RIGHT
+    doc.add_page_break()
+
+
+def add_contents(doc):
+    add_heading(doc, "Содержание", 1)
+    items = [
+        "Постановка задачи на практику",
+        "1 Инструкция по технике безопасности на рабочем месте",
+        "2 Должностные обязанности специалиста",
+        "3 Выполнение индивидуального задания",
+        "3.1 Проектирование модели базы данных",
+        "3.2 Словарь данных базы данных",
+        "3.3 Создание объектов базы данных",
+        "3.4 Руководство пользователя/администратора",
+        "3.5 Методы защиты данных в базе данных",
+        "4 Задание от предприятия",
+        "Заключение",
+        "Приложение А",
+        "Приложение Б",
+    ]
+    for item in items:
+        p = doc.add_paragraph()
+        r = p.add_run(item)
+        r.font.name = "Times New Roman"
+        r.font.size = Pt(14)
+    doc.add_page_break()
+
+
+def main():
+    doc = Document()
+    set_document_style(doc)
+    title_page(doc)
+    add_contents(doc)
+
+    add_heading(doc, "Постановка задачи на практику", 1)
+    add_paragraph(doc, "Задачей данного задания по модулю является проектирование и разработка базы данных, а также графического приложения для работы с ней. В качестве предметной области выбрана система автопроката, предназначенная для учета марок автомобилей, автомобилей, клиентов и договоров аренды.")
+    add_paragraph(doc, "Графическое приложение должно обеспечивать вход пользователя в систему, просмотр данных из базы данных, ввод новых записей, поиск, редактирование, удаление, а также формирование отчетных документов. Для разработки приложения выбрана технология Windows Forms на языке C#, а в качестве СУБД используется PostgreSQL.")
+    add_paragraph(doc, "Так как проектируемая система является многопользовательской, были спроектированы роли пользователей: администратор, менеджер и пользователь просмотра. Для защиты данных реализованы ограничения целостности на уровне базы данных, параметризованные запросы в приложении, разграничение прав и журналирование операций.")
+    add_paragraph(doc, "Задание по модулю ПМ.11 считается завершенным при выполнении следующих этапов работ:")
+    add_bullets(doc, [
+        "проектирование модели базы данных;",
+        "разработка базы данных в СУБД PostgreSQL;",
+        "разработка приложения для работы с базой данных;",
+        "реализация методов защиты на уровне БД и приложения;",
+        "оформление отчета о выполнении индивидуального задания.",
+    ])
+
+    add_heading(doc, "1 Инструкция по технике безопасности на рабочем месте", 1)
+    add_paragraph(doc, "При работе за компьютером необходимо соблюдать требования техники безопасности, правила эксплуатации оборудования и меры защиты информации. Рабочее место должно быть организовано так, чтобы исключить повышенную нагрузку на зрение, опорно-двигательный аппарат и нервную систему.")
+    add_bullets(doc, [
+        "перед началом работы проверить исправность компьютера, монитора, клавиатуры и мыши;",
+        "не работать с оборудованием при поврежденных кабелях и разъемах;",
+        "соблюдать расстояние от глаз до монитора не менее 50-70 см;",
+        "делать перерывы при длительной работе за компьютером;",
+        "использовать сложные пароли и не передавать учетные данные другим лицам;",
+        "не открывать подозрительные файлы и ссылки;",
+        "регулярно сохранять результаты работы и создавать резервные копии;",
+        "по окончании работы корректно завершать работу программ и выходить из учетной записи.",
+    ])
+
+    add_heading(doc, "2 Должностные обязанности специалиста", 1)
+    add_paragraph(doc, "В рамках выполнения задания специалист осуществляет анализ предметной области, проектирование структуры базы данных, создание таблиц и связей, разработку пользовательского приложения, настройку доступа и документирование результата.")
+    add_paragraph(doc, "К основным обязанностям специалиста при выполнении работы относятся: проектирование ER-модели, создание нормализованной реляционной схемы, разработка SQL-скриптов, заполнение базы тестовыми данными, создание приложения для ввода и обработки данных, реализация защиты информации и подготовка отчета.")
+
+    add_heading(doc, "3 Выполнение индивидуального задания", 1)
+    add_heading(doc, "3.1 Проектирование модели базы данных", 2)
+    add_paragraph(doc, "В ходе выполнения индивидуального задания была спроектирована база данных «Автопрокат». База данных приведена к третьей нормальной форме. Основными сущностями являются: марка автомобиля, автомобиль, клиент и договор аренды.")
+    add_paragraph(doc, "Связи между таблицами имеют следующий вид: одна марка может иметь несколько автомобилей; один автомобиль может участвовать в нескольких договорах аренды; один клиент может оформить несколько договоров аренды. Центральной таблицей предметной области является rental, связывающая клиента и автомобиль.")
+
+    add_heading(doc, "3.2 Словарь данных базы данных", 2)
+    add_paragraph(doc, "База данных состоит из следующих основных таблиц:")
+    add_bullets(doc, [
+        "brand - справочник марок автомобилей;",
+        "car - автомобили автопарка;",
+        "client - клиенты автопроката;",
+        "rental - договоры аренды;",
+        "app_user - учетные записи приложения;",
+        "audit_log - журнал операций;",
+        "app_context - служебный контекст текущего пользователя приложения.",
+    ])
+
+    add_table(doc, "Таблица 1 - Таблица brand", ["Название атрибута", "Тип данных", "Назначение", "Ключевое поле"], [
+        ["brand_id", "SERIAL", "Уникальный идентификатор марки", "Primary key"],
+        ["name", "VARCHAR(50)", "Название марки автомобиля", "UNIQUE"],
+        ["country", "VARCHAR(50)", "Страна-производитель", "Нет"],
+    ])
+    add_table(doc, "Таблица 2 - Таблица car", ["Название атрибута", "Тип данных", "Назначение", "Ключевое поле"], [
+        ["car_id", "SERIAL", "Уникальный идентификатор автомобиля", "Primary key"],
+        ["license_plate", "VARCHAR(15)", "Государственный номер", "UNIQUE"],
+        ["brand_id", "INT", "Ссылка на марку автомобиля", "Foreign key"],
+        ["year", "SMALLINT", "Год выпуска автомобиля", "CHECK"],
+        ["daily_rate", "NUMERIC(10,2)", "Стоимость аренды за день", "CHECK"],
+        ["status", "VARCHAR(20)", "Статус автомобиля", "CHECK"],
+    ])
+    add_table(doc, "Таблица 3 - Таблица client", ["Название атрибута", "Тип данных", "Назначение", "Ключевое поле"], [
+        ["client_id", "SERIAL", "Уникальный идентификатор клиента", "Primary key"],
+        ["full_name", "VARCHAR(100)", "ФИО клиента", "Нет"],
+        ["phone", "VARCHAR(20)", "Контактный телефон", "UNIQUE"],
+        ["address", "TEXT", "Адрес проживания", "Нет"],
+        ["license_no", "VARCHAR(20)", "Номер водительского удостоверения", "UNIQUE"],
+        ["created_at", "TIMESTAMPTZ", "Дата регистрации", "Нет"],
+    ])
+    add_table(doc, "Таблица 4 - Таблица rental", ["Название атрибута", "Тип данных", "Назначение", "Ключевое поле"], [
+        ["rental_id", "SERIAL", "Уникальный идентификатор договора", "Primary key"],
+        ["car_id", "INT", "Ссылка на автомобиль", "Foreign key"],
+        ["client_id", "INT", "Ссылка на клиента", "Foreign key"],
+        ["start_date", "DATE", "Дата начала аренды", "Нет"],
+        ["end_date", "DATE", "Плановая дата возврата", "CHECK"],
+        ["actual_return", "DATE", "Фактическая дата возврата", "Нет"],
+        ["total_cost", "NUMERIC(10,2)", "Итоговая стоимость", "CHECK"],
+        ["status", "VARCHAR(20)", "Статус договора", "CHECK"],
+    ])
+    add_table(doc, "Таблица 5 - Таблица app_user", ["Название атрибута", "Тип данных", "Назначение", "Ключевое поле"], [
+        ["user_id", "SERIAL", "Уникальный идентификатор пользователя", "Primary key"],
+        ["login", "VARCHAR(50)", "Логин пользователя", "UNIQUE"],
+        ["full_name", "VARCHAR(100)", "ФИО или наименование пользователя", "Нет"],
+        ["role", "VARCHAR(20)", "Роль пользователя", "CHECK"],
+        ["password_hash", "TEXT", "Хеш пароля", "Нет"],
+        ["password_salt", "TEXT", "Соль пароля", "Нет"],
+        ["is_active", "BOOLEAN", "Признак активности учетной записи", "Нет"],
+    ])
+    add_table(doc, "Таблица 6 - Таблица audit_log", ["Название атрибута", "Тип данных", "Назначение", "Ключевое поле"], [
+        ["audit_id", "BIGSERIAL", "Уникальный номер события", "Primary key"],
+        ["changed_at", "TIMESTAMPTZ", "Дата и время операции", "Нет"],
+        ["user_login", "TEXT", "Пользователь, выполнивший операцию", "Нет"],
+        ["action", "TEXT", "Тип операции", "Нет"],
+        ["entity", "TEXT", "Таблица", "Нет"],
+        ["entity_id", "INT", "Идентификатор измененной записи", "Нет"],
+        ["details", "TEXT", "Подробности изменения", "Нет"],
+    ])
+
+    add_heading(doc, "3.3 Создание объектов базы данных", 2)
+    add_paragraph(doc, "В ходе выполнения индивидуального задания были созданы таблицы, индексы, представления, роли, политики безопасности и триггеры аудита. Основная схема расположена в PostgreSQL в схеме garanin_gi базы dbwork.")
+    add_paragraph(doc, "Для ускорения выборок созданы индексы по внешним ключам, статусам и датам аренды. Для отчетности созданы представления v_rentals_full, v_available_cars и v_client_stats.")
+    add_code(doc, """CREATE VIEW v_rentals_full AS
+  SELECT r.rental_id, c.full_name AS client_name, c.phone AS client_phone,
+         ca.license_plate, b.name AS brand_name, r.start_date, r.end_date,
+         r.actual_return, r.total_cost, r.status
+  FROM rental r
+  JOIN client c ON c.client_id = r.client_id
+  JOIN car ca ON ca.car_id = r.car_id
+  JOIN brand b ON b.brand_id = ca.brand_id;""")
+
+    add_heading(doc, "3.4 Руководство пользователя/администратора", 2)
+    add_paragraph(doc, "Для запуска приложения необходимо открыть проект Windows Forms и выполнить команду dotnet run либо запустить собранный файл AutorentWinForms.exe. При запуске приложение подключается к серверу PostgreSQL edu-pg.itiscaf.ru, базе dbwork, схеме garanin_gi.")
+    add_paragraph(doc, "После запуска отображается форма авторизации. В системе предусмотрены три типа пользователей:")
+    add_table(doc, "Таблица 7 - Пользователи приложения", ["Логин", "Роль", "Доступ"], [
+        ["garanin_gi", "admin", "Полный доступ ко всем функциям"],
+        ["manager", "manager", "Работа с данными и отчетами"],
+        ["shagavaliev_iv", "viewer", "Просмотр основных таблиц без отчетов, защиты и аудита"],
+    ])
+    add_paragraph(doc, "Администратор имеет полный доступ к данным, отчетам, журналу операций и разделу защиты. Менеджер может добавлять, редактировать и удалять рабочие записи, формировать отчеты и просматривать журнал. Пользователь viewer может только просматривать основные таблицы: клиенты, автомобили, марки и аренда.")
+    add_paragraph(doc, "В основных вкладках приложения доступны функции поиска, просмотра, добавления, редактирования и удаления записей. Для формирования отчетов предусмотрен экспорт в HTML, TXT и CSV.")
+
+    add_heading(doc, "3.5 Методы защиты данных в базе данных", 2)
+    add_paragraph(doc, "В разработанной системе реализована комплексная защита информации на уровне базы данных и на уровне приложения. На уровне PostgreSQL используются ограничения целостности, роли, политики доступа, представления и триггеры журналирования.")
+    add_bullets(doc, [
+        "PRIMARY KEY исключает повторение идентификаторов записей;",
+        "FOREIGN KEY обеспечивает ссылочную целостность между таблицами;",
+        "UNIQUE запрещает повторение госномеров, телефонов, водительских удостоверений и названий марок;",
+        "CHECK ограничивает диапазоны значений и допустимые статусы;",
+        "ON DELETE RESTRICT запрещает удалять записи, используемые в связанных договорах;",
+        "Row Level Security применяется для таблицы rental;",
+        "audit_log фиксирует операции INSERT, UPDATE и DELETE.",
+    ])
+    add_paragraph(doc, "На уровне приложения реализованы вход по логину и паролю, разграничение интерфейса по ролям, параметризованные SQL-запросы через Npgsql и защита строки подключения через вынесение параметров в конфигурационный файл.")
+    add_code(doc, """CREATE ROLE rent_admin NOLOGIN;
+CREATE ROLE rent_manager NOLOGIN;
+CREATE ROLE rent_viewer NOLOGIN;
+
+GRANT SELECT, INSERT, UPDATE ON client, car, rental TO rent_manager;
+GRANT SELECT ON ALL TABLES IN SCHEMA public TO rent_viewer;
+
+ALTER TABLE rental ENABLE ROW LEVEL SECURITY;
+CREATE POLICY manager_access ON rental
+  FOR ALL TO rent_manager USING (true) WITH CHECK (true);""")
+    add_paragraph(doc, "Дополнительным методом защиты является журналирование операций. В базе данных создана таблица audit_log и триггеры, которые автоматически записывают сведения о действиях пользователя.")
+    add_code(doc, """CREATE TRIGGER trg_audit_client
+AFTER INSERT OR UPDATE OR DELETE ON client
+FOR EACH ROW EXECUTE FUNCTION fn_autorent_audit('client_id');""")
+
+    add_heading(doc, "4 Задание от предприятия", 1)
+    add_paragraph(doc, "В рамках выполнения задания была разработана информационная система для автоматизации учета автопроката. Работа включала проектирование базы данных, создание таблиц и связей, разработку приложения для пользователей, настройку доступа к удаленному серверу PostgreSQL и оформление отчетной документации.")
+    add_paragraph(doc, "Полученный программный продукт может использоваться менеджером автопроката для ведения клиентов, автомобилей, марок и договоров аренды. Администратор получает возможность контролировать журнал операций и параметры защиты, а пользователь просмотра может анализировать данные без риска их изменения.")
+
+    add_heading(doc, "Заключение", 1)
+    add_paragraph(doc, "В результате выполнения задания по модулю ПМ.11 были выполнены все основные этапы проектирования и разработки базы данных и приложения для предметной области «Автопрокат». Была разработана нормализованная структура базы данных PostgreSQL, включающая таблицы, связи, ограничения, индексы, представления и триггеры.")
+    add_paragraph(doc, "На этапе реализации было создано Windows Forms приложение на языке C#, обеспечивающее авторизацию пользователей, просмотр, поиск, ввод, редактирование и удаление данных, а также формирование отчетных документов. Приложение подключается к удаленной базе данных PostgreSQL и использует параметризованные запросы.")
+    add_paragraph(doc, "В системе реализованы методы защиты данных: разграничение ролей, ограничения целостности, проверка прав на уровне интерфейса, журналирование операций и использование защищенной конфигурации подключения. Таким образом, задание по модулю ПМ.11 выполнено в полном объеме.")
+
+    add_heading(doc, "Приложение А", 1)
+    p = doc.add_paragraph("(обязательное)")
+    p.alignment = WD_ALIGN_PARAGRAPH.CENTER
+    add_paragraph(doc, "Рисунок 1 - ER модель базы данных «Автопрокат»")
+    add_paragraph(doc, "Рисунок 2 - Реляционная модель базы данных «Автопрокат»")
+
+    add_heading(doc, "Приложение Б", 1)
+    p = doc.add_paragraph("(обязательное)")
+    p.alignment = WD_ALIGN_PARAGRAPH.CENTER
+    add_paragraph(doc, "Рисунок 3 - Создание таблиц brand, car, client, rental")
+    add_paragraph(doc, "Рисунок 4 - Создание представлений v_rentals_full, v_available_cars, v_client_stats")
+    add_paragraph(doc, "Рисунок 5 - Создание ролей и прав доступа")
+    add_paragraph(doc, "Рисунок 6 - Создание триггеров аудита")
+    add_code(doc, Path("database_security_postgresql.sql").read_text(encoding="utf-8")[:12000])
+
+    doc.save(OUT)
+    print(OUT)
+
+
+if __name__ == "__main__":
+    main()

+ 14 - 0
installer/AutorentSetup.csproj

@@ -0,0 +1,14 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <OutputType>WinExe</OutputType>
+    <TargetFramework>net9.0-windows</TargetFramework>
+    <UseWindowsForms>true</UseWindowsForms>
+    <Nullable>enable</Nullable>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <AssemblyName>AutorentSetup</AssemblyName>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <EmbeddedResource Include="app.zip" Condition="Exists('app.zip')" LogicalName="AppPackage.zip" />
+  </ItemGroup>
+</Project>

+ 227 - 0
installer/Program.cs

@@ -0,0 +1,227 @@
+using System.IO.Compression;
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+namespace AutorentSetup;
+
+internal static class Program
+{
+    [STAThread]
+    private static void Main()
+    {
+        ApplicationConfiguration.Initialize();
+        Application.Run(new SetupForm());
+    }
+}
+
+public sealed class SetupForm : Form
+{
+    private readonly TextBox _folderBox = new()
+    {
+        Text = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "AutorentWinForms"),
+        Width = 430
+    };
+
+    private readonly CheckBox _desktopShortcutBox = new()
+    {
+        Text = "Создать ярлык на рабочем столе",
+        Checked = true,
+        AutoSize = true
+    };
+
+    private readonly Button _installButton = new() { Text = "Установить", Width = 120, Height = 34 };
+    private readonly Label _statusLabel = new() { AutoSize = true, Text = "Готово к установке." };
+    private readonly ProgressBar _progress = new() { Width = 570, Height = 18 };
+
+    public SetupForm()
+    {
+        Text = "Установка Автопрокат";
+        StartPosition = FormStartPosition.CenterScreen;
+        FormBorderStyle = FormBorderStyle.FixedDialog;
+        MaximizeBox = false;
+        MinimizeBox = false;
+        ClientSize = new Size(640, 330);
+        Font = new Font("Segoe UI", 10F);
+
+        var title = new Label
+        {
+            Text = "Мастер установки ИС «Автопрокат»",
+            Font = new Font("Segoe UI", 15F, FontStyle.Bold),
+            AutoSize = true,
+            Location = new Point(28, 24)
+        };
+
+        var description = new Label
+        {
+            Text = "Программа установит приложение, файл настроек БД и необходимые зависимости.",
+            AutoSize = true,
+            Location = new Point(31, 62)
+        };
+
+        var folderLabel = new Label
+        {
+            Text = "Папка установки:",
+            AutoSize = true,
+            Location = new Point(31, 105)
+        };
+
+        _folderBox.Location = new Point(34, 132);
+
+        var browseButton = new Button
+        {
+            Text = "Обзор...",
+            Width = 100,
+            Height = 30,
+            Location = new Point(475, 130)
+        };
+        browseButton.Click += (_, _) => BrowseFolder();
+
+        _desktopShortcutBox.Location = new Point(34, 178);
+        _progress.Location = new Point(34, 222);
+        _statusLabel.Location = new Point(34, 250);
+
+        _installButton.Location = new Point(365, 282);
+        _installButton.Click += InstallButton_Click;
+
+        var closeButton = new Button
+        {
+            Text = "Отмена",
+            Width = 100,
+            Height = 34,
+            Location = new Point(475, 282)
+        };
+        closeButton.Click += (_, _) => Close();
+
+        Controls.AddRange([
+            title,
+            description,
+            folderLabel,
+            _folderBox,
+            browseButton,
+            _desktopShortcutBox,
+            _progress,
+            _statusLabel,
+            _installButton,
+            closeButton
+        ]);
+    }
+
+    private void BrowseFolder()
+    {
+        using var dialog = new FolderBrowserDialog
+        {
+            Description = "Выберите папку для установки Автопроката",
+            UseDescriptionForTitle = true,
+            SelectedPath = _folderBox.Text
+        };
+
+        if (dialog.ShowDialog(this) == DialogResult.OK)
+            _folderBox.Text = dialog.SelectedPath;
+    }
+
+    private void InstallButton_Click(object? sender, EventArgs e)
+    {
+        Install();
+    }
+
+    private void Install()
+    {
+        try
+        {
+            _installButton.Enabled = false;
+            _progress.Style = ProgressBarStyle.Marquee;
+            _statusLabel.Text = "Копирование файлов...";
+
+            var installPath = _folderBox.Text.Trim();
+            if (string.IsNullOrWhiteSpace(installPath))
+                throw new InvalidOperationException("Выберите папку установки.");
+
+            Directory.CreateDirectory(installPath);
+            ExtractApplication(installPath);
+            EnsureSettingsFile(installPath);
+
+            if (_desktopShortcutBox.Checked)
+                CreateShortcut("Автопрокат", Path.Combine(installPath, "AutorentWinForms.exe"));
+
+            _progress.Style = ProgressBarStyle.Blocks;
+            _progress.Value = 100;
+            _statusLabel.Text = "Установка завершена.";
+
+            MessageBox.Show(
+                "Установка завершена. Приложение можно запустить через ярлык или файл AutorentWinForms.exe.",
+                "Автопрокат",
+                MessageBoxButtons.OK,
+                MessageBoxIcon.Information);
+
+            _installButton.Text = "Готово";
+            _installButton.Enabled = true;
+            _installButton.Click -= InstallButton_Click;
+            _installButton.Click += (_, _) => Close();
+        }
+        catch (Exception ex)
+        {
+            _progress.Style = ProgressBarStyle.Blocks;
+            _progress.Value = 0;
+            _statusLabel.Text = "Ошибка установки.";
+            _installButton.Enabled = true;
+            MessageBox.Show(ex.Message, "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error);
+        }
+    }
+
+    private static void ExtractApplication(string installPath)
+    {
+        using var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("AppPackage.zip")
+            ?? throw new InvalidOperationException("В установщик не встроен пакет приложения app.zip. Запустите scripts\\build_installer.ps1.");
+
+        using var archive = new ZipArchive(stream, ZipArchiveMode.Read);
+        foreach (var entry in archive.Entries)
+        {
+            var destinationPath = Path.GetFullPath(Path.Combine(installPath, entry.FullName));
+            if (!destinationPath.StartsWith(Path.GetFullPath(installPath), StringComparison.OrdinalIgnoreCase))
+                throw new InvalidOperationException("Некорректный путь внутри установочного пакета.");
+
+            if (string.IsNullOrEmpty(entry.Name))
+            {
+                Directory.CreateDirectory(destinationPath);
+                continue;
+            }
+
+            Directory.CreateDirectory(Path.GetDirectoryName(destinationPath)!);
+            entry.ExtractToFile(destinationPath, overwrite: true);
+        }
+    }
+
+    private static void EnsureSettingsFile(string installPath)
+    {
+        var settings = Path.Combine(installPath, "dbsettings.json");
+        if (!File.Exists(settings))
+        {
+            File.WriteAllText(settings,
+                """
+                {
+                  "ConnectionString": "Host=edu-pg.itiscaf.ru;Port=5432;Database=dbwork;Username=YOUR_LOGIN;Password=YOUR_PASSWORD;Search Path=YOUR_SCHEMA,public;Pooling=true;Timeout=15;Command Timeout=30"
+                }
+                """);
+        }
+    }
+
+    private static void CreateShortcut(string name, string targetPath)
+    {
+        var desktop = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory);
+        var shortcutPath = Path.Combine(desktop, name + ".lnk");
+
+        var shellType = Type.GetTypeFromProgID("WScript.Shell");
+        if (shellType is null)
+            return;
+
+        dynamic shell = Activator.CreateInstance(shellType)!;
+        dynamic shortcut = shell.CreateShortcut(shortcutPath);
+        shortcut.TargetPath = targetPath;
+        shortcut.WorkingDirectory = Path.GetDirectoryName(targetPath);
+        shortcut.Description = "Информационная система Автопрокат";
+        shortcut.Save();
+
+        Marshal.FinalReleaseComObject(shortcut);
+        Marshal.FinalReleaseComObject(shell);
+    }
+}

+ 42 - 0
scripts/build_installer.ps1

@@ -0,0 +1,42 @@
+param(
+    [string]$Configuration = "Release",
+    [string]$Runtime = "win-x64"
+)
+
+$ErrorActionPreference = "Stop"
+
+$root = Split-Path -Parent $PSScriptRoot
+$appProject = Join-Path $root "AutorentWinForms.csproj"
+$installerProject = Join-Path $root "installer\AutorentSetup.csproj"
+$publishDir = Join-Path $root "publish\AutorentWinForms"
+$installerZip = Join-Path $root "installer\app.zip"
+$distDir = Join-Path $root "dist"
+
+Write-Host "Publishing application..."
+dotnet publish $appProject `
+    --configuration $Configuration `
+    --runtime $Runtime `
+    --self-contained true `
+    -p:PublishSingleFile=true `
+    -p:IncludeNativeLibrariesForSelfExtract=true `
+    --output $publishDir
+
+if (Test-Path $installerZip) {
+    Remove-Item -LiteralPath $installerZip -Force
+}
+
+Write-Host "Packing application files..."
+Compress-Archive -Path (Join-Path $publishDir "*") -DestinationPath $installerZip -Force
+
+Write-Host "Building graphical installer..."
+dotnet publish $installerProject `
+    --configuration $Configuration `
+    --runtime $Runtime `
+    --self-contained true `
+    -p:PublishSingleFile=true `
+    -p:IncludeNativeLibrariesForSelfExtract=true `
+    --output $distDir
+
+Write-Host ""
+Write-Host "Installer is ready:"
+Write-Host "  $(Join-Path $distDir 'AutorentSetup.exe')"

BIN
template_report.docx


Dosya farkı çok büyük olduğundan ihmal edildi
+ 1 - 0
template_text.txt


BIN
Отчет_ПМ11_Автопрокат.docx


Bu fark içinde çok fazla dosya değişikliği olduğu için bazı dosyalar gösterilmiyor