feat(bot): centralize defaults, refine ambiguity lifecycle, improve entity resolution and auto-create new projects

This commit is contained in:
2026-10-08 22:33:15 +03:30
parent 638076dbaa
commit 77c0cacbb6
12 changed files with 905 additions and 59 deletions
+50 -4
View File
@@ -9,7 +9,7 @@ from app import drafts as draft_store
from app.runtime import LinkedUser
from app.texts import CANCELLED, UNLINKED, esc
from . import quote, supplier, material, decisions
from . import quote, supplier, material, project, decisions
logger = logging.getLogger("bot.callbacks")
@@ -57,6 +57,45 @@ async def _dispatch(ctx, chat_id: int, data: str, cq: dict) -> None:
await decisions.handle_arrive_callback(ctx, chat_id, user, parts)
return
# ---- ambiguity sub-messages (Requirement 2.a) -------------------------
if parts[0] == "amb" and len(parts) >= 4:
action, prefix, amb_id = parts[1], parts[2], parts[3]
draft = await draft_store.get_by_prefix(prefix, chat_id)
if draft is None:
await ctx.send(chat_id, "این پیش‌نویس دیگر معتبر نیست.")
return
payload = draft.payload or {}
ambiguities = payload.get("ambiguities") or []
target_amb = next((a for a in ambiguities if a.get("id") == amb_id), None)
if not target_amb:
return
if action == "ok":
proposed = target_amb.get("proposed_patch") or {}
draft_store.merge_quote_patch(payload, proposed)
target_amb["status"] = "applied"
await quote.resolve_payload(ctx, payload)
await draft_store.save_payload(draft, mark_bale_synced=True)
# In-place edit main review card
await quote.render(ctx, chat_id, draft, fresh=False)
# Delete ONLY this ambiguity message
if target_amb.get("message_id"):
try:
await ctx.delete(chat_id, target_amb["message_id"])
except Exception:
pass
elif action == "no":
target_amb["status"] = "rejected"
await draft_store.save_payload(draft, mark_bale_synced=True)
# Delete ONLY this ambiguity message
if target_amb.get("message_id"):
try:
await ctx.delete(chat_id, target_amb["message_id"])
except Exception:
pass
return
# ---- pick a candidate for a draft field -------------------------------
if parts[0] == "pk" and len(parts) == 4:
draft = await draft_store.get_by_prefix(parts[1], chat_id)
@@ -68,8 +107,13 @@ async def _dispatch(ctx, chat_id: int, data: str, cq: dict) -> None:
# ---- draft commit / cancel --------------------------------------------
op, arg, prefix = parts[0], parts[1], parts[2] if len(parts) > 2 else ""
if op in ("q", "s", "m") and arg in ("ok", "ok2", "no"):
kind = {"q": draft_store.KIND_QUOTE, "s": draft_store.KIND_SUPPLIER, "m": draft_store.KIND_MATERIAL}[op]
if op in ("q", "s", "m", "p") and arg in ("ok", "ok2", "no"):
kind = {
"q": draft_store.KIND_QUOTE,
"s": draft_store.KIND_SUPPLIER,
"m": draft_store.KIND_MATERIAL,
"p": draft_store.KIND_PROJECT,
}[op]
draft = await draft_store.get_by_prefix(prefix, chat_id)
if draft is None or draft.kind != kind:
await ctx.send(chat_id, "این پیش‌نویس دیگر معتبر نیست.")
@@ -89,8 +133,10 @@ async def _dispatch(ctx, chat_id: int, data: str, cq: dict) -> None:
await quote.commit(ctx, chat_id, user, draft, only_resolved=(arg == "ok2"))
elif kind == draft_store.KIND_SUPPLIER:
await supplier.commit(ctx, chat_id, user, draft)
else:
elif kind == draft_store.KIND_MATERIAL:
await material.commit(ctx, chat_id, user, draft)
elif kind == draft_store.KIND_PROJECT:
await project.commit(ctx, chat_id, user, draft)
async def _apply_pick(ctx, chat_id: int, user: LinkedUser, draft, field: str, idx: int) -> None: