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
- 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).
- Un-archive the intended account before the write if it must be used again.
- 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
- Resolve accounts with active=True in the search domain in all integrations.
- Maintain an archived->replacement account mapping for chart cleanups.
- Before archiving accounts, verify no scheduled actions or external feeds still reference their IDs.
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
- The account %(name)s (%(code)s) is archived.
- You can only request a cancellation for invoice sent to the
- Only draft journal entries can be cancelled.
- You can't block a paid invoice.
- The account selected on your journal entry forces to provide
AI-assisted analysis of odoo/odoo@1e661df964 (2026-08-15).
Data as JSON: /api/errors/c2b6fd94740411da.
Report an issue: GitHub.