erpnext-errors-serverscripts

安装

npx skills add https://github.com/openaec-foundation/erpnext_anthropic_claude_development_skill_package --skill erpnext-errors-serverscripts
ERPNext Server Scripts - Error Handling
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 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 │ │ │ └─────────────────────────────────────────────────────────────────────┘ 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() │ │ │ └─────────────────────────────────────────────────────────────────────────┘ Error Methods Reference Quick Reference Method Stops Execution? User Sees? Logged? Use For frappe.throw() ✅ YES Dialog Error Log Validation errors frappe.msgprint() ❌ NO Dialog No Warnings frappe.log_error() ❌ NO No Error Log Debug/audit frappe.publish_realtime() ❌ NO Toast No Background updates frappe.throw() - Stop Execution

Basic throw - stops execution, rolls back transaction

frappe . throw ( "Customer is required" )

With title

frappe . throw ( "Amount cannot be negative" , title = "Validation Error" )

With exception type (for API scripts)

frappe . throw ( "Not authorized" , exc = frappe . PermissionError ) frappe . throw ( "Record not found" , 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.log_error() - Silent Logging

Basic error log

frappe . log_error ( "Something went wrong" , "My Script Error" )

With context data

frappe . log_error ( f"Failed to process invoice { doc . name } : { error_detail } " , "Invoice Processing Error" )

Log current exception (in controllers, not sandbox)

frappe . log_error ( frappe . get_traceback ( ) , "Unexpected Error" ) frappe.msgprint() - Warning Without Stopping

Simple warning

frappe . msgprint ( "Stock is running low" , indicator = "orange" )

With title

frappe . msgprint ( "This customer has pending payments" , title = "Warning" , indicator = "yellow" )

Alert style (top of page)

frappe . msgprint ( "Document will be processed in background" , alert = True ) Error Handling Patterns by Script Type Pattern 1: Document Event - Validation

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" )

Throw all errors at once

if errors : frappe . throw ( "
" . join ( errors ) , title = "Validation Errors" ) Pattern 2: Document Event - Conditional Warning

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" ) Pattern 3: Document Event - Safe Database Lookup

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 Pattern 4: API Script - Error Responses

Type: API

Method: get_customer_info

customer

frappe . form_dict . get ( "customer" )

Validate required parameter

if not customer : frappe . throw ( "Parameter 'customer' is required" , exc = frappe . ValidationError )

Check existence

if not frappe . db . exists ( "Customer" , customer ) : frappe . throw ( f"Customer ' { customer } ' not found" , 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 )

Success response

frappe . response [ "message" ] = { "customer" : customer , "credit_limit" : frappe . db . get_value ( "Customer" , customer , "credit_limit" ) } Pattern 5: Scheduler - Batch Processing with Error Isolation

Type: Scheduler Event

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

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' ) } " )

Log summary

if errors : frappe . log_error ( f"Processed: { processed } , Errors: { len ( errors ) } \n\n" + "\n" . join ( errors ) , "Invoice Processing Summary" )

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 } Pattern 6: Permission Query - Safe Fallback

Type: Permission Query

DocType: 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. Transaction Behavior Automatic Rollback on frappe.throw()

Type: Document Event - Before Save

All changes roll back if throw is called

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

Manual Commit in Scheduler

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 ( ) )

REQUIRED: Explicit commit

frappe . db . commit ( ) Partial Commit Pattern (Scheduler)

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 ( ) Critical Rules ✅ ALWAYS Validate inputs before database operations - Check existence before get_doc Use frappe.db.escape() for user input in SQL - Prevent SQL injection Add limit to queries in Scheduler scripts - Prevent memory issues Call frappe.db.commit() in Scheduler scripts - Changes aren't auto-saved Collect multiple errors before throwing - Better user experience Log errors in Scheduler scripts - No user to see the error ❌ NEVER Don't use try/except in Server Scripts - Blocked by sandbox Don't use raise statement - Use frappe.throw() instead Don't call doc.save() in Before Save event - Framework handles it Don't assume database values exist - Always check first Don't ignore empty results - Handle gracefully Quick Reference: Error Message Quality

❌ BAD - Technical, not actionable

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

✅ 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." ) Reference Files File Contents references/patterns.md Complete error handling patterns references/examples.md Full working examples references/anti-patterns.md Common mistakes to avoid See Also erpnext-syntax-serverscripts - Server Script syntax erpnext-impl-serverscripts - Implementation workflows erpnext-errors-clientscripts - Client-side error handling erpnext-database - Database operations

返回排行榜