Skip to content

Lead status change

Developers of the leads module and team members who press the buttons and want to know what each answer means.

  • Each card has five buttons. Callback data is stickers_lead:<lead_id>:<status>.
  • Only Telegram user ids listed in LEADS_ADMIN_IDS may change a status. Everyone else gets a pop-up Нет доступа (“No access”) and the card does not change.
  • Every real change writes one row to lead_status_history and rewrites the card. Pressing the current status changes nothing.
  • If the card cannot be edited (for example, it was deleted from the group), the status is still saved and the pop-up tells the user how to get a fresh card.
  • A separate fallback router answers buttons that no one else handles, so a button never spins forever.
  • Status buttons keep working during maintenance mode.

Defined in STATUSES and BUTTON_ROWS in services/leads.py.

Code Card label Button text
new 🆕 новая (new) — (cannot be set by a button)
in_work 🔧 в работе (in work) 🔧 Взял в работу (Took it)
contacted 📞 связались (contacted) 📞 Связались (Contacted)
deal ✅ сделка (deal) ✅ Сделка (Deal)
refused ❌ отказ (refused) ❌ Отказ (Refused)
spam 🚫 спам (spam) 🚫 Спам (Spam)

Row 1 holds in_work and contacted; row 2 holds deal, refused and spam. The button of the current status is prefixed with • . A status can be changed in any direction. An anonymised lead has no buttons.

The prefix stickers_ is deliberate: the group may host bots of other products, so commands, callback data and the hashtag #stickers_<id> all carry it.

sequenceDiagram
    autonumber
    participant U as Group member
    participant T as Telegram
    participant B as Bot leads router
    participant D as SQLite
    U->>T: Press a status button
    T->>B: callback_query with stickers_lead data
    alt user id not in LEADS_ADMIN_IDS
        B-->>U: Pop-up No access
    else callback data malformed
        B-->>U: Pop-up asking to refresh the card
    else lead missing or anonymised
        B-->>U: Pop-up with the reason
    else allowed
        B->>D: BEGIN IMMEDIATE, compare and update status, insert history row
        alt status unchanged
            B-->>U: Toast Already so
        else status changed
            B->>T: editMessageText with new card and keyboard
            alt edit accepted or not modified
                B-->>U: Toast Done
            else edit failed
                B-->>U: Pop-up Saved, card unavailable
            end
        end
    end

All in on_status in handlers/leads.py.

  1. Routing. The leads router is registered only when the module is enabled (LEADS_ENABLED=1 and LEADS_CHAT_ID set). Its callback filter is message.chat.id == LEADS_CHAT_ID, and the handler matches data starting with stickers_lead:.
  2. Access. cb.from_user.id must be in LEADS_ADMIN_IDS. Otherwise the log gets LEAD_STATUS_DENIED and the user gets Нет доступа as an alert. This check runs before the data is parsed.
  3. Parse. leads.parse_callback requires exactly three parts, the right prefix, a known status other than new, and a numeric id. A malformed value gives LEAD_STATUS_BAD_BUTTON and the alert Не понял кнопку — обнови карточку: /stickers_lead <номер> (“Did not understand the button — refresh the card”).
  4. Lead lookup. A missing lead gives Заявки #<id> нет в базе (“No such lead”). An anonymised lead gives Данные заявки удалены — статус не меняется (“Lead data was erased — the status does not change”).
  5. Write. db.lead_set_status opens BEGIN IMMEDIATE, compares the stored status with the new one and, if they differ, updates leads.status, status_by, status_by_name, status_at and inserts a lead_status_history row. Two admins pressing different buttons at the same moment produce two history rows in press order instead of one overwriting the other.
  6. Same status. No history row; toast Уже так: <label> (“Already so”); log LEAD_STATUS_SAME.
  7. Card edit. The card is rebuilt from the database (leads_bot.card_for): the first icon changes and the status block lists the last 3 changes with the admin’s name and the time in Tashkent. Log LEAD_STATUS #<id> -> <status> by user=<id>.
  8. Answer. Toast Готово: <label> (“Done”).

If editMessageText fails with anything other than “message is not modified”, the handler logs LEAD_CARD_EDIT_FAILED and answers with the alert Сохранено: <label>. Карточка недоступна — /stickers_lead <id> (“Saved. The card is unavailable”). The status and the history row are already committed.

/stickers_lead <id> posts the card again as a new message and stores the new chat_id / message_id (db.lead_card_moved). Later reminders and repeat pings reply to that fresh card. The old message keeps its old text.

register_fallback adds a last router that matches any callback starting with stickers_lead: or stickers_pick:. It fires when the leads router did not take the callback:

  • the module is switched off (LEADS_ENABLED=0, or LEADS_CHAT_ID not set);
  • the card lives in a chat other than the configured LEADS_CHAT_ID.

It logs LEAD_BUTTON_ORPHAN and answers with an alert: Заявки сейчас не ведутся этим ботом: модуль выключен или карточка из другого чата. Когда модуль включён — /stickers_lead <номер> пришлёт карточку заново. (“Leads are not handled by this bot right now: the module is off or the card is from another chat”). Without this router Telegram would show an endless spinner on the button.

/stickers_lead and /stickers_forget without a number show the last 5 leads as buttons with callback data stickers_pick:<action>:<lead_id>, where the action is lead, forget or forgettest. The same access check applies (LEAD_PICK_DENIED). After a forget action the picker message is rewritten without buttons, so a second press cannot erase anything again.

  • Guest routers (start, consent, maintenance, admin) filter on private chats, so nothing a group member types reaches the guest flow.
  • The maintenance middleware lets every non-private event through. Status buttons and /stickers_* commands work while generation is paused, including the automatic pause after a 402.
  • /stickers_* commands are accepted only in the leads group and only from LEADS_ADMIN_IDS; for anyone else the bot stays silent.

Last verified: 2026-11-13 against ai-sticker-pack@b715418 and ai-stickers-landing@e350ff5.