mcpField
O
odoo-workflow

odoo-workflow

github.com/unclecatvn开发14856 次安装2 周前更新
代码仓库

关于 odoo-workflow

odoo-workflow 是一项指导开发者在修改前追踪 Odoo 代码与引用的技能。它使用按版本工作的辅助命令和基于引用的上下文摘要,帮助处理 Python、XML、JavaScript 与数据变更并减少错误。

主要功能

  1. 01发现版本与运行环境
  2. 02追踪模型、字段和方法
  3. 03搜索 XML、JavaScript 与引用
  4. 04编写基于引用的上下文摘要
  5. 05测试与迁移操作指引

适用场景

  1. 01添加变更前核实字段是否存在
  2. 02查找正确的方法重载位置
  3. 03检查继承视图的锚点
  4. 04迁移前追踪模块依赖

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/unclecatvn/agent-skills --skill odoo-workflow

安装后在 Claude Code 中输入/odoo-workflow确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
definition-of-done.md
scripts/
odoo_trace.py
templates/
CLAUDE.md

详细介绍

Odoo 工作流——先追踪,再编码

每次修改 Odoo 都要遵循此流程。代价高昂的故障总是那些老问题:不存在的字段或方法、从未存在过的 xpath 锚点、挂接到错误方法的重写、重命名存储字段却未迁移、无人能审计的 sudo(),以及运行后一个测试都没执行的测试套件。解决办法很简单:没有引用依据,就不写代码。

适用范围

  • 完整流程——任何最终涉及 Odoo Python、XML、JS 或数据的工作:新增字段、重写、继承视图、OWL 补丁、向导、定时任务、控制器、报表、安全设置、迁移、错误修复、重构、“优化”、“清理”。粘贴的 traceback 也从这里开始:工作区模块中最内层的栈帧就是第一条引用依据。
  • 简要流程——关于 Odoo 符号的问题(“X 定义在哪里”、“sale.order 有没有 F”、“19 中哪个模型会获得此字段”):执行步骤 0–2,然后用简报作答,不写代码。
  • 快速流程——修改不涉及所编辑模块之外的符号,也不新增字段、重写、xmlid、xpath、组或 depends:例如模块自身代码中的标签、帮助文本或错别字,清单版本号更新,仅修改测试。简报只需一行,引用被编辑的代码行;跳过步骤 1、3 和 6;步骤 5 仍适用(字符串变更时要处理 i18n)。有任何疑问时,走完整流程。
  • 仅涉及文档或 git 的任务可跳过(使用 odoo-commit)。
  • 头脑风暴和规划类技能应在步骤 2 之后使用,且仅限业务意图仍不明确的情况——它们以上下文简报为输入。

步骤 0——运行环境、版本、参考包

内置辅助脚本 scripts/odoo_trace.py(Python 3.8+ 标准库,只读)用于解析运行环境并回答 Python 侧的追踪问题。每次调用时都要写出技能的完整基础目录——Shell 变量不会在工具调用之间保留。下文中的 odoo_trace 表示 python3 <skill_dir>/scripts/odoo_trace.py。

odoo_trace env

按以下顺序查找,命中即停止:命令行标志、.claude/odoo.json、.claude/launch.json 中 addons 覆盖当前工作目录的 odoo-bin 配置(这两个文件位于当前工作目录或最近的父目录中),然后是 addons_path 覆盖当前工作目录的唯一 *.conf 文件;从父目录向上搜索(每一级及其子目录)。脚本会输出 version(来自 odoo/release.py)、odoo_root、conf、python、dev_db,以及 Odoo 使用顺序中的 addons 根目录(第一个包含模块名称的根目录优先)。.claude/odoo.json 中的 odoo_version 或 .odoo-version 文件中的版本必须与 release.py 一致。

  • 退出码为 2 时,会输出 SETUP ERROR: 和原因。如果未找到运行环境:询问一次核心代码库的检出目录和 conf,然后在每次调用时(包括 roots)将它们作为 odoo_trace --odoo-root <core> --conf <conf> <command> 传入,并提议写入被 git 忽略的 .claude/odoo.json,包含 odoo_version、odoo_root、conf、python、dev_db(本机路径)。版本来源不一致,或有多个 conf 覆盖当前工作目录:询问哪个才是正确的。绝不能只针对自定义根目录进行追踪。
  • 用户或项目的 CLAUDE.md / AGENTS.md 中的明确说明优先于辅助脚本。若两者不一致,停止并询问。不要默默假定版本。
  • 使用 skills CLI 加载对应主版本的 Odoo 参考包 odoo-<major>;在 Claude Code 插件中则加载 <plugin>:odoo-<major>-0(例如 agent-skills:odoo-18-0)。阅读其中的 references/api-highlights.md(此版本中已移除或重命名的 API),并且只阅读任务所需的指南章节(执行 grep -n '^## ' <guide>,然后按范围读取)。如果缺少参考包,说明这一点并继续——真实源码才是权威,不能依赖记忆。

步骤 1 - 追踪调用链

Python 符号:辅助工具。 它使用 ast 解析类,应用该版本的 _name 规则,追踪 _inherit 父类(如 mail.thread、portal.mixin 等混入类)、_inherits 委托、base 扩展和核心 BaseModel,标记模块从未导入的文件中的类(UNIMPORTED), 并打印绝对路径 file:line。

问题命令
定义或扩展 MODEL 的类、其混入类、类型、_auto、_log_accessodoo_trace model MODEL
MODEL 上是否存在 FIELD(自身、父类、_inherits、魔法字段),每个定义及 comodel;会逐层检查点分隔的路径odoo_trace field MODEL FIELD[.FIELD...]
METHOD 的每个定义,按 super() 顺序排列(#1 最先运行)、super() 调用、钩子、AMBIGUOUS 配对odoo_trace method MODEL METHOD --module MODULE
MODULE 按加载顺序排列的传递依赖 dependsodoo_trace depends MODULE
xmlid 是否存在(已加载的 XML 记录或 CSV 行,或安装时生成)odoo_trace xmlid module.name
MODULE 中从未加载的文件(不在 data/assets 中,也未被导入)odoo_trace unlisted MODULE

最后一行是 VERDICT。退出码 0 = 找到,1 = NOT FOUND(结论会列出最接近的真实符号),2 = 设置错误或答案未知。只有退出码 1 才能支持 NOT FOUND 单元格。对于枢纽模型(sale.order、mail.thread)的列表会被截断;在 model、method 或 unlisted 后加上 --all 可打印完整列表。

XML、JS、控制器、使用情况:在相同根目录中 grep。 下文的 ROOTS 表示完整展开后的 $(odoo_trace roots):在 zsh 和 bash 中,命令替换会拆分成每个根目录一个参数;普通的 $ROOTS 字符串在 zsh 中不会拆分。--include glob 外的引号要保留。若 grep 警告 No such file or directory 或退出码为 2,说明命令有误,不能判定为 NOT FOUND。在 odoo_trace env 退出码为 2 时运行 grep 也一样有误(空的 $(odoo_trace roots) 会使 grep -r 只搜索当前工作目录);在路径包含空格的根目录中运行 grep 也是如此:路径会被拆分,macOS grep 会静默跳过这些片段,因此要对该根目录单独运行 grep,并将路径加引号。

# V1. Views on MODEL
grep -rlE --include='*.xml' "<field name=\"model\">MODEL</field>" ROOTS
# V2. Every view and QWeb template inheriting VIEW; -A1 also catches inherit_id split over two lines
grep -rnE -A1 --include='*.xml' "name=['\"]inherit_id['\"]|inherit_id=['\"]" ROOTS | grep -E ":[0-9]+:.*(ref|inherit_id)=['\"](MODULE\.)?VIEW['\"]|-[0-9]+-\s*ref=['\"](MODULE\.)?VIEW['\"]"
# V3. ANCHOR nodes inside one view record, absolute lines. A = the id line, B = the next </record>: grep -nE 'id="VIEW"|</record>' VIEW_FILE
awk -v a=A -v b=B 'NR>=a && NR<=b && /name="ANCHOR"/ {print FILENAME":"NR": "$0}' VIEW_FILE
# W1. OWL / QWeb client template: definition and every t-inherit (extension or primary)
grep -rnE --include='*.xml' "t-name=['\"]TEMPLATE['\"]|t-inherit=['\"]TEMPLATE['\"]" ROOTS
# W2. JS class or component: its definition (= import path) and every patch of it
grep -rnE --include='*.js' "^export (default )?(class|function|const) NAME[^A-Za-z0-9_]|patch\(\s*NAME[^A-Za-z0-9_]" ROOTS
# C1. Route (the second pattern finds a decorator split over lines), then every subclass of
#     its controller class (an `import CLASS as ALIAS` hit: run the class grep again with ALIAS)
grep -rnE --include='*.py' "@(http\.)?route\(.*['\"]URL['\"]|^\s*(route=)?\[?\s*['\"]URL['\"]" ROOTS
grep -rnE --include='*.py' "^class \w+\(([^)]*[ ,])?([A-Za-z_.]+\.)?CLASS[,)]|import.*[ ,]CLASS as " ROOTS
# S1. ACL rows and record rules on MODEL (dots as underscores)
grep -rn --include='ir.model.access.csv' "model_MODEL_UNDERSCORED," ROOTS
grep -rlE --include='*.xml' "model_MODEL_UNDERSCORED['\"]" ROOTS | grep security
# U1. Every use of NAME before renaming, removing, or changing the signature of a field or method
grep -rnwE --include='*.py' --include='*.xml' --include='*.js' --include='*.csv' --include='*.scss' "NAME" ROOTS
# T1. Tests to mirror
ls BASE_MODULE/tests/; grep -rnE "def test_.*METHOD" MODULE/tests

对于 U1,保留命中 MODEL 的结果并逐一列出:视图、QWeb、@api.depends、related=、 域、记录规则、定时任务和服务器操作 code、<function>、按钮 name=、JS orm.call 字符串、测试、迁移。对于通用名称(name、state、partner_id),将列表限制为 odoo_trace model MODEL 打印的模块以及 V1/V2 中的视图;如果仍超过 50 个命中,按模块显示数量,并在继续前询问。

只有当命中文件会被加载(odoo_trace unlisted MODULE 会列出从未加载的文件),且在 Python 中位于 MODEL 自身的类内、而非同一文件的其他类中时,grep 命中才计数。

阅读预算——这里会消耗 token:

  • 阅读代码范围,而非整个文件:使用 offset/limit 阅读,或使用 awk 'NR>=A && NR<=B'——切勿使用 sed -n 'A,Bp' | grep -n,因为其行号会从 1 重新开始,无法引用。每一跳最多 120 行。
  • 行内最多 3 跳。更深层或跨模块的调用流程交给 odoo-code-tracer Agent,并提供 odoo_version、pack_dir(已加载包的目录)、roots 和 helper(辅助工具的完整路径,以及 env 所需的任何标志),前提是该 Agent 已安装;否则继续使用上述命令。
  • 切勿重新阅读已在 Brief 中引用过的文件。

步骤 2 - 上下文摘要(无引用、无代码)

在任何代码之前编写摘要;每个受更改影响的符号占一行(以 Odoo 18 为例):

层符号来源(文件:行号)基础行为变更
模型purchase.orderaddons/purchase/models/purchase_order.py:19_name,继承 mail.thread继承
方法+superbutton_approvepurchase_order.py:528;purchase_stock/models/purchase_order.py:117 中的重写会调用 _create_picking仅当安装了 purchase_stock 时才会创建拣货单依赖 purchase_stock,调用 super,然后执行 X
路径stock.picking partner_id.commercial_partner_id.vatstock/models/stock_picking.py:630 -> base/models/res_partner.py:304 -> :247Many2one、Many2one、Char相关字段
视图+xpathpurchase.purchase_order_formpurchase/views/purchase_views.xml:130,锚点 partner_ref :185,表单中只有一个节点-在锚点后添加字段

层:模型 / 字段 / 方法+super / 路径 / 视图+xpath / 客户端 / 控制器 / 安全 / 公司 / 数据 / 迁移 / i18n / 测试。

来源单元格必须恰好符合以下一种情况:

  • file:line - 所引行本身能证明该声明(定义、记录、锚点节点)。
  • NOT FOUND - 辅助程序以状态码 1 退出,或对所有 ROOTS 在 MODEL 上进行干净的 grep 后未找到任何匹配项。 若存在最接近的实际符号,请注明。
  • UNCERTAIN: <缺少的内容> - 指明未读取的文件、运行时构建的域,或提供该锚点的其他模块视图。对于上方命令能够回答的问题,绝不使用此项。
  • AMBIGUOUS: <modules> - 覆盖项位于彼此不依赖的模块中。

凡是不符合 file:line 的单元格,均表示该行没有可用代码:删除该行、添加缺少的 depends,或提问。超过 15 行意味着任务应拆分;绝不能为了凑数而删除一行。

规则:

  • 重新定义的字段。 odoo_trace field 会按加载顺序列出每个定义;请为变更所依赖属性的每个定义提供引用。未重复定义的属性保留较早的值(selection_add 会扩展列表),除非较新的类没有继承较早的类:此时较早的属性会被丢弃,助手会打印 final class。
  • 路径(related=、@api.depends、depends=、domain=、domain_force)。对点分路径上的每一跳调用一次 field,每一跳提供一个引用。某一跳不存在,或路径末尾之前出现非关系字段,则该跳为 NOT FOUND。
  • 依赖项。 仅在 odoo_trace depends MODULE 外部模块中定义的符号,即使存在于当前数据库中,也无法保证已安装。请添加依赖项或删除该行。
  • 重载顺序。 请通过 odoo_trace method ... --module MODULE 获取,切勿凭记忆或 grep 顺序判断:最后加载的类先运行,其 super() 会调用较早的类。请将变更所依赖其重载的每个模块声明为 depends。标记为 AMBIGUOUS 的模块对仍为 AMBIGUOUS。
  • Xmlid。 以 odoo_trace xmlid 的结果为准:在清单加载的 XML 或 CSV 文件中的记录,或安装时生成的记录(model_*、field_*__*、selection__*、base.module_*、图表模板中的 account.<company id>_<name>),均归属于助手指定的模块。仅存在于清单从未加载的文件中的记录为 NOT FOUND(若初始化钩子可能会加载,则为 UNKNOWN)。切勿臆造 xmlid,也绝不要将生成的 xmlid 报告为缺失。视图 groups= 中缺失的组不会阻止安装:Odoo 只会记录 WARNING,并隐藏该节点,因为没有用户属于不存在的组。
  • 魔法字段。 切勿想当然;以 odoo_trace field 的结果为准。只有 _log_access 为 true 时,才存在 create_uid、create_date、write_uid、write_date;其默认值取决于 _auto,因此,报表和 SQL 视图模型(_auto = False)没有这些字段。
  • Xpath 锚点。 锚点必须位于基视图中,或位于 depends 中某个模块的继承视图中;否则为 UNCERTAIN。定位器会匹配第一个节点,因此请说明组合视图(V2 + V3)中有多少个节点带有该名称。
  • 公司。 如果 MODEL 或某个关联模型有 company_id,请提供引用,并说明该模型是否设置了 _check_company_auto。指向受公司约束的关联模型的新关系字段应设置 check_company=True。对于带有 company_id 的新模型,应像核心代码那样添加记录规则:[('company_id', 'in', company_ids)];如果公司为可选,则使用 [('company_id', 'in', company_ids + [False])]。
  • 架构变更。 重命名、删除或更改存储字段类型,更改或删除 selection 键,添加 required,向已有数据的表添加存储型计算字段,或重命名模型,都需要一行 migration:填写新的清单版本,以及迁移数据的 migrations/<version>/pre-*.py / post-*.py 脚本(def migrate(cr, version));仅当脚本版本高于已安装版本且不高于清单版本时,脚本才会运行。没有迁移脚本时,重命名的存储字段会在 -u 时丢失其列和数据,且不会发出提示。只有用户确认该模块从未安装在重要数据库中时,才可跳过。相关方案见该工具包的迁移指南。
  • sudo。 每个新增的 sudo() 或 with_user(SUPERUSER_ID) 都需要一行 security:说明其绕过了哪项 ACL 或记录规则(S1),以及调用前进行的检查(18+ 版本上的 check_access / has_access、访问令牌或组检查)。没有该行,就不要使用 sudo。
  • 优先复用,避免臆造:添加字段、辅助方法或计算字段之前,先查找是否已有可用项。
  • “代码整洁”/“遵循基础实现”意味着:遵循基础实现的模式,只做最小改动,绝不重写 super() 已经完成的工作。“优化”若指速度,则意味着:先测量(针对明确用例记录查询次数或耗时),修改后再次测量。

第 3 步 - 最简计划

最多 10 条:要修改的文件、明确不修改的文件,以及延期事项。除非 Brief 证明改动涉及两个模块,否则只涉及一个模块;每个涉及的 git 仓库各创建一个提交和 PR。遵循 YAGNI:不会变化的值不添加配置,只使用一次的内容不编写辅助函数,只有一种实现时不添加抽象。

第 4 步 - 实施

  • 遵循该 pack 的 Coding Conventions 和此版本的 api-highlights.md 列表;现有的 stable-addon 风格优先于个人偏好。
  • 在改动需要时,重写实际运行的最窄钩子(钩子 odoo_trace method 会打印),而不是包装它的公共方法。调用 super() 并只添加最小改动;绝不复制基类方法体。
  • 使用 _inherit = 'model'(字符串)扩展模型;新模型需显式设置 _name。不带 _name 的列表形式 _inherit 会创建一个以类名命名的新模型:在 19 中始终如此(并会警告),在 18 中则在列表包含两项时如此。
  • 每个新增的 XML 或 CSV 文件都要按加载顺序加入清单的 data(groups、ir.model.access.csv、视图、菜单);每个新增的静态文件都必须对应正确 assets 包中的条目。未列入清单的文件能够正常安装,但不会生效。
  • sudo() 命名:将通过 sudo() 获取的任何记录集绑定到一个以 _sudo 结尾的名称(partner_sudo = self.partner_id.sudo(), orders_sudo = self.env['sale.order'].sudo().search(domain))。单次调用(record.sudo().write(vals))无需变量。绝不从公共方法返回 _sudo 记录集,也不将其保留在 self 上。
  • Odoo 代码中不得出现 ponytail: 或其他“暂且简化”标记——代码要么正确,要么第 3 步的计划中要说明延期事项。
  • 新增的面向用户的字符串必须通过 _() / _t() / string= / help= 编写,以便提取。

第 5 步 - 完成标准

报告完成前,请阅读 references/definition-of-done.md:其中列出了各版本的准确命令。简而言之:

  • 整个检查清单只使用一个临时数据库 scratch_<module>:使用该模块的测试进行安装,测试摘要中计数 N > 0(没有摘要或显示 of 0 tests 均不算通过),不得有 ERROR/CRITICAL,并说明每条与该模块相关的 WARNING。除非用户要求,否则绝不对命名数据库(conf 的 db_name、staging、dev)执行 -i/-u。
  • 如果 Brief 中有 migration 行,则在带有先前版本的数据库副本上测试升级。
  • odoo_trace unlisted <module> 报告所有文件均已加载。
  • 每个新增模型都要有 ACL 行;适用 Company rule 时要有公司规则;每个 sudo() 都要有对应的 security 行。
  • 字符串有变更时重新生成 i18n;随项目发布的 .po 文件保持原文件名。
  • 如果项目有自己的 linter,则该 linter 对已修改文件的检查必须通过。
  • 所有涉及的 git 仓库均通过 Hygiene 检查。
  • 删除临时数据库及其 filestore。通过 odoo-commit 提交([TAG] module: description,不注明 AI 署名);每个仓库各创建一个提交和 PR。

第 6 步 - 交接

如果已安装,运行 odoo-code-review agent,并传入 odoo_version、pack_dir、roots 和 helper。如果未安装,则在报告完成前自行检查 references/definition-of-done.md 中的 Security 和 Hygiene 列表。

评论

支持 Markdown