commit 24943b8a73734057e3bcd4a29a69692a51c704f1
Author: SkyForces <64554063+SkyForcesGit@users.noreply.github.com>
Date: Thu Jul 2 16:42:37 2026 +0300
add initial project files.
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..dda4f8f
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,164 @@
+### Python template
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+*.py,cover
+.hypothesis/
+.pytest_cache/
+cover/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+db.sqlite3-journal
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+.pybuilder/
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+# For a library or package, you might want to ignore these files since the code is
+# intended to run in multiple environments; otherwise, check them in:
+# .python-version
+
+# pipenv
+# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+# However, in case of collaboration, if having platform-specific dependencies or dependencies
+# having no cross-platform support, pipenv may install dependencies that don't work, or not
+# install all needed dependencies.
+#Pipfile.lock
+
+# poetry
+# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
+# This is especially recommended for binary packages to ensure reproducibility, and is more
+# commonly ignored for libraries.
+# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
+#poetry.lock
+
+# pdm
+# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
+#pdm.lock
+# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
+# in version control.
+# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
+.pdm.toml
+.pdm-python
+.pdm-build/
+
+# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
+__pypackages__/
+
+# Celery stuff
+celerybeat-schedule
+celerybeat.pid
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+# pytype static type analyzer
+.pytype/
+
+# Cython debug symbols
+cython_debug/
+
+# PyCharm
+# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
+# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
+# and can be added to the global gitignore or merged into this file. For a more nuclear
+# option (not recommended) you can uncomment the following to ignore the entire idea folder.
+#.idea/
+
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..53bf941
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,18 @@
+Copyright 2026 SkyForces
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the “Software”), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..0fc1ccd
--- /dev/null
+++ b/README.md
@@ -0,0 +1,26 @@
+# BitrixDealsBot
+
+BitrixDealsBot — это телеграм-бот, который позволяет пользователям
+взаимодействовать с CRM-системой Bitrix24 для управления сделками и контактами.
+Цель бота — упростить процесс работы с CRM, предоставляя удобный интерфейс для
+обновления и отслеживания сделок прямо из Telegram.
+
+Задание выполняется в рамках учебной производственной практики для предприятия
+Интерволга.
+
+## Формулировка задания
+**Telegram-бот “Помощник менеджера CRM”**
+
+Telegram-бот для менеджера по продажам. Бот помогает быстро смотреть новые лиды,
+брать их в работу, менять статус и добавлять комментарии.
+
+**Стек:** Любой язык, любая БД, REST API Telegram, REST API Битрикс24
+
+**Функции:**
+- команда /leads показывает новые лиды (без ответственных);
+- команда /lead ### показывает карточку лида по указанному ID: имя, телефон, источник, статус;
+- кнопка “Взять” устанавливает ответственного;
+- кнопка “Позвонить позже” устанавливает ответственного и планирует звонок через 1 час;
+- кнопка “Закрыть” возвращает в список лидов /leads;
+- команда /history показывает историю действий;
+- интеграция с Битрикс24 через webhook.
\ No newline at end of file
diff --git a/app/main.py b/app/main.py
new file mode 100644
index 0000000..e76c1e9
--- /dev/null
+++ b/app/main.py
@@ -0,0 +1,120 @@
+import asyncio
+import html
+import os
+import decimal
+
+import httpx
+from aiogram import Bot, Dispatcher
+from aiogram.filters import Command
+from aiogram.types import Message
+from dotenv import load_dotenv
+
+load_dotenv()
+
+BOT_TOKEN = os.getenv("BOT_TOKEN")
+BITRIX_WEBHOOK_URL = os.getenv("BITRIX_WEBHOOK_URL")
+
+dp = Dispatcher()
+
+
+async def bitrix_call(method: str, params: dict | None = None) -> dict:
+ if not BITRIX_WEBHOOK_URL:
+ raise RuntimeError("BITRIX_WEBHOOK_URL is not set")
+
+ base_url = BITRIX_WEBHOOK_URL.rstrip("/") + "/"
+ url = base_url + method
+
+ async with httpx.AsyncClient(timeout=15) as client:
+ response = await client.post(url, json=params or {})
+ response.raise_for_status()
+ data = response.json()
+
+ if "error" in data:
+ description = data.get("error_description", data["error"])
+ raise RuntimeError(f"Bitrix API error: {description}")
+
+ return data
+
+
+def format_deal(deal: dict) -> str:
+ deal_id = html.escape(str(deal.get("ID", "—")))
+ title = html.escape(str(deal.get("TITLE", "Без названия")))
+ stage = html.escape(str(deal.get("STAGE_ID", "—")))
+ opportunity = html.escape(str(deal.get("OPPORTUNITY", "—")))
+ currency = html.escape(str(deal.get("CURRENCY_ID", "")))
+ date = html.escape(str(deal.get("DATE_CREATE", "—")))
+
+ return (
+ f"#{deal_id} — {title}\n"
+ f"Стадия: {stage}\n"
+ f"Сумма: {decimal.Decimal(opportunity):,.2f} {currency}\n"
+ f"Дата создания: {date}"
+ )
+
+
+@dp.message(Command("start"))
+async def start_handler(message: Message) -> None:
+ await message.answer(
+ "Привет. Команда /leads покажет последние сделки из Битрикс24."
+ )
+
+
+@dp.message(Command("leads"))
+async def leads_handler(message: Message) -> None:
+ await message.answer("Запрашиваю сделки...")
+
+ try:
+ data = await bitrix_call(
+ "crm.deal.list",
+ {
+ "order": {"DATE_CREATE": "DESC"},
+ "filter": {},
+ "select": [
+ "ID",
+ "TITLE",
+ "STAGE_ID",
+ "OPPORTUNITY",
+ "CURRENCY_ID",
+ "DATE_CREATE",
+ ],
+ "start": 0,
+ },
+ )
+
+ deals = data.get("result", [])
+
+ if not deals:
+ await message.answer("Сделки не найдены.")
+ return
+
+ text = "\n\n".join(format_deal(deal) for deal in deals[:10])
+
+ await message.answer(
+ f"Последние сделки:\n\n{text}",
+ parse_mode="HTML",
+ )
+
+ except httpx.HTTPStatusError as e:
+ await message.answer(
+ f"Ошибка HTTP при запросе к Битрикс24: {e.response.status_code}")
+
+ except httpx.RequestError:
+ await message.answer("Не удалось подключиться к Битрикс24.")
+
+ except RuntimeError as e:
+ await message.answer(f"Ошибка: {html.escape(str(e))}")
+
+ except Exception:
+ await message.answer("Произошла неизвестная ошибка.")
+
+
+async def main() -> None:
+ if not BOT_TOKEN:
+ raise RuntimeError("BOT_TOKEN is not set")
+
+ bot = Bot(token=BOT_TOKEN)
+ await dp.start_polling(bot)
+
+
+if __name__ == "__main__":
+ asyncio.run(main())
diff --git a/docs/adr/main.md b/docs/adr/main.md
new file mode 100644
index 0000000..15cc34c
--- /dev/null
+++ b/docs/adr/main.md
@@ -0,0 +1,13 @@
+# Архитектурные решения (ADR)
+
+## Введение
+
+Данный раздел содержит архитектурные решения (ADR) для проекта.
+Архитектурные решения описывают ключевые решения, принятые в процессе разработки
+системы, включая выбор технологий, подходов и структурных решений.
+
+## Оглавление
+
+**В данном разделе представлены следующие архитектурные решения:**
+
+_(В процессе разработки будут добавляться новые решения)_
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..71badf2
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,3 @@
+httpx~=0.28.1
+aiogram~=3.29.1
+python-dotenv~=1.2.2
\ No newline at end of file