先说结论
在 Odoo 里,门户用户能不能看到工时,不是“是否共享了项目”这一条规则说了算。
源码把它拆成了至少三层:
- 记录级 domain:门户用户到底能搜到哪些
account.analytic.line - 视图级切换:门户用户打开工时动作时,看到的是不是 portal 专用 list/form/kanban
- 项目隐私前提:项目本身必须处于
invited_users或portal之类允许门户访问的可见性
更细一点说,/home/ubuntu/odoo-temp/addons/hr_timesheet/models/hr_timesheet.py 里的 _timesheet_get_portal_domain() 已经说明白了:
- 只要是内部 timesheet 用户,直接走当前用户的 ir.rule
- 只要不是内部用户,就改走 portal 域
- portal 域不是“项目被共享即可”,而是:
message_partner_ids child_of commercial_partner- 或
partner_id child_of commercial_partner - 并且 项目
privacy_visibility in ['invited_users', 'portal']
这意味着门户工时的可见性,本质上是 客户关系 + 项目隐私 + 门户视图 的交集。
1. 第一层:门户 domain 并不是“项目共享开关”
_timesheet_get_portal_domain() 的 portal 分支非常关键:
- 左边条件:timesheet 的
message_partner_ids或partner_id要落在当前门户用户商业主体下 - 右边条件:项目隐私必须允许门户/受邀访问
这背后对应的是一个很清晰的产品哲学:
门户用户看到的是“和自己客户关系相关的工时”,不是“项目里所有人的内部作业明细”。
测试 addons/hr_timesheet/tests/test_portal_timesheet.py 也验证了这件事。
它先拿到 portal domain,然后创建一条绑定项目与任务的 timesheet,再分别测试三种情况:
- 当
task.partner_id = portal partner时,这条工时可见 - 把任务客户清空后,这条工时不可见
- 再把
project.partner_id = portal partner,工时重新可见
这说明可见性不是简单挂在“共享项目”按钮上,而是沿着 任务客户优先,项目客户兜底 的关系传播。
2. 第二层:为什么门户打开工时,不会直接复用内部员工视图
action_view_subtask_timesheet() 也很值得看。
这段逻辑会先判断当前用户是不是内部用户:
- 内部用户:保留完整视图体系,包含 graph 等内部分析能力
- 门户用户:优先替换成 portal 专用 list/form/kanban 视图
- 如果 portal 专用视图不存在,还会回退到原始视图 ID
测试里甚至专门做了两次验证:
- portal 专用视图存在时,动作返回 portal 版 tree/form/kanban
- 把这几个 portal 视图删掉后,动作仍能回退到原始视图,不致于直接报错
这说明 Odoo 不是只在 record rule 上做权限隔离,而是连展示层都做了“降级版门户体验”。
换句话说,门户工时不是把内部工时页面硬开放给客户,而是给客户一套受控、可回退的查看入口。
3. 第三层:为什么 access record 还是默认 inactive
addons/hr_timesheet/security/ir.model.access.xml 里有一条很微妙的配置:
analytic.account.analytic.line.timesheet.portal.usergroup_id = base.group_portalperm_read = 1- 但
active = 0
这类设计很典型:
- 权限能力先定义好
- 不默认大开闸
- 让真正的开放路径交给更细的规则、上下文和模块组合去控制
再结合 hr_timesheet.py 里 _compute_readonly_timesheet() 的注释,你会发现 Odoo 对 portal 工时非常谨慎:
- 即便其他模块可能给 portal 带来某些 write 能力
- 这里依然把“非内部用户默认只读”当成安全底线
所以如果你在实施里直接粗暴给 portal 开 analytic line 权限,很容易把 Odoo 原本的防线打穿。
4. 为什么很多人会误判“明明共享了项目却看不到工时”
常见误判主要有四种:
误判一:把 collaborator 当成 timesheet 可见性的唯一条件
collaborator 当然重要,但它不是最终 domain 的全部。 真正落到工时层,还要看 partner 归属链是否匹配。
误判二:只看项目 partner,不看任务 partner
源码测试已经说明:task.partner_id 会直接改变 portal 可见性,而且优先级非常高。
误判三:忽略项目隐私类型
就算 partner 关系能对上,项目 privacy_visibility 不在 invited_users / portal 范围内,也不会放开。
误判四:把 portal 页面报错当成权限问题
有时不是 record rule 错,而是 portal 专用视图没装、被删或 XMLID 丢失。action_view_subtask_timesheet() 已经把这种异常场景考虑进去了。
5. 实施建议:别把门户工时设计成“客户看全量内部工单流水”
Odoo 这套设计明显偏向“客户相关工时可见”,而不是“把内部管理系统完全镜像给外部用户”。
因此更合理的落地方式是:
- 把 task/project 的客户归属维护好
- 明确哪些项目允许门户可见
- 保留 portal 专用视图,避免直接暴露内部分析界面
- 如需更细颗粒度展示,用额外字段或 portal 页面做定制,不要先破基础 domain
参考源码
/home/ubuntu/odoo-temp/addons/hr_timesheet/models/hr_timesheet.py_timesheet_get_portal_domain()_compute_readonly_timesheet()action_open_timesheet_view_portal()/home/ubuntu/odoo-temp/addons/hr_timesheet/models/project_task.pyaction_view_subtask_timesheet()/home/ubuntu/odoo-temp/addons/hr_timesheet/security/ir.model.access.xml/home/ubuntu/odoo-temp/addons/hr_timesheet/tests/test_portal_timesheet.py
DISCUSSION
评论区