拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

hrms 培训反馈(Training Feedback)邮件通知模板与配置深度解析

hrms 培训反馈(Training Feedback)邮件通知模板与配置深度解析
  • 企业应用
  • 后端
  • 前端
  • 移动开发

【免费下载链接】hrms

Open Source HR and Payroll Software

项目地址:https://gitcode.com/GitHub_Trending/hr/hrms
点击查看免费下载

本篇指南以 hrms(开源 HR 与薪资软件)中内置的 "Training Feedback" 标准通知为线索,完整拆解其通知定义(Notification JSON)、邮件正文模板(Jinja/HTML)、收件人机制与底层培训数据流转。读完你不仅能逐行理解该通知模板的每个占位符与配置字段,还能掌握 Frappe 框架下"基于文档字段派发邮件通知"的标准做法,以及 Training Event → Training Result → Training Feedback 三张单据之间的状态联动规则。

一、通知模板在 hrms 培训流程中的定位

在 hrms 中,培训(Training)相关的单据与通知分散在 hrms/hr/doctype/ 目录下,其中与"培训反馈收集"直接相关的三张核心单据是:

单据作用关键源码/定义
Training Event培训事件主表,登记事件名称、类型、地点、起止时间与参与者training_event.py、training_event.json
Training Event Employee培训事件子表,逐人记录考勤(Present/Absent)与状态(Open/Invited/Completed/Feedback Submitted)training_event_employee.json
Training Result培训结果单,提交后触发本通知training_result.py
Training Feedback培训反馈单,员工提交后状态回写为 Feedback Submittedtraining_feedback.py

本篇文章讨论的通知文件位于 hrms/hr/notification/training_feedback/,该目录共 5 个文件,各自职责如下:

  • training_feedback.json—— Notification 单据的完整定义(消息内容、收件人、触发事件等);
  • training_feedback.md—— 邮件正文模板的源文件(与 JSON 中message字段内容一致);
  • training_feedback.py—— 可选的通知上下文钩子(get_context),当前为空实现;
  • training_feedback.html—— 另一份参考用的邮件模板(以doc前缀方式引用单据字段);
  • __init__.py—— 空文件,标识包结构。

二、通知定义文件:training_feedback.json 字段逐项解读

Frappe 框架中,通知本身也是一种 DocType(Notification)。training_feedback.json给出了这条内置通知的完整配置,关键字段如下:

{ "attach_print": 0, "channel": "Email", "creation": "2017-08-11 03:17:11.769210", "days_in_advance": 0, "docstatus": 0, "doctype": "Notification", "document_type": "Training Result", "enabled": 1, "event": "Submit", "is_standard": 1, "message": "<h3>{{_(\"Training Event\")}}</h3>\n<p>{{ message }}</p>\n\n<h4>{{_(\"Details\")}}</h4>\n{{_(\"Event Name\")}}: <a href=\"{{ event_link }}\">{{ name }}</a>\n<br>{{_(\"Event Location\")}}: {{ location }}\n<br>{{_(\"Start Time\")}}: {{ start_time }}\n<br>{{_(\"End Time\")}}: {{ end_time }}\n<br>{{_(\"Attendance\")}}: {{ attendance }}\n", "name": "Training Feedback", "recipients": [ { "email_by_document_field": "employee_emails" } ], "subject": "Please Share your Feedback For {{ doc.training_event }}" }

字段含义与配置要点:

字段取值说明
doctypeNotification通知记录本身是 Frappe 的 Notification 单据
nameTraining Feedback通知名称,即这条标准通知的唯一标识
is_standard1标准通知,随应用安装自动部署到站点
enabled1通知处于启用状态
channelEmail发送渠道为邮件(Frappe 通知还支持 Slack、System Notification 等渠道)
document_typeTraining Result监听对象:Training Result 单据发生指定事件时触发
eventSubmit触发事件:Training Result 提交(submit)时触发。可选的框架事件还包括 New、Save、Value Change、Days Before、Days After 等
attach_print0不随邮件附加单据的 PDF 打印件
days_in_advance0仅用于按日期提前触发的事件(Days Before/Days After),此处无实际作用
recipients[0].email_by_document_fieldemployee_emails收件人来源:从 Training Result 单据的employee_emails字段读取收件邮箱
subjectPlease Share your Feedback For {{ doc.training_event }}邮件主题。注意主题里使用doc.前缀引用提交单据的字段(doc.training_event),而正文模板用的是裸变量(详见第四节)
message见下文第三节邮件正文模板,即training_feedback.md的内容(JSON 中为转义后的字符串形式)

要点:通知的触发对象是Training Result,而不是 Training Event。也就是说,当 HR 将某次培训的结果单提交(Submit)后,系统自动向该结果单关联员工的邮箱发送"请提交培训反馈"的邮件。

三、邮件正文模板逐行拆解:training_feedback.md

training_feedback.md的完整原文如下,本文完整保留并逐行展开:

<h3>{{_("Training Event")}}</h3> <p>{{ message }}</p> <h4>{{_("Details")}}</h4> {{_("Event Name")}}: <a href="{{ event_link }}">{{ name }}</a> <br>{{_("Event Location")}}: {{ location }} <br>{{_("Start Time")}}: {{ start_time }} <br>{{_("End Time")}}: {{ end_time }} <br>{{_("Attendance")}}: {{ attendance }}

逐行说明:

  1. <h3>{{_("Training Event")}}</h3>—— 邮件标题段。{{ _("...") }}是 Frappe 的国际化(i18n)翻译函数,Jinja 渲染时会按当前站点语言从翻译表取词。该通知涉及的字符串("Training Event"、"Event Name"、"Event Location"、"Start Time"、"End Time"、"Attendance" 等)在 hrms/locale/ 下的各语言.po文件(如zh.po、zh_TW.po、de.po等)中均有对应翻译条目,验证了通知模板的多语言能力。
  2. <p>{{ message }}</p>—— 正文首段,输出通知上下文中的message变量。该变量由 Frappe 通知框架在渲染消息时注入,用于承载说明性文字。
  3. <h4>{{_("Details")}}</h4>—— "详情"小标题。
  4. {{_("Event Name")}}: <a href="{{ event_link }}">{{ name }}</a>—— 事件名称行。event_link为指向培训事件记录的链接地址,name为事件名称。从源码结构看,Training Event 采用"按字段自动命名"(autoname: field:event_name),event_name即事件名,见 training_event.json 的autoname配置;name即事件记录名(与event_name相同)。
  5. <br>{{_("Event Location")}}: {{ location }}—— 培训地点,对应 Training Event 的location字段(必填,见training_event.json中location的reqd: 1)。
  6. <br>{{_("Start Time")}}: {{ start_time }}—— 开始时间,对应 Training Event 的start_time(Datetime类型、必填)。
  7. <br>{{_("End Time")}}: {{ end_time }}—— 结束时间,对应 Training Event 的end_time(Datetime类型、必填)。
  8. <br>{{_("Attendance")}}: {{ attendance }}—— 参与者的考勤状态,对应 Training Event Employee 子表中的attendance字段,取值Present/Absent(见 training_event_employee.json)。

模板中占位符与底层字段的映射关系汇总:

模板变量含义对应字段/来源
message正文提示语Frappe 通知框架注入的上下文变量
event_link培训事件记录链接指向 Training Event 表单的 URL
name事件名称Training Event 的event_name(autoname 字段)
location培训地点Training Event.location
start_time开始时间Training Event.start_time
end_time结束时间Training Event.end_time
attendance考勤状态Training Event Employee.attendance(Present/Absent)

四、上下文变量来源与两个值得注意的实践问题

4.1 get_context 钩子:当前为空实现

training_feedback.py提供了标准的上下文钩子,但目前是空实现:

def get_context(context): # do your magic here pass

在 Frappe 通知机制中,get_context(context)允许为消息模板注入自定义上下文变量。当前为空,意味着模板中message、event_link、name、location等变量依赖框架默认上下文与关联单据数据提供;若某些变量未出现在渲染上下文中,Jinja 会将其渲染为空串,邮件中对应位置留白。需要为模板补充数据(如按参与员工定制考勤信息)时,可在get_context中填充context["..."]后由模板引用。

4.2 主题用doc.前缀、正文用裸变量:写法的对比

观察同一通知的两处模板写法可以发现一个对比:

  • 邮件主题(JSON 中的subject)使用了{{ doc.training_event }},显式引用提交单据(Training Result)上的training_event字段;
  • 邮件正文(training_feedback.md)使用{{ event_link }}、{{ name }}等裸变量,未加doc.前缀。

4.3 同目录参考模板的推荐写法

training_feedback.html 是同一目录下的另一份参考模板,它展示了更"显式、可追溯"的写法:

<p>{{ _("Hello") }},</p> <p>You attended training {{ frappe.utils.get_link_to_form( "Training Event", doc.training_event) }}</p> <p>{{ _("Please share your feedback to the training by clicking on 'Training Feedback' and then 'New'") }}</p>

这段模板直接通过frappe.utils.get_link_to_form("Training Event", doc.training_event)生成培训事件的可点击链接,并把操作引导(进入 Training Feedback、点击 New 新建反馈)写进邮件正文。对比可见:前者(.md)依赖注入变量、字段更省;后者(.html)显式引用单据字段、可读性更强。实际部署时若遇到正文占位符渲染为空,可参考.html的做法改用doc.前缀字段与frappe.utils.get_link_to_form组合。

五、底层数据流:从培训事件到反馈提交的状态机

通知只是"最后一公里",要真正理解它,需要看懂背后三张单据如何联动。以下链路均有源码佐证。

5.1 Training Event:参与者与邮箱的初始化

training_event.py 中:

def validate(self): self.set_employee_emails() self.validate_period() def set_employee_emails(self): self.employee_emails = ", ".join(get_employee_emails([d.employee for d in self.employees])) def validate_period(self): if time_diff_in_seconds(self.end_time, self.start_time) <= 0: frappe.throw(_("End time cannot be before start time")) def on_update_after_submit(self): self.set_status_for_attendees()
  • set_employee_emails()遍历子表employees,通过 ERPNext 的get_employee_emails工具函数(erpnext.setup.doctype.employee.employee)拼装出逗号分隔的员工邮箱串,写入employee_emails隐藏字段;
  • validate_period()校验结束时间不得早于开始时间;
  • on_update_after_submit()中set_status_for_attendees()依据event_status(Scheduled/Completed/Cancelled,见training_event.json)批量更新参与者状态:事件 Completed 时,考勤为 Present 且尚未提交反馈的员工状态置为 Completed;事件 Scheduled 时全部重置为 Open。

5.2 Training Result:触发通知并回写参与者状态

training_result.py:

def validate(self): training_event = frappe.get_doc("Training Event", self.training_event) if training_event.docstatus != 1: frappe.throw(_("{0} must be submitted").format(_("Training Event"))) self.employee_emails = ", ".join(get_employee_emails([d.employee for d in self.employees])) def on_submit(self): training_event = frappe.get_doc("Training Event", self.training_event) training_event.status = "Completed" for e in self.employees: for e1 in training_event.employees: if e1.employee == e.employee: e1.status = "Completed" break training_event.save()

要点:

  • validate()要求关联的 Training Event 必须已提交(docstatus == 1),同时自动填充employee_emails——这正是通知收件人(email_by_document_field)取值的来源;
  • on_submit()提交结果单时,把 Training Event 中对应参与者的状态批量置为 Completed,形成状态闭环;
  • Training Result 提交时,Notification 监听Submit事件即被触发,向employee_emails中的地址发送本模板邮件。

5.3 Training Feedback:校验规则与状态回写

training_feedback.py 定义了员工填写反馈时的业务约束:

def validate(self): training_event = frappe.get_doc("Training Event", self.training_event) if training_event.docstatus != 1: frappe.throw(_("{0} must be submitted").format(_("Training Event"))) emp_event_details = frappe.db.get_value( "Training Event Employee", {"parent": self.training_event, "employee": self.employee}, ["name", "attendance"], as_dict=True, ) if not emp_event_details: frappe.throw(_("Employee {0} not found in Training Event Participants.").format(...)) if emp_event_details.attendance == "Absent": frappe.throw(_("Feedback cannot be recorded for an absent Employee.")) def on_submit(self): # 将参与者的状态更新为 Feedback Submitted frappe.db.set_value("Training Event Employee", employee, "status", "Feedback Submitted") def on_cancel(self): # 取消反馈时回退为 Completed frappe.db.set_value("Training Event Employee", employee, "status", "Completed")

三条核心规则:培训事件必须已提交;提交者必须是该事件的参与者;考勤为 Absent 的员工不允许记录反馈。反馈提交(submit)后参与者状态变为Feedback Submitted,取消则回退为Completed。

整个生命周期可归纳为:

  1. HR 创建并提交Training Event,员工邮箱自动汇入employee_emails;
  2. 事件完成后 HR 提交Training Result,参与者状态置为 Completed,同时触发 Training Feedback 通知,向员工发送反馈邀请邮件;
  3. 员工点击邮件中的链接进入Training Feedback新建反馈,提交后状态变为 Feedback Submitted,收集闭环完成。

六、收件人机制:employee_emails 的自动填充

通知配置中recipients[0].email_by_document_field = "employee_emails"意味着收件人完全来自单据字段。这个字段在两处被自动维护:

  • Training Event 的validate()(见 training_event.py);
  • Training Result 的validate()(见 training_result.py)。

两者都调用 ERPNext 的get_employee_emails(employees)将员工列表转成邮箱串。因此:

  • 只要在事件/结果单中维护好参与者子表(employees),邮箱地址无需手工录入;
  • 通知发送时框架自动从employee_emails字段解析收件人,一次向全部参与者群发同一封反馈邀请。

七、测试用例对业务规则的验证

test_training_feedback.py 用两个用例把上面第五节总结的规则固化为自动化测试,可作为理解通知背后业务约束的权威依据:

  • test_employee_validations_for_feedback:员工未登记为培训事件参与者时创建反馈应抛出frappe.ValidationError;将参与者考勤置为 Absent 后同样应被拒绝;
  • test_training_feedback_status:反馈提交后,Training Event Employee 的status应更新为Feedback Submitted。

测试还演示了搭建场景的标准套路:create_training_program("Basic Training")创建培训项目、make_employee(...)创建员工、create_training_event(...)创建事件并submit()、将event_status置为 Completed 后保存(对应training_event.py的set_status_for_attendees逻辑)。

八、与 Training Scheduled 通知的对比

hrms/hr/notification/目录下还内置了另一条培训相关通知 training_scheduled(培训安排通知),其模板 training_scheduled.md 与反馈通知形成鲜明对照:

  • Training Scheduled模板基于doc上下文(doc.event_name、doc.introduction、doc.location),并使用 Jinja 条件语句按起止日期是否同一天分别渲染日期+时间段或起止时间,还通过frappe.utils.get_datetime格式化日期,通过frappe.utils.get_link_to_form生成事件链接;
  • Training Feedback模板则全部使用裸变量(event_link/name/location/start_time/end_time/attendance)。

两者对比可以总结出 hrms 团队两种模板组织风格的并存:一种是"上下文变量注入式"(简练,依赖框架注入),一种是"doc 字段直引式"(显式、可控、便于排错)。对于希望二次开发通知模板的读者,推荐以 Training Scheduled 的写法为参照,在 Feedback 通知中改用doc前缀与frappe.utils.get_link_to_form,可显著降低占位符解析失败的风险。

小结

hrms 的 Training Feedback 通知是一个"配置 + 模板 + 数据流"三位一体的典型范本:training_feedback.json定义了监听 Training Result 提交事件、按employee_emails字段群发的规则;training_feedback.md提供了内嵌国际化标签的 HTML 邮件正文;而 Training Event、Training Result、Training Feedback 三张单据的校验与提交逻辑(training_event.py、training_result.py、training_feedback.py)保证了"只有真实参与者、考勤在场者才能反馈"的业务约束,测试用例 test_training_feedback.py 则让这些规则可持续验证。掌握这条通知的配置与数据链路后,你可以在 hrms 内快速复刻类似的"单据事件驱动邮件通知"能力。

  • 企业应用
  • 后端
  • 前端
  • 移动开发

【免费下载链接】hrms

Open Source HR and Payroll Software

项目地址:https://gitcode.com/GitHub_Trending/hr/hrms
点击查看免费下载
上一篇:文本驱动CAD:如何用自然语言重新定义机械设计工作流
下一篇:3步完成InvokeAI部署:新手也能快速上手的AI绘画终极指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

返回列表