简介:本资源是Odoo 17官方数据库结构的完整中文版数据字典,专为Odoo开发者、实施顾问及进阶学习者设计,解决英文文档阅读门槛高、模型字段含义不明确、二次开发时难以快速定位核心表与关联关系等实际问题。压缩包仅含1个PDF文件(8.07MB),内容由Navicat自动生成,覆盖odoo数据库全量public模式下的200+核心数据表,如account_account、account_move_line、res_partner等,并逐表标注字段名、类型、约束及业务含义,目录层级清晰,支持关键词检索与跳转。已有133人下载学习,适合在定制开发、数据迁移、权限配置或调试SQL时作为权威参考手册随时查阅。字典中包含服务器连接信息、生成时间戳与版本标识,确保技术时效性与环境可追溯性,是理解Odoo底层数据建模逻辑不可或缺的实战型工具文档。
1. Odoo 17 数据字典(已翻译为中文):不是文档,是能直接查表、改字段、写SQL的“数据库导航仪”
你有没有在 Odoo 17 项目里卡过这种场景:
- 想给销售单加个自定义字段,但不确定该挂到
sale_order还是account_move表上? - 写报表 SQL 时翻遍模型定义,却找不到
account_move_line里那个叫balance的字段到底对应哪个物理列? - 调试一个权限问题,发现
res_groups和ir_model_access表之间有张中间表叫res_groups_implied_rel,但完全不知道它存的是谁 implied 谁?
这时候,官方文档里查不到、开发者模式点不开、PostgreSQLpsql \d+输出又太原始——你真正缺的,是一份带中文注释、按物理表组织、保留 Navicat 生成结构、覆盖 Odoo 17 核心模块全量表关系的数据字典。这不是 PDF 教程,也不是 API 手册,而是一份可打印、可搜索、可 Ctrl+F 定位、能直接抄表名/字段名/外键约束的「数据库导航仪」。它专为正在做 Odoo 二次开发、数据迁移、SQL 报表、性能调优或系统集成的工程师准备——尤其适合那些刚从 Odoo 15/16 升级上来、面对新引入的account_accrued_orders_wizard或account_payment_method_line等表一头雾水的开发者。这份资源不教你怎么装 Odoo,但它能让你在 30 秒内确认:account_move_reversal_new_move是个视图还是实体表?它的主键是不是id?它和account_move是一对多还是多对一?这才是真实开发里最常被卡住的“最后一公里”。
2. 为什么必须用这份中文数据字典:Odoo 原生机制决定你绕不开物理层
2.1 Odoo 的 ORM 层 vs 数据库层:两个世界,一套真相
Odoo 的强大在于其 ORM(对象关系映射):你写self.env['account.move'].search([('state', '=', 'posted')]),它自动翻译成 SQL;你定义fields.Many2one('res.partner'),它自动建外键、加索引、处理级联。但这种抽象是一把双刃剑——当你要做三件事时,ORM 会彻底隐身,你必须直面 PostgreSQL:
- 写高性能报表:ORM 的
read_group在千万级account_move_line上可能慢 8 秒,而一条带PARTITION BY和MATERIALIZED VIEW的原生 SQL 可压到 200ms; - 做跨库数据同步:把 Odoo 订单同步到外部 ERP,你得知道
sale_order表里date_order是timestamp with time zone类型,而validity_date是date,类型错一位就全量失败; - 排查数据不一致:用户说“付款没到账”,你查
account_payment状态是posted,但account_move_line里reconciled字段却是False——这时你得立刻定位account_partial_reconcile表的debit_move_id和credit_move_id是否真指向这两条记录。
提示:Odoo 17 的
account模块新增了account_cash_rounding表用于四舍五入处理,但它的strategy字段在 ORM 层叫rounding_method,而在数据库里实际列名是strategy。这种命名错位在stock、mrp模块中高频出现,仅靠.py模型文件根本无法穷举。
2.2 为什么 Navicat 生成的字典比pg_dump -s更实用?
pg_dump -s odoo输出的是纯 DDL(CREATE TABLE 语句),优点是权威,缺点是无业务语义、无字段注释、无表间关系可视化。而本资源基于 Navicat 16 生成,它做了三件关键事:
- 自动提取 COMMENT:Navicat 连接 PostgreSQL 后,会读取
pg_description系统表,把COMMENT ON COLUMN account_move.name IS 'Journal Entry Number'转成中文“记账凭证编号”; - 显式标注外键路径:比如
account_move_line.account_id→account_account.id,不仅写出字段名,还标出引用表和主键,避免你误以为account_id指向res_partner; - 保留模块分组逻辑:目录结构严格按 Odoo 模块划分(
account_,stock_,mrp_,hr_),而不是 PostgreSQL 的 schema 顺序。你在查mrp_bom_line时,不会被ir_attachment这类通用表干扰视线。
2.3 中文翻译不是简单机翻:术语统一性决定你能否少踩 3 小时坑
Odoo 社区中文翻译长期存在术语混乱:
journal有人译“日记账”,有人译“日志”,本字典统一为“日记账”(因account.journal在财务领域确指此意);reconcile在会计模块译“对账”,在银行模块译“核销”,本字典按上下文区分,绝不一刀切;implied这种抽象词,不译“隐含”而译“继承组”,因为res_groups_implied_rel实际表达的是“A 组自动获得 B 组权限”的继承关系。
这种粒度的翻译,直接反映在字段注释里。例如account_move_line.debit的注释是“借方金额(单位:公司本位币)”,括号里明确单位,而credit对应“贷方金额(单位:公司本位币)”。这种细节,在你写SUM(debit) - SUM(credit)做余额校验时,就是避免负数异常的关键。
3. 怎么用:从快速定位到批量导出,一份字典的六种打开方式
3.1 快速定位:用 Ctrl+F 查表名,3 秒锁定核心字段
这是最常用场景。假设你要给采购单增加“预计到货天数”字段,需确认挂载位置:
- 打开 PDF(或 Markdown 版本),Ctrl+F 输入
purchase_order; - 定位到
2.1.1.1.134. 表: purchase_order(以实际页码为准); - 往下看字段列表,找到
date_planned字段,注释为“预计到货日期”; - 注意其数据类型是
timestamp with time zone,且NOT NULL = False; - 再看外键部分:
partner_id → res_partner.id,确认供应商关联逻辑。
逻辑说明:
date_planned是purchase.order模型的字段,但物理表中它直接作为purchase_order.date_planned存在,无需 join 其他表。这和account_move.line_ids不同——后者是 ORM 关系字段,物理层不存在line_ids列,实际数据在account_move_line.move_id外键中。
3.2 关系追踪:顺着箭头画出你的数据流图
Odoo 表关系复杂,但字典用→符号固化了所有外键。以“查看某张销售单的全部付款记录”为例:
- 起点:
sale_order.id = 12345; - 第一步:
sale_order.id → account_move.sale_order_id(注意:Odoo 17 中account_move新增sale_order_id外键,指向销售单); - 第二步:
account_move.id → account_payment.move_id(付款单通过move_id关联到记账凭证); - 第三步:
account_payment.id → account_payment_register.payment_ids(注册向导关联付款);
整条链路在字典中是连续呈现的:sale_order→account_move→account_payment→account_payment_method_line
你不用翻 5 个模型文件,一眼看清数据如何穿透模块边界。
3.3 SQL 编写:复制粘贴字段名,避开大小写与下划线陷阱
Odoo ORM 字段名(如invoice_date)和数据库列名(如invoice_date)通常一致,但有 12% 的字段存在差异。典型案例如下:
| ORM 字段名 | 数据库列名 | 所在表 | 注释 | 风险 |
|---|---|---|---|---|
company_currency_id | company_currency_id | account_move | 公司本位币 | 一致,安全 |
user_id | create_uid | account_move | 创建人 | ORM 映射为create_uid,非user_id |
tax_ids | tax_id | account_move_line | 税率(注意是单数!) | 多对多字段在物理层拆成account_move_line_account_tax_rel中间表 |
参数说明:
tax_ids是 Many2many 字段,物理层不存于account_move_line表,而由中间表account_move_line_account_tax_rel的account_move_line_id和account_tax_id两列实现。你在写JOIN时,必须用中间表,而非直接ON aml.tax_ids = at.id——这种错误在初学者 SQL 中占比超 40%。
3.4 权限审计:用ir_model_data和ir_rule表反推数据可见性
当你发现某用户看不到account_analytic_line数据,别急着改ir.rule,先查字典:
- 定位
ir_rule表,看model_id字段注释:“关联的模型 ID(指向 ir_model.id)”; - 再查
ir_model表,找到model = 'account.analytic.line'对应的id; - 回到
ir_rule,筛选model_id = X,看domain_force字段值,如['|', ('company_id', '=', False), ('company_id', 'in', [user.company_id.id])]; - 最后查
account_analytic_line表结构,确认company_id字段是否存在、是否为integer类型、是否有索引。
这套组合拳,比在 Odoo 后台点 10 次“编辑规则”高效得多。
3.5 模块升级适配:对比 Odoo 16 与 17 表结构差异
Odoo 17 引入了account_accrued_orders_wizard(应计订单向导)和account_payment_method_line(支付方式行),而这些在 16 中不存在。字典中明确标注:
account_accrued_orders_wizard:类型为“表”,主键id,字段含date_start,date_end,journal_id,外键journal_id → account_journal.id;account_payment_method_line:类型为“表”,主键id,字段含payment_method_id,journal_id,sequence,外键双向指向account_payment_method和account_journal。
逻辑说明:
account_payment_method_line是 Odoo 17 支付方式配置的核心变更——它把原先account_payment_method表里的journal_id字段剥离,改为多对多关系,允许同一支付方式在多个日记账中启用。如果你的旧代码硬编码了payment_method.journal_id,升级后必报AttributeError。
3.6 批量导出:用 Python 脚本把字典转成 Excel 可筛选表格
字典 PDF 不便排序筛选,我们提供配套脚本将结构化内容转为 Excel:
# parse_dict_to_excel.py import re import pandas as pd from pathlib import Path def extract_tables_from_pdf(pdf_path): # 使用 PyMuPDF(fitz)提取文本,按标题层级切分 import fitz doc = fitz.open(pdf_path) text = "" for page in doc: text += page.get_text() doc.close() # 匹配 "表: xxx" 开头的块 table_blocks = re.split(r'表:\s+(.+?)\n', text) tables = [] for i in range(1, len(table_blocks), 2): if i+1 < len(table_blocks): table_name = table_blocks[i].strip() content = table_blocks[i+1] # 提取字段行:匹配 "字段名 类型 注释" fields = re.findall(r'^(\w+)\s+([^\n]+?)\s+(?=\w+\s|$)', content, re.M) for field in fields: tables.append({ 'table': table_name, 'field': field[0], 'type': field[1].split()[0] if field[1].split() else '', 'comment': ' '.join(field[1].split()[1:]) if len(field[1].split()) > 1 else '' }) return pd.DataFrame(tables) if __name__ == "__main__": df = extract_tables_from_pdf("odoo17_zh_dict.pdf") df.to_excel("odoo17_tables_fields.xlsx", index=False)逻辑说明:该脚本依赖
PyMuPDF(pip install PyMuPDF),它比pdfplumber更稳定解析 Navicat 生成的 PDF。输出 Excel 包含四列:table(表名)、field(字段名)、type(类型,如integer,character varying(128))、comment(中文注释)。你可以用 Excel 的“筛选”功能,一键找出所有含company_id的字段,或所有类型为timestamp with time zone的时间字段。
4. 避坑 / 常见问题 / 排查:六个血泪经验换来的边界警告
4.1 现象:查account_move_line表时,debit和credit字段值全为 0
原因:Odoo 17 默认启用accounting多币种,debit/credit存的是“公司本位币金额”,而你查的是currency_id != company_currency_id的记录,其本位币金额确为 0(实际值在amount_currency字段)。
解决:查多币种记录时,必须 JOINres_currency表,并用amount_currency * rate计算本位币,或直接查balance字段(balance = debit - credit)。
4.2 现象:account_payment表里state = 'posted',但account_move表无对应记录
原因:account_payment的move_id字段允许为空(NULL),当支付单未生成凭证时,move_id为NULL。字典中明确标注move_id → account_move.id的NULLABLE = True。
解决:SQL 查询必须加WHERE ap.move_id IS NOT NULL,否则LEFT JOIN account_move am ON ap.move_id = am.id会返回空am.*字段,误导你认为数据丢失。
4.3 现象:res_users表的login字段长度为character varying(64),但插入 50 个字符的邮箱报错
原因:login字段在res_users表中虽定义为varchar(64),但 Odoo ORM 层有额外校验:@api.constrains('login')要求符合邮箱正则,且login不能包含/、\等特殊字符。字典只反映数据库层,不覆盖 ORM 约束。
解决:插入前用re.match(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$', login)校验,或直接调用self.env['res.users'].create({'login': ...})让 ORM 处理。
4.4 现象:account_fiscal_position_tax表里tax_dest_id为NULL,但tax_src_id有值
原因:这是 Odoo 的“免税”配置逻辑——当tax_dest_id为NULL时,表示将tax_src_id对应的税码替换为“无税”。字典中tax_dest_id注释为“目标税码(NULL 表示免税)”,但新手易误判为数据异常。
解决:在报表中处理时,CASE WHEN aft.tax_dest_id IS NULL THEN '免税' ELSE (SELECT name FROM account_tax WHERE id = aft.tax_dest_id) END。
4.5 现象:account_analytic_line表的date字段值比account_move_line.date晚一天
原因:account_analytic_line.date是“分析行生效日期”,而account_move_line.date是“记账日期”,两者业务含义不同。字典中account_analytic_line.date注释为“生效日期(用于成本分摊计算)”,非“创建日期”。
解决:做时间维度分析时,必须明确使用场景:成本归集用analytic_line.date,财务核算用move_line.date,不可混用。
4.6 现象:ir_attachment表的res_model和res_id能查到附件,但res_field字段为空
原因:res_field仅在附件关联到模型的特定字段(如product.template.image_1920)时才填充;若附件是通过ir.attachment直接创建(如邮件附件),则res_field为NULL。字典中res_field注释为“关联字段名(NULL 表示全局附件)”。
解决:查询附件时,WHERE res_model = 'sale.order' AND res_id = 123即可,无需强求res_field IS NOT NULL。
5. 进阶用法:用字典驱动自动化脚本,把重复劳动压缩到 10 分钟
5.1 自动生成字段注释 SQL:给空数据库一键补全中文说明
当你新建一个 Odoo 17 数据库(如测试环境),PostgreSQL 表结构存在,但COMMENT全为空。手动COMMENT ON COLUMN500+ 字段不现实。字典可导出为 CSV,再生成批量注释脚本:
-- generate_comments.sql(由字典 CSV 生成) COMMENT ON COLUMN account_move.name IS '记账凭证编号'; COMMENT ON COLUMN account_move.state IS '状态(draft/posted/cancel)'; COMMENT ON COLUMN account_move.date IS '记账日期'; COMMENT ON COLUMN account_move.invoice_date IS '开票日期'; COMMENT ON COLUMN account_move.amount_total IS '总金额(公司本位币)'; -- ... 后续 495 行实现逻辑:用 Python 读取
odoo17_tables_fields.csv,过滤comment非空行,拼接COMMENT ON COLUMN {table}.{field} IS '{comment}';。执行该 SQL 后,任何 DBA 用psql \d+ account_move都能看到中文注释,极大提升协作效率。
5.2 构建字段血缘图谱:识别核心字段的上下游依赖
Odoo 中account_move_line.balance是核心指标,但它的值由debit/credit计算而来,而debit/credit又受account_account.user_type_id(科目类型)影响。字典帮你梳理出三层血缘:
| 层级 | 表/字段 | 依赖来源 | 业务影响 |
|---|---|---|---|
| L1(源头) | account_account.user_type_id | account_account_type.id | 决定科目是资产/负债/权益/收入/费用,影响balance正负逻辑 |
| L2(中间) | account_move_line.debit,credit | account_move_line.account_id→account_account.id | 实际录入的借贷金额 |
| L3(结果) | account_move_line.balance | debit - credit | 报表中显示的余额,不可直接修改 |
操作步骤:
- 在字典中查
account_move_line.balance,发现其注释为“余额(借方减贷方)”,类型numeric;- 查
account_move_line.debit和credit,确认它们是numeric类型,且NOT NULL = True;- 查
account_account.user_type_id,发现其外键指向account_account_type,而account_account_type表有type字段(asset/liability/equity/revenue/expense);- 结论:修改
account_account.user_type_id会改变balance的会计意义(如把资产科目改成费用科目,balance就从“结余”变成“发生额”)。
5.3 验证数据一致性:编写跨表校验 SQL 模板
字典中标注了所有外键,据此可写通用校验脚本。例如验证account_move_line的account_id是否都存在于account_account:
-- check_foreign_key_integrity.sql SELECT 'account_move_line.account_id' AS fk_field, COUNT(*) AS invalid_count FROM account_move_line aml LEFT JOIN account_account aa ON aml.account_id = aa.id WHERE aa.id IS NULL AND aml.account_id IS NOT NULL;参数说明:该模板可复用——只需替换
account_move_line、account_id、account_account、aa.id四处。字典中每个外键关系都明确写了→符号,如account_move_line.account_id → account_account.id,直接照抄即可。运行后若invalid_count > 0,说明存在“孤儿记录”,需清理或修复。
5.4 表结构变更追踪:用 Git 管理字典版本,捕捉 Odoo 升级风险
把字典 PDF 转为 Markdown(用pdf2md工具),并纳入 Git 仓库:
# 将 PDF 转为结构化 Markdown pdf2md odoo17_zh_dict.pdf > odoo17_dict.md git add odoo17_dict.md git commit -m "chore: add Odoo 17.0 data dictionary (Navicat generated)"当 Odoo 升级到 17.1 时,重新生成字典并提交:
pdf2md odoo171_zh_dict.pdf > odoo171_dict.md git diff odoo17_dict.md odoo171_dict.md | grep "^+" | grep "表:" # 输出:+2.1.1.1.201. 表: account_payment_token # +2.1.1.1.202. 表: payment_transaction逻辑说明:Git diff 能精准定位新增/删除的表。如发现
account_payment_token是 17.1 新增,则你所有涉及支付令牌的定制代码(如payment.token模型继承)都需同步升级,否则启动报错。这种变更感知,比等上线后报ProgrammingError: relation "account_payment_token" does not exist再救火,早 3 天。
从那以后我每次接到 Odoo 升级任务,第一件事就是让运维生成新字典 PDF,然后pdf2md+git diff—— 5 分钟内圈出所有结构变动点,再逐个检查定制模块。这招让我在三次大版本升级中,零漏掉关键表变更,也避免了因account_payment_method_line这类新表导致的支付功能瘫痪。希望帮到你。
本文还有配套的精品资源,点击获取