{"record":{"id":"0bb4f13189f769c6","repo":"odoo/odoo","slug":"only-posted-cancelled-journal-entries-can-be-reset","errorCode":null,"errorMessage":"Only posted/cancelled journal entries can be reset to draft.","messagePattern":"Only posted/cancelled journal entries can be reset to draft\\.","errorType":"exception","errorClass":"UserError","httpStatus":null,"severity":"error","filePath":"addons/account/models/account_move.py","lineNumber":6238,"sourceCode":"        :param partial_id: The id of an existing partial reconciled with the current invoice.\n        '''\n        self.ensure_one()\n        partial = self.env['account.partial.reconcile'].browse(partial_id)\n        return partial.unlink()\n\n    def button_set_checked(self):\n        self.set_moves_checked()\n\n    def check_selected_moves(self):\n        self.env['account.move'].browse(self.env.context.get('active_ids', [])).set_moves_checked()\n\n    def set_moves_checked(self, is_checked=True):\n        for move in self.filtered(lambda m: m.state == 'posted'):\n            move.checked = is_checked\n\n    def button_draft(self):\n        if any(move.state not in ('cancel', 'posted') for move in self):\n            raise UserError(_(\"Only posted/cancelled journal entries can be reset to draft.\"))\n        if any(move.need_cancel_request for move in self):\n            raise UserError(_(\"You can't reset to draft those journal entries. You need to request a cancellation instead.\"))\n\n        self._check_draftable()\n        # We delete next auto_post move if draft\n        self._unlink_next_draft_auto_post_moves()\n        # We remove all the analytics entries for this journal\n        self.line_ids.analytic_line_ids.with_context(skip_analytic_sync=True).unlink()\n        self.state = 'draft'\n        self.sending_data = False\n\n        self._detach_attachments()\n        return True\n\n    def _get_fields_to_detach(self):\n        \"\"\"\"\n        Returns a list of field names to detach on resetting an invoice to draft. Can be overridden by other modules to\n        add more fields.","sourceCodeStart":6220,"sourceCodeEnd":6256,"githubUrl":"https://github.com/odoo/odoo/blob/1e661df964b1b264c9cef3ab28430d4785be3fda/addons/account/models/account_move.py#L6220-L6256","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"# before\nmoves.button_draft()  # a draft entry in the set\n\n# after\nmoves.filtered(lambda m: m.state in ('posted', 'cancel')).button_draft()","handlingStrategy":"validation","validationCode":"resettable = moves.filtered(lambda m: m.state in ('posted', 'cancel'))\nif resettable:\n    resettable.button_draft()","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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."],"tags":["odoo","draft","state-check","invoice"],"backgroundTag":null,"analyzedSha":"1e661df964b1b264c9cef3ab28430d4785be3fda","analyzedAt":"2026-08-15T05:22:16.142Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}