# Tác Vụ Tạo Addon Nhanh

## Mục đích
Tạo nhanh addon Odoo tuân chuẩn OCA và cấu trúc đúng, tối ưu cho môi trường brownfield và chu kỳ phát triển ngắn.

## Khi dùng
**Dùng khi:**
- Cần tạo addon nhanh để nâng cấp brownfield
- Yêu cầu nghiệp vụ rõ ràng, phạm vi hẹp
- Cấu trúc addon chuẩn là đủ
- Deadline gấp (trong ngày)
- Tuân pattern sẵn có của dự án

**Dùng create-odoo-addon.md khi:**
- Addon phức tạp, nhiều tùy biến
- Nhiều điểm tích hợp, logic dày
- Cần tài liệu & kiểm thử toàn diện

## Điều kiện tiên quyết
**Thông tin cần:** tên addon, mục đích; phiên bản Odoo (13.0–18.0); yêu cầu nghiệp vụ & workflow; điểm tích hợp với module hiện có; phân loại OCA.  
**Môi trường:** dự án Odoo; quyền truy cập cấu trúc mẫu; repo Git.

## Quy trình tạo addon nhanh

### 1) Lập kế hoạch (5’)
**Metadata**
```yaml
addon_name: "[snake_case_addon_name]"
display_name: "[Tên hiển thị]"
odoo_version: "[16.0]"
category: "[Sales/Inventory/Accounting/...]" 
author: "[Your Company], OCA"
depends: ["base", "web"]
application: false
auto_install: false
```
**Ngữ cảnh ngắn:** Purpose; Primary Users; 2-3 Key Features; Integration với module nào.

### 2) Tạo cấu trúc (10’)
```bash
mkdir -p odoo/custom/src/addons/[addon_name]
cd odoo/custom/src/addons/[addon_name]
mkdir -p models views security data demo tests static/description static/src/js wizards
```
**__init__.py**
```python
from . import models
from . import wizards
```
**__manifest__.py**
```python
{
    'name': '[Display Name]',
    'version': '17.0.1.0.0',
    'category': '[Category]',
    'summary': '[Brief summary]',
    'description': """
[Addon Description]

This addon provides:
* [Feature 1]
* [Feature 2]
* [Feature 3]
    """,
    'author': '[Your Company], OCA',
    'website': 'https://kytoc.vn/',
    'license': 'AGPL-3',
    'depends': ['base', 'web'],
    'data': [
        'security/ir.model.access.csv',
        'views/[model_name]_views.xml',
        'views/menu_items.xml',
        'data/initial_data.xml',
    ],
    'demo': ['demo/demo_data.xml'],
    'installable': True,
    'auto_install': False,
    'application': False,
}
```

### 3) Model nhanh (15’)
**models/__init__.py**
```python
from . import [model_name]
```
**models/[model_name].py**
```python
from odoo import api, fields, models, _
from odoo.exceptions import ValidationError, UserError

class [ModelName](models.Model):
    _name = '[model.name]'
    _description = '[Model Description]'
    _inherit = ['mail.thread', 'mail.activity.mixin']
    _order = 'name'
    _rec_name = 'name'

    name = fields.Char(string='Name', required=True, tracking=True)
    description = fields.Text(string='Description')
    active = fields.Boolean(string='Active', default=True, tracking=True)
    company_id = fields.Many2one('res.company', string='Company',
                                 default=lambda self: self.env.company, required=True)
    user_id = fields.Many2one('res.users', string='Responsible',
                              default=lambda self: self.env.user, tracking=True)
    state = fields.Selection([
        ('draft', 'Draft'),
        ('confirmed', 'Confirmed'),
        ('done', 'Done'),
    ], string='Status', default='draft', tracking=True)

    @api.constrains('name')
    def _check_name(self):
        for record in self:
            if not record.name.strip():
                raise ValidationError(_('Name cannot be empty'))

    def action_confirm(self):
        self.ensure_one()
        if self.state != 'draft':
            raise UserError(_('Only draft records can be confirmed'))
        self.state = 'confirmed'
```

### 4) View nhanh (10’)
**views/[model_name]_views.xml**
```xml
<odoo>
  <record id="view_[model_name]_form" model="ir.ui.view">
    <field name="name">[model.name].form</field>
    <field name="model">[model.name]</field>
    <field name="arch" type="xml">
      <form string="[Model Display Name]">
        <header>
          <button name="action_confirm" type="object"
                  string="Confirm" class="oe_highlight"
                  invisible="state != 'draft'"/>
          <field name="state" widget="statusbar"/>
        </header>
        <sheet>
          <div class="oe_title">
            <h1><field name="name" placeholder="Enter name..."/></h1>
          </div>
          <group>
            <group>
              <field name="user_id"/>
              <field name="company_id" groups="base.group_multi_company"/>
            </group>
            <group>
              <field name="active"/>
            </group>
          </group>
          <notebook>
            <page string="Description">
              <field name="description" nolabel="1"/>
            </page>
          </notebook>
        </sheet>
        <div class="oe_chatter">
          <field name="message_follower_ids"/>
          <field name="activity_ids"/>
          <field name="message_ids"/>
        </div>
      </form>
    </field>
  </record>

  <record id="view_[model_name]_tree" model="ir.ui.view">
    <field name="name">[model.name].tree</field>
    <field name="model">[model.name]</field>
    <field name="arch" type="xml">
      <tree string="[Model Display Names]">
        <field name="name"/>
        <field name="user_id"/>
        <field name="state"/>
        <field name="company_id" groups="base.group_multi_company"/>
      </tree>
    </field>
  </record>

  <record id="view_[model_name]_search" model="ir.ui.view">
    <field name="name">[model.name].search</field>
    <field name="model">[model.name]</field>
    <field name="arch" type="xml">
      <search string="Search [Model Display Names]">
        <field name="name"/>
        <field name="user_id"/>
        <filter name="my_records" string="My Records" domain="[('user_id', '=', uid)]"/>
        <separator/>
        <filter name="draft" string="Draft" domain="[('state', '=', 'draft')]"/>
        <group expand="0" string="Group By">
          <filter string="Status" name="group_state" context="{'group_by': 'state'}"/>
          <filter string="Responsible" name="group_user" context="{'group_by': 'user_id'}"/>
        </group>
      </search>
    </field>
  </record>
</odoo>
```

**views/menu_items.xml**
```xml
<odoo>
  <record id="action_[model_name]" model="ir.actions.act_window">
    <field name="name">[Model Display Names]</field>
    <field name="res_model">[model.name]</field>
    <field name="view_mode">tree,form</field>
    <field name="help" type="html">
      <p class="o_view_nocontent_smiling_face">
        Create your first [model display name]!
      </p>
    </field>
  </record>

  <menuitem id="menu_[addon_name]_root" name="[Addon Display Name]" sequence="100"/>
  <menuitem id="menu_[model_name]" name="[Model Display Names]"
            parent="menu_[addon_name]_root" action="action_[model_name]" sequence="10"/>
</odoo>
```

### 5) Bảo mật nhanh (5’)
**security/ir.model.access.csv**
```csv
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_[model_name]_user,[model.name].user,model_[model_name],base.group_user,1,1,1,1
access_[model_name]_manager,[model.name].manager,model_[model_name],base.group_system,1,1,1,1
```

### 6) Dữ liệu & demo (5’)
**data/initial_data.xml**
```xml
<odoo>
  <data noupdate="1">
    <!-- Initial configuration data -->
  </data>
</odoo>
```
**demo/demo_data.xml**
```xml
<odoo>
  <data>
    <record id="demo_[model_name]_1" model="[model.name]">
      <field name="name">Demo [Model Name] 1</field>
      <field name="description">This is a demo record</field>
    </record>
    <record id="demo_[model_name]_2" model="[model.name]">
      <field name="name">Demo [Model Name] 2</field>
      <field name="description">Another demo record</field>
    </record>
  </data>
</odoo>
```

### 7) Tài liệu gọn (5’)
**README.rst (tối giản)**
```rst
===================
[Addon Display Name]
===================

.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
    :target: https://odoo-community.org/page/development-status
    :alt: Beta

|badge1|

[Mô tả ngắn về addon]

**Table of contents**

.. contents::
   :local:

Features
========
* [Feature 1]
* [Feature 2]
* [Feature 3]

Usage
=====
1. Tới menu [đường dẫn]
2. Tạo bản ghi mới
3. Điền thông tin bắt buộc
4. Confirm khi sẵn sàng
```

**static/description/index.html**
```html
<section class="oe_container">
  <div class="oe_row oe_spaced">
    <div class="oe_span12">
      <h2 class="oe_slogan">[Addon Display Name]</h2>
      <h3 class="oe_slogan">[Tagline ngắn]</h3>
    </div>
  </div>
</section>

<section class="oe_container oe_dark">
  <div class="oe_row oe_spaced">
    <div class="oe_span12">
      <h2>Features</h2>
      <ul>
        <li>[Feature 1]</li>
        <li>[Feature 2]</li>
        <li>[Feature 3]</li>
      </ul>
    </div>
  </div>
</section>
```

### 8) Kiểm thử nhanh (10’)
```bash
# Kiểm tra cấu trúc
find . -name "*.py" -exec python -m py_compile {} \;
find . -name "*.xml" -exec xmllint --noout {} \;
python -c "import ast; ast.parse(open('__manifest__.py').read())"

# Cài thử addon
cd [project_root]
docker-compose run --rm odoo odoo -d [test_db] -i [addon_name] --stop-after-init
docker-compose run --rm odoo click-odoo -d [test_db] --rollback -c "
module = env['ir.module.module'].search([('name', '=', '[addon_name]')])
print(f'Module state: {module.state}')
"
```
**Test chức năng cơ bản:** mở menu, tạo bản ghi, CRUD, workflow, kiểm tra lưu dữ liệu.

## Checklist tạo addon nhanh
**Cấu trúc**
- [ ] Cấu trúc thư mục đúng chuẩn Odoo
- [ ] Imports Python đầy đủ
- [ ] Manifest đầy đủ, hợp lệ
- [ ] XML well-formed
- [ ] Quy tắc access được khai báo

**Chức năng**
- [ ] Cài đặt không lỗi
- [ ] Menu hiển thị đúng
- [ ] Form mở và dùng được
- [ ] CRUD hoạt động
- [ ] Logic nghiệp vụ đúng

**Chất lượng**
- [ ] Tuân pattern OCA cơ bản
- [ ] Không lỗ hổng hiển nhiên
- [ ] Có xử lý lỗi tối thiểu
- [ ] Đặt tên nhất quán
- [ ] Có tài liệu tối thiểu

## Template tham chiếu nhanh
**Field phổ biến**
```python
char_field = fields.Char(string='Text', required=True)
text_field = fields.Text(string='Long Text')
integer_field = fields.Integer(string='Number', default=0)
float_field = fields.Float(string='Decimal', digits=(12, 2))
boolean_field = fields.Boolean(string='Yes/No', default=False)
date_field = fields.Date(string='Date')
datetime_field = fields.Datetime(string='Date & Time')
selection_field = fields.Selection([('option1','Option 1'), ('option2','Option 2')], string='Choose Option', default='option1')
many2one_field = fields.Many2one('res.partner', string='Partner')
one2many_field = fields.One2many('model.line', 'parent_id', string='Lines')
many2many_field = fields.Many2many('product.product', string='Products')
```

**Pattern view phổ biến**
```xml
<field name="field_name" readonly="1"/>
<field name="field_name" invisible="state != 'draft'"/>
<field name="field_name" required="state == 'confirmed'"/>
<field name="partner_id" domain="[('is_company', '=', True)]"/>
```

## Tiêu chí thành công
1) Cấu trúc addon tuân chuẩn Odoo/OCA  
2) Cài đặt không lỗi trên môi trường mục tiêu  
3) Chức năng cơ bản hoạt động đúng  
4) Menu & CRUD dùng được  
5) Mô hình bảo mật phù hợp  
6) Code theo pattern dự án  
7) Tài liệu cung cấp hướng dẫn cơ bản  
8) Sẵn sàng cho cải tiến lặp

## Lưu ý
- **Tốc độ vs. đầy đủ**: ưu tiên lõi trước  
- **Nhất quán pattern**: bám sát mẫu dự án  
- **Tuân chuẩn OCA**: dù phát triển nhanh vẫn giữ chuẩn  
- **Phát triển lặp**: chuẩn bị cho refactor/cải tiến sau  
- **Ưu tiên kiểm thử cài đặt & chức năng cơ bản**  
- **Tài liệu tối giản nhưng phải có**

Tạo addon nhanh nhằm có nền tảng chạy được sớm để tiếp tục cải tiến. Tập trung vào chức năng cốt lõi và pattern chuẩn.***