feat: add 24 Odoo skills (development, functional, devops, integrations)

This commit is contained in:
Ramon Rios
2026-03-04 13:40:32 -07:00
parent 159d04c9bf
commit 76810e1c42
24 changed files with 2611 additions and 0 deletions

View File

@@ -0,0 +1,105 @@
---
name: odoo-accounting-setup
description: "Expert guide for configuring Odoo Accounting: chart of accounts, journals, fiscal positions, taxes, payment terms, and bank reconciliation."
risk: safe
source: "self"
---
# Odoo Accounting Setup
## Overview
This skill guides functional consultants and business owners through setting up Odoo Accounting correctly from scratch. It covers chart of accounts configuration, journal setup, tax rules, fiscal positions, payment terms, and the bank statement reconciliation workflow.
## When to Use This Skill
- Setting up a new Odoo instance for a company for the first time.
- Configuring multi-currency or multi-company accounting.
- Troubleshooting tax calculation or fiscal position mapping errors.
- Creating payment terms for installment billing (e.g., Net 30, 50% upfront).
## How It Works
1. **Activate**: Mention `@odoo-accounting-setup` and describe your accounting scenario.
2. **Configure**: Receive step-by-step Odoo menu navigation with exact field values.
3. **Validate**: Get a checklist to verify your setup is complete and correct.
## Examples
### Example 1: Create a Payment Term (Net 30 with 2% Early Pay Discount)
```text
Menu: Accounting → Configuration → Payment Terms → New
Name: Net 30 / 2% Early Pay Discount
Company: [Your Company]
Lines:
Line 1:
- Due Type: Percent
- Value: 100%
- Due: 30 days (full balance due in 30 days)
Early Payment Discount (Odoo 16+):
Discount %: 2
Discount Days: 10
Balance Sheet Accounts:
- Gain: 4900 Early Payment Discounts Granted
- Loss: 5900 Early Payment Discounts Received
```
> **Note (v16+):** Use the built-in **Early Payment Discount** field instead of the old split-line workaround. Odoo now posts the discount automatically when the customer pays within the discount window and generates correct accounting entries.
### Example 2: Fiscal Position for EU VAT (B2B Intra-Community)
```text
Menu: Accounting → Configuration → Fiscal Positions → New
Name: EU Intra-Community B2B
Auto-detection: ON
- Country Group: Europe
- VAT Required: YES (customer must have EU VAT number)
Tax Mapping:
Tax on Sales (21% VAT) → 0% Intra-Community VAT
Tax on Purchases → 0% Reverse Charge
Account Mapping:
(Leave empty unless your localization requires account remapping)
```
### Example 3: Reconciliation Model for Bank Fees
```text
Menu: Accounting → Configuration → Reconciliation Models → New
Name: Bank Fee Auto-Match
Type: Write-off
Matching Order: 1
Conditions:
- Label Contains: "BANK FEE" OR "SERVICE CHARGE"
- Amount Type: Amount is lower than: $50.00
Action:
- Account: 6200 Bank Charges
- Tax: None
- Analytic: Administrative
```
## Best Practices
-**Do:** Install your country's **localization module** first (`l10n_us`, `l10n_mx`, etc.) before manually creating accounts — it sets up the correct chart of accounts.
-**Do:** Use **Fiscal Positions** to automate B2B vs B2C tax switching — never change taxes manually on individual invoices.
-**Do:** Lock accounting periods (Accounting → Actions → Lock Dates) after month-end closing to prevent retroactive edits.
-**Do:** Use the **Early Payment Discount** feature (v16+) instead of splitting payment term lines for discount modelling.
-**Don't:** Delete journal entries — always reverse them with a credit note or the built-in reversal function.
-**Don't:** Mix personal and business transactions in the same journal.
-**Don't:** Create manual journal entries to fix bank reconciliation mismatches — use the reconciliation model workflow instead.
## Limitations
- Does not cover **multi-currency revaluation** or foreign exchange gain/loss accounting in depth.
- **Country-specific e-invoicing** (CFDI, FatturaPA, SAF-T) requires additional localization modules — use `@odoo-l10n-compliance` for those.
- Payroll accounting integration (salary journals, deduction accounts) is not covered here — use `@odoo-hr-payroll-setup`.
- Odoo Community Edition does not include the full **lock dates** feature; some controls are Enterprise-only.

View File

@@ -0,0 +1,124 @@
---
name: odoo-automated-tests
description: "Write and run Odoo automated tests using TransactionCase, HttpCase, and browser tour tests. Covers test data setup, mocking, and CI integration."
risk: safe
source: "self"
---
# Odoo Automated Tests
## Overview
Odoo has a built-in testing framework based on Python's `unittest`. This skill helps you write `TransactionCase` unit tests, `HttpCase` integration tests, and JavaScript tour tests. It also covers running tests in CI pipelines.
## When to Use This Skill
- Writing unit tests for a custom model's business logic.
- Creating an HTTP test to verify a controller endpoint.
- Debugging test failures in a CI pipeline.
- Setting up automated test execution with `--test-enable`.
## How It Works
1. **Activate**: Mention `@odoo-automated-tests` and describe the feature to test.
2. **Generate**: Get complete test class code with setup, teardown, and assertions.
3. **Run**: Get the exact `odoo` CLI command to execute your tests.
## Examples
### Example 1: TransactionCase Unit Test (Odoo 15+ pattern)
```python
# tests/test_hospital_patient.py
from odoo.tests.common import TransactionCase
from odoo.tests import tagged
from odoo.exceptions import ValidationError
@tagged('post_install', '-at_install')
class TestHospitalPatient(TransactionCase):
@classmethod
def setUpClass(cls):
# Use setUpClass for performance — runs once per class, not per test
super().setUpClass()
cls.Patient = cls.env['hospital.patient']
cls.doctor = cls.env['res.users'].browse(cls.env.uid)
def test_create_patient(self):
patient = self.Patient.create({
'name': 'John Doe',
'doctor_id': self.doctor.id,
})
self.assertEqual(patient.state, 'draft')
self.assertEqual(patient.name, 'John Doe')
def test_confirm_patient(self):
patient = self.Patient.create({'name': 'Jane Smith'})
patient.action_confirm()
self.assertEqual(patient.state, 'confirmed')
def test_empty_name_raises_error(self):
with self.assertRaises(ValidationError):
self.Patient.create({'name': ''})
def test_access_denied_for_other_user(self):
# Test security rules by running as a different user
other_user = self.env.ref('base.user_demo')
with self.assertRaises(Exception):
self.Patient.with_user(other_user).create({'name': 'Test'})
```
> **`setUpClass` vs `setUp`:** Use `setUpClass` (Odoo 15+) for shared test data. It runs once per class and is significantly faster than `setUp` which re-initializes for every single test method.
### Example 2: Run Tests via CLI
```bash
# Run all tests for a specific module
./odoo-bin --test-enable --stop-after-init -d my_database -u hospital_management
# Run only tests tagged with a specific tag
./odoo-bin --test-enable --stop-after-init -d my_database \
--test-tags hospital_management
# Run a specific test class
./odoo-bin --test-enable --stop-after-init -d my_database \
--test-tags /hospital_management:TestHospitalPatient
```
### Example 3: HttpCase for Controller Testing
```python
from odoo.tests.common import HttpCase
from odoo.tests import tagged
@tagged('post_install', '-at_install')
class TestPatientController(HttpCase):
def test_patient_page_authenticated(self):
# Authenticate as a user, not with hardcoded password
self.authenticate(self.env.user.login, self.env.user.login)
resp = self.url_open('/hospital/patients')
self.assertEqual(resp.status_code, 200)
def test_patient_page_redirects_unauthenticated(self):
# No authenticate() call = public/anonymous user
resp = self.url_open('/hospital/patients', allow_redirects=False)
self.assertIn(resp.status_code, [301, 302, 403])
```
## Best Practices
-**Do:** Use `setUpClass()` with `cls.env` instead of `setUp()` — it is dramatically faster for large test suites.
-**Do:** Use `@tagged('post_install', '-at_install')` to run tests after all modules are installed.
-**Do:** Test both the happy path and error conditions (`ValidationError`, `AccessError`, `UserError`).
-**Do:** Use `self.with_user(user)` to test access control without calling `sudo()`.
-**Don't:** Use a production database for tests — always use a dedicated test database.
-**Don't:** Rely on test execution order — each `TransactionCase` test is rolled back in isolation.
-**Don't:** Hardcode passwords in `HttpCase.authenticate()` — use `self.env.user.login` or a fixture user.
## Limitations
- **JavaScript tour tests** require a running browser (via `phantomjs` or `Chrome headless`) and a live Odoo server — not covered in depth here.
- `HttpCase` tests are significantly slower than `TransactionCase` — use them only for controller/route verification.
- Does not cover **mocking external services** (e.g., mocking an SMTP server or payment gateway in tests).
- Test isolation is at the **transaction level**, not database level — tests that commit data (e.g., via `cr.commit()`) can leak state between tests.

View File

@@ -0,0 +1,112 @@
---
name: odoo-backup-strategy
description: "Complete Odoo backup and restore strategy: database dumps, filestore backup, automated scheduling, cloud storage upload, and tested restore procedures."
risk: safe
source: "self"
---
# Odoo Backup Strategy
## Overview
A complete Odoo backup must include both the **PostgreSQL database** and the **filestore** (attachments, images). This skill covers manual and automated backup procedures, offsite storage, and the correct restore sequence to bring a down Odoo instance back online.
## When to Use This Skill
- Setting up a backup strategy for a production Odoo instance.
- Automating daily backups with shell scripts and cron.
- Restoring Odoo after a server failure or data corruption event.
- Diagnosing a failed backup or corrupt restore.
## How It Works
1. **Activate**: Mention `@odoo-backup-strategy` and describe your server environment.
2. **Generate**: Receive a complete backup script tailored to your setup.
3. **Restore**: Get step-by-step restore instructions for any failure scenario.
## Examples
### Example 1: Manual Database + Filestore Backup
```bash
#!/bin/bash
# backup_odoo.sh
DATE=$(date +%Y%m%d_%H%M%S)
DB_NAME="odoo"
DB_USER="odoo"
FILESTORE_PATH="/var/lib/odoo/.local/share/Odoo/filestore/$DB_NAME"
BACKUP_DIR="/backups/odoo"
mkdir -p "$BACKUP_DIR"
# Step 1: Dump the database
pg_dump -U $DB_USER -Fc $DB_NAME > "$BACKUP_DIR/db_$DATE.dump"
# Step 2: Archive the filestore
tar -czf "$BACKUP_DIR/filestore_$DATE.tar.gz" -C "$FILESTORE_PATH" .
echo "✅ Backup complete: db_$DATE.dump + filestore_$DATE.tar.gz"
```
### Example 2: Automate with Cron (daily at 2 AM)
```bash
# Run: crontab -e
# Add this line:
0 2 * * * /opt/scripts/backup_odoo.sh >> /var/log/odoo_backup.log 2>&1
```
### Example 3: Upload to S3 (after backup)
```bash
# Add to backup script after tar command:
aws s3 cp "$BACKUP_DIR/db_$DATE.dump" s3://my-odoo-backups/db/
aws s3 cp "$BACKUP_DIR/filestore_$DATE.tar.gz" s3://my-odoo-backups/filestore/
# Optional: Delete local backups older than 7 days
find "$BACKUP_DIR" -type f -mtime +7 -delete
```
### Example 4: Full Restore Procedure
```bash
# Step 1: Stop Odoo
docker compose stop odoo # or: systemctl stop odoo
# Step 2: Recreate and restore the database
# (--clean alone fails if the DB doesn't exist; drop and recreate first)
dropdb -U odoo odoo 2>/dev/null || true
createdb -U odoo odoo
pg_restore -U odoo -d odoo db_YYYYMMDD_HHMMSS.dump
# Step 3: Restore the filestore
FILESTORE=/var/lib/odoo/.local/share/Odoo/filestore/odoo
rm -rf "$FILESTORE"/*
tar -xzf filestore_YYYYMMDD_HHMMSS.tar.gz -C "$FILESTORE"/
# Step 4: Restart Odoo
docker compose start odoo
# Step 5: Verify — open Odoo in the browser and check:
# - Can you log in?
# - Are recent records visible?
# - Are file attachments loading?
```
## Best Practices
-**Do:** Test restores monthly in a staging environment — a backup you've never restored is not a backup.
-**Do:** Follow the **3-2-1 rule**: 3 copies, 2 different media types, 1 offsite copy (e.g., S3 or a remote server).
-**Do:** Back up **immediately before every Odoo upgrade** — this is your rollback point.
-**Do:** Verify backup integrity: `pg_restore --list backup.dump` should complete without errors.
-**Don't:** Back up only the database without the filestore — all attachments and images will be missing after a restore.
-**Don't:** Store backups on the same disk or same server as Odoo — a disk or server failure destroys both.
-**Don't:** Run `pg_restore --clean` against a non-existent database — always create the database first.
## Limitations
- Does not cover **Odoo.sh built-in backups** — Odoo.sh has its own backup system accessible from the dashboard.
- This script assumes a **single-database** Odoo setup. Multi-database instances require looping over all databases.
- Filestore path may differ between installations (Docker volume vs. bare-metal). Always verify the path with `odoo-bin shell` before running a restore.
- Large filestores (100GB+) may require incremental backup tools like `rsync` or `restic` rather than full `tar.gz` archives.

View File

@@ -0,0 +1,139 @@
---
name: odoo-docker-deployment
description: "Production-ready Docker and docker-compose setup for Odoo with PostgreSQL, persistent volumes, environment-based configuration, and Nginx reverse proxy."
risk: safe
source: "self"
---
# Odoo Docker Deployment
## Overview
This skill provides a complete, production-ready Docker setup for Odoo, including PostgreSQL, persistent file storage, environment variable configuration, and an optional Nginx reverse proxy with SSL. It covers both development and production configurations.
## When to Use This Skill
- Spinning up a local Odoo development environment with Docker.
- Deploying Odoo to a VPS or cloud server (AWS, DigitalOcean, etc.).
- Troubleshooting Odoo container startup failures or database connection errors.
- Adding a reverse proxy with SSL to an existing Odoo Docker setup.
## How It Works
1. **Activate**: Mention `@odoo-docker-deployment` and describe your deployment scenario.
2. **Generate**: Receive a complete `docker-compose.yml` and `odoo.conf` ready to run.
3. **Debug**: Describe your container error and get a diagnosis with a fix.
## Examples
### Example 1: Production docker-compose.yml
```yaml
# Note: The top-level 'version' key is deprecated in Docker Compose v2+
# and can be safely omitted. Remove it to avoid warnings.
services:
db:
image: postgres:15
restart: always
environment:
POSTGRES_DB: odoo
POSTGRES_USER: odoo
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- postgres-data:/var/lib/postgresql/data
networks:
- odoo-net
odoo:
image: odoo:17.0
restart: always
depends_on:
db:
condition: service_healthy
ports:
- "8069:8069"
- "8072:8072" # Longpolling for live chat / bus
environment:
HOST: db
USER: odoo
PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- odoo-web-data:/var/lib/odoo
- ./addons:/mnt/extra-addons # Custom modules
- ./odoo.conf:/etc/odoo/odoo.conf
networks:
- odoo-net
volumes:
postgres-data:
odoo-web-data:
networks:
odoo-net:
```
### Example 2: odoo.conf
```ini
[options]
admin_passwd = ${ODOO_MASTER_PASSWORD} ; set via env or .env file
db_host = db
db_port = 5432
db_user = odoo
db_password = ${POSTGRES_PASSWORD} ; set via env or .env file
; addons_path inside the official Odoo Docker image (Debian-based)
addons_path = /mnt/extra-addons,/usr/lib/python3/dist-packages/odoo/addons
logfile = /var/log/odoo/odoo.log
log_level = warn
; Worker tuning for a 4-core / 8GB server:
workers = 9 ; (CPU cores × 2) + 1
max_cron_threads = 2
limit_memory_soft = 1610612736 ; 1.5 GB — soft kill threshold
limit_memory_hard = 2147483648 ; 2.0 GB — hard kill threshold
limit_time_cpu = 600
limit_time_real = 1200
limit_request = 8192
```
### Example 3: Common Commands
```bash
# Start all services in background
docker compose up -d
# Stream Odoo logs in real time
docker compose logs -f odoo
# Restart Odoo only (not DB — avoids data risk)
docker compose restart odoo
# Stop all services
docker compose down
# Backup the database to a local SQL dump
docker compose exec db pg_dump -U odoo odoo > backup_$(date +%Y%m%d).sql
# Update a custom module without restarting the server
docker compose exec odoo odoo -d odoo --update my_module --stop-after-init
```
## Best Practices
-**Do:** Store all secrets in a `.env` file and reference them with `${VAR}` — never hardcode passwords in `docker-compose.yml`.
-**Do:** Use `depends_on: condition: service_healthy` with a PostgreSQL healthcheck to prevent Odoo starting before the DB is ready.
-**Do:** Put Nginx in front of Odoo for SSL termination (Let's Encrypt / Certbot) — never expose Odoo directly on port 80/443.
-**Do:** Set `workers = (CPU cores × 2) + 1` in `odoo.conf``workers = 0` uses single-threaded mode and blocks all users.
-**Don't:** Expose port 5432 (PostgreSQL) to the public internet — keep it on the internal Docker network only.
-**Don't:** Use the `latest` or `17` Docker image tags in production — always pin to a specific patch-level tag (e.g., `odoo:17.0`).
-**Don't:** Mount `odoo.conf` and rely on it for secrets in CI/CD — use Docker secrets or environment variables instead.
## Limitations
- This skill covers **self-hosted Docker deployments** — Odoo.sh (cloud-managed hosting) has a completely different deployment model.
- **Horizontal scaling** (multiple Odoo containers behind a load balancer) requires shared filestore (NFS or S3-compatible storage) not covered here.
- Does not include an Nginx configuration template — consult the [official Odoo Nginx docs](https://www.odoo.com/documentation/17.0/administration/install/deploy.html) for the full reverse proxy config.
- The `addons_path` inside the Docker image may change with new base image versions — always verify after upgrading the Odoo image.

View File

@@ -0,0 +1,133 @@
---
name: odoo-ecommerce-configurator
description: "Expert guide for Odoo eCommerce and Website: product catalog, payment providers, shipping methods, SEO, and order-to-fulfillment workflow."
risk: safe
source: "self"
---
# Odoo eCommerce Configurator
## Overview
This skill helps you set up and optimize an Odoo-powered online store. It covers product publishing, payment gateway integration, shipping carrier configuration, cart and checkout customization, and the workflow from online order to warehouse fulfillment.
## When to Use This Skill
- Launching an Odoo eCommerce store for the first time.
- Integrating a payment provider (Stripe, PayPal, Adyen).
- Configuring shipping rates with carrier integration (UPS, FedEx, DHL).
- Optimizing product pages for SEO with Odoo Website tools.
## How It Works
1. **Activate**: Mention `@odoo-ecommerce-configurator` and describe your store scenario.
2. **Configure**: Receive step-by-step Odoo eCommerce setup with menu paths.
3. **Optimize**: Get SEO, conversion, and catalog best practices.
## Examples
### Example 1: Publish a Product to the Website
```text
Menu: Website → eCommerce → Products → Select Product
Fields to complete for a great product listing:
Name: Ergonomic Mesh Office Chair (keyword-rich)
Internal Reference: CHAIR-MESH-001 (required for inventory)
Sales Price: $299.00
Website Description (website tab): 150300 words of unique content
Publishing:
Toggle "Published" in the top-right corner of the product form
or via: Website → Go to Website → Toggle "Published" button
SEO (website tab → SEO section):
Page Title: Ergonomic Mesh Chair | Office Chairs | YourStore
Meta Description: Discover the most comfortable ergonomic mesh office
chair, designed for all-day support... (≤160 chars)
Website tab:
Can be Sold: YES
Website: yourstore.com (if running multiple websites)
```
### Example 2: Configure Stripe Payment Provider
```text
Menu: Website → Configuration → Payment Providers → Stripe → Configure
(or: Accounting → Configuration → Payment Providers → Stripe)
State: Test (use Test mode until fully validated, then switch to Enabled)
Credentials (from your Stripe Dashboard → Developers → API Keys):
Publishable Key: pk_live_XXXXXXXX
Secret Key: sk_live_XXXXXXXX (store securely; never expose client-side)
Payment Journal: Bank (USD)
Capture Mode: Automatic (charge card immediately on order confirmation)
or Manual (authorize only; charge later on fulfillment)
Webhook:
Add Odoo's webhook URL in Stripe Dashboard → Webhooks
URL: https://yourstore.com/payment/stripe/webhook
Events: payment_intent.succeeded, payment_intent.payment_failed
```
### Example 3: Set Up Flat Rate Shipping with Free Threshold
```text
Menu: Inventory → Configuration → Delivery Methods → New
Name: Standard Shipping (35 business days)
Provider: Fixed Price
Delivery Product: [Shipping] Standard (used for invoicing)
Pricing:
Price: $9.99
☑ Free if order amount is above: $75.00
Availability:
Countries: United States
States: All states
Publish to website:
☑ Published (visible to customers at checkout)
```
### Example 4: Set Up Abandoned Cart Recovery
```text
Menu: Email Marketing → Mailing Lists → (create a list if needed)
For automated abandoned cart emails in Odoo 16/17:
Menu: Marketing → Marketing Automation → New Campaign
Trigger: Odoo record updated
Model: eCommerce Cart (sale.order with state = 'draft')
Filter: Cart not updated in 1 hour AND not confirmed
Actions:
1. Wait 1 hour
2. Send Email: "You left something behind!" (use a recovery email template)
3. Wait 24 hours
4. Send Email: "Last chance — items selling fast"
Note: Some Odoo hosting plans may require "Email Marketing" app enabled.
```
## Best Practices
-**Do:** Use **Product Variants** (color, size) instead of duplicate products — cleaner catalog and shared inventory tracking.
-**Do:** Enable **HTTPS** (SSL certificate) via your hosting provider and set HSTS in Website → Settings → Security.
-**Do:** Set up **Abandoned Cart Recovery** using Marketing Automation or a scheduled email sequence.
-**Do:** Add a **Stripe webhook** so Odoo is notified of payment events in real time — without it, failed payments may not update correctly.
-**Don't:** Leave the payment provider in **Test mode** in production — no real charges will be processed.
-**Don't:** Publish products without an **Internal Reference (SKU)** — it breaks inventory tracking and order fulfillment.
-**Don't:** Use the same Stripe key for Test and Production environments — always rotate to live keys before going live.
## Limitations
- **Carrier integration** (live UPS/FedEx rate calculation) requires the specific carrier connector module (e.g., `delivery_ups`) and a carrier account API key.
- Does not cover **multi-website** configuration — running separate storefronts with different pricelists and languages requires Enterprise.
- **B2B eCommerce** (customer login required, custom catalog and prices per customer) has additional configuration steps not fully covered here.
- Odoo eCommerce does not support **subscription billing** natively — that requires the Enterprise **Subscriptions** module.

View File

@@ -0,0 +1,111 @@
---
name: odoo-edi-connector
description: "Guide for implementing EDI (Electronic Data Interchange) with Odoo: X12, EDIFACT document mapping, partner onboarding, and automated order processing."
---
# Odoo EDI Connector
## Overview
Electronic Data Interchange (EDI) is the standard for automated B2B document exchange — purchase orders, invoices, ASNs (Advance Shipping Notices). This skill guides you through mapping EDI transactions (ANSI X12 or EDIFACT) to Odoo business objects, setting up trading partner configurations, and automating inbound/outbound document flows.
## When to Use This Skill
- A retail partner requires EDI 850 (Purchase Orders) to do business with you.
- You need to send EDI 856 (ASN) when goods are shipped.
- Automating EDI 810 (Invoice) generation from Odoo confirmed deliveries.
- Mapping EDI fields to Odoo fields for a new trading partner.
## How It Works
1. **Activate**: Mention `@odoo-edi-connector` and specify the EDI transaction set and trading partner.
2. **Map**: Receive a complete field mapping table between EDI segments and Odoo fields.
3. **Automate**: Get Python code to parse incoming EDI files and create Odoo records.
## EDI ↔ Odoo Object Mapping
| EDI Transaction | Odoo Object |
|---|---|
| 850 Purchase Order | `sale.order` (inbound customer PO) |
| 855 PO Acknowledgment | Confirmation email / SO confirmation |
| 856 ASN (Advance Ship Notice) | `stock.picking` (delivery order) |
| 810 Invoice | `account.move` (customer invoice) |
| 846 Inventory Inquiry | `product.product` stock levels |
| 997 Functional Acknowledgment | Automated receipt confirmation |
## Examples
### Example 1: Parse EDI 850 and Create Odoo Sale Order (Python)
```python
from pyx12 import x12file # pip install pyx12
import xmlrpc.client
odoo_url = "https://myodoo.example.com"
db, uid, pwd = "my_db", 2, "api_key"
models = xmlrpc.client.ServerProxy(f"{odoo_url}/xmlrpc/2/object")
def process_850(edi_file_path):
"""Parse X12 850 Purchase Order and create Odoo Sale Order"""
with x12file.X12File(edi_file_path) as f:
for transaction in f.get_transaction_sets():
# Extract header info (BEG segment)
po_number = transaction['BEG'][3] # Purchase Order Number
po_date = transaction['BEG'][5] # Purchase Order Date
# Extract partner (N1 segment — Buyer)
partner_name = transaction['N1'][2]
# Find partner in Odoo
partner = models.execute_kw(db, uid, pwd, 'res.partner', 'search',
[[['name', 'ilike', partner_name]]])
partner_id = partner[0] if partner else False
# Extract line items (PO1 segments)
order_lines = []
for po1 in transaction.get_segments('PO1'):
sku = po1[7] # Product ID
qty = float(po1[2])
price = float(po1[4])
product = models.execute_kw(db, uid, pwd, 'product.product', 'search',
[[['default_code', '=', sku]]])
if product:
order_lines.append((0, 0, {
'product_id': product[0],
'product_uom_qty': qty,
'price_unit': price,
}))
# Create Sale Order
if partner_id and order_lines:
models.execute_kw(db, uid, pwd, 'sale.order', 'create', [{
'partner_id': partner_id,
'client_order_ref': po_number,
'order_line': order_lines,
}])
```
### Example 2: Send EDI 997 Acknowledgment
```python
def generate_997(isa_control, gs_control, transaction_control):
"""Generate a functional acknowledgment for received EDI"""
return f"""ISA*00* *00* *ZZ*YOURISAID *ZZ*PARTNERISAID *{today}*1200*^*00501*{isa_control}*0*P*>~
GS*FA*YOURGID*PARTNERGID*{today}*1200*{gs_control}*X*005010X231A1~
ST*997*0001~
AK1*PO*{gs_control}~
AK9*A*1*1*1~
SE*4*0001~
GE*1*{gs_control}~
IEA*1*{isa_control}~"""
```
## Best Practices
-**Do:** Store every raw EDI transaction in an audit log table before processing.
-**Do:** Always send a **997 Functional Acknowledgment** within 24 hours of receiving a transaction.
-**Do:** Negotiate a test cycle with trading partners before going live — use test ISA qualifier `T`.
-**Don't:** Process EDI files synchronously in web requests — queue them for async processing.
-**Don't:** Hardcode trading partner qualifiers — store them in a configuration table per partner.

View File

@@ -0,0 +1,106 @@
---
name: odoo-hr-payroll-setup
description: "Expert guide for Odoo HR and Payroll: salary structures, payslip rules, leave policies, employee contracts, and payroll journal entries."
risk: safe
source: "self"
---
# Odoo HR & Payroll Setup
## Overview
This skill guides HR managers and payroll accountants through setting up Odoo HR and Payroll correctly. It covers salary structure creation with Python-computed rules, time-off policies, employee contract types, and the payroll → accounting journal posting flow.
## When to Use This Skill
- Creating a salary structure with gross pay, deductions, and net pay.
- Configuring annual leave, sick leave, and public holiday policies.
- Troubleshooting incorrect payslip amounts or missing rule contributions.
- Setting up the payroll journal to correctly post to accounting.
## How It Works
1. **Activate**: Mention `@odoo-hr-payroll-setup` and describe your payroll scenario.
2. **Configure**: Receive step-by-step setup for salary rules and leave allocation.
3. **Debug**: Paste a salary rule or payslip issue and receive a root cause analysis.
## Examples
### Example 1: Salary Structure with Deductions
```text
Menu: Payroll → Configuration → Salary Structures → New
Name: US Employee Monthly
Payslip Code: MONTHLY
Rules (executed top-to-bottom — order matters):
Code | Name | Formula | Category
----- | ---------------------- | ------------------------------ | ---------
BASIC | Basic Wage | contract.wage | Basic
GROSS | Gross | BASIC | Gross
SS | Social Security (6.2%) | -GROSS * 0.062 | Deduction
MED | Medicare (1.45%) | -GROSS * 0.0145 | Deduction
FIT | Federal Income Tax | -GROSS * inputs.FIT_RATE.amount| Deduction
NET | Net Salary | GROSS + SS + MED + FIT | Net
```
> **Federal Income Tax:** The standard Odoo US localization does not expose a single `l10n_us_w4_rate` field. Use an **input** (salary input type) to pass the withholding rate per employee, or install a community US payroll module (OCA `l10n_us_hr_payroll`) which handles W4 filing status properly.
### Example 2: Configure a Time Off Type
```text
Menu: Time Off → Configuration → Time Off Types → New
Name: Annual Leave / PTO
Approval: Time Off Officer
Leave Validation: Time Off Officer (single approver)
or: "Both" for HR + Manager double approval
Allocation:
☑ Employees can allocate time off themselves
Requires approval: No
Negative Balance: Not allowed (employees cannot go negative)
Then create initial allocations:
Menu: Time Off → Managers → Allocations → New
Employee: [Each employee]
Time Off Type: Annual Leave / PTO
Allocation: 15 days
Validity: Jan 1 Dec 31 [current year]
```
### Example 3: Payroll Journal Entry Result
```text
After validating a payroll batch, Odoo generates:
Debit Salary Expense Account $5,000.00
Credit Social Security Payable $310.00
Credit Medicare Payable $72.50
Credit Federal Tax Payable (varies)
Credit Salary Payable $4,617.50+
When net salary is paid:
Debit Salary Payable $4,617.50
Credit Bank Account $4,617.50
Employer taxes (e.g., FUTA, SUTA) post as separate journal entries.
```
## Best Practices
-**Do:** Install your country's **payroll localization** (`l10n_us_hr_payroll`, `l10n_mx_hr_payroll`, etc.) before building custom rules — it provides pre-configured tax structures.
-**Do:** Use **salary rule inputs** (`inputs.ALLOWANCE.amount`) to pass variable values (bonuses, allowances, withholding rates) rather than hardcoding them in the rule formula.
-**Do:** Archive old salary structures rather than deleting them — active payslips reference their structure and will break if the structure is deleted.
-**Do:** Always set an active **Employee Contract** with correct dates and salary before generating payslips.
-**Don't:** Manually edit posted payslips — cancel and regenerate the payslip batch if corrections are needed.
-**Don't:** Use `contract.wage` in deduction rules without verifying whether the structure is monthly or annual — always check the contract wage period.
## Limitations
- **Odoo Payroll is Enterprise-only** — the Community Edition does not include the Payroll module (`hr_payroll`).
- US-specific compliance (W2, 941, state SUI/SDI filing) requires additional modules beyond the base localization; Odoo does not generate tax filings directly.
- Does not cover **multi-country payroll** (employees in different countries require separate structures and localizations).
- **Expense reimbursements** via payslip (e.g., mileage, home office) require a custom salary rule input and are not covered in standard HR Payroll documentation.

View File

@@ -0,0 +1,111 @@
---
name: odoo-inventory-optimizer
description: "Expert guide for Odoo Inventory: stock valuation (FIFO/AVCO), reordering rules, putaway strategies, routes, and multi-warehouse configuration."
risk: safe
source: "self"
---
# Odoo Inventory Optimizer
## Overview
This skill helps you configure and optimize Odoo Inventory for accuracy, efficiency, and traceability. It covers stock valuation methods, reordering rules, putaway strategies, warehouse routes, and multi-step flows (receive → quality → store).
## When to Use This Skill
- Choosing and configuring FIFO vs AVCO stock valuation.
- Setting up minimum stock reordering rules to avoid stockouts.
- Designing a multi-step warehouse flow (2-step receipt, 3-step delivery).
- Configuring putaway rules to direct products to specific storage locations.
- Troubleshooting negative stock, incorrect valuation, or missing moves.
## How It Works
1. **Activate**: Mention `@odoo-inventory-optimizer` and describe your warehouse scenario.
2. **Configure**: Receive step-by-step configuration instructions with exact Odoo menu paths.
3. **Optimize**: Get recommendations for reordering rules and stock accuracy improvements.
## Examples
### Example 1: Enable FIFO Stock Valuation
```text
Menu: Inventory → Configuration → Settings
Enable: Storage Locations
Enable: Multi-Step Routes
Costing Method: (set per Product Category, not globally)
Menu: Inventory → Configuration → Product Categories → Edit
Category: All / Physical Goods
Costing Method: First In First Out (FIFO)
Inventory Valuation: Automated
Account Stock Valuation: [Balance Sheet inventory account]
Account Stock Input: [Stock Received Not Billed]
Account Stock Output: [Stock Delivered Not Invoiced]
```
### Example 2: Set Up a Min/Max Reordering Rule
```text
Menu: Inventory → Operations → Replenishment → New
Product: Office Paper A4
Location: WH/Stock
Min Qty: 100 (trigger reorder when stock falls below this)
Max Qty: 500 (purchase up to this quantity)
Multiple Qty: 50 (always order in multiples of 50)
Route: Buy (triggers a Purchase Order automatically)
or Manufacture (triggers a Manufacturing Order)
```
### Example 3: Configure Putaway Rules
```text
Menu: Inventory → Configuration → Putaway Rules → New
Purpose: Direct products from WH/Input to specific bin locations
Rules:
Product Category: Refrigerated Goods
→ Location: WH/Stock/Cold Storage
Product: Laptop Model X
→ Location: WH/Stock/Electronics/Shelf A
(leave Product blank to apply the rule to an entire category)
Result: When a receipt is validated, Odoo automatically suggests
the correct destination location per product or category.
```
### Example 4: Configure 3-Step Warehouse Delivery
```text
Menu: Inventory → Configuration → Warehouses → [Your Warehouse]
Outgoing Shipments: Pick + Pack + Ship (3 steps)
Operations created automatically:
PICK — Move goods from storage shelf to packing area
PACK — Package items and print shipping label
OUT — Hand off to carrier / mark as shipped
```
## Best Practices
-**Do:** Use **Lots/Serial Numbers** for high-value or regulated items (medical devices, electronics).
-**Do:** Run a **physical inventory adjustment** at least quarterly (Inventory → Operations → Physical Inventory) to correct drift.
-**Do:** Set reordering rules on fast-moving items so purchase orders are generated automatically.
-**Do:** Enable **Putaway Rules** on warehouses with multiple storage zones — it eliminates manual location selection errors.
-**Don't:** Switch stock valuation method (FIFO ↔ AVCO) after recording transactions — it produces incorrect historical cost data.
-**Don't:** Use "Update Quantity" to fix stock errors — always use Inventory Adjustments to maintain a proper audit trail.
-**Don't:** Mix product categories with different costing methods in the same storage location without understanding the valuation impact.
## Limitations
- **Serial number tracking** at the individual unit level (SN per line) adds significant UI overhead; test performance with large volumes before enabling.
- Does not cover **landed costs** (import duties, freight allocation to product cost) — that requires the `stock_landed_costs` module.
- **Cross-warehouse stock transfers** have routing complexities (transit locations, intercompany invoicing) not fully covered here.
- Automated inventory valuation requires the **Accounting** module; Community Edition installations without it cannot post stock journal entries.

View File

@@ -0,0 +1,101 @@
---
name: odoo-l10n-compliance
description: "Country-specific Odoo localization: tax configuration, e-invoicing (CFDI, FatturaPA, SAF-T), fiscal reporting, and country chart of accounts setup."
---
# Odoo Localization & Compliance (l10n)
## Overview
Odoo provides localization modules (`l10n_*`) for 80+ countries that configure the correct chart of accounts, tax types, and fiscal reporting. This skill helps you install and configure the right localization, set up country-specific e-invoicing (Mexico CFDI, Italy FatturaPA, Poland SAF-T), and ensure fiscal compliance.
## When to Use This Skill
- Setting up Odoo for a company in a specific country (Mexico, Italy, Spain, US, etc.).
- Configuring country-required e-invoicing (electronic invoice submission to tax authorities).
- Setting up VAT/GST/IVA tax rules with correct fiscal positions.
- Generating required fiscal reports (VAT return, SAF-T, DIAN report).
## How It Works
1. **Activate**: Mention `@odoo-l10n-compliance` and specify your country and Odoo version.
2. **Install**: Get the exact localization module and configuration steps.
3. **Configure**: Receive tax code setup, fiscal position rules, and reporting guidance.
## Country Localization Modules
| Country | Module | Key Features |
|---|---|---|
| 🇺🇸 USA | `l10n_us` | GAAP CoA, Payroll (ADP bridge), 1099 reporting |
| 🇲🇽 Mexico | `l10n_mx_edi` | CFDI 4.0 e-invoicing, SAT integration, IEPS tax |
| 🇪🇸 Spain | `l10n_es` | SII real-time VAT, Modelo 303/390, AEAT |
| 🇮🇹 Italy | `l10n_it_edi` | FatturaPA XML, SDI submission, reverse charge |
| 🇵🇱 Poland | `l10n_pl` | SAF-T JPK_FA, VAT-7 return |
| 🇧🇷 Brazil | `l10n_br` | NF-e, NFS-e, SPED, ICMS/PIS/COFINS |
| 🇩🇪 Germany | `l10n_de` | SKR03/SKR04 CoA, DATEV export, UStVA |
| 🇨🇴 Colombia | `l10n_co_edi` | DIAN e-invoicing, UBL 2.1 |
## Examples
### Example 1: Configure Mexico CFDI 4.0
```
Step 1: Install module
Apps → Search "Mexico" → Install "Mexico - Accounting"
Also install: "Mexico - Electronic Invoicing" (l10n_mx_edi)
Step 2: Configure Company
Settings → Company → [Your Company]
Country: Mexico
RFC: Your RFC number (tax ID)
Company Type: Moral Person or Physical Person
Step 3: Upload SAT Certificates
Accounting → Configuration → Certificates → New
CSD Certificate (.cer file from SAT)
Private Key (.key file from SAT)
Password: Your FIEL password
Step 4: Issue a CFDI Invoice
Create invoice → Confirm → CFDI XML generated automatically
Sent to SAT → Receive UUID (folio fiscal)
PDF includes QR code + UUID for buyer verification
```
### Example 2: EU Intra-Community VAT Setup (Any EU Country)
```
Menu: Accounting → Configuration → Taxes → New
Tax Name: EU Intra-Community Sales (0%)
Tax Type: Sales
Tax Scope: Services or Goods
Tax Computation: Fixed
Amount: 0%
Tax Group: Intra-Community
Label on Invoice: "Intra-Community Supply - VAT Exempt per Art. 138 VAT Directive"
Fiscal Position (created separately):
Name: EU B2B Intra-Community
Auto-detect: Country Group = Europe + VAT Required = YES
Tax Mapping: Standard VAT Rate → 0% Intra-Community
```
### Example 3: Install and Validate a Localization
```bash
# Install via CLI (if module not in Apps)
./odoo-bin -d mydb --stop-after-init -i l10n_mx_edi
# Verify in Odoo:
# Apps → Installed → Search "l10n_mx" → Should show as Installed
```
## Best Practices
-**Do:** Install the localization module **before** creating any accounting entries — it sets up the correct accounts.
-**Do:** Use **Fiscal Positions** to automate tax switching for international customers (B2B vs B2C, domestic vs export).
-**Do:** Test e-invoicing in the **SAT/tax authority test environment** before going live.
-**Don't:** Manually create a chart of accounts if a localization module exists for your country.
-**Don't:** Mix localization tax accounts with custom accounts — it breaks fiscal reports.

View File

@@ -0,0 +1,99 @@
---
name: odoo-manufacturing-advisor
description: "Expert guide for Odoo Manufacturing: Bills of Materials (BoM), Work Centers, routings, MRP planning, and production order workflows."
risk: safe
source: "self"
---
# Odoo Manufacturing Advisor
## Overview
This skill helps you configure and optimize Odoo Manufacturing (MRP). It covers Bills of Materials (BoM), Work Centers, routing operations, production order lifecycle, and Material Requirements Planning (MRP) runs to ensure you never run short of materials.
## When to Use This Skill
- Creating or structuring Bills of Materials for finished goods.
- Setting up Work Centers with capacity and efficiency settings.
- Running an MRP to automatically generate purchase and production orders from demand.
- Troubleshooting production order discrepancies or component availability issues.
## How It Works
1. **Activate**: Mention `@odoo-manufacturing-advisor` and describe your manufacturing scenario.
2. **Configure**: Receive step-by-step instructions for BoM setup, routing, and MRP configuration.
3. **Plan**: Get guidance on running MRP and interpreting procurement messages.
## Examples
### Example 1: Create a Bill of Materials
```text
Menu: Manufacturing → Products → Bills of Materials → New
Product: Finished Widget v2
BoM Type: Manufacture This Product
Quantity: 1 (produce 1 unit per BoM)
Components Tab:
- Raw Plastic Sheet | Qty: 0.5 | Unit: kg
- Steel Bolt M6 | Qty: 4 | Unit: Units
- Rubber Gasket | Qty: 1 | Unit: Units
Operations Tab (requires "Work Orders" enabled in MFG Settings):
- Operation: Injection Molding | Work Center: Press A | Duration: 30 min
- Operation: Assembly | Work Center: Line 1 | Duration: 15 min
```
> **BoM Types explained:**
>
> - **Manufacture This Product** — standard production BoM, creates a Manufacturing Order
> - **Kit** — sold as a bundle; components are delivered separately (no MO created)
> - **Subcontracting** — components are sent to a subcontractor who returns the finished product
### Example 2: Configure a Work Center
```text
Menu: Manufacturing → Configuration → Work Centers → New
Work Center: CNC Machine 1
Working Hours: Standard 40h/week
Time Efficiency: 85% (machine downtime factored in; 85% = 34 effective hrs/week)
Capacity: 2 (can run 2 production operations simultaneously)
OEE Target: 90% (Overall Equipment Effectiveness KPI target)
Costs per Hour: $75.00 (used for manufacturing cost reporting)
```
### Example 3: Run the MRP Scheduler
```text
The MRP scheduler runs automatically via a daily cron job.
To trigger it manually:
Menu: Inventory → Operations → Replenishment → Run Scheduler
(or Manufacturing → Planning → Replenishment in some versions)
After running, review procurement exceptions:
Menu: Inventory → Operations → Replenishment
Message Types:
"Replenish" — Stock is below minimum; needs a PO or MO
"Reschedule" — An order's scheduled date conflicts with demand
"Cancel" — Demand no longer exists; the order can be cancelled
```
## Best Practices
-**Do:** Enable **Work Orders** in Manufacturing Settings to use routing and time-tracking per operation.
-**Do:** Use **BoM with variants** (via product attributes) for products that come in multiple configurations (color, size, voltage) — avoids duplicate BoMs.
-**Do:** Set **Lead Times** on components (vendor lead time + security lead time) so MRP schedules purchase orders in advance.
-**Do:** Use **Scrap Orders** when discarding defective components during production — never adjust stock manually.
-**Don't:** Manually create purchase orders for MRP-managed items — override MRP suggestions only when justified.
-**Don't:** Confuse **Kit** BoM with **Manufacture This Product** — a Kit never creates a Manufacturing Order.
## Limitations
- This skill targets **Odoo Manufacturing (mrp)** module. **Maintenance**, **PLM** (Product Lifecycle Management), and **Quality** modules are separate Enterprise modules not covered here.
- **Subcontracting** workflows (sending components to a third-party manufacturer) have additional receipt and valuation steps not fully detailed here.
- **Lot/serial number traceability** in production (tracking which lot was consumed per MO) adds complexity; test with small batches before full rollout.
- MRP calculations assume demand comes from **Sale Orders** and **Reordering Rules** — forecasts from external systems require custom integration.

View File

@@ -0,0 +1,102 @@
---
name: odoo-migration-helper
description: "Step-by-step guide for migrating Odoo custom modules between versions (v14→v15→v16→v17). Covers API changes, deprecated methods, and view migration."
risk: safe
source: "self"
---
# Odoo Migration Helper
## Overview
Migrating Odoo modules between major versions requires careful handling of API changes, deprecated methods, renamed fields, and new view syntax. This skill guides you through the migration process systematically, covering the most common breaking changes between versions.
## When to Use This Skill
- Upgrading a custom module from Odoo 14/15/16 to a newer version.
- Getting a checklist of things to check before running `odoo-upgrade`.
- Fixing deprecation warnings after a version upgrade.
- Understanding what changed between two specific Odoo versions.
## How It Works
1. **Activate**: Mention `@odoo-migration-helper`, specify your source and target versions, and paste your module code.
2. **Analyze**: Receive a list of breaking changes with before/after code fixes.
3. **Validate**: Get a migration checklist specific to your module's features.
## Key Migration Changes by Version
### Odoo 16 → 17
| Topic | Old (v16) | New (v17) |
|---|---|---|
| View visibility | `attrs="{'invisible': [...]}"` | `invisible="condition"` |
| Chatter | `<div class="oe_chatter">` | `<chatter/>` |
| Required/Readonly | `attrs="{'required': [...]}"` | `required="condition"` |
| Python minimum | 3.10 | 3.10+ |
| JS modules | Legacy `define(['web.core'])` | ES module `import` syntax |
### Odoo 15 → 16
| Topic | Old (v15) | New (v16) |
|---|---|---|
| Website published flag | `website_published = True` | `is_published = True` |
| Mail aliases | `alias_domain` on company | Moved to `mail.alias.domain` model |
| Report render | `_render_qweb_pdf()` | `_render_qweb_pdf()` (same, but signature changed) |
| Accounting move | `account.move.line` grouping | Line aggregation rules updated |
| Email threading | `mail_thread_id` | Deprecated; use `message_ids` |
## Examples
### Example 1: Migrate `attrs` visibility to Odoo 17
```xml
<!-- v16 — domain-based attrs -->
<field name="discount" attrs="{'invisible': [('product_type', '!=', 'service')]}"/>
<field name="discount" attrs="{'required': [('state', '=', 'sale')]}"/>
<!-- v17 — inline Python expressions -->
<field name="discount" invisible="product_type != 'service'"/>
<field name="discount" required="state == 'sale'"/>
```
### Example 2: Migrate Chatter block
```xml
<!-- v16 -->
<div class="oe_chatter">
<field name="message_follower_ids"/>
<field name="activity_ids"/>
<field name="message_ids"/>
</div>
<!-- v17 -->
<chatter/>
```
### Example 3: Migrate website_published flag (v15 → v16)
```python
# v15
record.website_published = True
# v16+
record.is_published = True
```
## Best Practices
-**Do:** Test with `--update=your_module` on each version before pushing to production.
-**Do:** Use the official [Odoo Upgrade Guide](https://upgrade.odoo.com/) to get an automated pre-upgrade analysis report.
-**Do:** Check OCA migration notes and the module's `HISTORY.rst` for community modules.
-**Do:** Run `npm run validate` after migration to catch manifest or frontmatter issues early.
-**Don't:** Skip intermediate versions — go v14→v15→v16→v17 sequentially; never jump.
-**Don't:** Forget to update `version` in `__manifest__.py` (e.g., `17.0.1.0.0`).
-**Don't:** Assume OCA modules are migration-ready; check their GitHub branch for the target version.
## Limitations
- Covers **v14 through v17** only — does not address v13 or older (pre-manifest era has fundamentally different module structure).
- The **Odoo.sh automated upgrade** path has additional steps not covered here; refer to Odoo.sh documentation.
- **Enterprise-specific modules** (e.g., `account_accountant`, `sign`) may have undocumented breaking changes; test on a staging environment with Enterprise license.
- JavaScript OWL component migration (v15 Legacy → v16 OWL) is a complex topic not fully covered by this skill.

View File

@@ -0,0 +1,102 @@
---
name: odoo-module-developer
description: "Expert guide for creating custom Odoo modules. Covers __manifest__.py, model inheritance, ORM patterns, and module structure best practices."
risk: safe
source: "self"
---
# Odoo Module Developer
## Overview
This skill transforms your AI into an expert Odoo custom module developer. It guides you through scaffolding new modules, defining models, setting up security, and following Odoo's coding conventions for both Community and Enterprise editions.
## When to Use This Skill
- Starting a new custom Odoo module from scratch.
- Extending an existing Odoo model (e.g., adding fields to `sale.order`).
- Troubleshooting module loading errors or manifest issues.
- Implementing `onchange`, `compute`, and `constraint` methods correctly.
## How It Works
1. **Activate**: Mention `@odoo-module-developer` and describe the module you want to build.
2. **Scaffold**: Get the full folder structure with `__manifest__.py`, `__init__.py`, models, views, and security files.
3. **Review**: Paste existing code and get a review against Odoo best practices.
## Examples
### Example 1: Scaffold a New Module
**Prompt:** `Create a module called "hospital_management" with a model for patients`
**Generated structure:**
```text
hospital_management/
├── __manifest__.py
├── __init__.py
├── models/
│ ├── __init__.py
│ └── hospital_patient.py
├── views/
│ └── hospital_patient_views.xml
├── security/
│ ├── ir.model.access.csv
│ └── security.xml
└── data/
```
**`__manifest__.py`:**
```python
{
'name': 'Hospital Management',
'version': '17.0.1.0.0',
'category': 'Healthcare',
'depends': ['base', 'mail'],
'data': [
'security/ir.model.access.csv',
'views/hospital_patient_views.xml',
],
'installable': True,
'license': 'LGPL-3',
}
```
**`models/hospital_patient.py`:**
```python
from odoo import models, fields, api
class HospitalPatient(models.Model):
_name = 'hospital.patient'
_description = 'Hospital Patient'
_inherit = ['mail.thread', 'mail.activity.mixin']
name = fields.Char(string='Patient Name', required=True, tracking=True)
birth_date = fields.Date(string='Birth Date')
doctor_id = fields.Many2one('res.users', string='Assigned Doctor')
state = fields.Selection([
('draft', 'New'),
('confirmed', 'Confirmed'),
('done', 'Done'),
], default='draft', tracking=True)
```
## Best Practices
-**Do:** Always prefix your model `_name` with a namespace (e.g., `hospital.patient`).
-**Do:** Use `_inherit = ['mail.thread']` to add chatter/logging automatically.
-**Do:** Specify `version` in manifest as `{odoo_version}.{major}.{minor}.{patch}`.
-**Do:** Set `'author'` and `'website'` in `__manifest__.py` so your module is identifiable in the Apps list.
-**Don't:** Modify core Odoo model files directly — always use `_inherit`.
-**Don't:** Forget to add new models to `ir.model.access.csv` or users will get access errors.
-**Don't:** Use spaces or uppercase in folder names — Odoo requires snake_case module names.
## Limitations
- Does not cover **OWL JavaScript components** or frontend widget development — use `@odoo-xml-views-builder` for view XML.
- **Odoo 13 and below** have a different module structure (no `__manifest__.py` auto-loading) — this skill targets v14+.
- Does not cover **multi-company** or **multi-website** configuration; those require additional model fields (`company_id`, `website_id`).
- Does not generate automated test files — use `@odoo-automated-tests` for that.

View File

@@ -0,0 +1,89 @@
---
name: odoo-orm-expert
description: "Master Odoo ORM patterns: search, browse, create, write, domain filters, computed fields, and performance-safe query techniques."
risk: safe
source: "self"
---
# Odoo ORM Expert
## Overview
This skill teaches you Odoo's Object Relational Mapper (ORM) in depth. It covers reading/writing records, building domain filters, working with relational fields, and avoiding common performance pitfalls like N+1 queries.
## When to Use This Skill
- Writing `search()`, `browse()`, `create()`, `write()`, or `unlink()` calls.
- Building complex domain filters for views or server actions.
- Implementing computed, stored, and related fields.
- Debugging slow queries or optimizing bulk operations.
## How It Works
1. **Activate**: Mention `@odoo-orm-expert` and describe what data operation you need.
2. **Get Code**: Receive correct, idiomatic Odoo ORM code with explanations.
3. **Optimize**: Ask for performance review on existing ORM code.
## Examples
### Example 1: Search with Domain Filters
```python
# Find all confirmed sale orders for a specific customer, created this year
import datetime
start_of_year = datetime.date.today().replace(month=1, day=1).strftime('%Y-%m-%d')
orders = self.env['sale.order'].search([
('partner_id', '=', partner_id),
('state', '=', 'sale'),
('date_order', '>=', start_of_year),
], order='date_order desc', limit=50)
# Note: pass dates as 'YYYY-MM-DD' strings in domains,
# NOT as fields.Date objects — the ORM serializes them correctly.
```
### Example 2: Computed Field
```python
total_order_count = fields.Integer(
string='Total Orders',
compute='_compute_total_order_count',
store=True
)
@api.depends('sale_order_ids')
def _compute_total_order_count(self):
for record in self:
record.total_order_count = len(record.sale_order_ids)
```
### Example 3: Safe Bulk Write (avoid N+1)
```python
# ✅ GOOD: One query for all records
partners = self.env['res.partner'].search([('country_id', '=', False)])
partners.write({'country_id': self.env.ref('base.us').id})
# ❌ BAD: Triggers a separate query per record
for partner in partners:
partner.country_id = self.env.ref('base.us').id
```
## Best Practices
-**Do:** Use `mapped()`, `filtered()`, and `sorted()` on recordsets instead of Python loops.
-**Do:** Use `sudo()` sparingly and only when you understand the security implications.
-**Do:** Prefer `search_count()` over `len(search(...))` when you only need a count.
-**Do:** Use `with_context(...)` to pass context values cleanly rather than modifying `self.env.context` directly.
-**Don't:** Call `search()` inside a loop — this is the #1 Odoo performance killer.
-**Don't:** Use raw SQL unless absolutely necessary; use ORM for all standard operations.
-**Don't:** Pass Python `datetime`/`date` objects directly into domain tuples — always stringify them as `'YYYY-MM-DD'`.
## Limitations
- Does not cover **`cr.execute()` raw SQL** patterns in depth — use the Odoo performance tuner skill for SQL-level optimization.
- **Stored computed fields** can cause significant write overhead at scale; this skill does not cover partitioning strategies.
- Does not cover **transient models** (`models.TransientModel`) or wizard patterns.
- ORM behavior can differ slightly between Odoo SaaS and On-Premise due to config overrides.

View File

@@ -0,0 +1,105 @@
---
name: odoo-performance-tuner
description: "Expert guide for diagnosing and fixing Odoo performance issues: slow queries, worker configuration, memory limits, PostgreSQL tuning, and profiling tools."
risk: safe
source: "self"
---
# Odoo Performance Tuner
## Overview
This skill helps diagnose and resolve Odoo performance problems — from slow page loads and database bottlenecks to worker misconfiguration and memory bloat. It covers PostgreSQL query tuning, Odoo worker settings, and built-in profiling tools.
## When to Use This Skill
- Odoo is slow in production (slow page loads, timeouts).
- Getting `MemoryError` or `Worker timeout` errors in logs.
- Diagnosing a slow database query using Odoo's profiler.
- Tuning `odoo.conf` for a specific server spec.
## How It Works
1. **Activate**: Mention `@odoo-performance-tuner` and describe your performance issue.
2. **Diagnose**: Share relevant log lines or config and receive a root cause analysis.
3. **Fix**: Get exact configuration changes with explanations.
## Examples
### Example 1: Recommended Worker Configuration
```ini
# odoo.conf — tuned for a 4-core, 8GB RAM server
workers = 9 # (CPU_cores × 2) + 1 — never set to 0 in production
max_cron_threads = 2 # background cron jobs; keep ≤ 2 to preserve user-facing capacity
limit_memory_soft = 1610612736 # 1.5 GB — worker is recycled gracefully after this
limit_memory_hard = 2147483648 # 2.0 GB — worker is killed immediately; prevents OOM crashes
limit_time_cpu = 600 # max CPU seconds per request
limit_time_real = 1200 # max wall-clock seconds per request
limit_request = 8192 # max requests before worker recycles (prevents memory leaks)
```
### Example 2: Find Slow Queries with PostgreSQL
```sql
-- Step 1: Enable pg_stat_statements extension (run once as postgres superuser)
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
-- Step 2: Also add to postgresql.conf and reload:
-- shared_preload_libraries = 'pg_stat_statements'
-- log_min_duration_statement = 1000 -- log queries taking > 1 second
-- Step 3: Find the top 10 slowest average queries
SELECT
LEFT(query, 100) AS query_snippet,
round(mean_exec_time::numeric, 2) AS avg_ms,
calls,
round(total_exec_time::numeric, 2) AS total_ms
FROM pg_stat_statements
ORDER BY mean_exec_time DESC
LIMIT 10;
-- Step 4: Check for missing indexes causing full table scans
SELECT schemaname, tablename, attname, n_distinct, correlation
FROM pg_stats
WHERE tablename = 'sale_order_line'
AND correlation < 0.5 -- low correlation = poor index efficiency
ORDER BY n_distinct DESC;
```
### Example 3: Use Odoo's Built-In Profiler
```text
Prerequisites: Run Odoo with ?debug=1 in the URL to enable debug mode.
Menu: Settings → Technical → Profiling
Steps:
1. Click "Enable Profiling" — set a duration (e.g., 60 seconds)
2. Navigate to and reproduce the slow action
3. Return to Settings → Technical → Profiling → View Results
What to look for:
- Total SQL queries > 100 on a single page → N+1 query problem
- Single queries taking > 100ms → missing DB index
- Same query repeated many times → missing cache, use @ormcache
- Python time high but SQL low → compute field inefficiency
```
## Best Practices
-**Do:** Use `mapped()`, `filtered()`, and `sorted()` on in-memory recordsets — they don't trigger additional SQL.
-**Do:** Add PostgreSQL B-tree indexes on columns frequently used in domain filters (`partner_id`, `state`, `date_order`).
-**Do:** Enable Odoo's HTTP caching for static assets and put a CDN (Cloudflare, AWS CloudFront) in front of the website.
-**Do:** Use `@tools.ormcache` decorator on methods pulled repeatedly with the same arguments.
-**Don't:** Set `workers = 0` in production — single-threaded mode serializes all requests and blocks all users on any slow operation.
-**Don't:** Ignore `limit_memory_soft` — workers exceeding it are recycled between requests; without the limit they grow unbounded and crash.
-**Don't:** Directly manipulate `prefetch_ids` on recordsets — rely on Odoo's automatic batch prefetching, which activates by default.
## Limitations
- PostgreSQL tuning (`shared_buffers`, `work_mem`, `effective_cache_size`) is highly server-specific and not covered in depth here — use [PGTune](https://pgtune.leopard.in.ua/) as a starting baseline.
- The built-in Odoo profiler only captures **Python + SQL** traces; JavaScript rendering performance requires browser DevTools.
- **Odoo.sh** managed hosting restricts direct PostgreSQL and `odoo.conf` access — some tuning options are unavailable.
- Does not cover **Redis-based session store** or **Celery task queue** optimizations, which are advanced patterns for very high-traffic instances.

View File

@@ -0,0 +1,116 @@
---
name: odoo-project-timesheet
description: "Expert guide for Odoo Project and Timesheets: task stages, billable time tracking, timesheet approval, budget alerts, and invoicing from timesheets."
risk: safe
source: "self"
---
# Odoo Project & Timesheet
## Overview
This skill helps you configure Odoo Project and Timesheets for service businesses, agencies, and consulting firms. It covers project setup with budgets, task stage management, employee timesheet logging, approval workflows, and converting approved timesheet hours to customer invoices.
## When to Use This Skill
- Setting up a new project with tasks, deadlines, and team assignments.
- Configuring billable vs. non-billable time tracking per project.
- Creating a timesheet approval workflow for managers.
- Invoicing customers based on logged hours (Time & Materials billing).
## How It Works
1. **Activate**: Mention `@odoo-project-timesheet` and describe your project or billing scenario.
2. **Configure**: Receive step-by-step setup instructions.
3. **Automate**: Get guidance on automatically generating invoices from approved timesheets.
## Examples
### Example 1: Create a Billable Project
```text
Menu: Project → New Project (or the "+" button in Project view)
Name: Website Redesign — Acme Corp
Customer: Acme Corporation
Billable: YES (toggle ON)
Settings tab:
Billing Type: Based on Timesheets (Time & Materials)
Service Product: Consulting Hours ($150/hr)
☑ Timesheets
☑ Task Dependencies
☑ Subtasks
Budget:
Planned Hours: 120 hours
Budget Alert: at 80% (96 hrs) → notify project manager
```
### Example 2: Log Time on a Task
```text
Method A — Directly inside the Task (recommended for accuracy):
Open Task → Timesheets tab → Add a Line
Employee: John Doe
Date: Today
Description: "Initial wireframes and site map" (required for clear invoices)
Duration: 3:30 (3 hours 30 minutes)
Method B — Timesheets app (for end-of-day bulk entry):
Menu: Timesheets → My Timesheets → New
Project: Website Redesign
Task: Wireframe Design
Duration: 3:30
```
### Example 3: Enable Timesheet Approval Before Invoicing
```text
Menu: Timesheets → Configuration → Settings
☑ Timesheet Approval (employees submit; managers approve)
Approval flow:
1. Employee submits timesheet at week/month end
2. Manager reviews: Timesheets → Managers → Timesheets to Approve
3. Manager clicks "Approve" → entries are locked and billable
4. Only approved entries flow into the invoice
If Approval is disabled, all logged hours are immediately billable.
```
### Example 4: Invoice from Timesheets
```text
Step 1: Verify approved hours
Menu: Timesheets → Managers → All Timesheets
Filter: Billable = YES, Timesheet Invoice State = "To Invoice"
Step 2: Generate Invoice
Menu: Sales → Orders → To Invoice → Timesheets (v15/v16)
or: Accounting → Customers → Invoiceable Time (v17)
Filter by Customer: Acme Corporation
Select entries → Create Invoices
Step 3: Invoice pre-populates with:
Product: Consulting Hours
Quantity: Sum of approved hours
Unit Price: $150.00
Total: Calculated automatically
```
## Best Practices
-**Do:** Enable **Timesheet Approval** so only manager-approved hours appear on customer invoices.
-**Do:** Set a **budget alert** at 80% of planned hours so PMs can intervene before overruns.
-**Do:** Require **timesheet descriptions** — vague entries like "Work done" on invoices destroy client trust.
-**Do:** Use **Subtasks** to break work into granular pieces while keeping the parent task on the Kanban board.
-**Don't:** Mix billable and internal projects without tagging — it corrupts profitability and utilization reports.
-**Don't:** Log time on the Project itself (without a Task) — it cannot be reported at the task level.
## Limitations
- **Timesheet Approval** is an Enterprise-only feature in some Odoo versions — verify your plan includes it.
- Does not cover **Project Forecast** (resource capacity planning) — that requires the Enterprise Forecast app.
- **Time & Materials** invoicing works well for hourly billing but is not suited for **fixed-price projects** — use milestones or manual invoice lines for those.
- Timesheet entries logged outside an active project-task pair (e.g., on internal projects) are not assignable to customer invoices without custom configuration.

View File

@@ -0,0 +1,104 @@
---
name: odoo-purchase-workflow
description: "Expert guide for Odoo Purchase: RFQ → PO → Receipt → Vendor Bill workflow, purchase agreements, vendor price lists, and 3-way matching."
risk: safe
source: "self"
---
# Odoo Purchase Workflow
## Overview
This skill guides you through the complete Odoo Purchase workflow — from sending a Request for Quotation (RFQ) to receiving goods and matching the vendor bill. It also covers purchase agreements, vendor price lists on products, automated reordering, and 3-way matching controls.
## When to Use This Skill
- Setting up the purchase flow for a new Odoo instance.
- Implementing purchase order approval workflows (2-level approval).
- Configuring vendor price lists with quantity-based discounts.
- Troubleshooting billing/receipt mismatches in 3-way matching.
## How It Works
1. **Activate**: Mention `@odoo-purchase-workflow` and describe your purchasing scenario.
2. **Configure**: Receive exact Odoo menu paths and field-by-field configuration.
3. **Troubleshoot**: Describe a billing or receiving issue and get a root cause diagnosis.
## Examples
### Example 1: Standard RFQ → PO → Receipt → Bill Flow
```text
Step 1: Create RFQ
Menu: Purchase → Orders → Requests for Quotation → New
Vendor: Acme Supplies
Add product lines with quantity and unit price
Step 2: Send RFQ to Vendor
Click "Send by Email" → Vendor receives PDF with RFQ details
Step 3: Confirm as Purchase Order
Click "Confirm Order" → Status changes to "Purchase Order"
Step 4: Receive Goods
Click "Receive Products" → Validate received quantities
(partial receipts are supported; PO stays open for remaining qty)
Step 5: Match Vendor Bill (3-Way Match)
Click "Create Bill" → Bill pre-filled from PO quantities
Verify: PO qty = Received qty = Billed qty
Post Bill → Register Payment
```
### Example 2: Enable 2-Level Purchase Approval
```text
Menu: Purchase → Configuration → Settings
Purchase Order Approval:
☑ Purchase Order Approval
Minimum Order Amount: $5,000
Result:
Orders ≤ $5,000 → Confirm directly to PO
Orders > $5,000 → Status: "Waiting for Approval"
A purchase manager must click "Approve"
```
### Example 3: Vendor Price List (Quantity Breaks on a Product)
```text
Vendor price lists are configured per product, not as a global menu.
Menu: Inventory → Products → [Select Product] → Purchase Tab
→ Vendor Pricelist section → Add a line
Vendor: Acme Supplies
Currency: USD
Price: $12.00
Min. Qty: 1
Add another line for quantity discount:
Min. Qty: 100 → Price: $10.50 (12.5% discount)
Min. Qty: 500 → Price: $9.00 (25% discount)
Result: Odoo automatically selects the right price on a PO
based on the ordered quantity for this vendor.
```
## Best Practices
-**Do:** Enable **Purchase Order Approval** for orders above your company's approval threshold.
-**Do:** Use **Purchase Agreements (Blanket Orders)** for recurring vendors with pre-negotiated annual contracts.
-**Do:** Set a **vendor lead time** on products (Purchase tab) so Odoo can schedule arrival dates accurately.
-**Do:** Set the **Bill Control** policy to "Based on received quantities" (not ordered qty) for accurate 3-way matching.
-**Don't:** Confirm a PO before prices are agreed — use Draft/RFQ status to negotiate first.
-**Don't:** Post a vendor bill without linking it to a receipt — bypassing 3-way matching creates accounting discrepancies.
-**Don't:** Delete a PO that has received quantities — archive it instead to preserve the stock and accounting trail.
## Limitations
- Does not cover **subcontracting purchase flows** — those require the Manufacturing module and subcontracting BoM type.
- **EDI-based order exchange** (automated PO import/export) requires custom integration — use `@odoo-edi-connector` for that.
- Vendor pricelist currency conversion depends on the active **currency rate** in Odoo; rates must be kept current for accuracy.
- The **2-level approval** is a binary threshold; more complex approval matrices (department-based, multi-tier) require custom development or the Approvals app.

View File

@@ -0,0 +1,95 @@
---
name: odoo-qweb-templates
description: "Expert in Odoo QWeb templating for PDF reports, email templates, and website pages. Covers t-if, t-foreach, t-field, and report actions."
risk: safe
source: "self"
---
# Odoo QWeb Templates
## Overview
QWeb is Odoo's primary templating engine, used for PDF reports, website pages, and email templates. This skill generates correct, well-structured QWeb XML with proper directives, translation support, and report action bindings.
## When to Use This Skill
- Creating a custom PDF report (invoice, delivery slip, certificate).
- Building a QWeb email template triggered by workflow actions.
- Designing Odoo website pages with dynamic content.
- Debugging QWeb rendering errors (`t-if`, `t-foreach` issues).
## How It Works
1. **Activate**: Mention `@odoo-qweb-templates` and describe the report or template needed.
2. **Generate**: Receive a complete `ir.actions.report` record and QWeb template.
3. **Debug**: Paste a broken template to identify and fix rendering issues.
## Examples
### Example 1: Custom PDF Report
```xml
<!-- Report Action -->
<record id="action_report_patient_card" model="ir.actions.report">
<field name="name">Patient Card</field>
<field name="model">hospital.patient</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">hospital_management.report_patient_card</field>
<field name="binding_model_id" ref="model_hospital_patient"/>
</record>
<!-- QWeb Template -->
<template id="report_patient_card">
<t t-call="web.html_container">
<t t-foreach="docs" t-as="doc">
<t t-call="web.external_layout">
<div class="page">
<h2>Patient Card</h2>
<table class="table table-bordered">
<tr>
<td><strong>Name:</strong></td>
<td><t t-field="doc.name"/></td>
</tr>
<tr>
<td><strong>Doctor:</strong></td>
<td><t t-field="doc.doctor_id.name"/></td>
</tr>
<tr>
<td><strong>Status:</strong></td>
<td><t t-field="doc.state"/></td>
</tr>
</table>
</div>
</t>
</t>
</t>
</template>
```
### Example 2: Conditional Rendering
```xml
<!-- Show a warning block only if the patient is not confirmed -->
<t t-if="doc.state == 'draft'">
<div class="alert alert-warning">
<strong>Warning:</strong> This patient has not been confirmed yet.
</div>
</t>
```
## Best Practices
-**Do:** Use `t-field` for model fields — Odoo auto-formats dates, monetary values, and booleans correctly.
-**Do:** Use `t-out` (Odoo 15+) for safe HTML output of non-field strings. Use `t-esc` only on Odoo 14 and below (it HTML-escapes output).
-**Do:** Call `web.external_layout` for PDF reports to automatically include the company header, footer, and logo.
-**Do:** Use `_lt()` (lazy translation) for translatable string literals inside Python report helpers, not inline `t-esc`.
-**Don't:** Use raw Python expressions inside QWeb — compute values in the model or a report `_get_report_values()` helper.
-**Don't:** Forget `t-as` when using `t-foreach`; without it, you can't access the current record in the loop body.
-**Don't:** Use `t-esc` where you intend to render HTML content — it will escape the tags and print them as raw text.
## Limitations
- Does not cover **website controller routing** for dynamic QWeb pages — that requires Python `http.route` knowledge.
- **Email template** QWeb has different variable scope than report QWeb (`object` vs `docs`) — this skill primarily focuses on PDF reports.
- QWeb JavaScript (used in Kanban/Form widgets) is a different engine; this skill covers **server-side QWeb only**.
- Does not cover **wkhtmltopdf configuration** for PDF rendering issues (page size, margins, header/footer overlap).

View File

@@ -0,0 +1,103 @@
---
name: odoo-rpc-api
description: "Expert on Odoo's external JSON-RPC and XML-RPC APIs. Covers authentication, model calls, record CRUD, and real-world integration examples in Python, JavaScript, and curl."
risk: safe
source: "self"
---
# Odoo RPC API
## Overview
Odoo exposes a powerful external API via JSON-RPC and XML-RPC, allowing any external application to read, create, update, and delete records. This skill guides you through authenticating, calling models, and building robust integrations.
## When to Use This Skill
- Connecting an external app (e.g., Django, Node.js, a mobile app) to Odoo.
- Running automated scripts to import/export data from Odoo.
- Building a middleware layer between Odoo and a third-party platform.
- Debugging API authentication or permission errors.
## How It Works
1. **Activate**: Mention `@odoo-rpc-api` and describe the integration you need.
2. **Generate**: Get copy-paste ready RPC call code in Python, JavaScript, or curl.
3. **Debug**: Paste an error and get a diagnosis with a corrected call.
## Examples
### Example 1: Authenticate and Read Records (Python)
```python
import xmlrpc.client
url = 'https://myodoo.example.com'
db = 'my_database'
username = 'admin'
password = 'my_api_key' # Use API keys, not passwords, in production
# Step 1: Authenticate
common = xmlrpc.client.ServerProxy(f'{url}/xmlrpc/2/common')
uid = common.authenticate(db, username, password, {})
print(f"Authenticated as UID: {uid}")
# Step 2: Call models
models = xmlrpc.client.ServerProxy(f'{url}/xmlrpc/2/object')
# Search confirmed sale orders
orders = models.execute_kw(db, uid, password,
'sale.order', 'search_read',
[[['state', '=', 'sale']]],
{'fields': ['name', 'partner_id', 'amount_total'], 'limit': 10}
)
for order in orders:
print(order)
```
### Example 2: Create a Record (Python)
```python
new_partner_id = models.execute_kw(db, uid, password,
'res.partner', 'create',
[{'name': 'Acme Corp', 'email': 'info@acme.com', 'is_company': True}]
)
print(f"Created partner ID: {new_partner_id}")
```
### Example 3: JSON-RPC via curl
```bash
curl -X POST https://myodoo.example.com/web/dataset/call_kw \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "call",
"id": 1,
"params": {
"model": "res.partner",
"method": "search_read",
"args": [[["is_company", "=", true]]],
"kwargs": {"fields": ["name", "email"], "limit": 5}
}
}'
# Note: "id" is required by the JSON-RPC 2.0 spec to correlate responses.
# Odoo 16+ also supports the /web/dataset/call_kw endpoint but
# prefer /web/dataset/call_kw for model method calls.
```
## Best Practices
-**Do:** Use **API Keys** (Settings → Technical → API Keys) instead of passwords — available from Odoo 14+.
-**Do:** Use `search_read` instead of `search` + `read` to reduce network round trips.
-**Do:** Always handle connection errors and implement retry logic with exponential backoff in production.
-**Do:** Store credentials in environment variables or a secrets manager (e.g., AWS Secrets Manager, `.env` file).
-**Don't:** Hardcode passwords or API keys directly in scripts — rotate them and use env vars.
-**Don't:** Call the API in a tight loop without batching — bulk operations reduce server load significantly.
-**Don't:** Use the master admin password for API integrations — create a dedicated integration user with minimum required permissions.
## Limitations
- Does not cover **OAuth2 or session-cookie-based authentication** — the examples use API key (token) auth only.
- **Rate limiting** is not built into the Odoo XMLRPC layer; you must implement throttling client-side.
- The XML-RPC endpoint (`/xmlrpc/2/`) does not support file uploads — use the REST-based `ir.attachment` model via JSON-RPC for binary data.
- Odoo.sh (SaaS) may block some API calls depending on plan; verify your subscription supports external API access.

View File

@@ -0,0 +1,109 @@
---
name: odoo-sales-crm-expert
description: "Expert guide for Odoo Sales and CRM: pipeline stages, quotation templates, pricelists, sales teams, lead scoring, and forecasting."
risk: safe
source: "self"
---
# Odoo Sales & CRM Expert
## Overview
This skill helps you configure and optimize Odoo Sales and CRM. It covers opportunity pipeline setup, automated lead assignment, quotation templates, pricelist strategies, sales team management, and the sales-to-invoice workflow.
## When to Use This Skill
- Designing CRM pipeline stages for your sales process.
- Creating a quotation template with optional products and bundles.
- Setting up pricelists with customer-tier pricing.
- Configuring automated lead assignment by territory or salesperson.
## How It Works
1. **Activate**: Mention `@odoo-sales-crm-expert` and describe your sales scenario.
2. **Configure**: Receive step-by-step Odoo setup instructions.
3. **Optimize**: Get recommendations for improving pipeline velocity and deal closure rate.
## Examples
### Example 1: Configure CRM Pipeline Stages
```text
Menu: CRM → Configuration → Stages → New
Typical B2B Pipeline:
Stage 1: New Lead (probability: 10%)
Stage 2: Qualified (probability: 25%)
Stage 3: Proposal Sent (probability: 50%)
Stage 4: Negotiation (probability: 75%)
Stage 5: Won (is_won: YES — marks opportunity as closed-won)
Stage 6: Lost (mark as lost via the "Mark as Lost" button)
Tips:
- Enable "Rotting Days" in CRM Settings to flag stale deals in red
- In Odoo 16+, Predictive Lead Scoring (AI) auto-updates probability
based on historical data. Disable it in Settings if you prefer manual
stage-based probability.
```
### Example 2: Create a Quotation Template
```text
Menu: Sales → Configuration → Quotation Templates → New
(Requires the "Sales Management" module — enabled in Sales Settings)
Template Name: SaaS Annual Subscription
Valid for: 30 days
Lines:
1. Platform License | Qty: 1 | Price: $1,200/yr | (required)
2. Onboarding Package | Qty: 1 | Price: $500 | Optional
3. Premium Support | Qty: 1 | Price: $300/yr | Optional
4. Extra User License | Qty: 0 | Price: $120/user | Optional
Signature & Payment:
☑ Online Signature required before order confirmation
☑ Online Payment (deposit) — 50% upfront
Notes section:
"Prices valid until expiration date. Subject to Schedule A terms."
```
### Example 3: Customer Tier Pricelist (VIP Discount)
```text
Menu: Sales → Configuration → Settings
☑ Enable Pricelists
Menu: Sales → Configuration → Pricelists → New
Name: VIP Customer — 15% Off
Currency: USD
Discount Policy: Show public price & discount on quotation
Rules:
Apply To: All Products
Compute Price: Discount
Discount: 15%
Min. Quantity: 1
Assign to a customer:
Customer record → Sales & Purchase tab → Pricelist → VIP Customer
```
## Best Practices
-**Do:** Use **Lost Reasons** (CRM → Configuration → Lost Reasons) to build a dataset of why deals are lost — invaluable for sales coaching.
-**Do:** Enable **Sales Teams** with revenue targets so pipeline forecasting is meaningful per team.
-**Do:** Set **Expected Revenue** and **Closing Date** on every opportunity — these feed the revenue forecast dashboard.
-**Do:** Use **Quotation Templates** to standardize offers and reduce quoting time across the team.
-**Don't:** Skip the CRM opportunity when selling — going directly from lead to invoice breaks pipeline analytics.
-**Don't:** Manually edit prices on quotation lines as a workaround — set up proper pricelists instead.
-**Don't:** Ignore the **Predictive Lead Scoring** feature in v16+ — configure it with historical data for accurate forecasting.
## Limitations
- **Commission rules** are not built into Odoo CRM out of the box — they require custom development or third-party modules.
- The **Quotation Template** optional product feature requires the **Sale Management** module; it is not available in the base `sale` module.
- **Territory-based lead assignment** (geographic routing) requires custom rules or the Enterprise Leads module.
- Odoo CRM does not have native **email sequence / cadence** automation — use the **Email Marketing** or **Marketing Automation** modules for drip campaigns.

View File

@@ -0,0 +1,92 @@
---
name: odoo-security-rules
description: "Expert in Odoo access control: ir.model.access.csv, record rules (ir.rule), groups, and multi-company security patterns."
risk: safe
source: "self"
---
# Odoo Security Rules
## Overview
Security in Odoo is managed at two levels: **model-level access** (who can read/write which models) and **record-level rules** (which records a user can see). This skill helps you write correct `ir.model.access.csv` entries and `ir.rule` domain-based record rules.
## When to Use This Skill
- Setting up access rights for a new custom module.
- Restricting records so users only see their own data or their company's data.
- Debugging "Access Denied" or "You are not allowed to access" errors.
- Implementing multi-company record visibility rules.
## How It Works
1. **Activate**: Mention `@odoo-security-rules` and describe the access scenario.
2. **Generate**: Get correct CSV access lines and XML record rules.
3. **Debug**: Paste an access error and get a diagnosis with the fix.
## Examples
### Example 1: ir.model.access.csv
```csv
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_hospital_patient_user,hospital.patient.user,model_hospital_patient,base.group_user,1,0,0,0
access_hospital_patient_manager,hospital.patient.manager,model_hospital_patient,base.group_erp_manager,1,1,1,1
```
> **Note:** Use `base.group_erp_manager` for ERP managers, not `base.group_system` — that group is reserved for Odoo's technical superusers. Always create a custom group for module-specific manager roles:
>
> ```xml
> <record id="group_hospital_manager" model="res.groups">
> <field name="name">Hospital Manager</field>
> <field name="category_id" ref="base.module_category_hidden"/>
> </record>
> ```
### Example 2: Record Rule — Users See Only Their Own Records
```xml
<record id="rule_hospital_patient_own" model="ir.rule">
<field name="name">Hospital Patient: Own Records Only</field>
<field name="model_id" ref="model_hospital_patient"/>
<field name="domain_force">[('create_uid', '=', user.id)]</field>
<field name="groups" eval="[(4, ref('base.group_user'))]"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_unlink" eval="False"/>
</record>
```
> **Important:** If you omit `<field name="groups">`, the rule becomes **global** and applies to ALL users, including admins. Always assign a group unless you explicitly intend a global restriction.
### Example 3: Multi-Company Record Rule
```xml
<record id="rule_hospital_patient_company" model="ir.rule">
<field name="name">Hospital Patient: Multi-Company</field>
<field name="model_id" ref="model_hospital_patient"/>
<field name="domain_force">
['|', ('company_id', '=', False),
('company_id', 'in', company_ids)]
</field>
<field name="groups" eval="[(4, ref('base.group_user'))]"/>
</record>
```
## Best Practices
-**Do:** Start with the most restrictive access and open up as needed.
-**Do:** Use `company_ids` (plural) in multi-company rules — it includes all companies the user belongs to.
-**Do:** Test rules using a non-admin user in debug mode — `sudo()` bypasses all record rules entirely.
-**Do:** Create dedicated security groups per module rather than reusing core Odoo groups.
-**Don't:** Give `perm_unlink = 1` to regular users unless deletion is explicitly required by the business process.
-**Don't:** Leave `group_id` blank in `ir.model.access.csv` unless you intend to grant public (unauthenticated) access.
-**Don't:** Use `base.group_system` for module managers — that grants full technical access including server configurations.
## Limitations
- Does not cover **field-level access control** (`ir.model.fields` read/write restrictions) — those require custom OWL or Python overrides.
- **Portal and public user** access rules have additional nuances not fully covered here; test carefully with `base.group_portal`.
- Record rules are **bypassed by `sudo()`** — any code running in superuser context ignores all `ir.rule` entries.
- Does not cover **row-level security via PostgreSQL** (RLS) — Odoo manages all security at the ORM layer.

View File

@@ -0,0 +1,99 @@
---
name: odoo-shopify-integration
description: "Connect Odoo with Shopify: sync products, inventory, orders, and customers using the Shopify API and Odoo's external API or connector modules."
---
# Odoo ↔ Shopify Integration
## Overview
This skill guides you through integrating Odoo with Shopify — syncing your product catalog, real-time inventory levels, incoming orders, and customer data. It covers both using the official Odoo Shopify connector (Enterprise) and building a custom integration via Shopify REST + Odoo XMLRPC APIs.
## When to Use This Skill
- Selling on Shopify while managing inventory in Odoo.
- Automatically creating Odoo sales orders from Shopify purchases.
- Keeping Odoo stock levels in sync with Shopify product availability.
- Mapping Shopify product variants to Odoo product templates.
## How It Works
1. **Activate**: Mention `@odoo-shopify-integration` and describe your sync scenario.
2. **Design**: Receive the data flow architecture and field mapping.
3. **Build**: Get code snippets for the Shopify webhook receiver and Odoo API caller.
## Data Flow Architecture
```
SHOPIFY ODOO
-------- ----
Product Catalog <──────sync────── Product Templates + Variants
Inventory Level <──────sync────── Stock Quants (real-time)
New Order ───────push──────> Sale Order (auto-confirmed)
Customer ───────push──────> res.partner (created if new)
Fulfillment <──────push────── Delivery Order validated
```
## Examples
### Example 1: Push an Odoo Sale Order for a Shopify Order (Python)
```python
import xmlrpc.client, requests
# Odoo connection
odoo_url = "https://myodoo.example.com"
db, uid, pwd = "my_db", 2, "api_key"
models = xmlrpc.client.ServerProxy(f"{odoo_url}/xmlrpc/2/object")
def create_odoo_order_from_shopify(shopify_order):
# Find or create customer
partner = models.execute_kw(db, uid, pwd, 'res.partner', 'search_read',
[[['email', '=', shopify_order['customer']['email']]]],
{'fields': ['id'], 'limit': 1}
)
partner_id = partner[0]['id'] if partner else models.execute_kw(
db, uid, pwd, 'res.partner', 'create', [{
'name': shopify_order['customer']['first_name'] + ' ' + shopify_order['customer']['last_name'],
'email': shopify_order['customer']['email'],
}]
)
# Create Sale Order
order_id = models.execute_kw(db, uid, pwd, 'sale.order', 'create', [{
'partner_id': partner_id,
'client_order_ref': f"Shopify #{shopify_order['order_number']}",
'order_line': [(0, 0, {
'product_id': get_odoo_product_id(line['sku']),
'product_uom_qty': line['quantity'],
'price_unit': float(line['price']),
}) for line in shopify_order['line_items']],
}])
return order_id
def get_odoo_product_id(sku):
result = models.execute_kw(db, uid, pwd, 'product.product', 'search_read',
[[['default_code', '=', sku]]], {'fields': ['id'], 'limit': 1})
return result[0]['id'] if result else False
```
### Example 2: Shopify Webhook for Real-Time Orders
```python
from flask import Flask, request
app = Flask(__name__)
@app.route('/webhook/shopify/orders', methods=['POST'])
def shopify_order_webhook():
shopify_order = request.json
order_id = create_odoo_order_from_shopify(shopify_order)
return {"odoo_order_id": order_id}, 200
```
## Best Practices
-**Do:** Use Shopify's **webhook system** for real-time order sync instead of polling.
-**Do:** Match products using **SKU / Internal Reference** as the unique key between both systems.
-**Do:** Validate Shopify webhook HMAC signatures before processing any payload.
-**Don't:** Sync inventory from both systems simultaneously without a "master system" — pick one as the source of truth.
-**Don't:** Use Shopify product IDs as the key — use SKUs which are stable across platforms.

View File

@@ -0,0 +1,123 @@
---
name: odoo-upgrade-advisor
description: "Step-by-step Odoo version upgrade advisor: pre-upgrade checklist, community vs enterprise upgrade path, OCA module compatibility, and post-upgrade validation."
risk: safe
source: "self"
---
# Odoo Upgrade Advisor
## Overview
Upgrading Odoo between major versions (e.g., v15 → v16 → v17) requires careful preparation, testing, and validation. This skill provides a structured pre-upgrade checklist, guides you through the upgrade tools (Odoo Upgrade Service and OpenUpgrade), and gives you a post-upgrade validation protocol.
## When to Use This Skill
- Planning a major Odoo version upgrade.
- Identifying which custom modules need to be migrated.
- Running the upgrade on a staging environment before production.
- Validating the system after an upgrade.
## How It Works
1. **Activate**: Mention `@odoo-upgrade-advisor`, state your current and target version.
2. **Plan**: Receive the full upgrade roadmap and risk assessment.
3. **Execute**: Get a step-by-step upgrade command sequence.
## Upgrade Paths
| From | To | Supported? | Tool |
|---|---|---|---|
| v16 | v17 | ✅ Direct | Odoo Upgrade Service / OpenUpgrade |
| v15 | v16 | ✅ Direct | Odoo Upgrade Service / OpenUpgrade |
| v14 | v15 | ✅ Direct | Odoo Upgrade Service / OpenUpgrade |
| v14 | v17 | ⚠️ Multi-hop | v14→v15→v16→v17 (cannot skip) |
| v13 or older | any | ❌ Not supported | Manual migration required |
## Examples
### Example 1: Pre-Upgrade Checklist
```text
BEFORE YOU START:
☑ 1. List all installed modules (Settings → Technical → Modules)
Export to CSV and review for custom/OCA modules
☑ 2. Check OCA compatibility matrix for each community module
https://github.com/OCA/maintainer-tools/wiki/Migration-Status
☑ 3. Take a full backup (database + filestore) — your restore point
☑ 4. Clone production to a staging environment
☑ 5. Run the Odoo Upgrade pre-analysis:
https://upgrade.odoo.com/ → Upload DB → Review breaking changes report
☑ 6. Review custom modules against migration notes
(use @odoo-migration-helper for per-module analysis)
☑ 7. Upgrade and test in staging → Fix all errors → Re-test
☑ 8. Schedule a production maintenance window
☑ 9. Notify users of scheduled downtime
☑ 10. Perform production upgrade → Validate → Go/No-Go decision
```
### Example 2: Community Upgrade with OpenUpgrade
```bash
# Clone OpenUpgrade for the TARGET version (e.g., upgrading to v17)
git clone https://github.com/OCA/OpenUpgrade.git \
--branch 17.0 \
--single-branch \
/opt/openupgrade
# Run the migration against your staging database
python3 /opt/openupgrade/odoo-bin \
--update all \
--database odoo_staging \
--config /etc/odoo/odoo.conf \
--stop-after-init \
--load openupgrade_framework
# Review the log for errors before touching production
tail -200 /var/log/odoo/odoo.log | grep -E "ERROR|WARNING|Traceback"
```
### Example 3: Post-Upgrade Validation Checklist
```text
After upgrading, validate these critical areas before going live:
Accounting:
☑ Trial Balance totals match the pre-upgrade snapshot
☑ Open invoices, bills, and payments are accessible
☑ Bank reconciliation can be performed on a test statement
Inventory:
☑ Stock valuation report matches pre-upgrade (run Inventory Valuation)
☑ Open Purchase Orders and Sale Orders are visible
HR / Payroll:
☑ All employee records are intact
☑ Payslips from the last 3 months are accessible and correct
Custom Modules:
☑ Every custom module loaded without ImportError or XML error
☑ Run the critical business workflows end-to-end:
Create sale order → confirm → deliver → invoice → payment
Users & Security:
☑ User logins work correctly
☑ Access rights are preserved (spot-check 3-5 users)
```
## Best Practices
-**Do:** Always upgrade on a **copy of production** (staging) first — never the live instance.
-**Do:** Keep the old version running until the new version is **fully validated and signed off**.
-**Do:** Check OCA's migration status page: [OCA Migration Status](https://github.com/OCA/maintainer-tools/wiki/Migration-Status)
-**Do:** Use the [Odoo Upgrade Service](https://upgrade.odoo.com/) pre-analysis report to get a list of breaking changes **before writing any code**.
-**Don't:** Skip intermediate versions — Odoo requires sequential upgrades (v14→v15→v16→v17).
-**Don't:** Upgrade custom modules and Odoo core simultaneously — adapt Odoo core first, then fix custom modules.
-**Don't:** Run OpenUpgrade against production directly — always test on a staging copy first.
## Limitations
- Covers **v14v17** only. Versions v13 and older have a fundamentally different module structure and require manual migration.
- **Enterprise-exclusive module changes** (e.g., `sign`, `account_accountant`) may have undocumented breaking changes not included in OpenUpgrade.
- The **Odoo.sh** automated upgrade path has a separate workflow (managed from the Odoo.sh dashboard) not covered here.
- OWL JavaScript component migration (legacy widget → OWL v16+) is a complex front-end topic beyond the scope of this skill.

View File

@@ -0,0 +1,129 @@
---
name: odoo-woocommerce-bridge
description: "Sync Odoo with WooCommerce: products, inventory, orders, and customers via WooCommerce REST API and Odoo external API."
---
# Odoo ↔ WooCommerce Bridge
## Overview
This skill guides you through building a reliable sync bridge between Odoo (the back-office ERP) and WooCommerce (the WordPress online store). It covers product catalog sync, real-time inventory updates, order import, and customer record management.
## When to Use This Skill
- Running a WooCommerce store with Odoo for inventory and fulfillment.
- Automatically pulling WooCommerce orders into Odoo as sale orders.
- Keeping WooCommerce product stock in sync with Odoo's warehouse.
- Mapping WooCommerce order statuses to Odoo delivery states.
## How It Works
1. **Activate**: Mention `@odoo-woocommerce-bridge` and describe your sync requirements.
2. **Design**: Get the field mapping table between WooCommerce and Odoo objects.
3. **Build**: Receive Python integration scripts using the WooCommerce REST API.
## Field Mapping: WooCommerce → Odoo
| WooCommerce | Odoo |
|---|---|
| `products` | `product.template` + `product.product` |
| `orders` | `sale.order` + `sale.order.line` |
| `customers` | `res.partner` |
| `stock_quantity` | `stock.quant` |
| `sku` | `product.product.default_code` |
| `order status: processing` | Sale Order: `sale` (confirmed) |
| `order status: completed` | Delivery: `done` |
## Examples
### Example 1: Pull WooCommerce Orders into Odoo (Python)
```python
from woocommerce import API
import xmlrpc.client
# WooCommerce client
wcapi = API(
url="https://mystore.com",
consumer_key="ck_xxxxxxxxxxxxx",
consumer_secret="cs_xxxxxxxxxxxxx",
version="wc/v3"
)
# Odoo client
odoo_url = "https://myodoo.example.com"
db, uid, pwd = "my_db", 2, "api_key"
models = xmlrpc.client.ServerProxy(f"{odoo_url}/xmlrpc/2/object")
def sync_orders():
# Get unprocessed WooCommerce orders
orders = wcapi.get("orders", params={"status": "processing", "per_page": 50}).json()
for wc_order in orders:
# Find or create Odoo partner
email = wc_order['billing']['email']
partner = models.execute_kw(db, uid, pwd, 'res.partner', 'search',
[[['email', '=', email]]])
if not partner:
partner_id = models.execute_kw(db, uid, pwd, 'res.partner', 'create', [{
'name': f"{wc_order['billing']['first_name']} {wc_order['billing']['last_name']}",
'email': email,
'phone': wc_order['billing']['phone'],
'street': wc_order['billing']['address_1'],
'city': wc_order['billing']['city'],
}])
else:
partner_id = partner[0]
# Create Sale Order in Odoo
order_lines = []
for item in wc_order['line_items']:
product = models.execute_kw(db, uid, pwd, 'product.product', 'search',
[[['default_code', '=', item['sku']]]])
if product:
order_lines.append((0, 0, {
'product_id': product[0],
'product_uom_qty': item['quantity'],
'price_unit': float(item['price']),
}))
models.execute_kw(db, uid, pwd, 'sale.order', 'create', [{
'partner_id': partner_id,
'client_order_ref': f"WC-{wc_order['number']}",
'order_line': order_lines,
}])
# Mark WooCommerce order as on-hold (processed by Odoo)
wcapi.put(f"orders/{wc_order['id']}", {"status": "on-hold"})
```
### Example 2: Push Odoo Stock to WooCommerce
```python
def sync_inventory_to_woocommerce():
# Get all products with a SKU from Odoo
products = models.execute_kw(db, uid, pwd, 'product.product', 'search_read',
[[['default_code', '!=', False], ['type', '=', 'product']]],
{'fields': ['default_code', 'qty_available']}
)
for product in products:
sku = product['default_code']
qty = int(product['qty_available'])
# Update WooCommerce by SKU
wc_products = wcapi.get("products", params={"sku": sku}).json()
if wc_products:
wcapi.put(f"products/{wc_products[0]['id']}", {
"stock_quantity": qty,
"manage_stock": True,
})
```
## Best Practices
-**Do:** Use **SKU** as the unique identifier linking WooCommerce products to Odoo products.
-**Do:** Run inventory sync on a **schedule** (every 15-30 min) rather than real-time to avoid rate limits.
-**Do:** Log all API calls and errors to a database table for debugging.
-**Don't:** Process the same WooCommerce order twice — flag it as processed immediately after import.
-**Don't:** Sync draft or cancelled WooCommerce orders to Odoo — filter by `status = processing` or `completed`.

View File

@@ -0,0 +1,102 @@
---
name: odoo-xml-views-builder
description: "Expert at building Odoo XML views: Form, List, Kanban, Search, Calendar, and Graph. Generates correct XML for Odoo 14-17 with proper visibility syntax."
risk: safe
source: "self"
---
# Odoo XML Views Builder
## Overview
This skill generates and reviews Odoo XML view definitions for Kanban, Form, List, Search, Calendar, and Graph views. It understands visibility modifiers, `groups`, `domain`, `context`, and widget usage across Odoo versions 1417, including the migration from `attrs` (v1416) to inline expressions (v17+).
## When to Use This Skill
- Creating a new form or list view for a custom model.
- Adding fields, tabs, or smart buttons to an existing view.
- Building a Kanban view with color coding or progress bars.
- Creating a search view with filters and group-by options.
## How It Works
1. **Activate**: Mention `@odoo-xml-views-builder` and describe the view you want.
2. **Generate**: Get complete, ready-to-paste XML view definitions.
3. **Review**: Paste existing XML and get fixes for common mistakes.
## Examples
### Example 1: Form View with Tabs
```xml
<record id="view_hospital_patient_form" model="ir.ui.view">
<field name="name">hospital.patient.form</field>
<field name="model">hospital.patient</field>
<field name="arch" type="xml">
<form string="Patient">
<header>
<button name="action_confirm" string="Confirm"
type="object" class="btn-primary"
invisible="state != 'draft'"/>
<field name="state" widget="statusbar"
statusbar_visible="draft,confirmed,done"/>
</header>
<sheet>
<div class="oe_title">
<h1><field name="name" placeholder="Patient Name"/></h1>
</div>
<notebook>
<page string="General Info">
<group>
<field name="birth_date"/>
<field name="doctor_id"/>
</group>
</page>
</notebook>
</sheet>
<chatter/>
</form>
</field>
</record>
```
### Example 2: Kanban View
```xml
<record id="view_hospital_patient_kanban" model="ir.ui.view">
<field name="name">hospital.patient.kanban</field>
<field name="model">hospital.patient</field>
<field name="arch" type="xml">
<kanban default_group_by="state" class="o_kanban_small_column">
<field name="name"/>
<field name="state"/>
<field name="doctor_id"/>
<templates>
<t t-name="kanban-card">
<div class="oe_kanban_content">
<strong><field name="name"/></strong>
<div>Doctor: <field name="doctor_id"/></div>
</div>
</t>
</templates>
</kanban>
</field>
</record>
```
## Best Practices
-**Do:** Use inline `invisible="condition"` (Odoo 17+) instead of `attrs` for show/hide logic.
-**Do:** Use `attrs="{'invisible': [...]}"` only if you are targeting Odoo 1416 — it is deprecated in v17.
-**Do:** Always set a `string` attribute on your view record for debugging clarity.
-**Do:** Use `<chatter/>` (v17) or `<div class="oe_chatter">` + field tags (v16 and below) for activity tracking.
-**Don't:** Use `attrs` in Odoo 17 — it is fully deprecated and raises warnings in logs.
-**Don't:** Put business logic in view XML — keep it in Python model methods.
-**Don't:** Use hardcoded `domain` strings in views when a `domain` field on the model can be used dynamically.
## Limitations
- Does not cover **OWL JavaScript widgets** or client-side component development.
- **Search panel views** (`<searchpanel>`) are not fully covered — those require frontend knowledge.
- Does not address **website QWeb views** — use `@odoo-qweb-templates` for those.
- **Cohort and Map views** (Enterprise-only) are not covered by this skill.