Skip to content

[18.0][FIX] helpdesk_ticket_partner_response: Error on New Ticket - #964

Open
CristianoMafraJunior wants to merge 1 commit into
OCA:18.0from
Escodoo:18.0-fix-helpdesk_ticket_partner_response
Open

CristianoMafraJunior wants to merge 1 commit into
OCA:18.0from
Escodoo:18.0-fix-helpdesk_ticket_partner_response

Conversation

@CristianoMafraJunior

Copy link
Copy Markdown
Member

Fixes #963

Summary

Fix stage auto-update not working when tickets are received via fetchmail,
and prevent a crash when a new email creates a ticket for the first time.

Problems Fixed

1. TypeError on new incoming emails

When a brand new email arrived with no existing ticket, routes[0][1]
(the ticket ID) was None. The code called int(None) without any guard,
crashing with:

TypeError: int() argument must be a string, a bytes-like object
or a real number, not 'NoneType'

This happened every time a customer sent a new support request by email,
making it impossible to receive tickets from the inbox.

2. Stage never changed when email arrived via fetchmail

The original code identified the sender by looking up the route user
(routes[0][3]) and comparing their partner with the ticket customer:

partner_id = (
    self.env["res.users"]
    .search([("id", "=", routes[0][3])], limit=1)
    .partner_id.id
)
if partner_id == ticket.partner_id.id:
    ...

When emails are processed via fetchmail, that user is always OdooBot or
the cron user
, never the actual customer. So the partner comparison always
failed and the stage update never happened — the feature was silently broken
for all email-based workflows.

3. browse() result not validated against the database

browse(id) in Odoo always returns a non-empty recordset even if the record
does not exist in the database. A bare if ticket: check would always be
True for any non-zero ID. ticket.exists() was added to perform the
actual database check.

Changes

models/mail_thread.py

  • Added if ticket_id: guard before browse() to safely skip new incoming
    emails with no existing ticket
  • message_dict is now passed to change_status_ticket_from_portal so the
    real email sender (author_id) can be read from the parsed message headers
  • Replaced the route user lookup with message_dict.get("author_id") to
    correctly identify the customer regardless of how the email was delivered
    (portal, fetchmail, etc.)
  • Replaced if ticket: with ticket.exists() for a proper database
    existence check

Before:

def change_status_ticket_from_portal(self, routes):
    if routes and routes[0][0] == "helpdesk.ticket":
        ticket_id = routes[0][1]
        ticket = self.env["helpdesk.ticket"].sudo().browse(int(ticket_id))
        partner_id = (
            self.env["res.users"]
            .search([("id", "=", routes[0][3])], limit=1)
            .partner_id.id
        )
        if (
            ticket
            and partner_id == ticket.partner_id.id
            and ticket.team_id.autoupdate_ticket_stage
            and ticket.stage_id in ticket.team_id.autopupdate_src_stage_ids
        ):
            ticket.stage_id = ticket.team_id.autopupdate_dest_stage_id.id

After:

def change_status_ticket_from_portal(self, routes, message_dict=None):
    if routes and routes[0][0] == "helpdesk.ticket":
        ticket_id = routes[0][1]
        if ticket_id:
            ticket = self.env["helpdesk.ticket"].sudo().browse(int(ticket_id))
            author_id = message_dict and message_dict.get("author_id")
            if (
                ticket.exists()
                and author_id == ticket.partner_id.id
                and ticket.team_id.autoupdate_ticket_stage
                and ticket.stage_id in ticket.team_id.autopupdate_src_stage_ids
            ):
                ticket.stage_id = ticket.team_id.autopupdate_dest_stage_id.id

tests/test_partner_response.py

  • message_process() helper now accepts an optional email_from parameter
    to allow simulating different senders in tests
  • Added test_no_change_stage_non_customer_through_mail: verifies that an
    email from a non-customer (e.g. internal user) does not trigger a stage
    change — this is the key behavioral test for the author_id fix
  • Added test_new_ticket_via_email_no_crash: reproduces the TypeError
    scenario (no thread_id) and ensures no exception is raised
  • Added test_ticket_without_team_no_crash: ensures a ticket with no team
    assigned does not crash on an incoming email reply; stage must remain
    unchanged since there is no autoupdate configuration
  • Added docstrings to all tests to document the intent of each scenario

Test Coverage

Test Scenario
test_change_stage_customer_answered Customer replies via portal → stage changes
test_no_change_stage_customer_answered Source stage not in configured list → no change
test_change_stage_deactivated Autoupdate disabled on team → no change
test_change_stage_customer_answered_through_mail Customer replies via fetchmail → stage changes
test_no_change_stage_customer_answered_through_mail Source stage not in configured list → no change
test_change_stage_deactivated_through_mail Autoupdate disabled → no change
test_no_change_stage_non_customer_through_mail Email from non-customer → no change (new)
test_new_ticket_via_email_no_crash New email, no existing ticket → no crash (new)
test_ticket_without_team_no_crash Ticket with no team → no crash, stage unchanged (new)

cc @agent-z28

@OCA-git-bot

Copy link
Copy Markdown
Contributor

Hi @peluko00,
some modules you are maintaining are being modified, check this out!

@peluko00 peluko00 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! Code review

@pedrobaeza pedrobaeza added this to the 18.0 milestone Mar 12, 2026
@pedrobaeza

pedrobaeza commented Mar 12, 2026 •

Copy link
Copy Markdown
Member

The issue is mentioning a problem in the previous line:

ticket = self.env["helpdesk.ticket"].sudo().browse(int(ticket_id))
TypeError: int() argument must be a string, a bytes-like object or a real number, not 'NoneType'

so this is not solving that problem, as the line remains untouched.

@CristianoMafraJunior
CristianoMafraJunior force-pushed the 18.0-fix-helpdesk_ticket_partner_response branch from 5ebf458 to b29c8f6 Compare March 19, 2026 17:14
@CristianoMafraJunior

Copy link
Copy Markdown
Member Author

The issue is mentioning a problem in the previous line:

ticket = self.env["helpdesk.ticket"].sudo().browse(int(ticket_id))
TypeError: int() argument must be a string, a bytes-like object or a real number, not 'NoneType'

so this is not solving that problem, as the line remains untouched.

The email routing can send routes[0][1] as None (when the ticket/thread is being created), so the old code crashed at int(ticket_id).
I changed change_status_ticket_from_portal() to guard against that case:

  • If ticket_id is None (or cannot be converted to an integer), the method simply returns without crashing.
  • Only when we have a valid numeric ticket_id do we browse() the ticket and apply the stage auto-update logic.

)
# When the email creates the ticket/thread, Odoo can route the message
# with a missing (None) id. In that case we must not crash.
if ticket_id is None:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need of this one.

ticket_id_int = int(ticket_id)
except (TypeError, ValueError):
return

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't add empty lines inside the method.

@github-actions

Copy link
Copy Markdown

There hasn't been any activity on this pull request in the past 4 months, so it has been marked as stale and it will be closed automatically if no further activity occurs in the next 30 days.
If you want this PR to never become stale, please ask a PSC member to apply the "no stale" label.

@github-actions github-actions Bot added the stale PR/Issue without recent activity, it'll be soon closed automatically. label Jul 26, 2026
@marcelsavegnago

Copy link
Copy Markdown
Member

ping @CristianoMafraJunior

image

@github-actions github-actions Bot removed the stale PR/Issue without recent activity, it'll be soon closed automatically. label Aug 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Error on New Ticket with helpdesk_ticket_partner_response

5 participants