erpnext-errors-serverscripts
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseERPNext Server Scripts - Error Handling
ERPNext Server Scripts - 错误处理
This skill covers error handling patterns for Server Scripts. For syntax, see . For implementation workflows, see .
erpnext-syntax-serverscriptserpnext-impl-serverscriptsVersion: v14/v15/v16 compatible
本内容涵盖Server Scripts的错误处理模式。如需了解语法,请查看。如需了解实现流程,请查看。
erpnext-syntax-serverscriptserpnext-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
快速参考
| Method | Stops Execution? | User Sees? | Logged? | Use For |
|---|---|---|---|---|
| ✅ YES | Dialog | Error Log | Validation errors |
| ❌ NO | Dialog | No | Warnings |
| ❌ NO | No | Error Log | Debug/audit |
| ❌ NO | Toast | No | Background updates |
| 方法 | 是否终止执行? | 用户可见? | 是否记录? | 适用场景 |
|---|---|---|---|---|
| ✅ 是 | 对话框 | 错误日志 | 验证错误 |
| ❌ 否 | 对话框 | 否 | 警告提示 |
| ❌ 否 | 否 | 错误日志 | 调试/审计 |
| ❌ 否 | 提示框 | 否 | 后台更新通知 |
frappe.throw() - Stop Execution
frappe.throw() - 终止执行
python
undefinedpython
undefinedBasic 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
undefinedpython
undefinedBasic 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")
undefinedfrappe.log_error(frappe.get_traceback(), "意外错误")
undefinedfrappe.msgprint() - Warning Without Stopping
frappe.msgprint() - 不终止执行的警告
python
undefinedpython
undefinedSimple 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
undefinedpython
undefinedType: 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")
undefinedif errors:
frappe.throw("<br>".join(errors), title="验证错误")
undefinedPattern 2: Document Event - Conditional Warning
模式2:文档事件 - 条件警告
python
undefinedpython
undefinedType: 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"
)
undefinedcredit_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"
)
undefinedPattern 3: Document Event - Safe Database Lookup
模式3:文档事件 - 安全数据库查询
python
undefinedpython
undefinedType: 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.territoryundefinedif 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.territoryundefinedPattern 4: API Script - Error Responses
模式4:API脚本 - 错误响应
python
undefinedpython
undefinedType: 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")
}
undefinedfrappe.response["message"] = {
"customer": customer,
"credit_limit": frappe.db.get_value("Customer", customer, "credit_limit")
}
undefinedPattern 5: Scheduler - Batch Processing with Error Isolation
模式5:调度任务 - 隔离错误的批量处理
python
undefinedpython
undefinedType: 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}undefinedfrappe.db.commit()
def process_invoice(invoice_name):
"""带错误处理的辅助函数"""
# 验证发票是否存在
if not frappe.db.exists("Sales Invoice", invoice_name):
return {"success": False, "error": "发票不存在"}
# 此处编写处理逻辑
return {"success": True}undefinedPattern 6: Permission Query - Safe Fallback
模式6:权限查询 - 安全降级
python
undefinedpython
undefinedType: 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".department = {frappe.db.escape(team)}"
else:
conditions = f".owner = {frappe.db.escape(user)}"
elif "Sales User" in user_roles:
# User sees only own invoices
conditions = f".owner = {frappe.db.escape(user)}"
else:
# No access - return impossible condition
conditions = "1=0"
tabSales InvoicetabSales InvoicetabSales Invoice
> **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".department = {frappe.db.escape(team)}"
else:
conditions = f".owner = {frappe.db.escape(user)}"
elif "Sales User" in user_roles:
# 销售用户仅可查看自己的发票
conditions = f".owner = {frappe.db.escape(user)}"
else:
# 无访问权限 - 返回恒假条件
conditions = "1=0"
tabSales InvoicetabSales InvoicetabSales Invoice
> **参考**:更多错误处理模式请查看`references/patterns.md`。
---Transaction Behavior
事务行为
Automatic Rollback on frappe.throw()
frappe.throw()触发的自动回滚
python
undefinedpython
undefinedType: 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
undefineddoc.status = "Processing" # 此项更改...
frappe.db.set_value("Counter", "main", "count", 100) # ...和此项更改...
if some_condition_fails:
frappe.throw("验证失败") # ...都会被回滚
undefinedManual Commit in Scheduler
调度任务中的手动提交
python
undefinedpython
undefinedType: 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()
undefinedfrappe.db.commit()
undefinedPartial Commit Pattern (Scheduler)
部分提交模式(调度任务)
python
undefinedpython
undefinedType: 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
✅ 务必遵守
- Validate inputs before database operations - Check existence before get_doc
- Use for user input in SQL - Prevent SQL injection
frappe.db.escape() - Add to queries in Scheduler scripts - Prevent memory issues
limit - Call in Scheduler scripts - Changes aren't auto-saved
frappe.db.commit() - Collect multiple errors before throwing - Better user experience
- Log errors in Scheduler scripts - No user to see the error
- 在数据库操作前验证输入 - 在调用get_doc前检查记录是否存在
- 在SQL中对用户输入使用- 防止SQL注入
frappe.db.escape() - 在调度任务的查询中添加- 避免内存问题
limit - 在调度任务中调用- 更改不会自动保存
frappe.db.commit() - 收集多个错误后再抛出 - 提升用户体验
- 在调度任务中记录错误 - 没有用户会看到错误提示
❌ NEVER
❌ 绝对禁止
- Don't use try/except in Server Scripts - Blocked by sandbox
- Don't use statement - Use
raiseinsteadfrappe.throw() - Don't call in Before Save event - Framework handles it
doc.save() - Don't assume database values exist - Always check first
- Don't ignore empty results - Handle gracefully
- 不要在Server Scripts中使用try/except - 沙箱环境会拦截
- 不要使用语句 - 请改用
raisefrappe.throw() - 不要在保存前事件中调用- 框架会自动处理
doc.save() - 不要假设数据库值一定存在 - 始终先检查
- 不要忽略空结果 - 优雅处理空值
Quick Reference: Error Message Quality
错误提示质量快速参考
python
undefinedpython
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
参考文件
| File | Contents |
|---|---|
| Complete error handling patterns |
| Full working examples |
| Common mistakes to avoid |
| 文件 | 内容 |
|---|---|
| 完整的错误处理模式 |
| 完整的可运行示例 |
| 需避免的常见错误 |
See Also
相关内容
- - Server Script syntax
erpnext-syntax-serverscripts - - Implementation workflows
erpnext-impl-serverscripts - - Client-side error handling
erpnext-errors-clientscripts - - Database operations
erpnext-database
- - Server Script语法
erpnext-syntax-serverscripts - - 实现流程
erpnext-impl-serverscripts - - 客户端错误处理
erpnext-errors-clientscripts - - 数据库操作
erpnext-database