1. Odoo 18二次开发入门:为什么选择模块化开发?
第一次接触Odoo二次开发时,很多人会问:为什么不直接修改源码?这个问题我也纠结过。直到有次升级系统,发现自己改过的核心代码全被覆盖,才真正理解模块化开发的价值。Odoo的模块化设计就像乐高积木,每个业务功能都是独立的模块,既能灵活组合又互不干扰。
最近给一家电商客户做库存预警系统时,我们完全通过新建模块实现功能。当客户临时需要增加供应商管理功能时,只需再开发一个供应商模块,两个模块通过依赖关系完美配合。这种开发方式让系统维护成本降低了60%,升级时再也不用提心吊胆。
2. 从零搭建库存预警模块
2.1 创建模块脚手架
先确保你已经配置好Odoo 18开发环境。打开终端,进入自定义模块目录(通常放在addons文件夹),执行这个神奇的命令:
python ../odoo-bin scaffold stock_alert my_addons这个命令会在my_addons目录下生成stock_alert模块的初始结构。我习惯用业务功能命名模块,比如这里的stock_alert就明确表示了库存预警功能。
生成的文件结构中,最需要关注的是:
__manifest__.py:模块的身份证models/:数据模型的家views/:用户界面的工作室security/:权限控制中心
2.2 配置manifest文件
打开__manifest__.py,这是模块的配置中心。最近做项目时发现几个容易踩的坑:
{ 'name': '库存预警系统', # 用中文名方便终端用户识别 'version': '1.0', 'summary': '实时监控库存水位,自动触发补货预警', 'description': """ 本模块提供: - 多级库存阈值设置 - 预警规则自定义 - 自动通知采购负责人 - 可视化预警仪表盘 """, # 详细描述会显示在应用商店 'author': '你的名字', 'website': 'https://yourdomain.com', 'category': 'Inventory', # 必须使用Odoo标准分类 'depends': ['stock'], # 依赖的模块,库存功能基于stock模块 'data': [ 'security/ir.model.access.csv', 'views/stock_warehouse_views.xml', ], 'demo': [], # 演示数据 'installable': True, 'application': True, 'license': 'LGPL-3', }特别注意depends配置,有次我忘记添加mail模块依赖,导致消息通知功能报错,排查了半天才发现是这个原因。
3. 构建库存预警数据模型
3.1 设计预警规则模型
在models/stock_alert.py中定义核心模型:
from odoo import models, fields, api class StockAlertRule(models.Model): _name = 'stock.alert.rule' _description = '库存预警规则' name = fields.Char('规则名称', required=True) active = fields.Boolean('启用', default=True) warehouse_id = fields.Many2one( 'stock.warehouse', '仓库', help="应用该规则的仓库" ) product_category_id = fields.Many2one( 'product.category', '产品分类', help="应用该规则的产品分类" ) threshold_type = fields.Selection([ ('quantity', '数量'), ('value', '金额'), ('day', '天数')], '阈值类型', default='quantity' ) warning_threshold = fields.Float('预警阈值') critical_threshold = fields.Float('严重阈值') action_ids = fields.One2many( 'stock.alert.action', 'rule_id', '触发动作' ) _sql_constraints = [ ('threshold_check', 'CHECK(warning_threshold < critical_threshold)', '预警阈值必须小于严重阈值') ]这个模型设计有几个实用技巧:
- 使用
active字段实现软删除 - 通过
help参数添加字段说明 - 使用SQL约束保证业务逻辑正确性
- 采用标准的
_name命名规范(模块名.模型名)
3.2 添加关联模型
继续在同一个文件中添加动作模型:
class StockAlertAction(models.Model): _name = 'stock.alert.action' _description = '预警触发动作' rule_id = fields.Many2one('stock.alert.rule', '所属规则') action_type = fields.Selection([ ('email', '发送邮件'), ('task', '创建任务'), ('order', '生成采购订单')], '动作类型', required=True ) recipient_ids = fields.Many2many( 'res.users', string='接收人' ) template_id = fields.Many2one( 'mail.template', '邮件模板', domain="[('model_id.model', '=', 'stock.alert.rule')]" ) @api.onchange('action_type') def _onchange_action_type(self): if self.action_type == 'email': return {'domain': {'template_id': [ ('model_id.model', '=', 'stock.alert.rule') ]}}这里展示了Odoo强大的动态域功能:当动作类型选择"email"时,模板选择框只显示适用于库存预警规则的邮件模板。
4. 实现业务逻辑
4.1 添加定时检查方法
在StockAlertRule类中添加核心业务逻辑:
def _check_stock_levels(self): """ 核心方法:检查库存水平并触发预警 """ ProductProduct = self.env['product.product'] StockQuant = self.env['stock.quant'] for rule in self: domain = [] if rule.warehouse_id: domain.append(('location_id.warehouse_id', '=', rule.warehouse_id.id)) if rule.product_category_id: domain.append(('product_id.categ_id', '=', rule.product_category_id.id)) quants = StockQuant.search(domain) for quant in quants: if rule.threshold_type == 'quantity': value = quant.quantity elif rule.threshold_type == 'value': value = quant.inventory_value else: # days value = (fields.Date.today() - quant.in_date.date()).days if value <= rule.critical_threshold: self._trigger_actions(quant, 'critical') elif value <= rule.warning_threshold: self._trigger_actions(quant, 'warning') def _trigger_actions(self, quant, level): """ 触发配置的动作 """ for action in self.action_ids: if action.action_type == 'email': self._send_alert_email(action, quant, level) elif action.action_type == 'task': self._create_alert_task(action, quant, level) elif action.action_type == 'order': self._generate_purchase_order(action, quant, level)这段代码实现了:
- 根据规则条件构建查询域
- 获取库存数据并计算比较值
- 根据阈值触发不同级别的预警
- 执行配置的各类动作
4.2 实现邮件通知功能
继续添加邮件发送方法:
def _send_alert_email(self, action, quant, level): """ 发送预警邮件 """ if not action.recipient_ids or not action.template_id: return template = action.template_id.with_context( default_model='stock.alert.rule', default_res_id=self.id, quant=quant, level=level, rule=self ) for recipient in action.recipient_ids: template.send_mail(recipient.id, force_send=True)这里使用了Odoo的邮件模板系统,通过上下文传递动态数据。建议提前在后台配置好邮件模板,包含类似这样的内容:
主题:库存预警 - ${level == 'critical' and '严重' or '警告'}级别 内容: 产品 ${quant.product_id.name} 在 ${quant.location_id.complete_name} 的库存${rule.threshold_type}已触发${level == 'critical' and '严重' or '警告'}预警! 当前值:${value} 阈值:${level == 'critical' and rule.critical_threshold or rule.warning_threshold}5. 设计用户界面
5.1 表单视图设计
在views/stock_warehouse_views.xml中添加:
<record id="view_stock_alert_rule_form" model="ir.ui.view"> <field name="name">stock.alert.rule.form</field> <field name="model">stock.alert.rule</field> <field name="arch" type="xml"> <form> <header> <button name="_check_stock_levels" string="立即检查" type="object" class="oe_highlight"/> <field name="active" widget="boolean_button"/> </header> <sheet> <group> <group> <field name="name"/> <field name="warehouse_id"/> <field name="product_category_id"/> </group> <group> <field name="threshold_type"/> <field name="warning_threshold"/> <field name="critical_threshold"/> </group> </group> <notebook> <page string="触发动作"> <field name="action_ids"> <tree editable="bottom"> <field name="action_type"/> <field name="recipient_ids" widget="many2many_tags"/> <field name="template_id"/> </tree> </field> </page> <page string="高级设置"> <!-- 可以添加更多高级选项 --> </page> </notebook> </sheet> </form> </field> </record>这个表单视图包含了:
- 顶部按钮:手动触发库存检查
- 基本信息区域:规则名称、适用范围
- 阈值设置区域:预警参数配置
- 标签页:动作配置和高级设置
5.2 列表视图设计
继续添加列表视图:
<record id="view_stock_alert_rule_tree" model="ir.ui.view"> <field name="name">stock.alert.rule.tree</field> <field name="model">stock.alert.rule</field> <field name="arch" type="xml"> <tree> <field name="name"/> <field name="warehouse_id"/> <field name="product_category_id"/> <field name="threshold_type"/> <field name="warning_threshold"/> <field name="critical_threshold"/> <field name="active"/> </tree> </field> </record>5.3 添加菜单和动作
最后配置菜单入口:
<!-- 动作定义 --> <record id="action_stock_alert_rule" model="ir.actions.act_window"> <field name="name">库存预警规则</field> <field name="res_model">stock.alert.rule</field> <field name="view_mode">tree,form</field> </record> <!-- 菜单定义 --> <menuitem id="menu_stock_alert_root" name="库存预警" parent="stock.menu_stock_root"/> <menuitem id="menu_stock_alert_rules" name="预警规则" parent="menu_stock_alert_root" action="action_stock_alert_rule"/>这里将新模块的菜单挂载到了库存管理的根菜单下,保持了系统的整体性。
6. 配置权限和安全规则
6.1 定义访问权限
在security/ir.model.access.csv中添加:
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink access_stock_alert_rule,stock.alert.rule,model_stock_alert_rule,base.group_user,1,1,1,1 access_stock_alert_action,stock.alert.action,model_stock_alert_action,base.group_user,1,1,1,1这个配置给普通用户(base.group_user)赋予了完整的CRUD权限。如果需要对不同角色设置不同权限,可以创建更多记录。
6.2 添加记录规则
有时候需要更细粒度的权限控制,可以在security/stock_alert_security.xml中添加记录规则:
<record id="stock_alert_rule_user_rule" model="ir.rule"> <field name="name">库存预警规则用户权限</field> <field name="model_id" ref="model_stock_alert_rule"/> <field name="domain_force">['|', ('warehouse_id', '=', False), ('warehouse_id.manager_id', '=', user.id)]</field> <field name="groups" eval="[(4, ref('stock.group_stock_user'))]"/> </record>这条规则限制普通库存用户只能看到自己管理的仓库的预警规则。
7. 测试与部署
7.1 开发环境测试
安装模块前,建议先运行Odoo的测试模式:
python odoo-bin -i stock_alert --test-enable这会自动执行模块中的测试用例。我们可以在tests/目录下添加测试类:
from odoo.tests.common import TransactionCase class TestStockAlert(TransactionCase): def setUp(self): super().setUp() self.Warehouse = self.env['stock.warehouse'] self.Product = self.env['product.product'] self.AlertRule = self.env['stock.alert.rule'] self.warehouse = self.Warehouse.create({ 'name': '测试仓库', 'code': 'TEST' }) self.product = self.Product.create({ 'name': '测试产品', 'type': 'product', 'categ_id': self.env.ref('product.product_category_all').id }) def test_alert_trigger(self): rule = self.AlertRule.create({ 'name': '测试规则', 'warehouse_id': self.warehouse.id, 'threshold_type': 'quantity', 'warning_threshold': 50, 'critical_threshold': 20 }) # 模拟库存变化 self.env['stock.quant'].create({ 'product_id': self.product.id, 'location_id': self.warehouse.lot_stock_id.id, 'quantity': 10 }) # 执行检查 rule._check_stock_levels() # 验证是否触发了严重预警 # 这里可以添加更多断言7.2 生产环境部署
当测试通过后,在生产环境安装模块时建议:
- 先在测试环境完整验证
- 备份生产数据库
- 使用
--stop-after-init参数先检查依赖:python odoo-bin -i stock_alert --stop-after-init - 确认无误后再正式安装
8. 扩展与优化
8.1 添加仪表盘视图
为了让预警信息更直观,可以添加看板视图:
<record id="view_stock_alert_kanban" model="ir.ui.view"> <field name="name">stock.alert.kanban</field> <field name="model">stock.alert.rule</field> <field name="arch" type="xml"> <kanban default_group_by="warehouse_id"> <templates> <t t-name="kanban-box"> <div class="oe_kanban_card"> <div class="oe_kanban_content"> <strong><field name="name"/></strong> <div>仓库: <field name="warehouse_id"/></div> <div>产品分类: <field name="product_category_id"/></div> <div class="progress"> <div class="progress-bar" role="progressbar" t-att-style="'width: ' + (100 * (warning_threshold - critical_threshold) / warning_threshold) + '%'"> </div> </div> </div> </div> </t> </templates> </kanban> </field> </record>8.2 实现自动定时检查
为了让系统自动运行库存检查,可以添加定时任务:
from odoo import api, models class IrCron(models.Model): _inherit = 'ir.cron' @api.model def _setup_stock_alert_cron(self): """ 创建或更新定时任务 """ cron = self.env.ref('stock_alert.cron_stock_alert', raise_if_not_found=False) if not cron: cron = self.env['ir.cron'].create({ 'name': '库存预警检查', 'model_id': self.env.ref('stock_alert.model_stock_alert_rule').id, 'state': 'code', 'code': 'model._check_stock_levels()', 'interval_number': 1, 'interval_type': 'hours', 'numbercall': -1, 'doall': False, 'active': True, }) return cron然后在__manifest__.py的data列表中添加:
'data': [ 'security/ir.model.access.csv', 'views/stock_warehouse_views.xml', 'data/ir_cron.xml', ],8.3 性能优化建议
当产品数量很多时,库存检查可能会比较耗时。可以考虑以下优化:
- 分批处理:将检查任务分成多个小任务
- 添加索引:在频繁查询的字段上添加数据库索引
- 缓存结果:对不常变动的数据使用缓存
- 异步执行:将耗时操作放到后台任务队列
def _check_stock_levels_batch(self, batch_size=100): """ 分批检查库存 """ for i in range(0, len(self), batch_size): batch = self[i:i+batch_size] batch._check_stock_levels() self.env.cr.commit() # 定期提交事务