返回 Skill 列表
extension
分类: 开发与工程无需 API Key

erpnext-impl-scheduler

Frappe 定时任务和后台作业的实现工作流(v14/v15/v16)。涵盖 hooks.py 中的 scheduler_events、frappe.enqueue、队列选择、作业去重以及错误处理。触发器:如何安排任务、后台作业、定时任务、异步处理、队列选择、作业去重、调度器实现。

person作者: jakexiaohubgithub

ERPNext Scheduler - Implementation

This skill helps you implement scheduled tasks and background jobs. For exact syntax, see erpnext-syntax-scheduler.

Version: v14/v15/v16 compatible

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       │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

Scheduler Event vs frappe.enqueue

| Aspect | Scheduler Event | frappe.enqueue | |--------|----------------|----------------| | Triggered by | Time/interval | Code execution | | Defined in | hooks.py | Python code | | Arguments | None (must be parameterless) | Any serializable data | | Use case | Daily cleanup, hourly sync | User-triggered long task | | Restart behavior | Runs on schedule | Lost if worker restarts |

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!)                          │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

Which Queue?

| Queue | Timeout | Use For | |-------|---------|---------| | short | 5 min | Quick operations (<1 min) | | default | 5 min | Standard tasks (1-3 min) | | long | 30 min | Heavy processing (>3 min) |

Rule: Always specify queue explicitly. Default is short.

Quick Start: Basic Scheduled Task

# 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()
# hooks.py
scheduler_events = {
    "daily": [
        "myapp.tasks.daily_cleanup"
    ]
}

After editing hooks.py: bench migrate

Quick Start: Background Job

# 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"}

Critical Rules

1. Scheduler tasks receive NO arguments

# ❌ WRONG
def my_task(doctype):  # Arguments not supported
    pass

# ✅ CORRECT
def my_task():  # Parameterless
    doctype = "Sales Invoice"  # Hardcode or read from settings

2. ALWAYS migrate after hooks.py changes

bench migrate  # Required to register new scheduler events

3. Jobs run as Administrator

Scheduler and enqueued jobs run with Administrator permissions. Always commit explicitly.

4. Commit after batches, not per record

# ❌ WRONG - Slow
for doc in docs:
    doc.save()
    frappe.db.commit()  # Commit per record

# ✅ CORRECT - Fast
for doc in docs:
    doc.save()
frappe.db.commit()  # Single commit after batch

5. Use job_id for deduplication (v15+)

enqueue(..., job_id="unique_identifier")  # Prevents duplicate jobs

Version Differences

| Aspect | v14 | v15 | v16 | |--------|-----|-----|-----| | Tick interval | 4 min | 60 sec | 60 sec | | Job dedup | job_name | job_id | job_id | | Cron support | ✅ | ✅ | ✅ |

V14 deduplication uses different parameter:

# v14
enqueue(..., job_name="unique_id")
# v15+
enqueue(..., job_id="unique_id")

Reference Files

| File | Contents | |------|----------| | workflows.md | Step-by-step implementation patterns | | decision-tree.md | Detailed decision flowcharts | | examples.md | Complete working examples | | anti-patterns.md | Common mistakes to avoid |


See Also

  • erpnext-syntax-scheduler - Exact syntax reference
  • erpnext-errors-serverscripts - Error handling patterns