From 1b5fc589fde8df22271c75fb7216a8b776a273fe Mon Sep 17 00:00:00 2001 From: "reza.g" Date: Tue, 29 Sep 2026 01:05:24 +0330 Subject: [PATCH] fix(bot): eliminate duplicate review cards caused by sync race and edit fallback --- ai procurement/app/drafts.py | 12 +++++++++--- ai procurement/app/handlers/quote.py | 26 +++++++++++++++++--------- ai procurement/app/runtime.py | 11 ++++++----- 3 files changed, 32 insertions(+), 17 deletions(-) diff --git a/ai procurement/app/drafts.py b/ai procurement/app/drafts.py index 10750fe..27db768 100644 --- a/ai procurement/app/drafts.py +++ b/ai procurement/app/drafts.py @@ -81,21 +81,27 @@ async def get_by_prefix(draft_prefix: str, chat_id: int | None = None) -> BotDra async def save_payload(draft: BotDraft, missing: dict | None = None, review_message_id: int | None = None, - source_message_id: int | None = None) -> None: + source_message_id: int | None = None, + mark_bale_synced: bool = False) -> None: async with SessionLocal() as session: + effective_review_msg_id = review_message_id if review_message_id is not None else draft.review_message_id values: dict = { "payload": draft.payload, "missing": missing, - "review_message_id": review_message_id or draft.review_message_id, + "review_message_id": effective_review_msg_id, } - if source_message_id: + if source_message_id is not None: values["source_message_id"] = source_message_id + draft.source_message_id = source_message_id + if mark_bale_synced: + values["last_bale_update_at"] = func.now() await session.execute( update(BotDraft) .where(BotDraft.id == draft.id) .values(**values) ) await session.commit() + draft.review_message_id = effective_review_msg_id async def set_status(draft_id: str, status: str) -> None: diff --git a/ai procurement/app/handlers/quote.py b/ai procurement/app/handlers/quote.py index b360adb..74df784 100644 --- a/ai procurement/app/handlers/quote.py +++ b/ai procurement/app/handlers/quote.py @@ -25,6 +25,7 @@ async def handle_entities( if target_draft is not None: draft = target_draft prev_review_msg_id = draft.review_message_id + draft.review_message_id = None payload = draft.payload merge_quote_patch(payload, patch) draft.source_message_id = incoming_msg_id @@ -36,11 +37,6 @@ async def handle_entities( ) await resolve_payload(ctx, payload) - await draft_store.save_payload( - draft, - missing={"fields": compute_quote_missing(payload)}, - source_message_id=incoming_msg_id, - ) if prev_review_msg_id: try: @@ -213,12 +209,24 @@ async def render(ctx, chat_id, draft, fresh: bool = True, note: str | None = Non missing={"fields": missing}, review_message_id=new_review_id, source_message_id=reply_to or draft.source_message_id, + mark_bale_synced=True, + ) + else: + await draft_store.save_payload( + draft, + missing={"fields": missing}, + source_message_id=reply_to or draft.source_message_id, + mark_bale_synced=True, ) - await draft_store.set_bale_updated_at(draft.id) else: - await ctx.edit(chat_id, draft.review_message_id, text, reply_markup=keyboard) - await draft_store.save_payload(draft, missing={"fields": missing}) - await draft_store.set_bale_updated_at(draft.id) + if not draft.review_message_id: + return + success = await ctx.edit(chat_id, draft.review_message_id, text, reply_markup=keyboard) + if success: + await draft_store.save_payload(draft, missing={"fields": missing}, mark_bale_synced=True) + else: + logger.warning("Could not edit review message %s for draft %s", draft.review_message_id, draft.id) + await draft_store.set_bale_updated_at(draft.id) # --------------------------------------------------------------------------- diff --git a/ai procurement/app/runtime.py b/ai procurement/app/runtime.py index 5be8fd7..fdaaabd 100644 --- a/ai procurement/app/runtime.py +++ b/ai procurement/app/runtime.py @@ -129,14 +129,15 @@ class BotContext: return message async def edit(self, chat_id: int, message_id: int | None, text: str, - reply_markup: dict | None = None) -> None: + reply_markup: dict | None = None) -> bool: if not message_id: - await self.send(chat_id, text, reply_markup=reply_markup) - return + return False try: await self.bale.edit_message_text(chat_id, message_id, text, reply_markup=reply_markup) - except Exception: - await self.send(chat_id, text, reply_markup=reply_markup) + return True + except Exception as exc: + logger.warning("failed to edit message %s in chat %s: %s", message_id, chat_id, exc) + return False async def delete(self, chat_id: int, message_id: int | None) -> bool: if not message_id: