# Odoo 19 Development Guide

This file provides guidance to AI agents when working with Odoo 19 code in this repository.

> **For setup instructions with different AI IDEs, see [AGENTS.md](./AGENTS.md)**

## Documentation Structure

The `skills/odoo-19.0/references/` directory contains modular guides for Odoo 19 development:

```
skills/odoo-19.0/
├── SKILL.md                       # Master index
├── references/                    # Development guides (18 files)
│   ├── odoo-19-actions-guide.md     # ir.actions.*, cron, bindings
│   ├── odoo-19-controller-guide.md  # HTTP, routing, controllers
│   ├── odoo-19-data-guide.md        # XML/CSV data files, records
│   ├── odoo-19-decorator-guide.md   # @api decorators
│   ├── odoo-19-development-guide.md # Manifest, wizards (overview)
│   ├── odoo-19-field-guide.md       # Field types, parameters
│   ├── odoo-19-manifest-guide.md    # __manifest__.py reference
│   ├── odoo-19-mixins-guide.md      # mail.thread, activities, etc.
│   ├── odoo-19-model-guide.md       # ORM, CRUD, search, domain
│   ├── odoo-19-migration-guide.md   # Migration scripts, hooks
│   ├── odoo-19-owl-guide.md         # OWL components, services
│   ├── odoo-19-performance-guide.md # N+1 prevention, optimization
│   ├── odoo-19-reports-guide.md     # QWeb reports, PDF/HTML
│   ├── odoo-19-security-guide.md    # ACL, record rules, security
│   ├── odoo-19-testing-guide.md     # Test classes, decorators
│   ├── odoo-19-transaction-guide.md # Savepoints, errors
│   ├── odoo-19-translation-guide.md # Translations, i18n
│   └── odoo-19-view-guide.md        # XML views, QWeb
├── CLAUDE.md                      # This file
└── AGENTS.md                      # AI agents setup
```

## Which Guide to Use

| Task                                  | Guide                                     |
| ------------------------------------- | ----------------------------------------- |
| Creating actions, menus, cron jobs    | `references/odoo-19-actions-guide.md`     |
| Creating a new module                 | `references/odoo-19-development-guide.md` |
| Configuring **manifest**.py           | `references/odoo-19-manifest-guide.md`    |
| Creating XML/CSV data files           | `references/odoo-19-data-guide.md`        |
| Writing ORM queries/search            | `references/odoo-19-model-guide.md`       |
| Defining model fields                 | `references/odoo-19-field-guide.md`       |
| Using @api decorators                 | `references/odoo-19-decorator-guide.md`   |
| Writing XML views                     | `references/odoo-19-view-guide.md`        |
| Fixing slow code/N+1 queries          | `references/odoo-19-performance-guide.md` |
| Handling database errors              | `references/odoo-19-transaction-guide.md` |
| Creating HTTP endpoints               | `references/odoo-19-controller-guide.md`  |
| Building OWL components               | `references/odoo-19-owl-guide.md`         |
| Upgrading modules/migrating data      | `references/odoo-19-migration-guide.md`   |
| Using mail.thread, activities, mixins | `references/odoo-19-mixins-guide.md`      |
| Creating QWeb reports                 | `references/odoo-19-reports-guide.md`     |
| Configuring security (ACL, rules)     | `references/odoo-19-security-guide.md`    |
| Writing tests                         | `references/odoo-19-testing-guide.md`     |
| Adding translations/localization      | `references/odoo-19-translation-guide.md` |

## Key Odoo 19 Changes

| Change             | Old (Odoo 17-)                 | New (Odoo 19)                              |
| ------------------ | ------------------------------ | ------------------------------------------ |
| List view tag      | `<tree>`                       | `<list>`                                   |
| Dynamic attributes | `attrs="{'invisible': [...]}"` | `invisible="..."` (direct)                 |
| Delete validation  | Override `unlink()`            | `@api.ondelete(at_uninstall=False)`        |
| Field aggregation  | `group_operator=`              | `aggregator=`                              |
| SQL queries        | `cr.execute()`                 | `SQL` class with `execute_query_dict()`    |
| Batch create       | Single dict                    | List of dicts (`create([{...}, {...}])`)   |
| SQL constraints    | `_sql_constraints = [...]`     | `models.Constraint(...)`                   |
| DB indexes         | `index=True` only              | `models.Index(...)` declarative            |
| Kanban template    | `t-name="kanban-box"`          | `t-name="card"`                            |
| QWeb output        | `t-esc`                        | `t-out` (t-esc deprecated)                 |
| Security groups    | `category_id` on `res.groups`  | `privilege_id` + `res.groups.privilege`    |
| Private methods    | `_` prefix convention          | `@api.private` decorator (enforced)        |
| Model naming       | `_name = 'res.users'` required | CamelCase class → auto-derive `_name`      |
| read_group         | `read_group()`                 | `_read_group()` / `formatted_read_group()` |

## Critical Anti-Patterns

| Anti-Pattern                                                   | Why Bad                      | Correct Approach                                   |
| -------------------------------------------------------------- | ---------------------------- | -------------------------------------------------- |
| `attrs="{'invisible': [...]}"`                                 | Deprecated in Odoo 18        | Use `invisible="..."` direct attribute             |
| `@api.depends('partner_id')` then accessing `partner_id.email` | N queries per record         | Add `@api.depends('partner_id.email')`             |
| `search()` inside loop                                         | N+1 queries                  | Use `search()` with `IN` domain or `_read_group()` |
| `create()` in loop                                             | N INSERT statements          | Batch: `create([{...}, {...}])`                    |
| Overriding `unlink()` for validation                           | Breaks module uninstall      | Use `@api.ondelete(at_uninstall=False)`            |
| Using `<tree>` in Odoo 19                                      | Deprecated tag               | Use `<list>` instead                               |
| Using `_sql_constraints`                                       | **Not supported in Odoo 19** | Use `models.Constraint(...)`                       |
| Using `t-esc` in templates                                     | Deprecated directive         | Use `t-out` instead                                |
| Using `category_id` in `res.groups`                            | Removed in Odoo 19           | Use `privilege_id` + `res.groups.privilege`        |
| Using `read_group()`                                           | Deprecated                   | Use `_read_group()` or `formatted_read_group()`    |

## @api Decorator Decision Tree

```
Need to define field behavior?
├── Field computed from other fields → @api.depends
│   └── CAN use dotted paths: `@api.depends('partner_id.email')`
├── Validate data → @api.constrains
│   └── CANNOT use dotted paths: only simple field names
├── Prevent record deletion → @api.ondelete (Odoo 18+)
└── Update form UI → @api.onchange
    └── NO CRUD operations allowed

Need to define method behavior?
├── Method-level, doesn't depend on self → @api.model
├── Mark method as non-RPC callable → @api.private
└── Normal record method → no decorator needed
```

## Common Patterns Reference

### N+1 Query Prevention

```python
# BAD: search in loop
for order in orders:
    payments = self.env['payment'].search([('order_id', '=', order.id)])

# GOOD: single query
payments = self.env['payment'].search_read([('order_id', 'in', orders.ids)])
```

### List View (Odoo 19)

```xml
<list string="Records" editable="bottom" multi_edit="1">
    <field name="state" decoration-success="state == 'done'"/>
    <field name="phone" optional="show"/>
</list>
```

### Delete Validation (Odoo 19)

```python
@api.ondelete(at_uninstall=False)
def _unlink_if_not_draft(self):
    if any(rec.state != 'draft' for rec in self):
        raise UserError("Cannot delete non-draft records")
```

## Module Structure

```
my_module/
├── __init__.py
├── __manifest__.py
├── models/
│   ├── __init__.py
│   └── my_model.py
├── views/
│   └── my_model_views.xml
├── security/
│   ├── ir.model.access.csv
│   └── my_module_security.xml
├── data/
│   └── my_module_data.xml
├── migrations/
│   └── 19.0.1.0/
│       └── post-migration.py
├── tests/
│   ├── __init__.py
│   └── test_my_model.py
├── wizard/
│   ├── __init__.py
│   └── my_wizard.py
├── controllers/
│   ├── __init__.py
│   └── my_controller.py
└── static/
    └── src/
        ├── js/
        │   └── my_component.js
        ├── xml/
        │   └── my_component.xml
        └── scss/
            └── my_style.scss
```

## Base Code Reference

The guides are based on Odoo 19 source code. Reference these files in your Odoo installation:

- `odoo/models.py` - ORM implementation
- `odoo/fields.py` - Field types
- `odoo/api.py` - Decorators
- `odoo/http.py` - HTTP layer
- `odoo/exceptions.py` - Exception types
- `odoo/tools/translate.py` - Translation system
- `odoo/addons/base/models/res_lang.py` - Language model
- `addons/web/static/src/core/l10n/translation.js` - JS translations
