3 Komitmen a36a2f7879 ... ba8c972c01

Pembuat SHA1 Pesan Tanggal
  HanamileH ba8c972c01 Add documentation 2 minggu lalu
  HanamileH d9d669fff0 Add compiled program 2 minggu lalu
  HanamileH ed208b2001 Upload source files 2 minggu lalu
10 mengubah file dengan 657 tambahan dan 2 penghapusan
  1. 53 2
      README.md
  2. TEMPAT SAMPAH
      dist/Program.rar
  3. 47 0
      init_schema.sql
  4. 4 0
      src/.env
  5. 89 0
      src/db.py
  6. 382 0
      src/gui.py
  7. 8 0
      src/main.py
  8. 3 0
      src/requirements.txt
  9. TEMPAT SAMPAH
      Диаграмма развёртывания.png
  10. 71 0
      Руководство оператора.md

+ 53 - 2
README.md

@@ -1,3 +1,54 @@
-# Practice
+# Аннотация
+Программа предназначена для ведения учета корреспонденции, связанной с организациями и сотрудниками. Она позволяет просматривать данные из базы PostgreSQL, добавлять, редактировать, удалять и искать записи о корреспонденции через графический интерфейс.
 
-Программа для учёта корреспонденций предприятий
+# Введение
+## Основные возможности
+- Просмотр списка организаций.
+- Просмотр списка сотрудников с привязкой к организации.
+- Добавление новой корреспонденции.
+- Редактирование существующей корреспонденции.
+- Удаление записей корреспонденции.
+- Поиск корреспонденции по номеру документа.
+- Подсчет количества корреспонденции по выбранной организации.
+
+# Назначение и условия применения
+Программа предназначена для работы с PostgreSQL-базой данных корреспонденций. Для корректной работы требуются:
+- Windows 10 или выше.
+- Доступ к базе данных PostgreSQL.
+- Графическая среда для отображения окна приложения.
+- Процессор Intel Core i3 или выше.
+- Оперативная память не менее 4 ГБ.
+- Видеокарта с поддержкой OpenGL 2.0+.
+
+# Установка
+1. [ ] Скачайте и распакуйте архив с программой.
+2. [ ] Укажите в файле `.env` данные для подключения к базе данных, полученные от администратора.
+3. [ ] Запустите файл `main.exe`.
+4. [ ] Введите ваш логин и пароль для подключения к базе данных.
+
+# Описание операций
+- Авторизация в системе происходит через ввод имени пользователя и пароля PostgreSQL.
+- После подключения доступны вкладки:
+  - `Компании` - просмотр организаций.
+  - `Сотрудники` - просмотр сотрудников.
+  - `Корреспонденция` - работа с записями корреспонденции.
+- В разделе корреспонденции доступны операции:
+  - добавление записи;
+  - изменение записи;
+  - удаление записи;
+  - поиск по номеру документа.
+- Для создания и редактирования записи используются поля:
+  - дата поступления;
+  - дата исполнения;
+  - сотрудник;
+  - тип документа;
+  - номер документа.
+
+# Термины и сокращения
+| Термин | Описание |
+| - | - |
+| БД | База данных |
+| PostgreSQL | Система управления базами данных |
+| GUI | Графический пользовательский интерфейс |
+| Корреспонденция | Входящий или внутренний документ, учитываемый в программе |
+| Организация | Компания или учреждение, связанное с сотрудниками и корреспонденцией |

TEMPAT SAMPAH
dist/Program.rar


+ 47 - 0
init_schema.sql

@@ -0,0 +1,47 @@
+-- Создание ENUM типов
+CREATE TYPE legal_form_enum AS ENUM (
+    'ИП',
+    'ООО',
+    'ОАО',
+    'ПАО',
+    'смешанное'
+);
+
+CREATE TYPE document_type_enum AS ENUM (
+    'информационный',
+    'организационно-распорядительный',
+    'запросно-ответный',
+    'инициативный',
+    'другой'
+);
+
+-- Таблица organization (организация)
+CREATE TABLE organization (
+    id SERIAL PRIMARY KEY,
+    name VARCHAR(64) UNIQUE NOT NULL,
+    legal_form legal_form_enum NOT NULL
+);
+
+-- Таблица employee (сотрудники)
+CREATE TABLE employee (
+    id SERIAL PRIMARY KEY,
+    name VARCHAR(64) NOT NULL,
+    lastname VARCHAR(64) NOT NULL,
+    patronymic VARCHAR(64),
+    organization_id INTEGER NOT NULL REFERENCES organization(id) ON DELETE RESTRICT
+);
+
+-- Таблица correspondence (корреспонденция)
+CREATE TABLE correspondence (
+    id SERIAL PRIMARY KEY,
+    document_type document_type_enum NOT NULL,
+    document_number VARCHAR(256) UNIQUE NOT NULL,
+    receipt_date DATE NOT NULL DEFAULT CURRENT_DATE,
+    execution_date DATE, -- Может быть NULL, если документ ещё не исполнен
+    employee_id INTEGER NOT NULL REFERENCES employee(id) ON DELETE RESTRICT,
+    
+    -- Проверка что execution_date позже receipt_date или NULL
+    CONSTRAINT check_execution_date CHECK (
+        execution_date IS NULL OR execution_date >= receipt_date
+    )
+);

+ 4 - 0
src/.env

@@ -0,0 +1,4 @@
+DB_NAME=exam
+DB_HOST=localhost
+DB_PORT=5432
+DB_SCHEMA=public

+ 89 - 0
src/db.py

@@ -0,0 +1,89 @@
+import psycopg2
+from psycopg2.extras import DictCursor
+import os
+from dotenv import load_dotenv
+
+# Список организаций, сотрудников и корреспонденций
+organizations = []
+employees = []
+correspondencies = []
+
+# Подключение к базе данных
+conn = None
+
+# Инициализация подключения к базе данных
+def init_connection(username, password):
+   global conn
+   # Загрузка переменных окружения из .env файла
+   load_dotenv()
+
+   # Подключение к базе данных
+   conn = psycopg2.connect(
+      dbname=os.getenv("DB_NAME"),
+      user=username,
+      password=password,
+      host=os.getenv("DB_HOST"),
+      port=os.getenv("DB_PORT")
+   )
+
+   # Изменяем текущую схему
+   with conn.cursor() as cur:
+      cur.execute(f"SET search_path TO {os.getenv('DB_SCHEMA')};")
+
+
+# Получение всех организаций
+def get_all_organizations():
+   global organizations
+   with conn.cursor(cursor_factory=DictCursor) as cur:
+      cur.execute("SELECT id, name, legal_form FROM organization;")
+      organizations = cur.fetchall()
+
+# Получение всех сотрудников
+def get_all_employees():
+   global employees
+   with conn.cursor(cursor_factory=DictCursor) as cur:
+      cur.execute("SELECT id, name, lastname, patronymic, organization_id FROM employee;")
+      employees = cur.fetchall()
+
+# Получение всех корреспонденций
+def get_all_correspondencies():
+   global correspondencies
+   with conn.cursor(cursor_factory=DictCursor) as cur:
+      cur.execute("SELECT id, document_type, document_number, receipt_date, execution_date, employee_id FROM correspondence;")
+      correspondencies = cur.fetchall()
+
+# Добавление новой корреспонденции
+def add_correspondence(document_type, document_number, receipt_date, execution_date, employee_id):
+   with conn.cursor() as cur:
+      cur.execute(
+         "INSERT INTO correspondence (document_type, document_number, receipt_date, execution_date, employee_id) VALUES (%s, %s, %s, %s, %s) RETURNING id;",
+         (document_type, document_number, receipt_date, execution_date, employee_id)
+      )
+      new_id = cur.fetchone()[0]
+      conn.commit()
+      return new_id
+   
+# Редактирование существующей корреспонденции
+def edit_correspondence(correspondence_id, document_type, document_number, receipt_date, execution_date, employee_id):
+   with conn.cursor() as cur:
+      cur.execute(
+         "UPDATE correspondence SET document_type = %s, document_number = %s, receipt_date = %s, execution_date = %s, employee_id = %s WHERE id = %s;",
+         (document_type, document_number, receipt_date, execution_date, employee_id, correspondence_id)
+      )
+      conn.commit()
+
+# Удаление корреспонденции
+def delete_correspondence(correspondence_id):
+   with conn.cursor() as cur:
+      cur.execute("DELETE FROM correspondence WHERE id = %s;", (correspondence_id,))
+      conn.commit()
+
+# Подсчитать количество корреспонденций для выбранной
+def count_correspondencies_by_organization(organization_id):
+   with conn.cursor() as cur:
+      cur.execute(
+         "SELECT COUNT(*) FROM correspondence c JOIN employee e ON c.employee_id = e.id WHERE e.organization_id = %s;",
+         (organization_id,)
+      )
+      count = cur.fetchone()[0]
+      return count

+ 382 - 0
src/gui.py

@@ -0,0 +1,382 @@
+from datetime import datetime
+from ara_imgui import imgui
+import psycopg2
+import db
+
+# Страница компаний
+def gui_organizations():
+   imgui.text("Список компаний:")
+
+   # Таблица значений
+   if imgui.begin_child("##organizations_table"):
+      imgui.columns(3)
+
+      # Заголовки столбцов
+      for title in ["id", "Наименование", "Тип"]:
+         imgui.text(title)
+         imgui.next_column()
+
+      imgui.separator()
+
+      # Значения
+      for o in db.organizations:
+         imgui.text(str(o["id"]))
+         imgui.next_column()
+         imgui.text(o["name"])
+         imgui.next_column()
+         imgui.text(o["legal_form"])
+         imgui.next_column()
+
+      imgui.columns(1)
+      imgui.end_child()
+
+# Страница сотрудников
+def gui_employees():
+   imgui.text("Список сотрудников:")
+
+   # Таблица значений
+   if imgui.begin_child("##employees_table"):
+      imgui.columns(5)
+
+      # Заголовки столбцов
+      for title in ["id", "Имя", "Фамилия", "Отчество", "Компания"]:
+         imgui.text(title)
+         imgui.next_column()
+
+      imgui.separator()
+
+      # Значения
+      for e in db.employees:
+         org = [o for o in db.organizations if o["id"] == e["organization_id"]][0]
+         imgui.text(str(e["id"]))
+         imgui.next_column()
+         imgui.text(e["name"])
+         imgui.next_column()
+         imgui.text(e["lastname"])
+         imgui.next_column()
+         imgui.text(e["patronymic"])
+         imgui.next_column()
+         imgui.text(f'{org["legal_form"]} "{org["name"]}"')
+         imgui.next_column()
+
+      imgui.columns(1)
+      imgui.end_child()
+
+# Страница корреспонденций
+DOCUMENT_TYPES = [
+   'информационный',
+   'организационно-распорядительный',
+   'запросно-ответный',
+   'инициативный',
+   'другой'
+]
+
+selected_correspondence_id = None # Выбранная корреспонденция (для редактирования)
+total_correspondencies = None # Общее количество корреспонденций для выбранной компании
+
+selected_employee_pos = 0     # Выбранный сотрудник
+selected_organization_pos = 0 # Выбранная компания
+receipt_date_input = ""       # Введенная дата поступления
+execution_date_input = ""     # Введенная дата исполнения
+document_type_pos = 0         # Выбраный тип документа
+document_number_input = ""    # Введенный номер документа
+
+search_document_name = "" # Имя докумнета для поиска
+
+error_text = None # Текст ошибки (если есть)
+
+def gui_correspondencies():
+   global selected_correspondence_id
+   global total_correspondencies
+   global selected_employee_pos, selected_organization_pos
+   global receipt_date_input, execution_date_input
+   global document_type_pos, document_number_input
+   global search_document_name
+   global error_text
+
+   # Данные для списка компаний
+   organizations_list = [f'{o["legal_form"]} "{o["name"]}"' for o in db.organizations]
+   selected_organization_id = db.organizations[selected_organization_pos]["id"]
+
+   # Выпадающий список для выбора компании
+   updated, selected_organization_pos = imgui.combo("Компания", selected_organization_pos, organizations_list)
+
+   if updated or (total_correspondencies is None):
+      selected_organization_id = db.organizations[selected_organization_pos]["id"]
+      total_correspondencies = db.count_correspondencies_by_organization(selected_organization_id)
+
+
+   imgui.text(f"Всего корреспонденций: {total_correspondencies}")
+
+   # Панель редактирования
+   imgui.separator()
+   imgui.text("Добавить корреспонденцию")
+   
+   # Дата поступления
+   if imgui.button("Сегодня ##1"):
+      receipt_date_input = datetime.today().strftime("%d-%m-%Y")
+
+   imgui.same_line()
+
+   receipt_date_input = imgui.input_text("Дата поступления", receipt_date_input)[1]
+   if imgui.is_item_hovered():
+      imgui.set_tooltip("В формате ДД-ММ-ГГГГ")
+      
+   # Дата исполнения
+   if imgui.button("Сегодня ##2"):
+      execution_date_input = datetime.today().strftime("%d-%m-%Y")
+
+   imgui.same_line()
+
+   execution_date_input = imgui.input_text("Дата исполнения (опционально)", execution_date_input)[1]
+   if imgui.is_item_hovered():
+      imgui.set_tooltip("В формате ДД-ММ-ГГГГ")
+
+   # Данные для списка сотрудников (только сотрудники выбранной компании)
+   employees_list = [f'{e["lastname"]} {e["name"]} {e["patronymic"]}' for e in db.employees if e["organization_id"] == selected_organization_id]
+
+   # Выпадающие список для выбора сотрудника
+   selected_employee_pos = imgui.combo("Сотрудник", selected_employee_pos, employees_list)[1]
+
+   # Выпадающий список для выбора типа документа
+   document_type_pos = imgui.combo("Тип документа", document_type_pos, DOCUMENT_TYPES)[1]
+
+   # Ввод номера документа
+   document_number_input = imgui.input_text("Номер документа", document_number_input)[1]
+
+   if selected_correspondence_id:
+      # Кнопка сохранения изменений
+      if imgui.button("Сохранить изменения"):
+         error_text = None
+
+         # Парсим введенные даты
+         try:
+            receipt_date = datetime.strptime(receipt_date_input, "%d-%m-%Y")
+
+         except ValueError:
+            error_text = "Неверный формат даты поступления"
+            return
+         
+         try:
+            execution_date = datetime.strptime(execution_date_input, "%d-%m-%Y") if execution_date_input else None
+
+         except ValueError:
+            error_text = "Неверный формат даты исполнения"
+            return
+         
+         # Проверяем, что дата исполнения не раньше даты поступления
+         if execution_date and execution_date < receipt_date:
+            error_text = "Дата исполнения не может быть раньше даты поступления"
+            return
+         
+         # Проверяем, что номер документа не пустой
+         if not document_number_input:
+            error_text = "Номер документа не может быть пустым"
+            return
+         
+         # Изменяем корреспонденцию в базе данных
+         db.edit_correspondence(
+            correspondence_id=selected_correspondence_id,
+            document_type=DOCUMENT_TYPES[document_type_pos],
+            document_number=document_number_input,
+            receipt_date=receipt_date,
+            execution_date=execution_date,
+            employee_id=db.employees[selected_employee_pos]["id"]
+         )
+         db.get_all_correspondencies()
+         selected_correspondence_id = None
+         receipt_date_input = ""
+         execution_date_input = ""
+         document_type_pos = 0
+         document_number_input = ""
+         selected_organization_pos = 0
+         selected_employee_pos = 0
+
+      # Кнопка отмены редактирования
+      imgui.same_line()
+
+      if imgui.button("Отмена"):
+         selected_correspondence_id = None
+         receipt_date_input = ""
+         execution_date_input = ""
+         document_type_pos = 0
+         document_number_input = ""
+         selected_organization_pos = 0
+         selected_employee_pos = 0
+         error_text = None
+
+   else:
+      # Кнопка добавления новой корреспонденции
+      if imgui.button("Добавить"):
+         error_text = None
+
+         # Парсим введенные даты
+         try:
+            receipt_date = datetime.strptime(receipt_date_input, "%d-%m-%Y")
+
+         except ValueError:
+            error_text = "Неверный формат даты поступления"
+            return
+         
+         try:
+            execution_date = datetime.strptime(execution_date_input, "%d-%m-%Y") if execution_date_input else None
+
+         except ValueError:
+            error_text = "Неверный формат даты исполнения"
+            return
+         
+         # Проверяем, что дата исполнения не раньше даты поступления
+         if execution_date and execution_date < receipt_date:
+            error_text = "Дата исполнения не может быть раньше даты поступления"
+            return
+         
+         # Проверяем, что номер документа не пустой
+         if not document_number_input:
+            error_text = "Номер документа не может быть пустым"
+            return
+
+         # Добавляем новую корреспонденцию в базу данных
+         try:
+            selected_employee_id = db.employees[selected_employee_pos]["id"]
+            db.add_correspondence(
+               document_type=DOCUMENT_TYPES[document_type_pos],
+               document_number=document_number_input,
+               receipt_date=receipt_date,
+               execution_date=execution_date,
+               employee_id=selected_employee_id
+            )
+
+            # Обновляем список корреспонденций
+            db.get_all_correspondencies()
+         except psycopg2.errors.UniqueViolation:
+            error_text = "Корреспонденция с таким номером уже существует"
+
+   # Выводим текст ошибки (если есть)
+   if error_text:
+      imgui.text_colored(imgui.ImVec4(1.0, 0.3, 0.3, 1.0), error_text)
+
+   # Панель поиска
+   imgui.separator()
+   search_document_name = imgui.input_text("Поиск по номеру документа", search_document_name)[1]
+
+   # Список всех корреспонденций
+   if imgui.begin_child("##correspondencies_table"):
+      imgui.columns(6)
+
+      # Заголовки столбцов
+      for title in ["Дата поступления", "Дата исполнения", "Сотрудник", "Тип документа", "Номер документа", "Действия"]:
+         imgui.text(title)
+         imgui.next_column()
+
+      imgui.separator()
+
+      # Значения
+      for i, c in enumerate(db.correspondencies):
+         # Данные сотрудника и компании для текущей корреспонденции
+         employee = next(e for e in db.employees if e["id"] == c["employee_id"])
+         organization = next(o for o in db.organizations if o["id"] == employee["organization_id"])
+
+         if organization["id"] != selected_organization_id:
+            continue
+
+         if search_document_name.lower() not in c["document_number"].lower():
+            continue
+
+         # Дата поступления/исполнения
+         imgui.text(c["receipt_date"].strftime("%d-%m-%Y"))
+         imgui.next_column()
+
+         imgui.text(c["execution_date"].strftime("%d-%m-%Y") if c["execution_date"] else "-")
+         imgui.next_column()
+
+         # Сотрудник
+         imgui.text(f'{employee["lastname"]} {employee["name"]} {employee["patronymic"]}')
+         imgui.next_column()
+
+         # Тип и номер документа
+         imgui.text(c["document_type"])
+         imgui.next_column()
+         imgui.text(c["document_number"])
+         imgui.next_column()
+
+         # Действия
+         if imgui.button(f"Изменить ##{i}"):
+            selected_correspondence_id = c["id"]
+            receipt_date_input = c["receipt_date"].strftime("%d-%m-%Y")
+            execution_date_input = c["execution_date"].strftime("%d-%m-%Y") if c["execution_date"] else ""
+            document_type_pos = DOCUMENT_TYPES.index(c["document_type"])
+            document_number_input = c["document_number"]
+            selected_employee_pos = employees_list.index(f'{employee["lastname"]} {employee["name"]} {employee["patronymic"]}')
+            error_text = None
+
+         imgui.same_line()
+         
+         if imgui.button(f"Удалить ##{i}"):
+            db.delete_correspondence(c["id"])
+            db.get_all_correspondencies()
+            selected_correspondence_id = None
+
+         imgui.next_column()
+
+      imgui.columns(1)
+      imgui.end_child()
+
+# Страница авторизации
+connected = False
+db_username = ""
+db_password = ""
+auth_error_text = None
+def gui_auth():
+   global connected
+   global db_username, db_password, auth_error_text
+
+   imgui.text("Авторизация")
+
+   imgui.text("Введите имя пользователя и пароль для подключения к базе данных")
+
+   db_username = imgui.input_text("Имя пользователя", db_username)[1]
+   db_password = imgui.input_text("Пароль", db_password)[1]
+
+   if auth_error_text:
+      imgui.text_colored(imgui.ImVec4(1.0, 0.3, 0.3, 1.0), auth_error_text)
+
+   if imgui.button("Подключиться"):
+      try:
+         init(username=db_username, password=db_password)
+         connected = True
+      except psycopg2.OperationalError:
+         auth_error_text = "Ошибка подключения к базе данных. Проверьте имя пользователя и пароль."
+
+
+# Главное меню
+selected_tab = 0 # Выбранная страница
+
+def gui_main():
+   global selected_tab
+
+   if not connected:
+      gui_auth()
+   else:
+      for i, title in enumerate(["Компании","Сотрудники", "Корреспонденция"]):
+         if imgui.button(title):
+            selected_tab = i
+
+         if i < 2:
+            imgui.same_line()
+
+
+      if selected_tab == 0:
+         gui_organizations()
+      elif selected_tab == 1:
+         gui_employees()
+      elif selected_tab == 2:
+         gui_correspondencies()
+      else:
+         imgui.text("Ошибка: неизвестная страница")
+
+
+def init(username, password):
+   db.init_connection(username=username, password=password)
+   db.get_all_organizations()
+   db.get_all_employees()
+   db.get_all_correspondencies()

+ 8 - 0
src/main.py

@@ -0,0 +1,8 @@
+from ara_imgui import App
+from gui import gui_main
+
+app = App("Менеджер корреспонденций")
+
+app.load_font(font_size=18)
+app.apply_theme("light")
+app.run(gui_main)

+ 3 - 0
src/requirements.txt

@@ -0,0 +1,3 @@
+ara_imgui
+psycopg2-binary
+python-dotenv

TEMPAT SAMPAH
Диаграмма развёртывания.png


+ 71 - 0
Руководство оператора.md

@@ -0,0 +1,71 @@
+# Аннотация
+Данное руководство описывает порядок подготовки, запуска и использования программы для учета корреспонденции в графическом интерфейсе. Документ рассчитан на пользователя, который работает с базой данных PostgreSQL и запускает приложение в Docker-контейнере.
+
+# Введение
+## Область применения
+Программа используется для ведения учета корреспонденции организаций, просмотра сотрудников и выполнения операций над записями в базе данных.
+
+## Основные возможности
+- подключение к PostgreSQL;
+- просмотр организаций и сотрудников;
+- создание, редактирование и удаление корреспонденции;
+- поиск корреспонденции по номеру документа;
+- отображение количества корреспонденции по организации.
+
+# Подготовка к работе
+## Порядок установки для администратора
+1. [ ] Создайте базу данных PostgreSQL или используйте существующую.
+2. [ ] Инициализируйте структуру базы данных используя `init_schema.sql`.
+3. [ ] При необходимости импортируйте данные в базу данных.
+4. [ ] Используя средства СУБД, создайте роли и предоставьте им необходимые права доступа к базе данных.
+5. [ ] Скачайте и распакуйте архив с программой.
+6. [ ] Укажите в файле `.env` данные для подключения к базе данных.
+7. [ ] Запустите файл `main.exe`.
+8. [ ] Введите ваш логин и пароль для подключения к базе данных.
+
+## Порядок проверки работоспособности
+1. [ ] После запуска убедитесь, что открылось окно приложения.
+2. [ ] Введите имя пользователя и пароль PostgreSQL.
+3. [ ] Нажмите кнопку подключения.
+4. [ ] Проверьте, что открылись вкладки `Компании`, `Сотрудники` и `Корреспонденция`.
+5. [ ] Проверьте отображение списков организаций и сотрудников.
+6. [ ] Создайте тестовую запись корреспонденции.
+7. [ ] Выполните поиск по номеру документа.
+8. [ ] Измените и удалите тестовую запись, чтобы убедиться в работоспособности операций.
+
+# Описание операций
+- `Компании` - выводит список организаций из базы данных.
+- `Сотрудники` - выводит список сотрудников с указанием организации.
+- `Корреспонденция` - позволяет выполнять основные операции с документами.
+- Добавление записи:
+  - укажите дату поступления;
+  - при необходимости укажите дату исполнения;
+  - выберите организацию и сотрудника;
+  - укажите тип и номер документа;
+  - нажмите кнопку добавления.
+- Редактирование записи:
+  - выберите запись;
+  - измените нужные поля;
+  - нажмите кнопку сохранения изменений.
+- Удаление записи:
+  - нажмите кнопку удаления напротив нужной строки.
+- Поиск:
+  - введите часть или полный номер документа в поле поиска.
+
+# Аварийные ситуации
+- Если приложение не подключается к базе данных, проверьте:
+  - корректность логина/пароля;
+  - корректность данных в `.env`;
+  - доступность PostgreSQL сервера.
+- Если ввод даты вызывает ошибку, проверьте формат:
+  - `ДД-ММ-ГГГГ`.
+- Если номер документа уже существует, создайте запись с другим номером.
+
+# Термины и сокращения
+| Термин | Описание |
+| - | - |
+| БД | База данных |
+| PostgreSQL | Система управления базами данных |
+| GUI | Графический пользовательский интерфейс |
+| Корреспонденция | Входящий или внутренний документ, учитываемый в программе |
+| Организация | Компания или учреждение, связанное с сотрудниками и корреспонденцией |