erpnext-impl-scheduler

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

ERPNext Scheduler - Implementation

ERPNext调度器 - 实现指南

This skill helps you implement scheduled tasks and background jobs. For exact syntax, see
erpnext-syntax-scheduler
.
Version: v14/v15/v16 compatible
本指南将帮助你实现定时任务与后台作业。如需精确语法参考,请查看
erpnext-syntax-scheduler
版本兼容性:支持v14/v15/v16版本

Main Decision: What Are You Trying to Do?

核心决策:你要实现什么功能?

┌─────────────────────────────────────────────────────────────────────┐
│ SCHEDULER DECISION                                                  │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│ Run at fixed intervals or times?                                   │
│ ├── YES → Scheduler Event (hooks.py)                               │
│ │         See: references/workflows.md §1-2                        │
│ │                                                                   │
│ └── NO → Run in response to user action?                           │
│          ├── YES → frappe.enqueue()                                │
│          │         See: references/workflows.md §3-4               │
│          │                                                          │
│          └── NO → Probably neither - reconsider requirements       │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ 调度器决策树                                                      │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│ 是否需要按固定间隔或时间运行?                                     │
│ ├── 是 → 调度器事件(hooks.py)                                   │
│ │         参考:references/workflows.md §1-2                        │
│ │                                                                   │
│ └── 否 → 是否需响应用户操作触发?                                 │
│          ├── 是 → frappe.enqueue()                                │
│ │         参考:references/workflows.md §3-4               │
│ │                                                          │
│          └── 否 → 不属于上述场景 - 请重新审视需求               │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

Scheduler Event vs frappe.enqueue

调度器事件 vs frappe.enqueue

AspectScheduler Eventfrappe.enqueue
Triggered byTime/intervalCode execution
Defined inhooks.pyPython code
ArgumentsNone (must be parameterless)Any serializable data
Use caseDaily cleanup, hourly syncUser-triggered long task
Restart behaviorRuns on scheduleLost if worker restarts
维度调度器事件frappe.enqueue
触发方式时间/间隔代码执行
定义位置hooks.pyPython代码
参数支持无(必须无参数)支持任何可序列化数据
适用场景每日清理、每小时同步用户触发的长耗时任务
重启行为按计划自动运行若Worker重启则任务丢失

Which Scheduler Event Type?

选择哪种调度器事件类型?

┌─────────────────────────────────────────────────────────────────────┐
│ SCHEDULER EVENT TYPE                                                │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│ Simple recurring interval?                                         │
│ ├── Every minute    → scheduler_events.cron["* * * * *"]          │
│ ├── Hourly          → scheduler_events.hourly                      │
│ ├── Daily           → scheduler_events.daily                       │
│ ├── Weekly          → scheduler_events.weekly                      │
│ └── Monthly         → scheduler_events.monthly                     │
│                                                                     │
│ Complex schedule (e.g., "weekdays at 9am")?                        │
│ └── scheduler_events.cron["0 9 * * 1-5"]                          │
│                                                                     │
│ Run after every request?                                           │
│ └── scheduler_events.all (use sparingly!)                          │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ 调度器事件类型选择                                                  │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│ 是否为简单重复间隔?                                               │
│ ├── 每分钟    → scheduler_events.cron["* * * * *"]          │
│ ├── 每小时    → scheduler_events.hourly                      │
│ ├── 每天      → scheduler_events.daily                       │
│ ├── 每周      → scheduler_events.weekly                      │
│ └── 每月      → scheduler_events.monthly                     │
│                                                                     │
│ 是否为复杂调度(如"工作日上午9点")?                        │
│ └── scheduler_events.cron["0 9 * * 1-5"]                          │
│                                                                     │
│ 是否需在每次请求后运行?                                           │
│ └── scheduler_events.all(请谨慎使用!)                          │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

Which Queue?

选择哪种队列?

QueueTimeoutUse For
short
5 minQuick operations (<1 min)
default
5 minStandard tasks (1-3 min)
long
30 minHeavy processing (>3 min)
Rule: Always specify queue explicitly. Default is
short
.
队列超时时间适用场景
short
5分钟快速操作(耗时<1分钟)
default
5分钟标准任务(耗时1-3分钟)
long
30分钟重型处理(耗时>3分钟)
规则:务必显式指定队列。默认队列为
short

Quick Start: Basic Scheduled Task

快速入门:基础定时任务

python
undefined
python
undefined

myapp/tasks.py

myapp/tasks.py

import frappe
def daily_cleanup(): """Daily cleanup task - no parameters allowed""" frappe.db.delete("Error Log", {"creation": ("<", frappe.utils.add_days(None, -30))}) frappe.db.commit()

```python
import frappe
def daily_cleanup(): """每日清理任务 - 不允许传入参数""" frappe.db.delete("Error Log", {"creation": ("<", frappe.utils.add_days(None, -30))}) frappe.db.commit()

```python

hooks.py

hooks.py

scheduler_events = { "daily": [ "myapp.tasks.daily_cleanup" ] }

After editing hooks.py: `bench migrate`
scheduler_events = { "daily": [ "myapp.tasks.daily_cleanup" ] }

编辑hooks.py后执行:`bench migrate`

Quick Start: Background Job

快速入门:后台作业

python
undefined
python
undefined

myapp/api.py

myapp/api.py

import frappe from frappe import enqueue
@frappe.whitelist() def process_documents(doctype, filters): enqueue( "myapp.tasks.process_batch", queue="long", timeout=1800, job_id=f"process_{doctype}_{frappe.session.user}", # v15+ dedup doctype=doctype, filters=filters ) return {"status": "queued"}
undefined
import frappe from frappe import enqueue
@frappe.whitelist() def process_documents(doctype, filters): enqueue( "myapp.tasks.process_batch", queue="long", timeout=1800, job_id=f"process_{doctype}_{frappe.session.user}", # v15+ 版本支持去重 doctype=doctype, filters=filters ) return {"status": "queued"}
undefined

Critical Rules

重要规则

1. Scheduler tasks receive NO arguments

1. 调度器任务不接收任何参数

python
undefined
python
undefined

❌ WRONG

❌ 错误示例

def my_task(doctype): # Arguments not supported pass
def my_task(doctype): # 不支持传入参数 pass

✅ CORRECT

✅ 正确示例

def my_task(): # Parameterless doctype = "Sales Invoice" # Hardcode or read from settings
undefined
def my_task(): # 无参数 doctype = "Sales Invoice" # 硬编码或从配置读取
undefined

2. ALWAYS migrate after hooks.py changes

2. 修改hooks.py后必须执行migrate

bash
bench migrate  # Required to register new scheduler events
bash
bench migrate  # 必须执行此命令以注册新的调度器事件

3. Jobs run as Administrator

3. 作业以Administrator权限运行

Scheduler and enqueued jobs run with Administrator permissions. Always commit explicitly.
调度器和入队作业均以Administrator权限运行,请务必显式提交事务。

4. Commit after batches, not per record

4. 批量处理后提交,而非逐条记录提交

python
undefined
python
undefined

❌ WRONG - Slow

❌ 错误示例 - 效率低下

for doc in docs: doc.save() frappe.db.commit() # Commit per record
for doc in docs: doc.save() frappe.db.commit() # 逐条记录提交

✅ CORRECT - Fast

✅ 正确示例 - 高效

for doc in docs: doc.save() frappe.db.commit() # Single commit after batch
undefined
for doc in docs: doc.save() frappe.db.commit() # 批量处理后单次提交
undefined

5. Use job_id for deduplication (v15+)

5. 使用job_id实现作业去重(v15及以上版本)

python
enqueue(..., job_id="unique_identifier")  # Prevents duplicate jobs
python
enqueue(..., job_id="unique_identifier")  # 防止重复作业

Version Differences

版本差异

Aspectv14v15v16
Tick interval4 min60 sec60 sec
Job dedup
job_name
job_id
job_id
Cron support
V14 deduplication uses different parameter:
python
undefined
维度v14v15v16
调度间隔4分钟60秒60秒
作业去重参数
job_name
job_id
job_id
Cron支持
V14版本的作业去重使用不同参数:
python
undefined

v14

v14版本

enqueue(..., job_name="unique_id")
enqueue(..., job_name="unique_id")

v15+

v15及以上版本

enqueue(..., job_id="unique_id")

---
enqueue(..., job_id="unique_id")

---

Reference Files

参考文件

FileContents
workflows.mdStep-by-step implementation patterns
decision-tree.mdDetailed decision flowcharts
examples.mdComplete working examples
anti-patterns.mdCommon mistakes to avoid

文件内容
workflows.md分步实现模式
decision-tree.md详细决策流程图
examples.md完整可运行示例
anti-patterns.md需避免的常见错误

See Also

相关参考

  • erpnext-syntax-scheduler
    - Exact syntax reference
  • erpnext-errors-serverscripts
    - Error handling patterns
  • erpnext-syntax-scheduler
    - 精确语法参考
  • erpnext-errors-serverscripts
    - 错误处理模式