Files
duty-teller/duty_teller/handlers/import_duty_schedule.py
Nikolay Tatarinov 28973489a5 Refactor project structure and enhance Docker configuration
- Updated `.dockerignore` to exclude test and development artifacts, optimizing the Docker image size.
- Refactored `main.py` to delegate execution to `duty_teller.run.main()`, simplifying the entry point.
- Introduced a new `duty_teller` package to encapsulate core functionality, improving modularity and organization.
- Enhanced `pyproject.toml` to define a script for running the application, streamlining the execution process.
- Updated README documentation to reflect changes in project structure and usage instructions.
- Improved Alembic environment configuration to utilize the new package structure for database migrations.
2026-02-18 13:03:14 +03:00

120 lines
4.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Import duty-schedule: /import_duty_schedule (admin only). Two steps: handover time -> JSON file."""
import asyncio
import duty_teller.config as config
from telegram import Update
from telegram.ext import CommandHandler, ContextTypes, MessageHandler, filters
from duty_teller.db.session import session_scope
from duty_teller.importers.duty_schedule import (
DutyScheduleParseError,
parse_duty_schedule,
)
from duty_teller.services.import_service import run_import
from duty_teller.utils.handover import parse_handover_time
async def import_duty_schedule_cmd(
update: Update, context: ContextTypes.DEFAULT_TYPE
) -> None:
if not update.message or not update.effective_user:
return
if not config.is_admin(update.effective_user.username or ""):
await update.message.reply_text("Доступ только для администраторов.")
return
context.user_data["awaiting_handover_time"] = True
await update.message.reply_text(
"Укажите время пересменки в формате ЧЧ:ММ и часовой пояс, "
"например 09:00 Europe/Moscow или 06:00 UTC."
)
async def handle_handover_time_text(
update: Update, context: ContextTypes.DEFAULT_TYPE
) -> None:
if not update.message or not update.effective_user or not update.message.text:
return
if not context.user_data.get("awaiting_handover_time"):
return
if not config.is_admin(update.effective_user.username or ""):
return
text = update.message.text.strip()
parsed = parse_handover_time(text)
if parsed is None:
await update.message.reply_text(
"Не удалось разобрать время. Укажите, например: 09:00 Europe/Moscow"
)
return
hour_utc, minute_utc = parsed
context.user_data["handover_utc_time"] = (hour_utc, minute_utc)
context.user_data["awaiting_handover_time"] = False
context.user_data["awaiting_duty_schedule_file"] = True
await update.message.reply_text("Отправьте файл в формате duty-schedule (JSON).")
async def handle_duty_schedule_document(
update: Update, context: ContextTypes.DEFAULT_TYPE
) -> None:
if not update.message or not update.message.document or not update.effective_user:
return
if not context.user_data.get("awaiting_duty_schedule_file"):
return
handover = context.user_data.get("handover_utc_time")
if not handover or not config.is_admin(update.effective_user.username or ""):
return
if not (update.message.document.file_name or "").lower().endswith(".json"):
await update.message.reply_text("Нужен файл с расширением .json")
return
hour_utc, minute_utc = handover
file_id = update.message.document.file_id
file = await context.bot.get_file(file_id)
raw = bytes(await file.download_as_bytearray())
try:
result = parse_duty_schedule(raw)
except DutyScheduleParseError as e:
context.user_data.pop("awaiting_duty_schedule_file", None)
context.user_data.pop("handover_utc_time", None)
await update.message.reply_text(f"Ошибка разбора файла: {e}")
return
def run_import_with_scope():
with session_scope(config.DATABASE_URL) as session:
return run_import(session, result, hour_utc, minute_utc)
loop = asyncio.get_running_loop()
try:
num_users, num_duty, num_unavailable, num_vacation = await loop.run_in_executor(
None, run_import_with_scope
)
except Exception as e:
await update.message.reply_text(f"Ошибка импорта: {e}")
else:
total = num_duty + num_unavailable + num_vacation
parts = [f"{num_users} пользователей", f"{num_duty} дежурств"]
if num_unavailable:
parts.append(f"{num_unavailable} недоступностей")
if num_vacation:
parts.append(f"{num_vacation} отпусков")
await update.message.reply_text(
"Импорт выполнен: " + ", ".join(parts) + f" (всего {total} событий)."
)
finally:
context.user_data.pop("awaiting_duty_schedule_file", None)
context.user_data.pop("handover_utc_time", None)
import_duty_schedule_handler = CommandHandler(
"import_duty_schedule", import_duty_schedule_cmd
)
handover_time_handler = MessageHandler(
filters.TEXT & ~filters.COMMAND,
handle_handover_time_text,
)
duty_schedule_document_handler = MessageHandler(
filters.Document.FileExtension("json"),
handle_duty_schedule_document,
)