etm-terrain/handlers/fin_chantier.py

149 lines
5.2 KiB
Python

import logging
from datetime import datetime
from telegram import Update
from telegram.ext import ContextTypes
from models.session import set_state, get_data, clear_data, MENU
from handlers import get_menu_keyboard, MENU_TEXT
from handlers.chantier import nom_depuis_dossier
from services.nextcloud import upload_photo, create_deck_card
from services.telegram import notify_fin_chantier as send_notification
from config import DECK_BOARD_ID, DECK_COL_FIN
log = logging.getLogger(__name__)
_albums: dict[str, dict] = {}
async def handle_photo(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
msg = update.message
if not msg or not msg.photo:
if msg:
await msg.reply_text("❌ Veuillez envoyer une photo.")
return
caption = msg.caption or ""
group_id = msg.media_group_id
ouvrier = msg.from_user.full_name
file_id = msg.photo[-1].file_id
user_id = msg.from_user.id
chantier_folder = get_data(user_id, "chantier_folder")
if group_id:
if group_id not in _albums:
_albums[group_id] = {
"photos": [],
"caption": "",
"ouvrier": ouvrier,
"chat_id": msg.chat_id,
"user_id": user_id,
"chantier_folder": chantier_folder,
}
context.job_queue.run_once(
_process_album_job,
3,
data={"group_id": group_id},
name=str(group_id),
)
if caption:
_albums[group_id]["caption"] = caption
_albums[group_id]["photos"].append(file_id)
else:
if not chantier_folder and not caption:
await msg.reply_text(
"❌ Légende obligatoire.\nFormat : `NOM_Ville / Notes`",
parse_mode="Markdown",
)
return
await msg.reply_text("⏳ Upload en cours...")
try:
await _upload_photos([file_id], ouvrier, context.bot, chantier_folder=chantier_folder, caption=caption)
except Exception as exc:
log.error("_upload_photos single : %s", exc)
await msg.reply_text("❌ Erreur lors de l'upload.")
finally:
clear_data(user_id)
set_state(user_id, MENU)
await msg.reply_text(MENU_TEXT, reply_markup=get_menu_keyboard())
async def _process_album_job(context: ContextTypes.DEFAULT_TYPE) -> None:
group_id = context.job.data["group_id"]
album = _albums.pop(group_id, None)
if not album:
return
caption = album.get("caption", "")
chat_id = album["chat_id"]
user_id = album["user_id"]
chantier_folder = album.get("chantier_folder")
if not chantier_folder and not caption:
await context.bot.send_message(
chat_id=chat_id,
text="❌ Album reçu sans chantier ni légende.\nFormat : `NOM_Ville / Notes`",
parse_mode="Markdown",
)
set_state(user_id, MENU)
await context.bot.send_message(chat_id=chat_id, text=MENU_TEXT, reply_markup=get_menu_keyboard())
return
n = len(album["photos"])
await context.bot.send_message(chat_id=chat_id, text=f"⏳ Upload de {n} photo(s) en cours...")
try:
await _upload_photos(album["photos"], album["ouvrier"], context.bot, chantier_folder=chantier_folder, caption=caption)
except Exception as exc:
log.error("_upload_photos album : %s", exc)
await context.bot.send_message(chat_id=chat_id, text="❌ Erreur lors de l'upload.")
finally:
clear_data(user_id)
set_state(user_id, MENU)
await context.bot.send_message(chat_id=chat_id, text=MENU_TEXT, reply_markup=get_menu_keyboard())
async def _upload_photos(
file_ids: list[str],
ouvrier: str,
bot,
*,
chantier_folder: str | None = None,
caption: str = "",
) -> None:
now = datetime.now()
if chantier_folder:
nc_dir = f"Chantiers/{chantier_folder}/30_Photos_Chantier"
chantier_name = nom_depuis_dossier(chantier_folder)
notes = caption.strip()
else:
chantier_name, notes = _parse_caption(caption)
nc_dir = f"Chantiers/{now.strftime('%y%m%d')}_{chantier_name}/30_Photos_Chantier"
uploaded = 0
for i, file_id in enumerate(file_ids):
try:
tg_file = await bot.get_file(file_id)
file_bytes = bytes(await tg_file.download_as_bytearray())
ts = now.strftime("%y%m%d_%H%M%S")
ok = await upload_photo(file_bytes, f"{nc_dir}/photo_{i + 1:02d}_{ts}.jpg")
if ok:
uploaded += 1
except Exception as exc:
log.error("Erreur upload photo %d : %s", i + 1, exc)
try:
await send_notification(bot, ouvrier, chantier_name, uploaded, f"Nextcloud/{nc_dir}/")
except Exception as exc:
log.error("notify_fin_chantier : %s", exc)
if DECK_BOARD_ID and DECK_COL_FIN:
await create_deck_card(
DECK_BOARD_ID,
DECK_COL_FIN,
f"Fin chantier — {chantier_name}",
f"Ouvrier : {ouvrier}\n{len(file_ids)} photo(s)\nNotes : {notes}",
)
def _parse_caption(caption: str) -> tuple[str, str]:
chantier, _, notes = caption.partition("/")
return chantier.strip(), notes.strip()