erpnext-errors-serverscripts

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

ERPNext Server Scripts - Error Handling

ERPNext Server Scripts - 错误处理

This skill covers error handling patterns for Server Scripts. For syntax, see
erpnext-syntax-serverscripts
. For implementation workflows, see
erpnext-impl-serverscripts
.
Version: v14/v15/v16 compatible

本内容涵盖Server Scripts的错误处理模式。如需了解语法,请查看
erpnext-syntax-serverscripts
。如需了解实现流程,请查看
erpnext-impl-serverscripts
版本:兼容v14/v15/v16

CRITICAL: Sandbox Limitations for Error Handling

重要提示:沙箱环境对错误处理的限制

┌─────────────────────────────────────────────────────────────────────┐
│ ⚠️  SANDBOX RESTRICTIONS AFFECT ERROR HANDLING                      │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│ ❌ NO try/except blocks (blocked in RestrictedPython)               │
│ ❌ NO raise statements (use frappe.throw instead)                   │
│ ❌ NO import traceback                                              │
│                                                                     │
│ ✅ frappe.throw() - Stop execution, show error                      │
│ ✅ frappe.log_error() - Log to Error Log doctype                    │
│ ✅ frappe.msgprint() - Show message, continue execution             │
│ ✅ Conditional checks before operations                             │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────┐
│ ⚠️  沙箱限制会影响错误处理                                          │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│ ❌ 禁止使用try/except代码块(RestrictedPython中被拦截)               │
│ ❌ 禁止使用raise语句(请改用frappe.throw)                           │
│ ❌ 禁止导入traceback模块                                              │
│                                                                     │
│ ✅ frappe.throw() - 终止执行,显示错误提示                            │
│ ✅ frappe.log_error() - 将错误记录到Error Log文档类型中                │
│ ✅ frappe.msgprint() - 显示提示信息,继续执行                        │
│ ✅ 在操作前进行条件检查                                             │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

Main Decision: How to Handle the Error?

核心决策:如何处理错误?

┌─────────────────────────────────────────────────────────────────────────┐
│ WHAT TYPE OF ERROR ARE YOU HANDLING?                                    │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│ ► Validation error (must stop save/submit)?                             │
│   └─► frappe.throw() with clear message                                 │
│                                                                         │
│ ► Warning (inform user, allow continue)?                                │
│   └─► frappe.msgprint() with indicator                                  │
│                                                                         │
│ ► Log error for debugging (no user impact)?                             │
│   └─► frappe.log_error()                                                │
│                                                                         │
│ ► API error response (HTTP error)?                                      │
│   └─► frappe.throw() with exc parameter OR set response                 │
│                                                                         │
│ ► Scheduler task error?                                                 │
│   └─► frappe.log_error() + continue processing other items              │
│                                                                         │
│ ► Prevent operation but not with error dialog?                          │
│   └─► Return early + frappe.msgprint()                                  │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────────┐
│ 你要处理的是什么类型的错误?                                            │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│ ► 验证错误(必须阻止保存/提交)?                                       │
│   └─► 使用带清晰提示的frappe.throw()                                     │
│                                                                         │
│ ► 警告信息(通知用户,允许继续操作)?                                  │
│   └─► 使用带标识的frappe.msgprint()                                     │
│                                                                         │
│ ► 记录错误用于调试(不影响用户)?                                      │
│   └─► 使用frappe.log_error()                                           │
│                                                                         │
│ ► API错误响应(HTTP错误)?                                            │
│   └─► 使用带exc参数的frappe.throw() 或直接设置响应                        │
│                                                                         │
│ ► 调度任务错误?                                                       │
│   └─► 使用frappe.log_error() + 继续处理其他项                          │
│                                                                         │
│ ► 阻止操作但不弹出错误对话框?                                          │
│   └─► 提前返回 + frappe.msgprint()                                      │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

Error Methods Reference

错误处理方法参考

Quick Reference

快速参考

MethodStops Execution?User Sees?Logged?Use For
frappe.throw()
✅ YESDialogError LogValidation errors
frappe.msgprint()
❌ NODialogNoWarnings
frappe.log_error()
❌ NONoError LogDebug/audit
frappe.publish_realtime()
❌ NOToastNoBackground updates
方法是否终止执行?用户可见?是否记录?适用场景
frappe.throw()
✅ 是对话框错误日志验证错误
frappe.msgprint()
❌ 否对话框警告提示
frappe.log_error()
❌ 否错误日志调试/审计
frappe.publish_realtime()
❌ 否提示框后台更新通知

frappe.throw() - Stop Execution

frappe.throw() - 终止执行

python
undefined
python
undefined

Basic throw - stops execution, rolls back transaction

基础用法 - 终止执行,回滚事务

frappe.throw("Customer is required")
frappe.throw("客户为必填项")

With title

带标题的用法

frappe.throw("Amount cannot be negative", title="Validation Error")
frappe.throw("金额不能为负数", title="验证错误")

With exception type (for API scripts)

带异常类型(适用于API脚本)

frappe.throw("Not authorized", exc=frappe.PermissionError) frappe.throw("Record not found", exc=frappe.DoesNotExistError)
frappe.throw("未授权", exc=frappe.PermissionError) frappe.throw("记录未找到", exc=frappe.DoesNotExistError)

With formatted message

带格式化提示的用法

frappe.throw( f"Credit limit exceeded. Limit: {credit_limit}, Requested: {amount}", title="Credit Check Failed" )

**Exception Types for API Scripts:**

| Exception | HTTP Code | Use For |
|-----------|:---------:|---------|
| `frappe.ValidationError` | 417 | Validation failures |
| `frappe.PermissionError` | 403 | Access denied |
| `frappe.DoesNotExistError` | 404 | Record not found |
| `frappe.AuthenticationError` | 401 | Not logged in |
| `frappe.OutgoingEmailError` | 500 | Email send failed |
frappe.throw( f"信用额度已超出。额度:{credit_limit}, 申请金额:{amount}", title="信用检查失败" )

**API脚本适用的异常类型:**

| 异常 | HTTP状态码 | 适用场景 |
|-----------|:---------:|---------|
| `frappe.ValidationError` | 417 | 验证失败 |
| `frappe.PermissionError` | 403 | 访问被拒绝 |
| `frappe.DoesNotExistError` | 404 | 记录未找到 |
| `frappe.AuthenticationError` | 401 | 未登录 |
| `frappe.OutgoingEmailError` | 500 | 邮件发送失败 |

frappe.log_error() - Silent Logging

frappe.log_error() - 静默记录错误

python
undefined
python
undefined

Basic error log

基础错误记录

frappe.log_error("Something went wrong", "My Script Error")
frappe.log_error("发生未知错误", "我的脚本错误")

With context data

带上下文数据的记录

frappe.log_error( f"Failed to process invoice {doc.name}: {error_detail}", "Invoice Processing Error" )
frappe.log_error( f"处理发票{doc.name}失败:{error_detail}", "发票处理错误" )

Log current exception (in controllers, not sandbox)

记录当前异常(适用于控制器,不适用于沙箱环境)

frappe.log_error(frappe.get_traceback(), "Unexpected Error")
undefined
frappe.log_error(frappe.get_traceback(), "意外错误")
undefined

frappe.msgprint() - Warning Without Stopping

frappe.msgprint() - 不终止执行的警告

python
undefined
python
undefined

Simple warning

简单警告

frappe.msgprint("Stock is running low", indicator="orange")
frappe.msgprint("库存不足", indicator="orange")

With title

带标题的警告

frappe.msgprint( "This customer has pending payments", title="Warning", indicator="yellow" )
frappe.msgprint( "该客户存在未付款项", title="警告", indicator="yellow" )

Alert style (top of page)

顶部提示样式

frappe.msgprint( "Document will be processed in background", alert=True )

---
frappe.msgprint( "文档将在后台处理", alert=True )

---

Error Handling Patterns by Script Type

按脚本类型分类的错误处理模式

Pattern 1: Document Event - Validation

模式1:文档事件 - 验证

python
undefined
python
undefined

Type: Document Event

类型:文档事件

Event: Before Save

事件:保存前

Collect all errors, show together

收集所有错误,一次性显示

errors = []
if not doc.customer: errors.append("Customer is required")
if doc.grand_total <= 0: errors.append("Total must be greater than zero")
if not doc.items: errors.append("At least one item is required") else: for idx, item in enumerate(doc.items, 1): if not item.item_code: errors.append(f"Row {idx}: Item Code is required") if (item.qty or 0) <= 0: errors.append(f"Row {idx}: Quantity must be positive")
errors = []
if not doc.customer: errors.append("客户为必填项")
if doc.grand_total <= 0: errors.append("总金额必须大于0")
if not doc.items: errors.append("至少需要一个商品项") else: for idx, item in enumerate(doc.items, 1): if not item.item_code: errors.append(f"第{idx}行:商品编码为必填项") if (item.qty or 0) <= 0: errors.append(f"第{idx}行:数量必须为正数")

Throw all errors at once

一次性抛出所有错误

if errors: frappe.throw("<br>".join(errors), title="Validation Errors")
undefined
if errors: frappe.throw("<br>".join(errors), title="验证错误")
undefined

Pattern 2: Document Event - Conditional Warning

模式2:文档事件 - 条件警告

python
undefined
python
undefined

Type: Document Event

类型:文档事件

Event: Before Save

事件:保存前

Warning: doesn't stop save

警告:不阻止保存

credit_limit = frappe.db.get_value("Customer", doc.customer, "credit_limit") or 0
if credit_limit > 0 and doc.grand_total > credit_limit: frappe.msgprint( f"Order total ({doc.grand_total}) exceeds credit limit ({credit_limit})", title="Credit Warning", indicator="orange" )
undefined
credit_limit = frappe.db.get_value("Customer", doc.customer, "credit_limit") or 0
if credit_limit > 0 and doc.grand_total > credit_limit: frappe.msgprint( f"订单总金额({doc.grand_total})超出信用额度({credit_limit})", title="信用警告", indicator="orange" )
undefined

Pattern 3: Document Event - Safe Database Lookup

模式3:文档事件 - 安全数据库查询

python
undefined
python
undefined

Type: Document Event

类型:文档事件

Event: Before Save

事件:保存前

Always validate before database lookup

在数据库查询前始终进行验证

if doc.customer: customer_data = frappe.db.get_value( "Customer", doc.customer, ["credit_limit", "disabled", "territory"], as_dict=True )
# Check if customer exists
if not customer_data:
    frappe.throw(f"Customer {doc.customer} not found")

# Check if disabled
if customer_data.disabled:
    frappe.throw(f"Customer {doc.customer} is disabled")

# Use the data
doc.territory = customer_data.territory
undefined
if doc.customer: customer_data = frappe.db.get_value( "Customer", doc.customer, ["credit_limit", "disabled", "territory"], as_dict=True )
# 检查客户是否存在
if not customer_data:
    frappe.throw(f"客户{doc.customer}不存在")

# 检查客户是否被禁用
if customer_data.disabled:
    frappe.throw(f"客户{doc.customer}已被禁用")

# 使用查询到的数据
doc.territory = customer_data.territory
undefined

Pattern 4: API Script - Error Responses

模式4:API脚本 - 错误响应

python
undefined
python
undefined

Type: API

类型:API

Method: get_customer_info

方法:get_customer_info

customer = frappe.form_dict.get("customer")
customer = frappe.form_dict.get("customer")

Validate required parameter

验证必填参数

if not customer: frappe.throw("Parameter 'customer' is required", exc=frappe.ValidationError)
if not customer: frappe.throw("参数'customer'为必填项", exc=frappe.ValidationError)

Check existence

检查客户是否存在

if not frappe.db.exists("Customer", customer): frappe.throw(f"Customer '{customer}' not found", exc=frappe.DoesNotExistError)
if not frappe.db.exists("Customer", customer): frappe.throw(f"客户'{customer}'不存在", exc=frappe.DoesNotExistError)

Check permission

检查权限

if not frappe.has_permission("Customer", "read", customer): frappe.throw("You don't have permission to view this customer", exc=frappe.PermissionError)
if not frappe.has_permission("Customer", "read", customer): frappe.throw("你没有查看该客户的权限", exc=frappe.PermissionError)

Success response

成功响应

frappe.response["message"] = { "customer": customer, "credit_limit": frappe.db.get_value("Customer", customer, "credit_limit") }
undefined
frappe.response["message"] = { "customer": customer, "credit_limit": frappe.db.get_value("Customer", customer, "credit_limit") }
undefined

Pattern 5: Scheduler - Batch Processing with Error Isolation

模式5:调度任务 - 隔离错误的批量处理

python
undefined
python
undefined

Type: Scheduler Event

类型:调度事件

Cron: 0 9 * * * (daily at 9:00)

定时规则:0 9 * * *(每天9点执行)

processed = 0 errors = []
invoices = frappe.get_all( "Sales Invoice", filters={"status": "Unpaid", "docstatus": 1}, fields=["name", "customer"], limit=100 # ALWAYS limit in scheduler )
for inv in invoices: # Isolate errors per item - don't let one failure stop all if not frappe.db.exists("Customer", inv.customer): errors.append(f"{inv.name}: Customer not found") continue
# Safe processing
result = process_invoice(inv.name)
if result.get("success"):
    processed += 1
else:
    errors.append(f"{inv.name}: {result.get('error', 'Unknown error')}")
processed = 0 errors = []
invoices = frappe.get_all( "Sales Invoice", filters={"status": "Unpaid", "docstatus": 1}, fields=["name", "customer"], limit=100 # 调度任务中务必设置查询限制 )
for inv in invoices: # 为每个项隔离错误 - 单个项失败不影响全部处理 if not frappe.db.exists("Customer", inv.customer): errors.append(f"{inv.name}:客户不存在") continue
# 安全处理
result = process_invoice(inv.name)
if result.get("success"):
    processed += 1
else:
    errors.append(f"{inv.name}:{result.get('error', '未知错误')}")

Log summary

记录处理总结

if errors: frappe.log_error( f"Processed: {processed}, Errors: {len(errors)}\n\n" + "\n".join(errors), "Invoice Processing Summary" )
if errors: frappe.log_error( f"已处理:{processed}, 错误数:{len(errors)}\n\n" + "\n".join(errors), "发票处理总结" )

REQUIRED: commit in scheduler

必须执行:调度任务中提交事务

frappe.db.commit()
def process_invoice(invoice_name): """Helper function with error handling""" # Validate invoice exists if not frappe.db.exists("Sales Invoice", invoice_name): return {"success": False, "error": "Invoice not found"}
# Process logic here
return {"success": True}
undefined
frappe.db.commit()
def process_invoice(invoice_name): """带错误处理的辅助函数""" # 验证发票是否存在 if not frappe.db.exists("Sales Invoice", invoice_name): return {"success": False, "error": "发票不存在"}
# 此处编写处理逻辑
return {"success": True}
undefined

Pattern 6: Permission Query - Safe Fallback

模式6:权限查询 - 安全降级

python
undefined
python
undefined

Type: Permission Query

类型:权限查询

DocType: Sales Invoice

文档类型:Sales Invoice

Safe role check

安全角色检查

user_roles = frappe.get_roles(user) or []
if "System Manager" in user_roles: conditions = "" # Full access elif "Sales Manager" in user_roles: # Manager sees team's invoices team = frappe.db.get_value("User", user, "department") if team: conditions = f"
tabSales Invoice
.department = {frappe.db.escape(team)}" else: conditions = f"
tabSales Invoice
.owner = {frappe.db.escape(user)}" elif "Sales User" in user_roles: # User sees only own invoices conditions = f"
tabSales Invoice
.owner = {frappe.db.escape(user)}" else: # No access - return impossible condition conditions = "1=0"

> **See**: `references/patterns.md` for more error handling patterns.

---
user_roles = frappe.get_roles(user) or []
if "System Manager" in user_roles: conditions = "" # 完全访问权限 elif "Sales Manager" in user_roles: # 销售经理可查看团队的发票 team = frappe.db.get_value("User", user, "department") if team: conditions = f"
tabSales Invoice
.department = {frappe.db.escape(team)}" else: conditions = f"
tabSales Invoice
.owner = {frappe.db.escape(user)}" elif "Sales User" in user_roles: # 销售用户仅可查看自己的发票 conditions = f"
tabSales Invoice
.owner = {frappe.db.escape(user)}" else: # 无访问权限 - 返回恒假条件 conditions = "1=0"

> **参考**:更多错误处理模式请查看`references/patterns.md`。

---

Transaction Behavior

事务行为

Automatic Rollback on frappe.throw()

frappe.throw()触发的自动回滚

python
undefined
python
undefined

Type: Document Event - Before Save

类型:文档事件 - 保存前

All changes roll back if throw is called

如果调用throw,所有更改都会回滚

doc.status = "Processing" # This change... frappe.db.set_value("Counter", "main", "count", 100) # ...and this...
if some_condition_fails: frappe.throw("Validation failed") # ...are ALL rolled back
undefined
doc.status = "Processing" # 此项更改... frappe.db.set_value("Counter", "main", "count", 100) # ...和此项更改...
if some_condition_fails: frappe.throw("验证失败") # ...都会被回滚
undefined

Manual Commit in Scheduler

调度任务中的手动提交

python
undefined
python
undefined

Type: Scheduler Event

类型:调度事件

Changes are NOT auto-committed in scheduler

调度任务中的更改不会自动提交

for item in items: frappe.db.set_value("Item", item.name, "last_sync", frappe.utils.now())
for item in items: frappe.db.set_value("Item", item.name, "last_sync", frappe.utils.now())

REQUIRED: Explicit commit

必须执行:显式提交事务

frappe.db.commit()
undefined
frappe.db.commit()
undefined

Partial Commit Pattern (Scheduler)

部分提交模式(调度任务)

python
undefined
python
undefined

Type: Scheduler Event

类型:调度事件

Process in batches with intermediate commits

按批次处理并进行中间提交

BATCH_SIZE = 50 items = frappe.get_all("Item", filters={"sync_pending": 1}, limit=500)
for i in range(0, len(items), BATCH_SIZE): batch = items[i:i + BATCH_SIZE]
for item in batch:
    frappe.db.set_value("Item", item.name, "sync_pending", 0)

# Commit after each batch - partial progress saved
frappe.db.commit()

---
BATCH_SIZE = 50 items = frappe.get_all("Item", filters={"sync_pending": 1}, limit=500)
for i in range(0, len(items), BATCH_SIZE): batch = items[i:i + BATCH_SIZE]
for item in batch:
    frappe.db.set_value("Item", item.name, "sync_pending", 0)

# 每批次处理后提交 - 保存部分进度
frappe.db.commit()

---

Critical Rules

关键规则

✅ ALWAYS

✅ 务必遵守

  1. Validate inputs before database operations - Check existence before get_doc
  2. Use
    frappe.db.escape()
    for user input in SQL
    - Prevent SQL injection
  3. Add
    limit
    to queries in Scheduler scripts
    - Prevent memory issues
  4. Call
    frappe.db.commit()
    in Scheduler scripts
    - Changes aren't auto-saved
  5. Collect multiple errors before throwing - Better user experience
  6. Log errors in Scheduler scripts - No user to see the error
  1. 在数据库操作前验证输入 - 在调用get_doc前检查记录是否存在
  2. 在SQL中对用户输入使用
    frappe.db.escape()
    - 防止SQL注入
  3. 在调度任务的查询中添加
    limit
    - 避免内存问题
  4. 在调度任务中调用
    frappe.db.commit()
    - 更改不会自动保存
  5. 收集多个错误后再抛出 - 提升用户体验
  6. 在调度任务中记录错误 - 没有用户会看到错误提示

❌ NEVER

❌ 绝对禁止

  1. Don't use try/except in Server Scripts - Blocked by sandbox
  2. Don't use
    raise
    statement
    - Use
    frappe.throw()
    instead
  3. Don't call
    doc.save()
    in Before Save event
    - Framework handles it
  4. Don't assume database values exist - Always check first
  5. Don't ignore empty results - Handle gracefully

  1. 不要在Server Scripts中使用try/except - 沙箱环境会拦截
  2. 不要使用
    raise
    语句
    - 请改用
    frappe.throw()
  3. 不要在保存前事件中调用
    doc.save()
    - 框架会自动处理
  4. 不要假设数据库值一定存在 - 始终先检查
  5. 不要忽略空结果 - 优雅处理空值

Quick Reference: Error Message Quality

错误提示质量快速参考

python
undefined
python
undefined

❌ BAD - Technical, not actionable

❌ 错误示例 - 技术化,无操作指引

frappe.throw("KeyError: customer") frappe.throw("NoneType has no attribute 'name'") frappe.throw("Query failed")
frappe.throw("KeyError: customer") frappe.throw("NoneType has no attribute 'name'") frappe.throw("查询失败")

✅ GOOD - Clear, actionable

✅ 正确示例 - 清晰,有操作指引

frappe.throw("Please select a customer before saving") frappe.throw(f"Customer '{doc.customer}' not found. Please verify the customer exists.") frappe.throw("Could not calculate totals. Please ensure all items have valid quantities.")

---
frappe.throw("请在保存前选择客户") frappe.throw(f"客户'{doc.customer}'不存在,请确认客户信息是否正确。") frappe.throw("无法计算总金额,请确保所有商品项的数量有效。")

---

Reference Files

参考文件

FileContents
references/patterns.md
Complete error handling patterns
references/examples.md
Full working examples
references/anti-patterns.md
Common mistakes to avoid

文件内容
references/patterns.md
完整的错误处理模式
references/examples.md
完整的可运行示例
references/anti-patterns.md
需避免的常见错误

See Also

相关内容

  • erpnext-syntax-serverscripts
    - Server Script syntax
  • erpnext-impl-serverscripts
    - Implementation workflows
  • erpnext-errors-clientscripts
    - Client-side error handling
  • erpnext-database
    - Database operations
  • erpnext-syntax-serverscripts
    - Server Script语法
  • erpnext-impl-serverscripts
    - 实现流程
  • erpnext-errors-clientscripts
    - 客户端错误处理
  • erpnext-database
    - 数据库操作