Creates complete Odoo 16.0 modules with proper structure, manifests, models, views, and security. This skill should be used when the user requests creation of a new Odoo module, such as "Create a new module for inventory tracking" or "I need a new POS customization module" or "Generate module structure for vendor management".
Creates complete Odoo 16.0 modules with proper structure, manifests, models, views, and security files. Use when user requests a new Odoo module like "Create inventory tracking module" or "I need a POS customization module".
/plugin marketplace add jamshu/jamshi-marketplace/plugin install odoo-dev@jamshi-marketplaceThis skill inherits all available tools. When active, it can use any tool Claude has access to.
assets/index.htmlThis skill enables creation of complete, production-ready Odoo 16.0 Enterprise modules with proper directory structure, manifest files, models, views, security configurations, and documentation. It follows OCA guidelines and Siafa project standards.
Ask clarifying questions to collect essential information:
stock_batch_tracking, pos_custom_receipt)stock, account, point_of_sale)addons-stock, addons-pos, addons-account)Identify which type of module to create based on the purpose:
A. Simple Model Module - CRUD operations for a new business entity
B. Extension Module - Extends existing Odoo models
C. POS Customization - Point of Sale enhancements
D. Stock/Inventory Enhancement - Warehouse and inventory features
E. Accounting Customization - Financial module extensions
F. Report Module - Custom reports (PDF, Excel)
G. Integration Module - External API/service connectors
H. Widget/UI Customization - Frontend enhancements
Create the complete directory structure with all required files:
module_name/
├── __init__.py
├── __manifest__.py
├── models/
│ ├── __init__.py
│ └── [model_files].py
├── views/
│ ├── [model]_views.xml
│ └── menu_views.xml
├── security/
│ ├── security_groups.xml (if needed)
│ └── ir.model.access.csv
├── data/ (optional)
│ └── data.xml
├── wizards/ (if needed)
│ ├── __init__.py
│ └── [wizard_name].py
├── report/ (if reports needed)
│ ├── __init__.py
│ ├── [report_name].py
│ └── templates/
│ └── [report_template].xml
├── static/
│ ├── description/
│ │ ├── icon.png
│ │ └── index.html
│ └── src/ (for JS/CSS if needed)
│ ├── js/
│ └── css/
└── tests/ (recommended)
├── __init__.py
└── test_[module].py
Create manifest with standard metadata:
{
'name': '[Module Display Name]',
'version': '16.0.1.0.0',
'category': '[Category]',
'summary': '[Brief one-line description]',
'description': """
[Detailed multi-line description of module functionality]
Key Features:
- Feature 1
- Feature 2
- Feature 3
""",
'author': 'Jamshid K',
'website': 'https://siafadates.com',
'license': 'LGPL-3',
'depends': [
'base',
# Additional dependencies
],
'data': [
'security/security_groups.xml', # Load first
'security/ir.model.access.csv',
'views/[model]_views.xml',
'views/menu_views.xml',
'data/data.xml', # If needed
'report/templates/[report].xml', # If needed
],
'assets': { # If JS/CSS needed
'web.assets_backend': [
'module_name/static/src/js/*.js',
'module_name/static/src/css/*.css',
],
},
'demo': [], # Demo data if applicable
'installable': True,
'auto_install': False,
'application': False, # True for standalone apps
}
Create model files following Odoo ORM best practices:
from odoo import models, fields, api
from odoo.exceptions import UserError, ValidationError
import logging
_logger = logging.getLogger(__name__)
class ModelName(models.Model):
"""Description of the model."""
_name = 'module.model'
_description = 'Model Description'
_inherit = ['mail.thread', 'mail.activity.mixin'] # If needed
_order = 'create_date desc'
# Fields
name = fields.Char(
string='Name',
required=True,
index=True,
tracking=True,
help='Primary identifier for this record'
)
active = fields.Boolean(
string='Active',
default=True,
help='If unchecked, this record will be hidden'
)
state = fields.Selection([
('draft', 'Draft'),
('confirmed', 'Confirmed'),
('done', 'Done'),
('cancel', 'Cancelled'),
], string='Status', default='draft', required=True, tracking=True)
company_id = fields.Many2one(
'res.company',
string='Company',
required=True,
default=lambda self: self.env.company
)
# Relational fields
partner_id = fields.Many2one('res.partner', string='Partner')
line_ids = fields.One2many('module.model.line', 'parent_id', string='Lines')
# Computed fields
total_amount = fields.Float(
string='Total Amount',
compute='_compute_total_amount',
store=True
)
# Constraints
_sql_constraints = [
('name_unique', 'UNIQUE(name, company_id)', 'Name must be unique per company!'),
]
@api.depends('line_ids', 'line_ids.amount')
def _compute_total_amount(self):
"""Compute total amount from lines."""
for record in self:
record.total_amount = sum(record.line_ids.mapped('amount'))
@api.onchange('partner_id')
def _onchange_partner_id(self):
"""Update fields when partner changes."""
if self.partner_id:
# Logic here
pass
@api.constrains('total_amount')
def _check_total_amount(self):
"""Validate total amount is positive."""
for record in self:
if record.total_amount < 0:
raise ValidationError('Total amount must be positive!')
def action_confirm(self):
"""Confirm the record."""
self.ensure_one()
if self.state != 'draft':
raise UserError('Only draft records can be confirmed!')
self.write({'state': 'confirmed'})
_logger.info('Record %s confirmed by user %s', self.name, self.env.user.name)
Create XML view definitions:
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Tree View -->
<record id="view_model_tree" model="ir.ui.view">
<field name="name">module.model.tree</field>
<field name="model">module.model</field>
<field name="arch" type="xml">
<tree string="Model Name">
<field name="name"/>
<field name="partner_id"/>
<field name="state" decoration-info="state == 'draft'"
decoration-success="state == 'done'"/>
<field name="total_amount" sum="Total"/>
<field name="company_id" groups="base.group_multi_company"/>
</tree>
</field>
</record>
<!-- Form View -->
<record id="view_model_form" model="ir.ui.view">
<field name="name">module.model.form</field>
<field name="model">module.model</field>
<field name="arch" type="xml">
<form string="Model Name">
<header>
<button name="action_confirm" string="Confirm" type="object"
class="oe_highlight" states="draft"/>
<field name="state" widget="statusbar"
statusbar_visible="draft,confirmed,done"/>
</header>
<sheet>
<div class="oe_title">
<h1>
<field name="name" placeholder="Name..."/>
</h1>
</div>
<group>
<group>
<field name="partner_id"/>
<field name="company_id" groups="base.group_multi_company"/>
</group>
<group>
<field name="total_amount"/>
<field name="active"/>
</group>
</group>
<notebook>
<page string="Lines">
<field name="line_ids">
<tree editable="bottom">
<field name="name"/>
<field name="amount"/>
</tree>
</field>
</page>
</notebook>
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids"/>
<field name="activity_ids"/>
<field name="message_ids"/>
</div>
</form>
</field>
</record>
<!-- Search View -->
<record id="view_model_search" model="ir.ui.view">
<field name="name">module.model.search</field>
<field name="model">module.model</field>
<field name="arch" type="xml">
<search string="Search Model">
<field name="name"/>
<field name="partner_id"/>
<filter string="Draft" name="draft" domain="[('state', '=', 'draft')]"/>
<filter string="Done" name="done" domain="[('state', '=', 'done')]"/>
<separator/>
<filter string="Archived" name="inactive" domain="[('active', '=', False)]"/>
<group expand="0" string="Group By">
<filter string="Partner" name="partner" context="{'group_by': 'partner_id'}"/>
<filter string="Status" name="state" context="{'group_by': 'state'}"/>
</group>
</search>
</field>
</record>
<!-- Action -->
<record id="action_model" model="ir.actions.act_window">
<field name="name">Model Name</field>
<field name="res_model">module.model</field>
<field name="view_mode">tree,form</field>
<field name="context">{}</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Create your first record!
</p>
<p>
Click the create button to add a new record.
</p>
</field>
</record>
</odoo>
Create security groups (if needed):
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="module_category" model="ir.module.category">
<field name="name">Module Category</field>
<field name="sequence">100</field>
</record>
<record id="group_user" model="res.groups">
<field name="name">User</field>
<field name="category_id" ref="module_category"/>
<field name="implied_ids" eval="[(4, ref('base.group_user'))]"/>
</record>
<record id="group_manager" model="res.groups">
<field name="name">Manager</field>
<field name="category_id" ref="module_category"/>
<field name="implied_ids" eval="[(4, ref('group_user'))]"/>
</record>
</odoo>
Create access rights CSV:
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_model_user,module.model.user,model_module_model,group_user,1,1,1,0
access_model_manager,module.model.manager,model_module_model,group_manager,1,1,1,1
Use the odoo-test-creator skill to create comprehensive test suites for the module. The odoo-test-creator skill provides:
To create tests with the odoo-test-creator skill, simply invoke:
Use the odoo-test-creator skill to create tests for [module_name]
The skill will:
Quick Test Example (if not using the skill):
from odoo.tests.common import TransactionCase
from odoo.exceptions import UserError
class TestModel(TransactionCase):
"""Test cases for module.model"""
def setUp(self):
"""Set up test data"""
super().setUp()
self.Model = self.env['module.model']
# Use existing records when possible
self.partner = self.env['res.partner'].search([], limit=1)
if not self.partner:
self.skipTest("No partner available for testing")
def test_01_create_model(self):
"""Test creating a model record"""
record = self.Model.create({
'name': 'Test Record',
'partner_id': self.partner.id,
})
self.assertTrue(record)
self.assertEqual(record.state, 'draft')
def test_02_constraint_validation(self):
"""Test constraint validation"""
record = self.Model.create({
'name': 'Test Record',
'partner_id': self.partner.id,
})
with self.assertRaises(UserError) as context:
record.write({'invalid_field': 'invalid_value'})
self.assertIn('expected error', str(context.exception))
Important: For production modules, always use the odoo-test-creator skill to ensure comprehensive test coverage and proper handling of Siafa-specific constraints.
Follow these standards when generating module code:
Naming Conventions
snake_case (e.g., stock_batch_tracking)module.model (e.g., stock.batch.tracking)snake_case (e.g., batch_number, expiry_date)snake_case with verb prefix (e.g., action_confirm, _compute_total)view_model_type (e.g., view_batch_tracking_form)Import Order
# Standard library
import logging
from datetime import datetime
# Odoo imports
from odoo import models, fields, api, _
from odoo.exceptions import UserError, ValidationError
from odoo.tools import float_compare, float_is_zero
Field Attributes
string parameterhelp text for complex fieldstracking=True for important fieldsindex=True for searchable fieldscompany_id for multi-company supportMethod Decorators
@api.depends() for computed fields@api.onchange() for onchange methods@api.constrains() for validation@api.model for class-level methodsError Handling
UserError for user-facing errorsValidationError for constraint violations_loggerSecurity
Reference the assets/templates/ directory for complete templates by module type:
simple_model/ - Basic CRUD moduleextension/ - Inheriting existing modelspos_custom/ - POS customizationsstock_enhancement/ - Inventory featuresreport_module/ - Custom reportsContains complete module templates for different module types. Use these as starting points and customize based on specific requirements.
Default module icon. Replace with custom icon if needed (PNG, 128x128px recommended).
Module description HTML template for the Apps menu.
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.