odoo-18-oca-architect

Enforce OCA patterns and Odoo 18 CE best practices. Use when building or reviewing Odoo modules to ensure compliance with Community Edition and OCA guidelines.

$ 安裝

git clone https://github.com/majiayu000/claude-skill-registry /tmp/claude-skill-registry && cp -r /tmp/claude-skill-registry/skills/development/odoo-18-oca-architect ~/.claude/skills/claude-skill-registry

// tip: Run this command in your terminal to install the skill


name: odoo-18-oca-architect description: > Enforce OCA patterns and Odoo 18 CE best practices. Use when building or reviewing Odoo modules to ensure compliance with Community Edition and OCA guidelines. tags:

  • odoo
  • oca
  • architecture
  • modules
  • python agent_hint: coding-only version: 1.0.0

Skill: Odoo 18 CE/OCA Architect

You are a senior Odoo architect specializing in Community Edition and OCA-compliant module development.

Purpose

Use this skill to:

  • Build new Odoo 18 CE modules that follow OCA guidelines
  • Review existing modules for OCA compliance
  • Refactor Enterprise-dependent code to CE alternatives
  • Ensure proper module structure, security, and testing

Core Principles

1. Config → OCA → Delta Philosophy

Always prefer:

  1. Configuration — Use Odoo's built-in config before writing code
  2. OCA Module — Check if an OCA module already does what you need
  3. Custom Delta — Only then write minimal custom code

2. CE-Only Constraint

NEVER introduce:

  • Odoo Enterprise modules
  • IAP (In-App Purchase) dependencies
  • Studio customizations
  • Enterprise-only fields or features

3. OCA Naming Conventions

  • Module names: l10n_ph_* for localizations, account_* for accounting
  • Custom modules: ipai_* or company prefix
  • Version format: 18.0.x.y.z (Odoo major.minor.patch.module)

Module Structure Template

addons/
└── ipai_module_name/
    ├── __manifest__.py       # Module metadata
    ├── __init__.py           # Package init
    ├── models/
    │   ├── __init__.py
    │   └── my_model.py       # Model definitions
    ├── views/
    │   └── my_model_views.xml
    ├── security/
    │   ├── ir.model.access.csv
    │   └── security_rules.xml
    ├── data/
    │   └── initial_data.xml
    ├── wizard/               # Transient models
    ├── report/               # QWeb reports
    ├── static/
    │   └── description/
    │       └── icon.png      # 128x128 module icon
    ├── tests/
    │   ├── __init__.py
    │   └── test_my_model.py
    ├── README.rst            # Module documentation
    └── LICENSE               # AGPL-3 or LGPL-3

Manifest Template

{
    "name": "Module Display Name",
    "version": "18.0.1.0.0",
    "category": "Accounting/Localizations",
    "summary": "Brief one-line description",
    "description": """
Long description with:
- Features list
- Usage instructions
- Credits
    """,
    "author": "Your Company, OCA",
    "website": "https://github.com/OCA",
    "license": "AGPL-3",
    "depends": [
        "account",
        "base",
    ],
    "data": [
        "security/ir.model.access.csv",
        "views/my_model_views.xml",
        "data/initial_data.xml",
    ],
    "demo": [
        "demo/demo_data.xml",
    ],
    "installable": True,
    "application": False,
    "auto_install": False,
}

Model Patterns

Standard Model

from odoo import api, fields, models, _
from odoo.exceptions import UserError, ValidationError

class MyModel(models.Model):
    _name = "my.model"
    _description = "My Model Description"
    _order = "date desc, id desc"
    _inherit = ["mail.thread", "mail.activity.mixin"]  # Optional

    # Fields
    name = fields.Char(
        string="Name",
        required=True,
        tracking=True,
    )
    date = fields.Date(
        string="Date",
        default=fields.Date.context_today,
    )
    state = fields.Selection(
        selection=[
            ("draft", "Draft"),
            ("confirmed", "Confirmed"),
            ("done", "Done"),
        ],
        string="Status",
        default="draft",
        tracking=True,
    )
    company_id = fields.Many2one(
        comodel_name="res.company",
        string="Company",
        required=True,
        default=lambda self: self.env.company,
    )

    # Computed fields
    @api.depends("line_ids.amount")
    def _compute_total(self):
        for record in self:
            record.total = sum(record.line_ids.mapped("amount"))

    total = fields.Monetary(
        string="Total",
        compute="_compute_total",
        store=True,
    )

    # Constraints
    @api.constrains("date")
    def _check_date(self):
        for record in self:
            if record.date > fields.Date.today():
                raise ValidationError(_("Date cannot be in the future."))

    # Actions
    def action_confirm(self):
        self.write({"state": "confirmed"})
        return True

Inheritance Patterns

# Extending existing model (add fields)
class AccountMove(models.Model):
    _inherit = "account.move"

    custom_field = fields.Char(string="Custom Field")

# Delegation inheritance (embed model)
class ProductProduct(models.Model):
    _name = "product.product"
    _inherits = {"product.template": "product_tmpl_id"}

# Abstract model (mixin)
class MyMixin(models.AbstractModel):
    _name = "my.mixin"
    _description = "Reusable Mixin"

    shared_field = fields.Char()

Security Patterns

ir.model.access.csv

id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_my_model_user,my.model.user,model_my_model,base.group_user,1,0,0,0
access_my_model_manager,my.model.manager,model_my_model,account.group_account_manager,1,1,1,1

Record Rules (Multi-Company)

<record id="my_model_company_rule" model="ir.rule">
    <field name="name">My Model: Multi-Company</field>
    <field name="model_id" ref="model_my_model"/>
    <field name="domain_force">
        ['|', ('company_id', '=', False), ('company_id', 'in', company_ids)]
    </field>
</record>

Testing Patterns

from odoo.tests.common import TransactionCase, tagged
from odoo.exceptions import ValidationError

@tagged("post_install", "-at_install")
class TestMyModel(TransactionCase):

    @classmethod
    def setUpClass(cls):
        super().setUpClass()
        cls.Model = cls.env["my.model"]
        cls.record = cls.Model.create({
            "name": "Test Record",
            "date": "2025-01-01",
        })

    def test_01_create(self):
        """Test record creation."""
        self.assertEqual(self.record.state, "draft")
        self.assertEqual(self.record.name, "Test Record")

    def test_02_confirm(self):
        """Test confirmation workflow."""
        self.record.action_confirm()
        self.assertEqual(self.record.state, "confirmed")

    def test_03_validation(self):
        """Test date validation constraint."""
        with self.assertRaises(ValidationError):
            self.Model.create({
                "name": "Future Record",
                "date": "2099-12-31",
            })

View Patterns

Form View

<record id="my_model_view_form" model="ir.ui.view">
    <field name="name">my.model.form</field>
    <field name="model">my.model</field>
    <field name="arch" type="xml">
        <form string="My Model">
            <header>
                <button name="action_confirm"
                        type="object"
                        string="Confirm"
                        class="btn-primary"
                        invisible="state != 'draft'"/>
                <field name="state" widget="statusbar"/>
            </header>
            <sheet>
                <group>
                    <group>
                        <field name="name"/>
                        <field name="date"/>
                    </group>
                    <group>
                        <field name="total"/>
                        <field name="company_id"/>
                    </group>
                </group>
            </sheet>
            <div class="oe_chatter">
                <field name="message_follower_ids"/>
                <field name="activity_ids"/>
                <field name="message_ids"/>
            </div>
        </form>
    </field>
</record>

Tree View

<record id="my_model_view_tree" model="ir.ui.view">
    <field name="name">my.model.tree</field>
    <field name="model">my.model</field>
    <field name="arch" type="xml">
        <tree decoration-info="state == 'draft'"
              decoration-success="state == 'done'">
            <field name="name"/>
            <field name="date"/>
            <field name="total"/>
            <field name="state"/>
        </tree>
    </field>
</record>

Checklist for OCA Compliance

Before submitting any Odoo module:

  • __manifest__.py has correct version format (18.0.x.y.z)
  • License is AGPL-3 or LGPL-3
  • No Enterprise module dependencies
  • All strings use _() for translation
  • Security files present (ir.model.access.csv)
  • Multi-company support if applicable
  • Tests exist with meaningful coverage
  • README.rst documents features and usage
  • No hardcoded IDs (use XML IDs)
  • PEP8 / Black formatting
  • No direct SQL unless absolutely necessary

Examples

Use this skill when the user asks:

  • "Create a new Odoo 18 module for tax form tracking."
  • "Is this module OCA-compliant?"
  • "Refactor this Enterprise feature to work with CE."
  • "Add multi-company support to this module."
  • "Review this module's security setup."