odoo/odoo · error · UserError

You cannot use an archived account.

Error message

You cannot use an archived account.

What it means

Raised in account.move.line.write(): before applying the write, if vals contains 'account_id' pointing to an account whose active flag is False, the write is refused outright with this UserError. Unlike the constraint-based check (error 104), this is a fast-path guard in write() itself with no is_imported/context escape — any programmatic assignment of an archived account fails.

Source

Thrown at addons/account/models/account_move_line.py:1819

                        line.move_id._message_log(
                            body=_("Journal Item %s created", line._get_html_link(title=f"#{line.id}")),
                            tracking_value_ids=tracking_value_ids
                        )

        lines.move_id._synchronize_business_models(['line_ids'])
        # Remove analytic lines created for draft AMLs, after analytic_distribution has been updated
        lines.filtered(lambda l: l.parent_state == 'draft').analytic_line_ids.with_context(skip_analytic_sync=True).unlink()
        return lines

    def write(self, vals):
        if not vals:
            return True
        protected_fields = self._get_lock_date_protected_fields()
        account_to_write = self.env['account.account'].browse(vals['account_id']) if 'account_id' in vals else None

        # Check writing a archived account.
        if account_to_write and not account_to_write.active:
            raise UserError(_('You cannot use an archived account.'))

        inalterable_fields = set(self._get_integrity_hash_fields()).union({'inalterable_hash'})
        hashed_moves = self.move_id.filtered('inalterable_hash')
        violated_fields = set(vals) & inalterable_fields
        if hashed_moves and violated_fields:
            raise UserError(_(
                "You cannot edit the following fields: %(fields)s.\n"
                "The following entries are already hashed:\n%(entries)s",
                fields=[f['string'] for f in self.fields_get(violated_fields).values()],
                entries='\n'.join(hashed_moves.mapped('name')),
            ))

        line_to_write = self
        vals = self._sanitize_vals(vals)
        matching2lines = None  # lazy cache
        lines_to_unreconcile = self.env['account.move.line']
        st_lines_to_unreconcile = self.env['account.bank.statement.line']
        tax_lock_check_ids = []

View on GitHub (pinned to 1e661df964)

Solutions

  1. Resolve the target account by code/xml_id among ACTIVE accounts only (search([('code','=',code),('active','in',[True,False])]) then check active, or filter active in the domain).
  2. Un-archive the intended account before the write if it must be used again.
  3. If the account was archived on purpose, pick its replacement account (maintain a mapping old->new) and use that in the write.

Example fix

# before
line.write({'account_id': account_by_code.id})

# after
account = self.env['account.account'].search([('code', '=', code), ('company_id', '=', company.id), ('active', '=', True)], limit=1)
if not account:
    raise UserError(_("No active account with code %s", code))
line.write({'account_id': account.id})
Defensive patterns

Strategy: validation

Validate before calling

account = self.env['account.account'].browse(vals['account_id'])
if 'account_id' in vals and not account.active:
    replacement = self.env['account.account'].search([
        ('code', '=', account.code),
        ('company_id', '=', account.company_id.id),
        ('active', '=', True),
    ], limit=1)
    vals['account_id'] = replacement.id if replacement else account.id
    if not replacement:
        account.write({'active': True})

Type guard

def account_is_usable(account):
    """An account usable on journal items must be active."""
    return bool(account) and account.active

Try / catch

from odoo.exceptions import UserError
try:
    line.write({'account_id': account.id})
except UserError as e:
    if 'archived account' in str(e):
        account.write({'active': True})  # or map to a replacement account
        line.write({'account_id': account.id})
    else:
        raise

Prevention

When it happens

Trigger: Calling write({'account_id': <id>}) (or any vals dict including account_id) on account.move.line records where the browsed account.account row has active=False. Common in scripts that move lines between accounts, apply fiscal position remappings, or import mappings resolved by account code where the code now belongs to an archived account.

Common situations: Chart cleanups archiving accounts that external integrations still reference by ID/code; migration tools remapping old account codes; scheduled server actions reassigning accounts; partner property fields pointing to archived accounts applied via write.

Related errors


AI-assisted analysis of odoo/odoo@1e661df964 (2026-08-15). Data as JSON: /api/errors/c2b6fd94740411da. Report an issue: GitHub.