feat(bot): support concurrent drafts and reply-based edits with error resilience
This commit is contained in:
@@ -75,18 +75,36 @@ async def handle_message(ctx: BotContext, chat_id: int, message: dict) -> None:
|
||||
if user is None:
|
||||
return # gate either started linking or is mid-linking
|
||||
|
||||
draft = await draft_store.get_active(chat_id)
|
||||
draft_summary = _draft_summary(draft)
|
||||
message_id = message.get("message_id")
|
||||
reply_to = message.get("reply_to_message")
|
||||
reply_to_msg_id = reply_to.get("message_id") if reply_to else None
|
||||
|
||||
target_draft = None
|
||||
if reply_to_msg_id is not None:
|
||||
target_draft = await draft_store.get_by_message_id(chat_id, reply_to_msg_id)
|
||||
if target_draft is None:
|
||||
await ctx.send(
|
||||
chat_id,
|
||||
"⚠️ پیشنویس فعال مربوط به این پیام یافت نشد (ممکن است ثبت نهایی، منقضی یا لغو شده باشد).\nبرای ثبت استعلام جدید، پیام خود را بدون ریپلای ارسال فرمایید.",
|
||||
reply_to=message_id,
|
||||
)
|
||||
return
|
||||
|
||||
draft_summary = _draft_summary(target_draft) if target_draft else None
|
||||
result = await understand(settings, text, draft_summary)
|
||||
intent, patch = result.get("intent"), result.get("patch") or {}
|
||||
logger.info("chat %s intent=%s conf=%s", chat_id, intent, result.get("confidence"))
|
||||
logger.info("chat %s intent=%s conf=%s (is_edit=%s)", chat_id, intent, result.get("confidence"), target_draft is not None)
|
||||
|
||||
if intent in ("register_quote", "update_draft"):
|
||||
await quote.handle_entities(ctx, chat_id, user, patch, source)
|
||||
await quote.handle_entities(
|
||||
ctx, chat_id, user, patch, source,
|
||||
incoming_msg_id=message_id,
|
||||
target_draft=target_draft,
|
||||
)
|
||||
elif intent == "commit_draft":
|
||||
active = await draft_store.get_active(chat_id)
|
||||
active = target_draft or await draft_store.get_active(chat_id)
|
||||
if active is None:
|
||||
await ctx.send(chat_id, NOTHING_TO_COMMIT)
|
||||
await ctx.send(chat_id, NOTHING_TO_COMMIT, reply_to=message_id)
|
||||
elif active.kind == draft_store.KIND_QUOTE:
|
||||
await quote.commit(ctx, chat_id, user, active)
|
||||
elif active.kind == draft_store.KIND_SUPPLIER:
|
||||
@@ -94,15 +112,20 @@ async def handle_message(ctx: BotContext, chat_id: int, message: dict) -> None:
|
||||
elif active.kind == draft_store.KIND_MATERIAL:
|
||||
await material.commit(ctx, chat_id, user, active)
|
||||
elif intent == "discard_draft":
|
||||
active = await draft_store.get_active(chat_id)
|
||||
active = target_draft or await draft_store.get_active(chat_id)
|
||||
if active:
|
||||
await draft_store.set_status(active.id, "cancelled")
|
||||
ctx.pending.pop(chat_id)
|
||||
await ctx.send(chat_id, CANCELLED)
|
||||
if active.review_message_id:
|
||||
try:
|
||||
await ctx.delete(chat_id, active.review_message_id)
|
||||
except Exception:
|
||||
pass
|
||||
ctx.pending.pop(chat_id, None)
|
||||
await ctx.send(chat_id, CANCELLED, reply_to=message_id)
|
||||
elif intent == "show_draft":
|
||||
active = await draft_store.get_active(chat_id)
|
||||
active = target_draft or await draft_store.get_active(chat_id)
|
||||
if active is None:
|
||||
await ctx.send(chat_id, NOTHING_TO_COMMIT)
|
||||
await ctx.send(chat_id, NOTHING_TO_COMMIT, reply_to=message_id)
|
||||
elif active.kind == draft_store.KIND_QUOTE:
|
||||
await quote.render(ctx, chat_id, active, fresh=False)
|
||||
elif active.kind == draft_store.KIND_SUPPLIER:
|
||||
|
||||
@@ -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})
|
||||
|
||||
Reference in New Issue
Block a user