Works with the AI Agents you already use

    Claude CodeClaude CodeCursorCursorCodex CLICodex CLIMuseMuseDotsDots+22 more

    Master Odoo — Version-Aware Odoo Developer (17–20)

    1

    Get correct Odoo 17, 18, 19 and 20 code for the exact version you work on — and catch outdated patterns before they break your module.

    Free

    Security scanned
    Master Odoo — Version-Aware Odoo Developer (17–20)

    Master Odoo — Version-Aware Odoo Developer (17–20)

    Example session with this skill installed

    In my Odoo 19 module service_desk, add a Service Request model: a unique reference, a customer, a priority, a status (New → In Progress → Done) with buttons, a chatter, list / form / search views, a menu, and access rights for internal users (only managers can delete).

    • Read your context and instructions
    • Compiled the master odoo
    • Generated the data export

    Target series detected: 19.0 (from __manifest__.py). Delta deltas/odoo-19.md applied.

    Plan

    • Model service.request with mail.thread + mail.activity.mixin (chatter and tracking).
    • Reference generated by an ir.sequence (SR/2026/00001), unique in the database.
    • Two access levels: internal users (read, write, create) and a Service Desk Manager group (+ delete).
    • Views: list, form with a status bar and action buttons, search with filters and group-by, menu.

    19.0-specific choices

    Need19.0 wayWhy
    Unique referencemodels.Constraint_sql_constraints is ignored in 19.0: the constraint would silently not exist
    List view<list><tree> is not a view type since 18.0
    Chatter<chatter/>18.0+ markup
    Search group-bybare <group>expand / string make the view fail to load in 19.0

    __manifest__.py

    {
        'name': 'Service Desk',
        'version': '19.0.1.0.0',
        'summary': 'Track customer service requests',
        'author': 'Your Company',
        'license': 'LGPL-3',
        'depends': ['mail'],
        'data': [
            'security/security.xml',
            'security/ir.model.access.csv',
            'data/sequence.xml',
            'views/service_request_views.xml',
        ],
        'installable': True,
        'application': True,
    }
    

    models/service_request.py

    from odoo import _, api, fields, models
    
    class ServiceRequest(models.Model):
        _name = 'service.request'
        _description = 'Service Request'
        _inherit = ['mail.thread', 'mail.activity.mixin']
        _order = 'priority desc, id desc'
    
        name = fields.Char(string='Reference', required=True, readonly=True, copy=False,
                           default=lambda self: _('New'))
        partner_id = fields.Many2one('res.partner', string='Customer', required=True, tracking=True)
        priority = fields.Selection([('0', 'Normal'), ('1', 'High'), ('2', 'Urgent')],
                                    default='0', tracking=True)
        state = fields.Selection([('new', 'New'), ('in_progress', 'In Progress'), ('done', 'Done')],
                                 default='new', required=True, tracking=True)
        description = fields.Html()
    
        # 19.0: _sql_constraints is ignored (warning only) — models.Constraint creates
        # service_request_name_unique in the database.
        _name_unique = models.Constraint('UNIQUE(name)', "This reference already exists.")
    
        @api.model_create_multi
        def create(self, vals_list):
            for vals in vals_list:
                if vals.get('name', _('New')) == _('New'):
                    vals['name'] = self.env['ir.sequence'].next_by_code('service.request') or _('New')
            return super().create(vals_list)
    
        def action_start(self):
            self.write({'state': 'in_progress'})
    
        def action_done(self):
            self.write({'state': 'done'})
    

    security/security.xml

    <odoo>
        <record id="group_service_manager" model="res.groups">
            <field name="name">Service Desk Manager</field>
            <field name="implied_ids" eval="[Command.link(ref('base.group_user'))]"/>
        </record>
    </odoo>
    

    security/ir.model.access.csv

    id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
    access_service_request_user,service.request user,model_service_request,base.group_user,1,1,1,0
    access_service_request_manager,service.request manager,model_service_request,group_service_manager,1,1,1,1
    

    data/sequence.xml

    <odoo noupdate="1">
        <record id="seq_service_request" model="ir.sequence">
            <field name="name">Service Request</field>
            <field name="code">service.request</field>
            <field name="prefix">SR/%(year)s/</field>
            <field name="padding">5</field>
        </record>
    </odoo>
    

    views/service_request_views.xml

    <odoo>
        <record id="service_request_view_list" model="ir.ui.view">
            <field name="name">service.request.list</field>
            <field name="model">service.request</field>
            <field name="arch" type="xml">
                <list decoration-danger="priority == '2'" decoration-muted="state == 'done'">
                    <field name="name"/>
                    <field name="partner_id"/>
                    <field name="priority" widget="priority"/>
                    <field name="state" widget="badge"/>
                </list>
            </field>
        </record>
    
        <record id="service_request_view_form" model="ir.ui.view">
            <field name="name">service.request.form</field>
            <field name="model">service.request</field>
            <field name="arch" type="xml">
                <form>
                    <header>
                        <button name="action_start" type="object" string="Start" class="btn-primary"
                                invisible="state != 'new'"/>
                        <button name="action_done" type="object" string="Mark as Done" class="btn-primary"
                                invisible="state != 'in_progress'"/>
                        <field name="state" widget="statusbar"/>
                    </header>
                    <sheet>
                        <div class="oe_title"><h1><field name="name"/></h1></div>
                        <group>
                            <field name="partner_id"/>
                            <field name="priority" widget="priority"/>
                        </group>
                        <field name="description" placeholder="Describe the request..."/>
                    </sheet>
                    <chatter/>
                </form>
            </field>
        </record>
    
        <record id="service_request_view_search" model="ir.ui.view">
            <field name="name">service.request.search</field>
            <field name="model">service.request</field>
            <field name="arch" type="xml">
                <search>
                    <field name="name"/>
                    <field name="partner_id"/>
                    <filter name="open" string="Open" domain="[('state', '!=', 'done')]"/>
                    <filter name="urgent" string="Urgent" domain="[('priority', '=', '2')]"/>
                    <group>
                        <filter name="group_state" string="Status" context="{'group_by': 'state'}"/>
                        <filter name="group_partner" string="Customer" context="{'group_by': 'partner_id'}"/>
                    </group>
                </search>
            </field>
        </record>
    
        <record id="service_request_action" model="ir.actions.act_window">
            <field name="name">Service Requests</field>
            <field name="res_model">service.request</field>
            <field name="view_mode">list,form</field>
            <field name="context">{'search_default_open': 1}</field>
        </record>
    
        <menuitem id="service_desk_menu_root" name="Service Desk" sequence="40"/>
        <menuitem id="service_request_menu" parent="service_desk_menu_root" action="service_request_action" sequence="10"/>
    </odoo>
    

    (plus __init__.py → from . import models and models/__init__.py → from . import service_request)

    Check

    $ python3 scripts/check_module.py service_desk
    == service_desk (series 19.0) — 0 error(s), 0 warning(s)
    

    Next steps

    1. Update the module (-u service_desk) and assign the Service Desk Manager group to the right users.
    2. Add tests: reference uniqueness (IntegrityError on a duplicate) and access rights (a non-manager cannot delete).
    3. If several companies use the desk, add company_id and a multi-company access rule.

    access.csv

    CSV · data export

    Generated

    Example file from a real run - the skill writes it into your workspace.

    Connects securely to your tools. The creator never sees your data.

    About this skill

    Stop AI assistants from writing Odoo code for the wrong version

    Odoo changes its conventions at every release, and most AI assistants mix them up. They write <tree> on 18.0, name_get() on 18.0, _sql_constraints on 19.0 — where Odoo silently skips the constraint — or get_param() on 20.0, which installs fine and crashes on first use.

    Master Odoo makes your agent write code for one target version at a time — 17.0, 18.0, 19.0 or 20.0 — with conventions checked against Odoo's own source code.

    What you get

    • The right version, every time: the skill reads the target version from your module's manifest before writing anything, and never mixes the conventions of two versions.
    • Complete, consistent modules: models and fields, XML views, security, data files, scheduled actions, controllers, QWeb reports, mixins, translations, OWL components, website themes and tests.
    • Secure by default: access rights always included, record rules, safe SQL, escaped QWeb output, CSRF-aware controllers.
    • A built-in code check: 77 verified rules scan your module for its version and point to the exact file, line and fix for every outdated pattern.
    • No invented APIs: when something is uncertain for a version, the agent says so instead of guessing.

    What changes most — and what the skill handles

    17.018.019.020.0
    List view<tree><list><list><list>
    SQL constraints_sql_constraints_sql_constraintsmodels.Constraintmodels.Constraint
    Access rightsir.model.access.csvsamesame (group_ids)ir.access.csv
    System parametersget_paramget_paramget_paramget_str / get_int…
    Kanban card / chatterkanban-box / oe_chattercard / <chatter/>samesame
    OWL2223

    Verified, not guessed

    Every version-specific fact was checked on the official odoo/odoo source code of each version, and each version was exercised on real Odoo installations.

    What's inside

    • SKILL.md — the protocol your agent follows.
    • deltas/ — one file per version (17.0 → 20.0), authoritative over the general guides.
    • reference/ — 16 focused guides: ORM, fields, views, security, manifest, decorators, controllers, data, actions, reports, mixins, tests, transactions, translation, performance, OWL.
    • scripts/check_module.py — read-only code check (Python 3.8+, standard library only).

    Works with

    Any agent that supports the open SKILL.md standard: Claude Code, Codex CLI, Cursor, Gemini CLI and more. The code check needs an agent that can run shell commands; without it, the agent still applies every rule from the guides.

    Not included

    Migrating a module from one version to another, and server setup or deployment.

    Why this beats prompting it yourself

    A general-purpose agent mixes the conventions it has seen across Odoo versions: it writes <tree> on 18.0, _sql_constraints on 19.0 or get_param() on 20.0 — code that looks right and fails silently or at load time. This skill pins your agent to the version in your manifest, gives it one verified rule set per version, and checks the result before you install it.

    Known limitations

    • Odoo 17.0 to 20.0 only; older versions are refused rather than guessed.
    • No migration between versions, and no server setup or deployment.
    • The code check works on patterns: it can, rarely, flag a line that is correct in context — each finding explains why, so you can decide.
    • No Enterprise source code is included; Enterprise-only views and modules are not covered.

    How to use

    Skills on Agensi run inside your AI agent. Copy the prompt, paste it into your agent, and it connects to Agensi and installs this skill for you. If a payment is needed, your agent shows it to you first.

    Reviews

    No reviews yet

    Be one of the first to try it. Every listed skill passes our trust checks below.

    Security scanned

    Passed our 8-point scan before listing

    Fresh listing

    Recently published to Agensi

    Free forever

    No account required to browse

    Trust & safety

    Security scanned

    Verified clean today

    • Free to download with an account

    Requires

    Python

    Listedtoday

    What's inside

    Frequently Asked Questions