先说结论
很多人第一次看到 Odoo 企业版的项目文档,会把它理解成:
- 项目上多了一个
documents_folder_id - 用户往里传文件
- 项目页顺手多了个 Documents 按钮
但如果你去看 /home/ubuntu/odoo-temp/enterprise/documents_project/,会发现官方的设计重点根本不只是“挂一个文件夹”,而是下面这条完整链路:
- 公司先定义“项目根文件夹”
documents_project_folder_id - 新建项目时自动生成专属子文件夹
- 项目改名、换可见性、换公司时,文件夹要跟着校正
- 上传到项目文件夹的文档,会自动继承项目客户
partner_id - 即使不共享整个项目,也可以单独共享文档上传入口
- 删除、归档项目相关文件夹时,还有专门保护逻辑
一句话说:
Odoo 企业版项目文档的真正目标,不是“项目里能放文件”,而是“项目协作产生的文件,如何带着正确的归属、权限和生命周期进入 Documents 体系”。
第一层:项目文件夹为什么会自动创建
在 documents_project/models/project_project.py 里,project.project 增加了:
documents_folder_iddocument_countdocument_ids
最关键的不是字段本身,而是 create() 里会调用 _create_missing_folders()。
也就是说,只要项目启用了这套能力,Odoo 在创建项目时就会自动:
- 以公司级
documents_project_folder_id作为父级目录 - 新建一个和项目同名的子文件夹
- 根据项目可见性初始化内部权限
这里的默认权限特别值得注意:
privacy_visibility是employees/portal时,文件夹内部权限给到edit- 否则给
none
这说明源码的思路是:
项目文档权限,不是 Documents 自己凭空决定,而是先从项目的协作可见性出发。
所以它从第一步就不是“自由建个文件夹”,而是“项目配置驱动文档空间初始化”。
第二层:Documents 按钮为什么只是入口,不是核心
源码里既有 stat button,也有 embedded action:
action_view_documents_project()- 项目表单 / 仪表盘里的 Documents 嵌入动作
- 项目 kanban 菜单里的 Documents 入口
从界面上看,这像是在项目里补一个快捷方式。
但 action_view_documents_project() 真正做的事,是把 Documents 视图的上下文切换到:
- 当前项目
- 当前项目文档根目录
searchpanel_default_user_folder_idno_documents_unique_folder_id
也就是说,这个按钮的意义不是“打开一个普通列表”,而是:
- 让用户以项目视角进入 Documents
- 默认聚焦到该项目目录树
- 避免用户误以为自己在全局 Documents 里乱翻
所以 Documents 按钮只是呈现层;真正重要的是后面的文件夹归属和继承规则。
第三层:为什么上传到项目文件夹的文档会自动带客户
documents_project/models/documents_document.py 里最值得讲的,是 _prepare_create_values() 和 _get_link_to_project_values()。
官方做法不是让每个上传动作都手写“把客户带上”,而是:
- 创建文档前先看目标
folder_id - 如果这是普通文件而不是 folder,且不是已有业务记录附件
- 找这个目录最近的、且只绑定了一个项目的祖先目录
- 把该项目的
partner_id自动塞进文档值里
这意味着什么?
这不是“项目文件夹能放文件”
而是:
文档一旦进入项目目录,就会尽可能继承项目业务上下文。
这个上下文当前最关键的是客户 partner_id。
它解决了一个特别现实的问题:
- 顾问、客户、项目经理都可能把文件扔进项目目录
- 但 Documents 后续搜索、过滤、协作和外发,经常又依赖 partner 归属
如果项目目录里的文件没有业务对象上下文,文档系统就会退化成网盘。
Odoo 显然不想要这个结果。
第四层:为什么“最近的唯一项目祖先目录”这个判断很重要
_get_project_from_closest_ancestor() 的逻辑相当讲究。
它不是简单看“这个目录是不是某项目目录”,而是顺着 parent_path 往上找:
- 哪个祖先目录绑定了项目
- 并且这个目录只绑定了 一个 项目
- 取最近的那个祖先目录
这套设计的含义很强:
1. 允许项目目录下面继续分子目录
比如你在项目文件夹下再建:
- 合同
- 交付物
- 会议纪要
- 验收资料
这些子目录里的文档,仍然可以继承项目上下文。
2. 避免共享目录导致归属混乱
如果一个目录被多个项目共用,源码就不会再武断地把文档自动归到某一个项目客户。
换句话说:
只有归属足够明确时,系统才自动补业务上下文;一旦有歧义,就宁可不乱猜。
这是很典型的 Odoo 设计风格。
第五层:项目改名、换目录、换公司时,为什么文档空间也要跟着变
项目改名
在 project_project.write() 里,如果满足两个条件:
- 当前文件夹只绑定这一个项目
- 文件夹名称本来就和项目名一致
那么项目改名时,文件夹也会跟着改名。
这说明 Odoo 不想把项目文档目录变成很快失真的“历史名字壳”。
更换 documents_folder_id
如果项目被改到另一个文件夹,源码会把原项目根目录下的子文档重新挂到新目录下。
重点是:
- 不是只改项目上的 Many2one
- 而是连项目根目录下的文档组织关系一起迁移
所以这里改的是“文档空间归属”,不是“显示入口”。
更换公司
这个部分约束更严。
源码会检查:
- 项目文件夹若属于某公司,项目公司必须一致,或者文件夹公司为空
- 如果一个文件夹同时被多个项目使用,而这些项目还在别的公司,就禁止你随便改公司
- 公司级
documents_project_folder_id也不能指向别家公司的文件夹
这背后的原则很清晰:
项目文档可以共享目录,但不能跨公司乱共享到破坏公司边界。
第六层:为什么删除 / 归档项目文件夹会被保护
documents_project 对删除和归档做了不少保护,很多人平时不会注意,但它们非常关键。
1. 不能删 Projects 基础目录
documents_project.document_project_folder 是受保护的基础目录。
不仅它自己不能删,连它的祖先目录也不能删。测试里专门覆盖了这个场景。
2. 不能删正在被项目使用的目录
如果某个目录或其上级还被项目拿来当 documents_folder_id,删除时会直接报错。
3. 不能归档项目基础目录
基础目录被归档同样会触发校验失败。
4. 项目删除后,相关目录会被自动归档,而不是粗暴物理删除
_archive_folder_on_projects_unlinked() 的逻辑是:
- 如果一个目录关联的项目都被删掉了
- 则把该目录归档
这个做法非常合理,因为项目文档往往带审计价值:
- 你不一定想继续在界面里活跃显示
- 但也不应该直接硬删掉
所以这里是“生命周期退场”,不是“数据彻底抹掉”。
第七层:附件为什么会自动建议放进项目文件夹
documents_project/models/ir_attachment.py 还补了一层体验优化。
当附件来自:
project.projectproject.task
get_documents_operation_add_destination() 会自动把 Documents 的目标目录建议成对应项目的 documents_folder_id。
这看起来像个小细节,其实非常重要。
因为真实使用里,很多文件最初不是从 Documents 页面上传的,而是:
- 在项目 chatter 里上传
- 在任务里补附件
- 从活动上传入口提交文件
如果这些附件默认不知道该落哪里,项目文档体系就会断链。
官方这里等于是在补一句:
只要这份附件属于项目或项目任务,它就应该优先被纳入项目文档空间。
第八层:为什么“共享项目”和“共享项目文件上传入口”不是一回事
tests/test_routes.py 里有一个特别值得写进实施经验的测试:
- 不共享整个项目给 portal 用户
- 只给项目目录的文档权限
- portal 用户依然可以通过
/documents/upload/<token>上传文件 - 文件会正确进入项目目录
这说明一件很重要的事:
项目协作权限,和项目文档上传权限,在 Odoo 企业版里是可以拆开的。
也就是说,客户可能:
- 看不到完整项目后台或共享项目界面
- 但仍然可以通过受控上传口,把交付材料、签字文件或反馈资料送进项目目录
这正是很多实施场景真正需要的能力。
它比“给客户开项目共享”更保守,也更好控边界。
第九层:活动上传为什么不会凭空多出两份文档
测试里还覆盖了一个很细的点:
- 任务上安排一个“上传文件”类型 activity
- 该 activity 绑定项目文件夹
- 上传时先有一个临时文档
- 完成 activity 后,最终只有一份真实文档留在项目目录
这个设计在防什么?
它防的是典型协作系统常见问题:
- 活动有一份占位记录
- 附件上传又新建一份文件
- 最后用户看到重复文档
Odoo 这里明确保证:
- 流程上可以先有临时文档承接上传动作
- 但结果上不制造重复垃圾记录
所以它不是“能传就行”,而是把协作动作和文档落地的边界也处理了。
这套设计到底解决了什么问题
如果没有 documents_project,项目文档通常会落进三种混乱状态:
1. 文件是有了,但没有项目上下文
最后只能靠文件名、群聊和人脑找资料。
2. 文件在项目里能看见,但 Documents 里不可管理
这样文档系统和项目系统是断开的。
3. 外部人要交资料时,只能粗暴开项目权限
这会把协作边界放得过大。
而 Odoo 企业版这套实现,把问题拆成了三段:
- 归属:项目自动建目录,目录继承项目上下文
- 治理:改名、迁移、公司边界、删除归档都有规则
- 协作:附件、活动、外链上传都能落进同一项目文档空间
这才是它比“项目下放附件”更高级的地方。
新手最容易误解的 5 件事
1. 以为项目文档就是项目字段指向一个目录
不对,后面还有自动建档、权限映射、文档值继承和生命周期保护。
2. 以为只有项目本身共享了,客户才能上传项目资料
不对,项目目录可以单独授权上传入口。
3. 以为项目改名只影响项目,不影响文档空间
不对,满足条件时文件夹会同步改名。
4. 以为目录改了只是 UI 入口变了
不对,项目根目录下的文档归属也会一起迁移。
5. 以为项目删除后文档目录就该直接删掉
不对,源码更倾向于归档,保留审计和追溯价值。
实战里最该注意什么
1. 实施时先设计公司级“项目根目录”
不要等项目跑起来后,再临时调整 documents_project_folder_id,否则很容易碰到迁移和公司约束问题。
2. 如果客户说“上传的文件没有自动带客户”,先查目录归属是否唯一
尤其要确认:
- 文档是不是传到了项目目录或其子目录
- 上游祖先目录是否只绑定一个项目
- 文档是否本来就是已挂业务记录的附件
3. 如果业务只需要客户交付资料,不一定要开整个项目共享
很多时候给受控的文档上传入口,更符合最小权限原则。
4. 多公司环境下,不要图省事让不同公司的项目共用一个带公司属性的目录
源码已经把这类情况视为高风险边界,会在写入时卡住你。
一句话记忆法
Odoo 企业版项目文档不是“项目下面一个文件夹”,而是“项目自动生成文档空间,再把权限、客户归属、附件落点和外部上传入口一起接进 Documents 体系”的完整设计。
DISCUSSION
评论区