"""Inline keyboard builders. Bale keeps Telegram's 64-byte ``callback_data`` cap, so every callback is a short op-code plus the 8-char draft prefix; the full state lives in bot_drafts (or the in-memory pending-flow map) and is looked up on tap. """ def _kb(rows: list[list[dict]]) -> dict: return {"inline_keyboard": rows} def _btn(text: str, data: str) -> dict: return {"text": text, "callback_data": data} # callback scheme (≤64 bytes): # q:ok:{d8} / q:no:{d8} / q:ok2:{d8} quote commit / cancel / commit-resolved-only # s:ok:{d8} / s:no:{d8} supplier # m:ok:{d8} / m:no:{d8} material # pk:{d8}:{field}:{idx} pick candidate for a draft field # qc:{idx} / ac:{op} / ar:{op} pending-flow buttons (accept/arrive) def draft_actions(prefix: str, kind: str, has_partial_option: bool = False) -> list[list[dict]]: op = {"quote": "q", "supplier": "s", "material": "m", "project": "p"}.get(kind, "q") rows = [[_btn("✅ ثبت نهایی", f"{op}:ok:{prefix}"), _btn("❌ انصراف", f"{op}:no:{prefix}")]] if has_partial_option: rows.insert(0, [_btn("✅ ثبت فقط اقلام مشخص", f"{op}:ok2:{prefix}")]) return rows def pick_buttons(prefix: str, field: str, names: list[str]) -> list[list[dict]]: return [[_btn(name[:60], f"pk:{prefix}:{field}:{i}")] for i, name in enumerate(names)] def quote_review_keyboard( prefix: str, pick_rows: list[list[dict]], has_partial_option: bool = False, wizard_url: str | None = None, ) -> dict: rows = list(pick_rows) if wizard_url: rows.append([{"text": "🖥 بازبینی و تکمیل در ویزارد", "url": wizard_url}]) rows += draft_actions(prefix, "quote", has_partial_option=has_partial_option) return _kb(rows) def simple_review_keyboard(prefix: str, kind: str, pick_rows: list[list[dict]]) -> dict: rows = list(pick_rows) rows += draft_actions(prefix, kind) return _kb(rows) def quote_pick_keyboard(pairs: list[tuple[str, str]]) -> dict: """Pick among candidate quotes: pairs of (label, quote_id).""" return _kb([[_btn(label[:60], f"qc:{i}")] for i, (label, _) in enumerate(pairs)]) def accept_keyboard(termsheet_labels: list[str]) -> dict: rows = [[_btn(f"شرایط: {label[:40]}", f"ac:ts:{i}")] for i, label in enumerate(termsheet_labels)] rows.append([_btn("✅ تأیید پذیرش", "ac:ok"), _btn("❌ انصراف", "ac:no")]) return _kb(rows) def arrive_keyboard() -> dict: return _kb([ [_btn("📦 تحویل کامل همه اقلام", "ar:full")], [_btn("✅ ثبت مقادیر اعلام‌شده", "ar:ok"), _btn("❌ انصراف", "ar:no")], ]) def cancel_keyboard() -> dict: return _kb([[_btn("❌ انصراف", "ac:no")]]) def ambiguity_keyboard(prefix: str, amb_id: str) -> dict: """Inline buttons for ambiguity sub-message (≤64 bytes callback_data).""" return _kb([ [_btn("✅ تأیید و اعمال در پیش‌نویس", f"amb:ok:{prefix}:{amb_id}")], [_btn("❌ تایید نمی‌شود", f"amb:no:{prefix}:{amb_id}")], ])