🗂️ Реструктуризация vault: логичная организация папок

Изменения:
- Интеграция СУП/ → Работа/Интеграция СУП/ (рабочие заметки вместе)
- Релизы/ → Работа/Релизы/ (рабочие заметки вместе)
- Другое/Собеседование .Net → Работа/ (рабочая тема)
- Другое/Своя игра → Личное/ (хобби)
- Smart home/ → Квартира/ (всё про квартиру в одном месте)
- Flutter/, Go/ → Dev/Flutter/, Dev/Go/ (учебные роадмапы)
- Dev/Pulse/competitors-analysis → Projects/Pulse/ (проект Pulse в одной папке)
- Twilio.md → Dev/ (техническая заметка)
- Tokens.md → Личное/Tokens (API keys).md
- Игры.md → Личное/Игры.md
- Удалены пустые папки: Другое/, Smart home/, Интеграция СУП/, Релизы/

Структура:
  Авто/        — автомобили
  Dev/         — разработка, изучение (Go, Flutter, репозитории)
  Квартира/    — квартира + умный дом
  Личное/      — личное, токены, игры, команды
  Мама/        — проекты для мамы
  Projects/    — Pulse App
  Работа/      — BPMSoft, интеграции, релизы
  Япония/      — путешествие
  saved-articles/ — сохранённые статьи
  voice-notes/ — голосовые заметки
This commit is contained in:
Cosmo
2026-03-26 08:52:13 +00:00
parent 5919c0123e
commit 0569bd5a0f
21 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,156 @@
## Статусы объектов
Заявка
- Черновик - 1
- На утверждении - 2
- На рассмотрении в ЦПРП - 3
- Зарегистрирована (финальный) - 4
- Отклонена - 5
- Отозвана - 6
Проект
- Открыт - E0001
- Закрыт (финальный) - E0002
- Приостановлен - E0003
- На закрытии в ЦПРП (финальный) - E0005
- На закрытии в ФС (финальный) - E0006
- Аннулирован (финальный) - E0009
- Закрыт для списания TS - E0027
## Процесс
1. После получения AP ID (Id проекта из САП) в CRM могут происходить изменения данных по сделке (заявке).
2. Все изменения фиксируются в системе CRM и отправляются в СУП в конце рабочего дня
3. По всем измененным заявкам вначале отправляем запрос на получение текущих статусов заявок и проектов
Пример запроса смотри в "Получение статусов заявок и проектов" (Пример запроса)
4. После получения статусов заявок и проектов система CRM отбирает только те заявки, которые могут быть изменены
1. Заявка "Зарегистрирована" и Проект "Проект открыт"
2. Заявка "Зарегистрирована" и Проект "Проект закрыт для списания TS"
5. По подходящим заявкам из п.4 система CRM формирует запросы для отправки в СУП с использованием Temporal
Пример запроса смотри в "Изменение данных по заявке - проекту" (Примеры запроса)
6. Периодически опрашиваем систему СУП о готовности изменения данных по заявке и после получения ответа изменяем статусы заявок в CRM
## Авторизация
Используется кастомный header `X-Erp-Integration`
Значение хранится в конфигах у системы СУП.
Для дев и прод разные значения.
Данные передаются в формате base64, при получении значения необходимо его раскодировать и сравнить со значением хранящимся в конфиге.
Если значения не соответствуют, то вернуть ошибку и код HTTP 403 - Unauthorized.
## Получение статусов заявок и проектов
Используется endpoint с методом POST - синхронный процесс
Предлагаемый вариант
`api/1.0/openRequest/getPresaleProjectsStatus` **Обсудить данный момент после реализации и предоставить команде CRM**
Пример запроса
```json
{
    "projects": [
        {
            "projectId": "AP001", // AP ID проекта
        },
        {
            "projectId": "AP002", // AP ID проекта
        }
    ]
}
```
Пример ответа
```json
{
    "projects": [
        {
            "projectId": "AP001",
            "applicationStatus": "4",
            "projectStatus": "E0001"
        },
        {
            "projectId": "AP002",
            "applicationStatus": "4",
            "projectStatus": "E0005"
        }
    ]
}
```
## Изменение данных по проекту
### Заявка на перевыпуск
Используется endpoint с методом POST - асинхронный процесс
Предлагаемый вариант
`api/1.0/openRequest/createPresaleProject` **Обсудить данный момент после реализации и предоставить команде CRM**
В данном процессе будет использоваться метод `createPresaleProject`, также как и для создания некоммерческого проекта, но с условием, что в параметр `projectId` будет заполнен, что даст сигнал о том, что это заявка на перевыпуск проекта.
Пример запроса
```json
{
    "orderName": "Сделка-1", // Название проекта
    "orderDate": "2025-12-31", // Дата сделки из CRM
    "clientId": "123-123", // Код клиента из R3
    "headProjectId": "00123123", // ИНС руководителя проекта
    "managerProjectId": "00123124", // ИНС менеджера по продажам
    "directorProjectId": "00123125", // ИНС директора проекта
    "orderAmount": 12345.67, // Сумма сделки
    "projectId": "AP001", // AP ID проекта
    "applicationId": "57ef229c-d8e4-4da5-b87a-7205e7eb7c43" // Id заявки в CRM
}
```
Успешный ответ
```json
{
"success": true,
"errorMessage": ""
}
```
Неуспешный ответ
```json
{
"success": false,
"errorMessage": "Текст ошибки"
}
```
### Проверка готовности проекта с измененными данными
После изменения статуса заявки на Черновик, ожидаем согласования заявки и система CRM (Temporal) будет периодически опрашивать СУП о готовности перевыпуска и согласовании изменений по проекту.
Ответ считается окончательным, если статус != Waiting, то есть Error или Success.
Предлагаемый вариант
`api/1.0/openRequest/view?applicationId={applicationId}` **Обсудить данный момент после реализации и предоставить команде CRM**
Примеры ответов
Идет согласование изменений
```json
{
"status": "Waiting",
"projectId": "AP001",
"errorMessage": ""
}
```
Согласование изменений успешно
```json
{
"status": "Success",
"projectId": "AP001",
"errorMessage": ""
}
```
Ошибка или отказ по изменениям в проекте
```json
{
"status": "Error",
"projectId": "AP001",
"errorMessage": "Описание ошибки"
}
```

View File

@@ -0,0 +1,34 @@
## Создание пресейл-проекта
### Создание заявки на проект в CRM
При соблюдении условий для создания заявки в CRM создается запись в объекте "Заявка" и проставляются значения из сделки.
После создания заявки выполняется запрос на async сервис со входными данными
```json
{
"orderName": "Альфа-Пласт ADC", // Название сделки
"orderDate": "2025-12-31", // Дата сделки
"clientId": "123-123-123", // Значение `Код клиента из R3`
"managerProjectId": "00123456", // Значение ИНС сотрудника
"headProjectId": "00123457", // Значение ИНС сотрудника
"directorProjectId": "00123458", // Значение ИНС сотрудника
"orderAmount": 12345.67, // Сумма сделки
"productAttribute": 1, // (1 - ПО/ПО+ТП/ТП. 2 - Консалтинг. 3 - ПО+ТП+Консалтинг/ТП+Консалтинг/ПО+Консалтинг)
"applicationId": "57ef229c-d8e4-4da5-b87a-7205e7eb7c43", // Id заявки в CRM, проставляется в СУП для поиска дублей и обновления данных
"projectId": "" // AP ID (Пустое - создание проекта. Заполненное - редактирование проекта)
}
```
### Создание заявки на проект в ERP
При получении данных по endpoint `api/1.0/openRequest/createPresaleProject`
Система возвращает успешный или неуспешный ответ при создании.
После этого в workflow запускается активность ожидания
### Получение статуса готовности проекта в ERP
Async опрашивает ERP сис
### Обновление заявки в CRM
### Уведомление CRM об ошибке в ERP

View File

@@ -0,0 +1,20 @@
1. Проставить значение настройки "NrbErpHeadProject"
2. Проставить значение настройки "NrbAsyncServiceUri" `https://async.prod.plt.adsw.io/`
3. Проставить значение настройки "NrbPresaleProjectServiceKey"
4. Проставить значение настройки "NrbIsERPIntegrationEnabled"
5. Проставить значение настройки "NrbOpenProjectUrl"
6. Проставить значение настройки "NrbErpServiceSecret"
7. Проверить настройку "GatewayURLKey"
Секреты CRM
1. Dev CRM `20b6702d-a3f7-4dfa-baad-640da98b727c`
2. Preprod CRM `347bf64d-8920-4de6-a0f1-654c2ab15fda`
3. Prod CRM `24b9aef3-dffb-4f9e-9c5d-fa8a9802276f`
Cекреты ERP
1. Dev `067d72e8-2728-493b-b7b0-4fa4c482d921`
2. Prod `ff915955-844e-4a93-a933-c43cfc84adf4`
Dev Private Gateway - `https://private-api-gateway-feature-itdev-1381.dev.plt.adsw.io/query`
Dev Async - `https://async-feature-itdev-1381.dev.plt.adsw.io/`

View File

@@ -0,0 +1,85 @@
## Авторизация
Используется кастомный header `X-Erp-Integration`
Значение хранится в конфигах у системы СУП.
Для дев и прод разные значения.
Данные передаются в формате base64, при получении значения необходимо его раскодировать и сравнить со значением хранящимся в конфиге.
Если значения не соответствуют, то вернуть ошибку и код HTTP 403 - Unauthorized.
## Создание некоммерческого проекта
Используется endpoint с методом POST
Предлагаемый вариант
`api/1.0/openRequest/createPresaleProject` **Обсудить данный момент после реализации и предоставить команде CRM**
### Запрос
```json
{
"orderName": "Альфа-Пласт ADC",
"orderDate": "2025-12-31",
"clientId": "123-123-123", // Значение `Код клиента из R3`
"managerProjectId": "00123456", // Значение ИНС
"headProjectId": "00123457", // Значение ИНС
"directorProjectId": "00123458", // Значение ИНС
"orderAmount": 12345.67,
"productAttribute": 1, // (1 - ПО/ПО+ТП/ТП. 2 - Консалтинг. 3 - ПО+ТП+Консалтинг/ТП+Консалтинг/ПО+Консалтинг)
"applicationId": "57ef229c-d8e4-4da5-b87a-7205e7eb7c43",
"projectId": "" // AP ID (Пустое - создание проекта. Заполненное - редактирование проекта)
}
```
### Ответ
Успешный
```json
{
"success": true,
"errorMessage": ""
}
```
Неуспешный
```json
{
"success": false,
"errorMessage": "Текст ошибки"
}
```
## Проверка готовности создания проекта
Используется endpoint с методом GET
Предлагаемый вариант
`api/1.0/openRequest/view?applicationId={applicationId}` **Обсудить данный момент после реализации и предоставить команде CRM**
Главная суть метода, что мы по значению `applicationId` из пункта `Создание некоммерческого проекта` получаем готовность создания проекта.
Проект считается созданным, если у него заполнено поле `ProjectId` (уточнить как называется в базе данных поле `AP ID`)
### Ответ
Проект еще создается
```json
{
"status": "Waiting",
"projectId": "",
"errorMessage": ""
}
```
Создание проекта завершено
```json
{
"status": "Success",
"projectId": "123-123-124",
"errorMessage": ""
}
```
Ошибка при создании проекта
```json
{
"status": "Error",
"projectId": "",
"errorMessage": "Описание ошибки"
}
```

View File

@@ -0,0 +1,90 @@
## Авторизация
Используется кастомный header `X-Erp-Integration`
Значение хранится в конфигах у системы СУП.
Для дев и прод разные значения.
При получении значения необходимо его сравнить со значением хранящимся в конфиге.
Если значения не соответствуют, то вернуть ошибку.
## Создание клиента
Используется endpoint с методом POST
Предлагаемый вариант
`api/1.0/openRequest/createClient`
### Запрос
```json
{
"name": "Альфа-Пласт",
"shortName": "Альфа-Пласт",
"managerId": "00123456", // Значение ИНС сотрудника
"countryCode": "RU", // Двухбуквенный код страны
"industryCode": "0015", // Код отрасли ERP
"authorId": "00123456", // Значение ИНС сотрудника
"accountId": "57ef229c-d8e4-4da5-b87a-7205e7eb7c42"
}
```
```
"name" - Наименование клиента
"shortName" - Краткое название
"managerId" - Менеджер по продажам
"countryCode" - Страна
"industryCode" - Отрасль
"authorId" - Автор клиента в CRM
"accountId" - Id контрагента в CRM
```
### Ответ
Успешный
```json
{
"success": true,
"errorMessage": ""
}
```
Неуспешный
```json
{
"success": false,
"errorMessage": "Текст ошибки"
}
```
## Проверка готовности создания клиента
Используется endpoint с методом GET
Предлагаемый вариант
`api/1.0/openRequest/viewAccount?accountId={accountId}`
Главная суть метода, что мы по значению `accountId` из пункта `Создание клиента` получаем готовность создания клиента.
Клиент считается созданным, если у него заполнено поле `ClientId` (уточнить как называется в базе данных поле `Код клиента из R3`)
### Ответ
Клиент еще создается
```json
{
"status": "Waiting",
"clientId": "",
"errorMessage": ""
}
```
Создание клиента завершено
```json
{
"status": "Success",
"clientId": "0000101202",
"errorMessage": ""
}
```
Ошибка при создании клиента
```json
{
"status": "Error",
"clientId": "",
"errorMessage": "Описание ошибки"
}
```

View File

@@ -0,0 +1,31 @@
## Описание ошибок
### Получение готовности проекта /viewApplication
| **Причина** | **Код статуса** | **Сообщение** |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | -------------------------------------------------------------------------------------------- |
| Значение заголовка X-Erp-Integration не совпадает со значением, прописанным в конфигурации приложения | 403 | - |
| Не найден проект по указанному applicationId | 404 | Project not found or an error occurred while creating. |
| Не найден сотрудник по указанному значению ИНС в свойстве headProjectId (руководитель проекта) | 500 | projectManagerEmployee not found or an invalid ID was provided. |
| Не найден сотрудник по указанному значению ИНС в свойстве directorProjectId (директор проекта) | 500 | projectDirectorEmployee not found or an invalid ID was provided. |
| Не найден клиент по указанному коду R3 | 500 | customer not found or an invalid ID was provided. |
| У указанного клиента нет привязанного к нему менеджера по продажам, и при этом не указан явный идентификатор менеджера по продажам в свойстве managerProjectId тела | 500 | Customer does not have a sale manager, and sale manager ID was not specified in the request. |
| Не найден сотрудник по указанному значению ИНС в свойстве managerProjectId (менеджер по продажам проекта) | 500 | saleManager not found or an invalid ID was provided. |
| Произошла ошибка при регистрации проекта в SAP | 500 | SAP returned error code. Project not registered. |
| **При перевыпуске**: заявка, привязанная к ID сделки, не найдена, либо проект ещё не зарегистрирован | 500 | Project not found or not yet created. |
| **При перевыпуске**: Статус заявки не равен значению «Зарегистрирована» | 500 | Invalid request status. |
| **При перевыпуске**: не найден проект, соответствующий заявке | 500 | Error getting corresponding project. |
| **При перевыпуске**: Статус проекта не соответствует значениям «Открыт» либо «Закрыт для списания TS» | 500 | Invalid project status. |
| **При перевыпуске**: есть активная заявка на закрытие проекта | 500 | An active close request already exists for this project, meaning it cannot be reissued. |
### Создание проекта /createPresaleProject
| **Причина** | **Код статуса** | **Сообщение** |
| ----------------------------------------------------------------------------------------------------- | --------------- | -------------------------------------------------------------------------- |
| Значение заголовка X-Erp-Integration не совпадает со значением, прописанным в конфигурации приложения | 403 | - |
| Нет тела запроса | 400 | No body has been provided. |
| Проект с указанным applicationId уже существует, при этом projectId не указан (повторное создание) | 400 | Cannot create project. Project with the same applicationId already exists. |
### Получение статусов заявок и проектов /getPresaleProjectsStatus
| **Причина** | **Код статуса** | **Сообщение** |
| ----------------------------------------------------------------------------------------------------- | --------------- | ------------- |
| Значение заголовка X-Erp-Integration не совпадает со значением, прописанным в конфигурации приложения | 403 | - |

View File

@@ -0,0 +1,12 @@
## Ошибка при создании проекта
- В CRM создается заявка
- Отправляем запрос в СУП `/createPresaleProject`
- Создается заявка в СУП и связывается с заявкой CRM `applicationId проставлен`
- СУП получает ошибку от SAP о создании проекта
- СУП возвращает ошибку в CRM `SAP returned error code. Project not registered`
- CRM пытается повторно отправить запрос на создание проекта с такими же входными данными, но получает ошибку `Project with the same applicationId already exists`
Предлагаемые действия:
- Если в СУП приходит запрос с `applicationId`, который уже привязан к какой-то заявке, то проверять эту заявку.
- Если у заявки не проставлен

View File

@@ -0,0 +1,3 @@
- Проставить в `NrbElmaSyncEndpoint` значение `https://elma.itdev.adsw.io/pub/v1/app/approvalDeal/{0}/{1}`
- Проставить в `NrbElmaApiKey` значение токена из ELMA prod
- Синхронизировать справочники `NrbSyncBPMSoftAndElmaLookupProcess`

View File

@@ -0,0 +1,4 @@
1. Заполнить настройку "Email адрес для копии при добавлении продукта Prosperity"
2. Изменить настройку "Добавлять копию для продукта Prosperity"
3. NrbAddressEmailSender - проверить заполнение
4. Удалить объект "IT Ландшафт" из Custom перед релизом NrbITLandscape

View File

@@ -0,0 +1,2 @@
[3 сентября](obsidian://open?vault=local&file=%D0%A0%D0%B5%D0%BB%D0%B8%D0%B7%D1%8B%2F3%20%D1%81%D0%B5%D0%BD%D1%82%D1%8F%D0%B1%D1%80%D1%8F)
[17 сентября](obsidian://open?vault=local&file=%D0%A0%D0%B5%D0%BB%D0%B8%D0%B7%D1%8B%2F17%20%D1%81%D0%B5%D0%BD%D1%82%D1%8F%D0%B1%D1%80%D1%8F)

View File

@@ -0,0 +1,111 @@
## Объектно-ориентирование программирование
### Абстракция
Абстрагирование — это способ выделить набор наиболее важных атрибутов и методов и исключить незначимые. Соответственно, абстракция — это использование всех таких характеристик для описания объекта. Важно представить объект минимальным набором полей и методов без ущерба для решаемой задачи.
Пример: объекту класса «программист» вряд ли понадобятся свойства «умение готовить еду» или «любимый цвет». Они не влияют на его особенности как программиста. А вот «основной язык программирования» и «рабочие навыки» — важные свойства, без которых программиста не опишешь.
Набор атрибутов и методов, доступный извне, работает как интерфейс для доступа к объекту. Через них к нему могут обращаться другие структуры данных, причем им не обязательно знать, как именно объект устроен внутри.
### Инкапсуляция
Каждый объект — независимая структура. Все, что ему нужно для работы, уже есть у него внутри. Если он пользуется какой-то переменной, она будет описана в теле объекта, а не снаружи в коде. Это делает объекты более гибкими. Даже если внешний код перепишут, логика работы не изменится.
Инкапсуляция помогает с легкостью управлять кодом. Выше мы сказали, что для обращения к объекту не нужно понимать, как работают его методы. Начальнику разработчика Ивана не обязательно знать, как именно он программирует: главное — чтобы выполнялись поставленные задачи.
Внутреннее устройство одного объекта закрыто от других: извне «видны» только значения атрибутов и результаты выполнения методов.
### Наследование
Можно создавать классы и объекты, которые похожи друг на друга, но немного отличаются — имеют дополнительные атрибуты и методы. Более общее понятие в таком случае становится «родителем», а более специфичное и подробное — «наследником».
Упомянутый программист Иван — это человек. Но «человек» — более общее определение, которое не описывает свойства, важные именно для программиста. Можно сказать, что класс «программист» унаследован от класса «человек»: программист тоже является человеком, но у него есть дополнительные свойства.
В таком случае разработчик Иван будет и человеком, и программистом одновременно. У него будут наборы свойств от обоих классов.
У одного «родителя» может быть несколько дочерних структур. Например, от «человека» можно наследовать не только «программиста», но и «директора».
Наследование позволяет реализовывать сложные схемы с четкой иерархией «от общего к частному». Это облегчает понимание и масштабирование кода. Не нужно много раз переписывать в разных объектах одни и те же свойства. Достаточно унаследовать эти объекты от одного «родителя», и «родительские» свойства применятся автоматически.
### Полиморфизм
Одинаковые методы разных объектов могут выполнять задачи разными способами. Например, у «человека» есть метод «работать». У «программиста» реализация этого метода будет означать написание кода, а у «директора» — рассмотрение управленческих вопросов. Но глобально и то, и другое будет работой.
Тут важны единый подход и договоренности между специалистами. Если метод называется delete, то он должен что-то удалять. Как именно — зависит от объекта, но заниматься такой метод должен именно удалением. Более того: если оговорено, что «удаляющий» метод называется delete, то не нужно для какого-то объекта называть его remove или иначе. Это вносит путаницу в код.
## SOLID
### Single Responsibility Principle (Принцип единственной ответственности)
Это значит, что каждый класс должен выполнять только **одну** четко определенную функцию. Если он решает более одной задачи, это может привести к сложностям в поддержке и расширении кода.
**Как следовать принципу SRP**
Сначала общую задачу нужно декомпозировать на несколько подзадач. Затем каждую подзадачу реализовать в отдельном классе.
### Open/Closed Principle (Принцип открытости/закрытости)
**Программные сущности (классы, модули, функции и т.д.) должны быть открыты для расширения, но закрыты для модификации.**
Это значит, что программные компоненты нужно спроектировать таким образом, чтобы их можно было расширять новым функционалом, не меняя исходный код. Изменения при добавлении нового кода могут сломать уже работающую логику.
**Как следовать принципу OCP**
В этом помогут абстракции, интерфейсы и наследование. Это позволит добавить новый функционал через расширение, а не модификацию существующего кода.
**Задача**
У ресторана есть система управления заказами. Нужно сделать так, чтобы она поддерживала различные виды платежей: наличные, кредитные карты и мобильные платежи.
### Liskov Substitution Principle (Принцип подстановки Барбары Лисков)
**Производные классы должны заменять свои базовые классы.**
Это значит, что объекты базовых классов должны быть заменяемы объектами производных классов без изменения ожидаемого поведения программы.
**_Базовый класс_** — это класс, от которого производные классы наследуют свойства и методы.
**Как следовать принципу LSP**
Производный класс должен сохранять все свойства базового класса и не изменять их семантику. Нужно добиться того, чтобы объекты производных классов могли безопасно заменять друг друга и базовый класс.
**Задача**
Предположим, что есть система для работы с геометрическими фигурами, в которой есть базовый класс **Shape** и производные классы **Circle** и **Square**.
### Interface Segregation Principle (Принцип разделения интерфейса)
**Клиенты не должны зависеть от интерфейсов, которые они не используют.**
Это значит, что нужно создавать только небольшие и узконаправленные интерфейсы, не перегруженные ненужными методами.
**Как следовать принципу ISP**
Каждый интерфейс должен существовать для определенных задач и содержать только те методы, которые эти задачи решают.
**Задача**
Предположим, у нас есть система управления документами с интерфейсом **Document,** содержащим методы для работы с документами, такие как **open()**, **save()** и **close().** Однако одним клиентам требуется только возможность открывать и закрывать документы, а другим — только сохранять и закрывать их.
### Dependency Inversion Principle (Принцип инверсии зависимостей)
**Модули верхнего уровня не должны зависеть от модулей нижнего уровня. Оба типа модулей должны зависеть от абстракций. Абстракции не должны зависеть от деталей, но детали должны зависеть от абстракций.**
Это значит, что код должен быть организован таким образом, чтобы зависимости между компонентами программы были основаны на абстракциях, а не на конкретных реализациях. Таким образом, компоненты легко заменить или изменить без воздействия на другие части системы.
**Как следовать принципу DIP**
— Использовать интерфейсы или абстрактные классы для определения зависимостей между компонентами.
— Следовать остальным принципам SOLID, чтобы создавать хорошо структурированные и модульные системы.
— Применять шаблоны проектирования, такие как **Dependency Injection** (Внедрение зависимостей) или **Inversion of Control** (Инверсия управления), чтобы передавать зависимости извне вместо того, чтобы создавать их внутри компонентов.
**Внедрение зависимостей**: объект не создает свои зависимости самостоятельно, они предоставляются ему извне, например через конструктор, методы или свойства.
**Инверсия управления**: управление частью приложения переносится на внешний фреймворк или контейнер, управляющий жизненным циклом объектов.
**Задача**
Нужно разработать приложение для работы с базой данных студентов. Каждый студент имеет имя, возраст и список предметов, которые он изучает.