feat(bot): support concurrent drafts and reply-based edits with error resilience

This commit is contained in:
2026-09-29 00:48:08 +03:30
parent 09245cdc2c
commit a2e7c27e85
10 changed files with 146 additions and 36 deletions
+40 -12
View File
@@ -16,18 +16,39 @@ logger = logging.getLogger("bot.quote")
FIELD_FA = {"sup": "تأمین‌کننده", "prj": "پروژه"}
async def handle_entities(ctx, chat_id, user, patch: dict, source: str | None) -> None:
draft = await draft_store.get_active(chat_id, KIND_QUOTE)
if draft is not None:
async def handle_entities(
ctx, chat_id, user, patch: dict, source: str | None,
incoming_msg_id: int | None = None,
target_draft = None,
) -> None:
prev_review_msg_id = None
if target_draft is not None:
draft = target_draft
prev_review_msg_id = draft.review_message_id
payload = draft.payload
merge_quote_patch(payload, patch)
draft.source_message_id = incoming_msg_id
else:
payload = new_quote_payload()
merge_quote_patch(payload, patch)
draft = await draft_store.create(chat_id, KIND_QUOTE, payload, source=source)
draft = await draft_store.create(
chat_id, KIND_QUOTE, payload, source=source, source_message_id=incoming_msg_id
)
await resolve_payload(ctx, payload)
await draft_store.save_payload(draft, missing={"fields": compute_quote_missing(payload)})
await render(ctx, chat_id, draft, fresh=draft.review_message_id is None)
await draft_store.save_payload(
draft,
missing={"fields": compute_quote_missing(payload)},
source_message_id=incoming_msg_id,
)
if prev_review_msg_id:
try:
await ctx.delete(chat_id, prev_review_msg_id)
except Exception as e:
logger.warning("Could not delete previous review message %s: %s", prev_review_msg_id, e)
await render(ctx, chat_id, draft, fresh=True, reply_to=incoming_msg_id)
# ---------------------------------------------------------------------------
@@ -172,7 +193,8 @@ def _keyboard(payload: dict, draft_id: str, wizard_url: str | None = None) -> di
return quote_review_keyboard(prefix, pick_rows, has_partial_option=has_partial, wizard_url=wizard_url)
async def render(ctx, chat_id, draft, fresh: bool = True, note: str | None = None) -> None:
async def render(ctx, chat_id, draft, fresh: bool = True, note: str | None = None,
reply_to: int | None = None) -> None:
from app.config import settings
payload = draft.payload
@@ -182,11 +204,17 @@ async def render(ctx, chat_id, draft, fresh: bool = True, note: str | None = Non
text += f"\n\n🖥 مشاهده و تکمیل در ویزارد:\n{wizard_url}"
keyboard = _keyboard(payload, str(draft.id), wizard_url=wizard_url)
if fresh:
message = await ctx.send(chat_id, text, reply_markup=keyboard)
draft.review_message_id = (message or {}).get("message_id")
await draft_store.save_payload(draft, missing={"fields": missing},
review_message_id=draft.review_message_id)
await draft_store.set_bale_updated_at(draft.id)
message = await ctx.send(chat_id, text, reply_markup=keyboard, reply_to=reply_to)
new_review_id = (message or {}).get("message_id")
if new_review_id:
draft.review_message_id = new_review_id
await draft_store.save_payload(
draft,
missing={"fields": missing},
review_message_id=new_review_id,
source_message_id=reply_to or draft.source_message_id,
)
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})