erpnext-impl-clientscripts
npx skills add https://github.com/openaec-foundation/erpnext_anthropic_claude_development_skill_package --skill erpnext-impl-clientscripts
Agent 安装分布
Skill 文档
ERPNext Client Scripts – Implementation (EN)
This skill helps you determine HOW to implement client-side features. For exact syntax, see erpnext-syntax-clientscripts.
Version: v14/v15/v16 compatible
Main Decision: Client or Server?
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
â Must the logic ALWAYS execute? â
â (including imports, API calls, Server Scripts) â
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ¤
â YES â Server-side (Controller or Server Script) â
â NO â What is the primary goal? â
â âââ UI feedback/UX improvement â Client Script â
â âââ Show/hide fields â Client Script â
â âââ Link filters â Client Script â
â âââ Data validation â BOTH (client + server) â
â âââ Calculations â Depends on criticality â
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
Rule of thumb: Client Scripts for UX, Server for integrity.
Decision Tree: Which Event?
WHAT DO YOU WANT TO ACHIEVE?
â
ââ⺠Set link field filters
â âââ setup (once, early in lifecycle)
â
ââ⺠Add custom buttons
â âââ refresh (after each form load/save)
â
ââ⺠Show/hide fields based on condition
â âââ refresh + {fieldname} (both needed)
â
ââ⺠Validation before save
â âââ validate (use frappe.throw on error)
â
ââ⺠Action after successful save
â âââ after_save
â
ââ⺠Calculation on field change
â âââ {fieldname}
â
ââ⺠Child table row added
â âââ {tablename}_add
â
ââ⺠Child table field changed
â âââ Child DocType event: {fieldname}
â
ââ⺠One-time initialization
âââ setup or onload
â See references/decision-tree.md for complete decision tree.
Implementation Workflows
Workflow 1: Dynamic Field Visibility
Scenario: Show “delivery_date” only when “requires_delivery” is checked.
frappe.ui.form.on('Sales Order', {
refresh(frm) {
// Initial state on form load
frm.trigger('requires_delivery');
},
requires_delivery(frm) {
// Toggle on checkbox change AND refresh
frm.toggle_display('delivery_date', frm.doc.requires_delivery);
frm.toggle_reqd('delivery_date', frm.doc.requires_delivery);
}
});
Why both events?
refresh: Sets correct state when form opens{fieldname}: Responds to user interaction
Workflow 2: Cascading Dropdowns
Scenario: Filter “city” based on selected “country”.
frappe.ui.form.on('Customer', {
setup(frm) {
// Filter MUST be in setup for consistency
frm.set_query('city', () => ({
filters: {
country: frm.doc.country || ''
}
}));
},
country(frm) {
// Clear city when country changes
frm.set_value('city', '');
}
});
Workflow 3: Automatic Calculations
Scenario: Calculate total in child table with discount.
frappe.ui.form.on('Sales Invoice', {
discount_percentage(frm) {
calculate_totals(frm);
}
});
frappe.ui.form.on('Sales Invoice Item', {
qty(frm, cdt, cdn) {
calculate_row_amount(frm, cdt, cdn);
},
rate(frm, cdt, cdn) {
calculate_row_amount(frm, cdt, cdn);
},
amount(frm) {
// Recalculate document total on row change
calculate_totals(frm);
}
});
function calculate_row_amount(frm, cdt, cdn) {
let row = frappe.get_doc(cdt, cdn);
frappe.model.set_value(cdt, cdn, 'amount', row.qty * row.rate);
}
function calculate_totals(frm) {
let total = 0;
(frm.doc.items || []).forEach(row => {
total += row.amount || 0;
});
let discount = total * (frm.doc.discount_percentage || 0) / 100;
frm.set_value('grand_total', total - discount);
}
Workflow 4: Fetching Server Data
Scenario: Populate customer details on customer selection.
frappe.ui.form.on('Sales Order', {
async customer(frm) {
if (!frm.doc.customer) {
// Clear fields if customer cleared
frm.set_value({
customer_name: '',
territory: '',
credit_limit: 0
});
return;
}
// Fetch customer details
let r = await frappe.db.get_value('Customer',
frm.doc.customer,
['customer_name', 'territory', 'credit_limit']
);
if (r.message) {
frm.set_value({
customer_name: r.message.customer_name,
territory: r.message.territory,
credit_limit: r.message.credit_limit
});
}
}
});
Workflow 5: Validation with Server Check
Scenario: Check credit limit before save.
frappe.ui.form.on('Sales Order', {
async validate(frm) {
if (frm.doc.customer && frm.doc.grand_total) {
let r = await frappe.call({
method: 'myapp.api.check_credit',
args: {
customer: frm.doc.customer,
amount: frm.doc.grand_total
}
});
if (r.message && !r.message.allowed) {
frappe.throw(__('Credit limit exceeded. Available: {0}',
[r.message.available]));
}
}
}
});
â See references/workflows.md for more workflow patterns.
Integration Matrix
| Client Script Action | Requires Server-side |
|---|---|
| Link filters | Optional: custom query |
| Fetch server data | frappe.db.* or whitelisted method |
| Call document method | @frappe.whitelist() in controller |
| Complex validation | Server Script or controller validation |
| Create document | frappe.db.insert or whitelisted method |
Client + Server Combination
// CLIENT: frm.call invokes controller method
frm.call('calculate_taxes')
.then(() => frm.reload_doc());
// SERVER (controller): MUST have @frappe.whitelist
class SalesInvoice(Document):
@frappe.whitelist()
def calculate_taxes(self):
# complex calculation
self.tax_amount = self.grand_total * 0.21
self.save()
Checklist: Implementation Steps
New Client Script Feature
-
[ ] Determine scope
- UI/UX only? â Client script only
- Data integrity? â Also server validation
-
[ ] Choose events
- Use decision tree above
- Combine refresh + fieldname for visibility
-
[ ] Implement basics
- Start with
frappe.ui.form.on - Test with console.log first
- Start with
-
[ ] Add error handling
try/catcharound async callsfrappe.throwfor validation errors
-
[ ] Test edge cases
- New document (frm.is_new())
- Empty field (null checks)
- Child table empty/filled
-
[ ] Translate strings
- All UI text in
__()
- All UI text in
Critical Rules
| Rule | Why |
|---|---|
refresh_field() after child table change |
UI synchronization |
set_query in setup event |
Consistent filter behavior |
frappe.throw() for validation, not msgprint |
Stops save action |
| Async/await for server calls | Prevent race conditions |
Check frm.is_new() for buttons |
Prevent errors on new doc |
Related Skills
erpnext-syntax-clientscriptsâ Exact syntax and method signatureserpnext-errors-clientscriptsâ Error handling patternserpnext-syntax-whitelistedâ Server methods for frm.callerpnext-databaseâ frappe.db.* client-side API
â See references/examples.md for 10+ complete implementation examples.