odoo/odoo · error · UserError
Only posted/cancelled journal entries can be reset to draft.
Error message
Only posted/cancelled journal entries can be reset to draft.
What it means
UserError raised at the start of button_draft (Reset to Draft): only moves in 'cancel' or 'posted' state may be reset to draft. Selecting anything else (typically already-draft entries, or the selection containing one) aborts the batch before _check_draftable runs.
Source
Thrown at addons/account/models/account_move.py:6238
:param partial_id: The id of an existing partial reconciled with the current invoice.
'''
self.ensure_one()
partial = self.env['account.partial.reconcile'].browse(partial_id)
return partial.unlink()
def button_set_checked(self):
self.set_moves_checked()
def check_selected_moves(self):
self.env['account.move'].browse(self.env.context.get('active_ids', [])).set_moves_checked()
def set_moves_checked(self, is_checked=True):
for move in self.filtered(lambda m: m.state == 'posted'):
move.checked = is_checked
def button_draft(self):
if any(move.state not in ('cancel', 'posted') for move in self):
raise UserError(_("Only posted/cancelled journal entries can be reset to draft."))
if any(move.need_cancel_request for move in self):
raise UserError(_("You can't reset to draft those journal entries. You need to request a cancellation instead."))
self._check_draftable()
# We delete next auto_post move if draft
self._unlink_next_draft_auto_post_moves()
# We remove all the analytics entries for this journal
self.line_ids.analytic_line_ids.with_context(skip_analytic_sync=True).unlink()
self.state = 'draft'
self.sending_data = False
self._detach_attachments()
return True
def _get_fields_to_detach(self):
""""
Returns a list of field names to detach on resetting an invoice to draft. Can be overridden by other modules to
add more fields.View on GitHub (pinned to 1e661df964)
Solutions
- Restrict the selection to posted or cancelled entries only, then reset.
- In code, pre-filter: moves.filtered(lambda m: m.state in ('posted', 'cancel')).button_draft().
- Make list-view buttons conditional on state via dynamic domains.
Example fix
# before
moves.button_draft() # a draft entry in the set
# after
moves.filtered(lambda m: m.state in ('posted', 'cancel')).button_draft() Defensive patterns
Strategy: validation
Validate before calling
resettable = moves.filtered(lambda m: m.state in ('posted', 'cancel'))
if resettable:
resettable.button_draft() Prevention
- Filter selections to posted/cancelled before Reset to Draft.
- Make the button invisible for non-posted states in views.
- Guard double-clicks: idempotency-check state in custom handlers.
When it happens
Trigger: Calling button_draft() on a recordset where any move.state is not in ('cancel', 'posted') — most often a draft move selected together with posted ones in a list view bulk action.
Common situations: Bulk Reset to Draft over a mixed selection; users double-clicking Reset to Draft (second click sees state 'draft'); automation calling button_draft on all moves of a partner including fresh drafts.
Related errors
- You can only register payment for posted journal entries.
- Cannot create a sale document in a non sale journal
- Validated entries can only be changed by your accountant.
- You cannot edit the journal of an account move with a sequen
- You cannot modify the following readonly fields on a posted
AI-assisted analysis of odoo/odoo@1e661df964 (2026-08-15).
Data as JSON: /api/errors/0bb4f13189f769c6.
Report an issue: GitHub.