简介
日期和时间戳贯穿于企业的每一个流程:订单是什么时候下的?货物什么时候要发?员工何时打卡?在 Odoo 里,Datetime 类型就是用来记录这些带有具体时刻信息的数据的标准工具。
与只保存日期的 Date 字段不同,Datetime 同时记录日期与具体时刻(精确到秒)。这个差别在跨时区团队、需要按小时或分钟级别跟踪事件的场景下尤其关键,否则会引入看不见但致命的时间偏差。
本指南聚焦 Odoo 中的 Datetime 字段:它保存什么、在数据模型中的行为、如何用 Odoo Studio 或 Python 创建与配置,以及若干贴合业务的实战示例和建议。
Odoo 中的 Datetime 字段是什么?
在 Odoo 的 ORM 中,fields.Datetime 用来保存带时间的时间点(通常精确到秒)。在 PostgreSQL 里,对应为 TIMESTAMP 列。Odoo 在后台以 UTC 存储所有 Datetime 值,展示时再按每个用户配置的时区转换成本地时间。
从界面上看,Datetime 在表单里通常呈现为可同时选择日期和时间的控件:日历选择器加上时间输入。在列表视图或报表中,显示格式会随用户语言与时区设置而变化。
下面举例说明在 Python 模型里如何定义一个 Datetime 字段:
from odoo import fields, models
class SaleOrder(models.Model):
_inherit = 'sale.order'
x_confirmed_on = fields.Datetime(
string='Confirmed On',
default=fields.Datetime.now,
readonly=True,
copy=False,
)
string 参数决定界面标签;default 可以自动填入当前时间;readonly 常用于审计类时间戳,避免用户手动篡改。
在 Odoo Studio 中,该类型被称为 “Date & Time(日期与时间)”。通过 Studio 创建的字段会自动带上 x_studio_ 前缀;而通过代码或 API 创建时,你自己定义字段名。
字段的工作原理
当你在模型中声明 Datetime 字段,Odoo 会在安装或升级模块时自动在数据库中创建相应列——通常无需你手写任何 SQL 迁移。
一个容易让人迷惑的点是时区处理:数据库内部永远以 UTC 存值。举例:巴黎用户设置会议为下午 15:00,存入数据库时会转换为 UTC(13:00)。纽约用户打开同条记录时,界面会显示当地时间(9:00)。ORM 根据每个用户配置的时区自动做显示转换。
关键字段属性速览
下面列出 Datetime 字段在 Odoo 中最常用、也最重要的属性:
- default:常用 fields.Datetime.now 作为默认值,以便在创建记录时自动填入当前时间(以 UTC 为准)。
- required:将字段设为必填,强制在表单和模型层面提供值。
- readonly:阻止界面编辑,适用于系统自动生成的时间戳。
- compute:通过 Python 方法动态计算字段值,基于其他字段或业务逻辑。
- store:与 compute 配合使用时,决定是否将计算结果写入数据库,便于搜索与报表。
- copy:控制复制记录时该字段是否被拷贝。默认 True。对于事件类时间戳应设为 False。
- index:在数据库上建立索引,适用于大表中频繁作为筛选条件的时间字段(如排程时间)。
在视图中的呈现方式
表单视图中,Datetime 以日期+时间选择器出现,用户可以在同一控件中选择日历和填写具体时刻。列表视图展示为按用户语言与时区格式化的文本。搜索视图支持基于时间的区间过滤(之前、之后、之间等)。
你也可以为 Datetime 字段使用 date_range 小部件,在表单中直接提供时间区间选择,适合用于排程窗口或有起止时间的任务。
Datetime 与 Date:如何选择合适类型
判断标准很简单:如果只关心哪一天,选 fields.Date;如果需要到小时或分钟的精度,选 fields.Datetime。不要为了“以后可能用到时间”而滥用 Datetime。
适合使用 Date 的场景:发票到期日、生日、保质期、合同续签日期等只与日期相关的业务。
适合使用 Datetime 的场景:订单确认时间、会议开始时刻、员工打卡记录、仓库调度时间等需要精确到时分的场景。
滥用 Datetime 会增加时区处理复杂度而无实际收益。遇到犹豫时,问一句:时间点是否真的对业务流程有影响?
实际业务场景示例
Datetime 在 Odoo 的各个模块中广泛出现,以下用五个常见业务场景来说明其实际价值。
CRM:潜在客户活动追踪
在 CRM 中,多个 Datetime 字段记录关键事件发生的时间,例如记录何时将线索标为跟进、何时设置了最后期限等。销售经理通过这些时间点评估响应速度、发现滞留机会并统计团队活动。额外的自定义时间戳还能记录报价发送或电话沟通的具体时刻,帮助精确衡量销售节奏。
销售:订单确认时间
sale.order 上的 date_order 字段就是 Datetime,用于记录订单确认的精确时间。这个时间对于按小时/天统计销量、计算订单处理周期、或者审核确认后发生的变更都非常重要,按此字段筛选报表是销售分析中的常见操作。
仓储:排程发货/收货时间
stock.picking 的 scheduled_date 字段用于计划收发货时间。Odoo 可以基于该时间触发自动化动作,例如当送货超期若干小时后发出提醒邮件,从而在客户主动联系前就能进行沟通,提升服务体验。
生产:生产开始与结束时间
生产订单用 Datetime 记录开工与完工时刻,这些数据直接参与产能规划、效率统计与绩效分析。对于多班次生产企业,精确到分钟的数据有助于识别哪个时段或哪个操作工成为瓶颈。
人力:考勤与请假记录
人事模块里的考勤上下班打卡、请假起止时间都依赖 Datetime。工资与加班计算往往要求分钟级精度,任何缺失或错误的时间戳都会直接影响薪资核算,因此这些字段必须准确可靠。
如何创建或自定义 Datetime 字段
添加 Datetime 字段主要有三种方式,选择哪种取决于你是偏好免编码的业务配置还是基于代码的可控开发流程。
使用 Odoo Studio(无代码)
Odoo Studio 是内置的自定义工具,允许在不写代码的情况下增加字段。通过 Studio 添加 Datetime 的步骤通常如下:
- 从主菜单打开 Odoo Studio。
- 定位到需要修改的表单视图。
- 从侧边栏拖入一个 Date & Time 字段到表单上。
- 在字段属性面板中设置标签、是否必填,以及可选的默认值。
- 保存并退出 Studio。
Studio 会自动以 x_studio_ 前缀创建字段并把它加入到视图中,无需你手动做数据库迁移。对于业务人员想快速在表单上添加时间戳,这是推荐的方法。
在自定义模块中使用 Python(开发者方式)
对于需要版本控制、跨环境部署的自定义,建议在模块内用 Python 定义字段:
from odoo import fields, models
class ResPartner(models.Model):
_inherit = 'res.partner'
x_last_contact_date = fields.Datetime(
string='Last Contact Date',
default=fields.Datetime.now,
copy=False,
)
在模型中声明字段后,别忘了将其加入相应的视图 XML 中以便在界面显示。Odoo 会在安装或升级模块时自动在数据库中创建 TIMESTAMP 列。
通过 XML-RPC API 创建字段
如果你要在部署流水线或远程脚本中编程化管理 Odoo,自行通过 XML-RPC API 创建字段也是可行的:
field_id = models.execute_kw(
ODOO_DB, uid, ODOO_API_KEY,
'ir.model.fields', 'create',
[{
'name': 'x_last_contact_date',
'field_description': 'Last Contact Date',
'model_id': model_id,
'ttype': 'datetime',
'state': 'manual',
}]
)
设置 ttype 为 datetime 告诉 Odoo 创建 Datetime 字段;state 设置为 manual 表示此字段是通过 UI 或 API 创建(而非模块声明)的,这在自动化配置脚本中是常见做法。
最佳实践要点
1. 将 fields.Datetime.now 作为函数引用传入(不要带括号)
设置默认值时应写 default=fields.Datetime.now(不要加括号)。如果加了括号,函数会在类加载时被调用一次,之后所有新建记录都会共享同一个“被冻住”的时间戳。正确写法保证每条记录在创建时获得实时的时间值。
2. 事件类时间戳设置 copy=False
对记录“何时发生”的字段(如确认时间、完成时间)应设 copy=False。这样在复制记录时不会把原记录的历史时间带到新记录上,避免污染审计与统计数据。
3. 通过 API 写入时间时务必使用 UTC
通过 XML-RPC 或其他 API 写入 Datetime 值时,务必以 UTC 格式(YYYY-MM-DD HH:MM:SS)传入。API 不会自动进行时区转换;传入本地时间会导致数据库中记录的实际时刻出现偏差,日后排查困难。
4. 自动生成的时间应设为 readonly
系统生成的时间戳通常应在界面上设为只读,防止用户随意改动。若确有必要允许修改,应通过字段级别权限严格控制,而不是直接放开编辑。
5. 不需要时间就选 Date
如果场景只需记录日期(如到期日、续签日),应优先使用 fields.Date。Datetime 会引入不必要的时区与时间组件,增加实现与使用复杂度。
常见错误与陷阱
读取原始数据库值时的时区误解
最常见的错误来源之一是直接读取数据库或 API 返回的原始时间戳——那是 UTC,而不是用户本地时间。很多集成或报表以原始 API 输出为基础,结果时间偏移数小时。对外展示时务必在客户端或集成层做时区转换。
通过 API 写入本地时间的问题
若你把本地时间(例如巴黎时间的 15:00)直接写给 Odoo API,而没有转换为 UTC,数据库会把它当成 UTC 存储,导致界面上对本地用户来说显示错误(可能提前或延后几个小时),这类问题常在生产环境出现并且不易察觉。
误用 default=fields.Datetime.now()(带括号)的危害
给 default 加括号会在类加载时评估一次,造成所有后续创建的记录都用同一个固定时间,表面上看起来正常但会破坏基于创建时间的任何分析,这是一个悄无声息却影响深远的 bug。
忘记对事件时间设 copy=False 的后果
如果不把 copy 设为 False,复制记录时会连带复制所有时间戳,导致新记录带有旧记录的历史时间,污染历史报表与审计线索。这个小配置错误往往会造成较大数据质量问题。
将 Datetime 用在只需 Date 的场景上的问题
在仅需日期的场景使用 Datetime 会让用户看到不必要的时间字段、在每次展示时触发时区计算且毫无收益,界面也会因此显得繁琐。选择恰当的字段类型可让模型更简洁易维护。
总结
当业务需要时间精度时,Datetime 是非常有价值的字段类型——从线索打开时间到生产开工、从订单确认到员工打卡,几乎每个模块都会用到。
最重要的概念是:数据库里以 UTC 存储时间。界面会帮用户按时区显示,但任何通过 API 的外部读写都必须显式处理时区。大多数与时区相关的集成问题都源自对这一点的误解。
除此之外,确保用正确的 default 语法、在事件时间上设置 copy=False,并在不需要时间精度时选择 Date,可以让你的数据模型更干净、报表更可靠。
我们(Dasolo)为企业提供 Odoo 的实施、定制和优化服务。无论是设计合理的数据模型、为业务流程添加自定义字段,还是从头构建完整模块,我们都有经验丰富的团队可以协助。 欢迎联系我们 让我们聊聊你的 Odoo 项目吧。