codymaster 4.6.0 → 4.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -1
- package/README.md +80 -30
- package/dist/browse-server.js +251 -0
- package/dist/cli/command-registry.js +26 -0
- package/dist/cli/commands/agent.js +120 -0
- package/dist/cli/commands/dashboard.js +93 -0
- package/dist/cli/commands/design-studio.js +111 -0
- package/dist/cli/commands/distro.js +25 -0
- package/dist/cli/commands/engineering.js +488 -0
- package/dist/cli/commands/project.js +324 -0
- package/dist/cli/commands/skill-chain.js +269 -0
- package/dist/cli/commands/system.js +89 -0
- package/dist/cli/commands/task.js +254 -0
- package/dist/cli/update-check.js +83 -0
- package/dist/cm-config.js +110 -0
- package/dist/cm-suggest.js +77 -0
- package/dist/distro-validate.js +54 -0
- package/dist/guardian-core.js +74 -0
- package/dist/index.js +36 -2759
- package/dist/mcp-context-server.js +60 -1
- package/dist/mcp-skills-tools.js +81 -0
- package/dist/retro-summary.js +70 -0
- package/dist/second-opinion-providers.js +79 -0
- package/dist/sprint-pipeline.js +228 -0
- package/dist/storage-backend.js +5 -60
- package/dist/utils/cli-utils.js +76 -0
- package/dist/utils/skill-utils.js +32 -0
- package/install.sh +274 -50
- package/package.json +16 -5
- package/scripts/build-skills.mjs +51 -0
- package/scripts/gate-0-repo-hygiene.js +75 -0
- package/scripts/postinstall.js +55 -0
- package/scripts/security-scan.js +1 -1
- package/scripts/validate-skills.mjs +42 -0
- package/scripts/viking-demo.ts +105 -0
- package/skills/CLAUDE.md +2 -2
- package/skills/cm-ads-tracker/SKILL.md +3 -6
- package/skills/cm-browse/SKILL.md +28 -0
- package/skills/cm-conductor-worktrees/SKILL.md +24 -0
- package/skills/cm-content-factory/SKILL.md +1 -1
- package/skills/cm-content-factory/landing/docs/content/changelog.md +36 -0
- package/skills/cm-content-factory/landing/docs/content/deployment.md +46 -0
- package/skills/cm-content-factory/landing/docs/content/execution-flow.md +67 -0
- package/skills/cm-content-factory/landing/docs/content/openspace.md +27 -0
- package/skills/cm-content-factory/landing/docs/content/openviking.md +33 -0
- package/skills/cm-content-factory/landing/docs/content/use-cases.md +26 -0
- package/skills/cm-content-factory/landing/docs/content/v5-intro.md +28 -0
- package/skills/cm-content-factory/landing/docs/index.html +240 -0
- package/skills/cm-content-factory/landing/index.html +99 -99
- package/skills/cm-content-factory/landing/script.js +42 -0
- package/skills/cm-content-factory/landing/translations.js +400 -400
- package/skills/cm-design-studio/SKILL.md +30 -0
- package/skills/cm-ecosystem-roadmap/SKILL.md +11 -0
- package/skills/cm-engineering-meta/SKILL.md +69 -0
- package/skills/cm-growth-hacking/SKILL.md +1 -12
- package/skills/cm-guardian-runtime/SKILL.md +22 -0
- package/skills/cm-mcp-engineering/SKILL.md +18 -0
- package/skills/cm-notebooklm/SKILL.md +1 -17
- package/skills/cm-post-deploy-canary/SKILL.md +18 -0
- package/skills/cm-qa-visual-cli/SKILL.md +18 -0
- package/skills/cm-retro-cli/SKILL.md +19 -0
- package/skills/cm-second-opinion-cli/SKILL.md +19 -0
- package/skills/cm-secret-shield/SKILL.md +2 -2
- package/skills/cm-sprint-bus/SKILL.md +29 -0
- package/skills/cm-tdd/SKILL.md +61 -74
- package/skills/profiles/README.md +21 -0
- package/skills/profiles/core.txt +23 -0
- package/skills/profiles/design.txt +6 -0
- package/skills/profiles/full.txt +58 -0
- package/skills/profiles/growth.txt +10 -0
- package/skills/profiles/knowledge.txt +7 -0
- package/scripts/test-gemini.js +0 -13
- package/skills/cm-frappe-agent/SKILL.md +0 -134
- package/skills/cm-frappe-agent/agents/doctype-architect.md +0 -596
- package/skills/cm-frappe-agent/agents/erpnext-customizer.md +0 -643
- package/skills/cm-frappe-agent/agents/frappe-backend.md +0 -814
- package/skills/cm-frappe-agent/agents/frappe-custom-frontend.md +0 -557
- package/skills/cm-frappe-agent/agents/frappe-debugger.md +0 -625
- package/skills/cm-frappe-agent/agents/frappe-fixer.md +0 -275
- package/skills/cm-frappe-agent/agents/frappe-frontend.md +0 -660
- package/skills/cm-frappe-agent/agents/frappe-installer.md +0 -158
- package/skills/cm-frappe-agent/agents/frappe-performance.md +0 -307
- package/skills/cm-frappe-agent/agents/frappe-planner.md +0 -419
- package/skills/cm-frappe-agent/agents/frappe-remote-ops.md +0 -153
- package/skills/cm-frappe-agent/agents/github-workflow.md +0 -286
- package/skills/cm-frappe-agent/commands/frappe-app.md +0 -351
- package/skills/cm-frappe-agent/commands/frappe-backend.md +0 -162
- package/skills/cm-frappe-agent/commands/frappe-bench.md +0 -254
- package/skills/cm-frappe-agent/commands/frappe-debug.md +0 -263
- package/skills/cm-frappe-agent/commands/frappe-doctype-create.md +0 -272
- package/skills/cm-frappe-agent/commands/frappe-doctype-field.md +0 -310
- package/skills/cm-frappe-agent/commands/frappe-erpnext.md +0 -210
- package/skills/cm-frappe-agent/commands/frappe-fix.md +0 -59
- package/skills/cm-frappe-agent/commands/frappe-frontend.md +0 -210
- package/skills/cm-frappe-agent/commands/frappe-fullstack.md +0 -243
- package/skills/cm-frappe-agent/commands/frappe-github.md +0 -57
- package/skills/cm-frappe-agent/commands/frappe-install.md +0 -52
- package/skills/cm-frappe-agent/commands/frappe-plan.md +0 -442
- package/skills/cm-frappe-agent/commands/frappe-remote.md +0 -58
- package/skills/cm-frappe-agent/commands/frappe-test.md +0 -356
- package/skills/cm-frappe-agent/docs/README.md +0 -51
- package/skills/cm-frappe-agent/docs/agents-catalog.md +0 -113
- package/skills/cm-frappe-agent/docs/architecture.md +0 -149
- package/skills/cm-frappe-agent/docs/commands-catalog.md +0 -82
- package/skills/cm-frappe-agent/docs/resources-catalog.md +0 -66
- package/skills/cm-frappe-agent/docs/sitemap-urls.txt +0 -52
- package/skills/cm-frappe-agent/docs/sitemap.md +0 -81
- package/skills/cm-frappe-agent/docs/sop/user-guide.md +0 -178
- package/skills/cm-frappe-agent/docs/sop/vibe-coding-guide.md +0 -122
- package/skills/cm-frappe-agent/resources/7-layer-architecture.md +0 -985
- package/skills/cm-frappe-agent/resources/bench_commands.md +0 -73
- package/skills/cm-frappe-agent/resources/code-patterns-guide.md +0 -948
- package/skills/cm-frappe-agent/resources/common_pitfalls.md +0 -266
- package/skills/cm-frappe-agent/resources/doctype-registry.md +0 -158
- package/skills/cm-frappe-agent/resources/installation-guide.md +0 -289
- package/skills/cm-frappe-agent/resources/rest-api-patterns.md +0 -182
- package/skills/cm-frappe-agent/resources/scaffold_checklist.md +0 -82
- package/skills/cm-frappe-agent/resources/upgrade_patterns.md +0 -113
- package/skills/cm-frappe-agent/resources/web-form-patterns.md +0 -252
- package/skills/cm-frappe-agent/skills/bench-commands/SKILL.md +0 -621
- package/skills/cm-frappe-agent/skills/client-scripts/SKILL.md +0 -642
- package/skills/cm-frappe-agent/skills/doctype-patterns/SKILL.md +0 -576
- package/skills/cm-frappe-agent/skills/frappe-api/SKILL.md +0 -740
- package/skills/cm-frappe-agent/skills/remote-operations/SKILL.md +0 -47
- package/skills/cm-frappe-agent/skills/server-scripts/SKILL.md +0 -608
- package/skills/cm-frappe-agent/skills/web-forms/SKILL.md +0 -46
- package/skills/frappe-app-builder.zip +0 -0
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: remote-operations
|
|
3
|
-
description: REST API patterns and best practices for managing remote Frappe/ERPNext sites via curl.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Remote Operations
|
|
7
|
-
|
|
8
|
-
Patterns for interacting with remote Frappe/ERPNext sites via REST API.
|
|
9
|
-
|
|
10
|
-
## When to Use
|
|
11
|
-
|
|
12
|
-
- Managing Frappe Cloud sites (no bench CLI access)
|
|
13
|
-
- Operating on remote self-hosted Frappe instances
|
|
14
|
-
- Automating document operations across sites
|
|
15
|
-
- Managing Web Forms from code
|
|
16
|
-
|
|
17
|
-
## Key References
|
|
18
|
-
|
|
19
|
-
- `resources/rest-api-patterns.md` — Complete curl patterns for CRUD, RPC, reports
|
|
20
|
-
- `resources/doctype-registry.md` — DocType discovery and exploration
|
|
21
|
-
- `resources/web-form-patterns.md` — Web Form client script patterns
|
|
22
|
-
- `agents/frappe-remote-ops.md` — Full remote operations agent
|
|
23
|
-
|
|
24
|
-
## Quick Reference
|
|
25
|
-
|
|
26
|
-
### Authentication
|
|
27
|
-
```
|
|
28
|
-
Authorization: token <api_key>:<api_secret>
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
### Common Operations
|
|
32
|
-
| Operation | Method | Endpoint |
|
|
33
|
-
|-----------|--------|----------|
|
|
34
|
-
| Get doc | GET | `/api/resource/{DocType}/{name}` |
|
|
35
|
-
| List docs | GET | `/api/resource/{DocType}` |
|
|
36
|
-
| Create doc | POST | `/api/resource/{DocType}` |
|
|
37
|
-
| Update doc | PUT | `/api/resource/{DocType}/{name}` |
|
|
38
|
-
| Delete doc | DELETE | `/api/resource/{DocType}/{name}` |
|
|
39
|
-
| Run method | POST | `/api/method/{method}` |
|
|
40
|
-
| Get value | POST | `/api/method/frappe.client.get_value` |
|
|
41
|
-
| Get count | POST | `/api/method/frappe.client.get_count` |
|
|
42
|
-
|
|
43
|
-
### Security Rules
|
|
44
|
-
1. Never expose API keys in output
|
|
45
|
-
2. Confirm before destructive operations
|
|
46
|
-
3. Store credentials in `.env` files
|
|
47
|
-
4. Rate-limit batch operations
|
|
@@ -1,608 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: server-scripts
|
|
3
|
-
description: Frappe server-side Python patterns for controllers, document events, whitelisted APIs, background jobs, and database operations. Use when writing controller logic, creating APIs, handling document events, or processing data on the server.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Frappe Server Scripts Reference
|
|
7
|
-
|
|
8
|
-
Complete reference for server-side Python development in Frappe Framework.
|
|
9
|
-
|
|
10
|
-
## When to Use This Skill
|
|
11
|
-
|
|
12
|
-
- Writing document controllers
|
|
13
|
-
- Creating whitelisted API endpoints
|
|
14
|
-
- Handling document lifecycle events
|
|
15
|
-
- Background job processing
|
|
16
|
-
- Database operations and queries
|
|
17
|
-
- Permission checks and validation
|
|
18
|
-
- Email and notification handling
|
|
19
|
-
|
|
20
|
-
## Controller Location
|
|
21
|
-
|
|
22
|
-
```
|
|
23
|
-
my_app/
|
|
24
|
-
└── my_module/
|
|
25
|
-
└── doctype/
|
|
26
|
-
└── my_doctype/
|
|
27
|
-
└── my_doctype.py # Python controller
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
## Document Controller
|
|
31
|
-
|
|
32
|
-
### Complete Controller Template
|
|
33
|
-
|
|
34
|
-
```python
|
|
35
|
-
# my_doctype.py
|
|
36
|
-
import frappe
|
|
37
|
-
from frappe import _
|
|
38
|
-
from frappe.model.document import Document
|
|
39
|
-
from frappe.utils import nowdate, nowtime, flt, cint, getdate, add_days
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
class MyDocType(Document):
|
|
43
|
-
# ===== NAMING =====
|
|
44
|
-
|
|
45
|
-
def autoname(self):
|
|
46
|
-
"""Custom naming logic"""
|
|
47
|
-
self.name = f"{self.prefix}-{frappe.generate_hash()[:8].upper()}"
|
|
48
|
-
|
|
49
|
-
def before_naming(self):
|
|
50
|
-
"""Called before autoname"""
|
|
51
|
-
pass
|
|
52
|
-
|
|
53
|
-
# ===== VALIDATION =====
|
|
54
|
-
|
|
55
|
-
def before_validate(self):
|
|
56
|
-
"""Called before validate"""
|
|
57
|
-
self.set_defaults()
|
|
58
|
-
|
|
59
|
-
def validate(self):
|
|
60
|
-
"""Main validation - called on insert and update"""
|
|
61
|
-
self.validate_dates()
|
|
62
|
-
self.validate_amounts()
|
|
63
|
-
self.calculate_totals()
|
|
64
|
-
self.set_status()
|
|
65
|
-
|
|
66
|
-
def before_save(self):
|
|
67
|
-
"""Called after validate, before database write"""
|
|
68
|
-
self.update_modified_info()
|
|
69
|
-
|
|
70
|
-
# ===== INSERT =====
|
|
71
|
-
|
|
72
|
-
def before_insert(self):
|
|
73
|
-
"""Called before new document is inserted"""
|
|
74
|
-
self.set_initial_values()
|
|
75
|
-
|
|
76
|
-
def after_insert(self):
|
|
77
|
-
"""Called after new document is inserted"""
|
|
78
|
-
self.create_related_documents()
|
|
79
|
-
self.send_notification()
|
|
80
|
-
|
|
81
|
-
# ===== UPDATE =====
|
|
82
|
-
|
|
83
|
-
def on_update(self):
|
|
84
|
-
"""Called after document is saved (insert or update)"""
|
|
85
|
-
self.update_related_documents()
|
|
86
|
-
self.clear_cache()
|
|
87
|
-
|
|
88
|
-
def after_save(self):
|
|
89
|
-
"""Called after on_update, always runs"""
|
|
90
|
-
pass
|
|
91
|
-
|
|
92
|
-
def on_change(self):
|
|
93
|
-
"""Called when document changes in database"""
|
|
94
|
-
pass
|
|
95
|
-
|
|
96
|
-
# ===== SUBMISSION =====
|
|
97
|
-
|
|
98
|
-
def before_submit(self):
|
|
99
|
-
"""Called before document is submitted"""
|
|
100
|
-
self.validate_for_submit()
|
|
101
|
-
|
|
102
|
-
def on_submit(self):
|
|
103
|
-
"""Called after document is submitted"""
|
|
104
|
-
self.create_gl_entries()
|
|
105
|
-
self.update_stock()
|
|
106
|
-
|
|
107
|
-
def on_update_after_submit(self):
|
|
108
|
-
"""Called when submitted doc is updated (limited fields)"""
|
|
109
|
-
pass
|
|
110
|
-
|
|
111
|
-
# ===== CANCELLATION =====
|
|
112
|
-
|
|
113
|
-
def before_cancel(self):
|
|
114
|
-
"""Called before document is cancelled"""
|
|
115
|
-
self.validate_cancellation()
|
|
116
|
-
|
|
117
|
-
def on_cancel(self):
|
|
118
|
-
"""Called after document is cancelled"""
|
|
119
|
-
self.reverse_gl_entries()
|
|
120
|
-
self.reverse_stock()
|
|
121
|
-
|
|
122
|
-
# ===== DELETION =====
|
|
123
|
-
|
|
124
|
-
def before_delete(self):
|
|
125
|
-
"""Called before document is deleted"""
|
|
126
|
-
self.check_dependencies()
|
|
127
|
-
|
|
128
|
-
def after_delete(self):
|
|
129
|
-
"""Called after document is deleted"""
|
|
130
|
-
self.cleanup_related()
|
|
131
|
-
|
|
132
|
-
def on_trash(self):
|
|
133
|
-
"""Called when document is trashed"""
|
|
134
|
-
pass
|
|
135
|
-
|
|
136
|
-
def after_restore(self):
|
|
137
|
-
"""Called after document is restored from trash"""
|
|
138
|
-
pass
|
|
139
|
-
|
|
140
|
-
# ===== CUSTOM METHODS =====
|
|
141
|
-
|
|
142
|
-
def set_defaults(self):
|
|
143
|
-
"""Set default values"""
|
|
144
|
-
if not self.posting_date:
|
|
145
|
-
self.posting_date = nowdate()
|
|
146
|
-
if not self.company:
|
|
147
|
-
self.company = frappe.defaults.get_user_default("Company")
|
|
148
|
-
|
|
149
|
-
def validate_dates(self):
|
|
150
|
-
"""Validate date fields"""
|
|
151
|
-
if self.end_date and getdate(self.start_date) > getdate(self.end_date):
|
|
152
|
-
frappe.throw(_("End Date cannot be before Start Date"))
|
|
153
|
-
|
|
154
|
-
if getdate(self.posting_date) > getdate(nowdate()):
|
|
155
|
-
frappe.throw(_("Posting Date cannot be in the future"))
|
|
156
|
-
|
|
157
|
-
def validate_amounts(self):
|
|
158
|
-
"""Validate amount fields"""
|
|
159
|
-
for item in self.items:
|
|
160
|
-
if flt(item.qty) <= 0:
|
|
161
|
-
frappe.throw(_("Row {0}: Quantity must be greater than 0").format(item.idx))
|
|
162
|
-
if flt(item.rate) < 0:
|
|
163
|
-
frappe.throw(_("Row {0}: Rate cannot be negative").format(item.idx))
|
|
164
|
-
|
|
165
|
-
def calculate_totals(self):
|
|
166
|
-
"""Calculate document totals"""
|
|
167
|
-
self.total = 0
|
|
168
|
-
for item in self.items:
|
|
169
|
-
item.amount = flt(item.qty) * flt(item.rate)
|
|
170
|
-
self.total += item.amount
|
|
171
|
-
|
|
172
|
-
self.tax_amount = flt(self.total) * flt(self.tax_rate) / 100
|
|
173
|
-
self.grand_total = flt(self.total) + flt(self.tax_amount)
|
|
174
|
-
|
|
175
|
-
def set_status(self):
|
|
176
|
-
"""Set document status based on state"""
|
|
177
|
-
if self.docstatus == 0:
|
|
178
|
-
self.status = "Draft"
|
|
179
|
-
elif self.docstatus == 1:
|
|
180
|
-
if self.is_completed():
|
|
181
|
-
self.status = "Completed"
|
|
182
|
-
else:
|
|
183
|
-
self.status = "Submitted"
|
|
184
|
-
elif self.docstatus == 2:
|
|
185
|
-
self.status = "Cancelled"
|
|
186
|
-
|
|
187
|
-
def is_completed(self):
|
|
188
|
-
"""Check if document is completed"""
|
|
189
|
-
return all(item.delivered_qty >= item.qty for item in self.items)
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
## Whitelisted APIs
|
|
193
|
-
|
|
194
|
-
### Basic API
|
|
195
|
-
|
|
196
|
-
```python
|
|
197
|
-
@frappe.whitelist()
|
|
198
|
-
def get_customer_details(customer):
|
|
199
|
-
"""Get customer details
|
|
200
|
-
|
|
201
|
-
Args:
|
|
202
|
-
customer (str): Customer ID
|
|
203
|
-
|
|
204
|
-
Returns:
|
|
205
|
-
dict: Customer details with outstanding amount
|
|
206
|
-
"""
|
|
207
|
-
if not customer:
|
|
208
|
-
frappe.throw(_("Customer is required"))
|
|
209
|
-
|
|
210
|
-
doc = frappe.get_doc("Customer", customer)
|
|
211
|
-
|
|
212
|
-
return {
|
|
213
|
-
"customer_name": doc.customer_name,
|
|
214
|
-
"customer_type": doc.customer_type,
|
|
215
|
-
"territory": doc.territory,
|
|
216
|
-
"credit_limit": flt(doc.credit_limit),
|
|
217
|
-
"outstanding": get_customer_outstanding(customer)
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
@frappe.whitelist()
|
|
222
|
-
def create_invoice(customer, items):
|
|
223
|
-
"""Create sales invoice from data
|
|
224
|
-
|
|
225
|
-
Args:
|
|
226
|
-
customer (str): Customer ID
|
|
227
|
-
items (str): JSON string of items
|
|
228
|
-
|
|
229
|
-
Returns:
|
|
230
|
-
str: Invoice name
|
|
231
|
-
"""
|
|
232
|
-
items = frappe.parse_json(items)
|
|
233
|
-
|
|
234
|
-
doc = frappe.get_doc({
|
|
235
|
-
"doctype": "Sales Invoice",
|
|
236
|
-
"customer": customer,
|
|
237
|
-
"items": [{
|
|
238
|
-
"item_code": item.get("item_code"),
|
|
239
|
-
"qty": flt(item.get("qty")),
|
|
240
|
-
"rate": flt(item.get("rate"))
|
|
241
|
-
} for item in items]
|
|
242
|
-
})
|
|
243
|
-
|
|
244
|
-
doc.insert()
|
|
245
|
-
doc.submit()
|
|
246
|
-
|
|
247
|
-
return doc.name
|
|
248
|
-
```
|
|
249
|
-
|
|
250
|
-
### Guest API
|
|
251
|
-
|
|
252
|
-
```python
|
|
253
|
-
@frappe.whitelist(allow_guest=True)
|
|
254
|
-
def get_public_data():
|
|
255
|
-
"""Public API - no login required"""
|
|
256
|
-
return {
|
|
257
|
-
"status": "ok",
|
|
258
|
-
"message": "This is public data"
|
|
259
|
-
}
|
|
260
|
-
```
|
|
261
|
-
|
|
262
|
-
### Method-Restricted API
|
|
263
|
-
|
|
264
|
-
```python
|
|
265
|
-
@frappe.whitelist(methods=["POST"])
|
|
266
|
-
def create_record(data):
|
|
267
|
-
"""Only accepts POST requests"""
|
|
268
|
-
data = frappe.parse_json(data)
|
|
269
|
-
doc = frappe.get_doc(data)
|
|
270
|
-
doc.insert()
|
|
271
|
-
return {"name": doc.name}
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
@frappe.whitelist(methods=["GET", "POST"])
|
|
275
|
-
def flexible_endpoint(**kwargs):
|
|
276
|
-
"""Accepts GET and POST"""
|
|
277
|
-
return kwargs
|
|
278
|
-
```
|
|
279
|
-
|
|
280
|
-
### Permission-Checked API
|
|
281
|
-
|
|
282
|
-
```python
|
|
283
|
-
@frappe.whitelist()
|
|
284
|
-
def sensitive_operation(doctype, name):
|
|
285
|
-
"""API with permission check"""
|
|
286
|
-
# Check permission
|
|
287
|
-
if not frappe.has_permission(doctype, "write", name):
|
|
288
|
-
frappe.throw(_("Not permitted"), frappe.PermissionError)
|
|
289
|
-
|
|
290
|
-
# Proceed with operation
|
|
291
|
-
doc = frappe.get_doc(doctype, name)
|
|
292
|
-
# ... do something
|
|
293
|
-
|
|
294
|
-
return {"status": "success"}
|
|
295
|
-
```
|
|
296
|
-
|
|
297
|
-
## Database Operations
|
|
298
|
-
|
|
299
|
-
### Reading Data
|
|
300
|
-
|
|
301
|
-
```python
|
|
302
|
-
# Get single document
|
|
303
|
-
doc = frappe.get_doc("Customer", "CUST-001")
|
|
304
|
-
|
|
305
|
-
# Get with filters
|
|
306
|
-
doc = frappe.get_doc("Customer", {"customer_name": "John Corp"})
|
|
307
|
-
|
|
308
|
-
# Get single value
|
|
309
|
-
name = frappe.db.get_value("Customer", "CUST-001", "customer_name")
|
|
310
|
-
|
|
311
|
-
# Get multiple values
|
|
312
|
-
values = frappe.db.get_value("Customer", "CUST-001",
|
|
313
|
-
["customer_name", "territory"], as_dict=True)
|
|
314
|
-
|
|
315
|
-
# Get list
|
|
316
|
-
customers = frappe.db.get_all("Customer",
|
|
317
|
-
filters={"status": "Active"},
|
|
318
|
-
fields=["name", "customer_name", "territory"],
|
|
319
|
-
order_by="customer_name asc",
|
|
320
|
-
limit=10
|
|
321
|
-
)
|
|
322
|
-
|
|
323
|
-
# Complex filters
|
|
324
|
-
invoices = frappe.db.get_all("Sales Invoice",
|
|
325
|
-
filters={
|
|
326
|
-
"status": ["in", ["Paid", "Unpaid"]],
|
|
327
|
-
"grand_total": [">", 1000],
|
|
328
|
-
"posting_date": [">=", "2024-01-01"],
|
|
329
|
-
"customer": ["like", "%Corp%"]
|
|
330
|
-
},
|
|
331
|
-
fields=["name", "customer", "grand_total"]
|
|
332
|
-
)
|
|
333
|
-
|
|
334
|
-
# Pluck single field
|
|
335
|
-
names = frappe.db.get_all("Customer",
|
|
336
|
-
filters={"status": "Active"},
|
|
337
|
-
pluck="name"
|
|
338
|
-
)
|
|
339
|
-
|
|
340
|
-
# Count
|
|
341
|
-
count = frappe.db.count("Customer", {"status": "Active"})
|
|
342
|
-
|
|
343
|
-
# Exists check
|
|
344
|
-
exists = frappe.db.exists("Customer", "CUST-001")
|
|
345
|
-
```
|
|
346
|
-
|
|
347
|
-
### Raw SQL
|
|
348
|
-
|
|
349
|
-
```python
|
|
350
|
-
# Simple query
|
|
351
|
-
result = frappe.db.sql("""
|
|
352
|
-
SELECT name, customer_name, grand_total
|
|
353
|
-
FROM `tabSales Invoice`
|
|
354
|
-
WHERE status = %s AND grand_total > %s
|
|
355
|
-
ORDER BY creation DESC
|
|
356
|
-
LIMIT 10
|
|
357
|
-
""", ("Paid", 1000), as_dict=True)
|
|
358
|
-
|
|
359
|
-
# Named parameters
|
|
360
|
-
result = frappe.db.sql("""
|
|
361
|
-
SELECT * FROM `tabCustomer`
|
|
362
|
-
WHERE territory = %(territory)s
|
|
363
|
-
AND status = %(status)s
|
|
364
|
-
""", {"territory": "West", "status": "Active"}, as_dict=True)
|
|
365
|
-
|
|
366
|
-
# Aggregation
|
|
367
|
-
total = frappe.db.sql("""
|
|
368
|
-
SELECT SUM(grand_total) as total
|
|
369
|
-
FROM `tabSales Invoice`
|
|
370
|
-
WHERE status = 'Paid'
|
|
371
|
-
""")[0][0] or 0
|
|
372
|
-
```
|
|
373
|
-
|
|
374
|
-
### Writing Data
|
|
375
|
-
|
|
376
|
-
```python
|
|
377
|
-
# Create document
|
|
378
|
-
doc = frappe.get_doc({
|
|
379
|
-
"doctype": "Customer",
|
|
380
|
-
"customer_name": "New Customer",
|
|
381
|
-
"customer_type": "Company"
|
|
382
|
-
})
|
|
383
|
-
doc.insert()
|
|
384
|
-
|
|
385
|
-
# Update document
|
|
386
|
-
doc = frappe.get_doc("Customer", "CUST-001")
|
|
387
|
-
doc.customer_name = "Updated Name"
|
|
388
|
-
doc.save()
|
|
389
|
-
|
|
390
|
-
# Quick update (bypasses controller)
|
|
391
|
-
frappe.db.set_value("Customer", "CUST-001", "status", "Inactive")
|
|
392
|
-
|
|
393
|
-
# Update multiple fields
|
|
394
|
-
frappe.db.set_value("Customer", "CUST-001", {
|
|
395
|
-
"status": "Inactive",
|
|
396
|
-
"disabled": 1
|
|
397
|
-
})
|
|
398
|
-
|
|
399
|
-
# Delete
|
|
400
|
-
frappe.delete_doc("Customer", "CUST-001")
|
|
401
|
-
|
|
402
|
-
# Commit transaction
|
|
403
|
-
frappe.db.commit()
|
|
404
|
-
|
|
405
|
-
# Rollback
|
|
406
|
-
frappe.db.rollback()
|
|
407
|
-
```
|
|
408
|
-
|
|
409
|
-
## Background Jobs
|
|
410
|
-
|
|
411
|
-
### Enqueue Jobs
|
|
412
|
-
|
|
413
|
-
```python
|
|
414
|
-
# Basic enqueue
|
|
415
|
-
frappe.enqueue(
|
|
416
|
-
"my_app.tasks.process_data",
|
|
417
|
-
queue="default",
|
|
418
|
-
customer="CUST-001"
|
|
419
|
-
)
|
|
420
|
-
|
|
421
|
-
# With options
|
|
422
|
-
frappe.enqueue(
|
|
423
|
-
method="my_app.tasks.heavy_task",
|
|
424
|
-
queue="long", # short, default, long
|
|
425
|
-
timeout=1800, # 30 minutes
|
|
426
|
-
is_async=True,
|
|
427
|
-
job_name="Heavy Task",
|
|
428
|
-
now=False, # True to run immediately
|
|
429
|
-
enqueue_after_commit=True,
|
|
430
|
-
# Task arguments
|
|
431
|
-
document_name="DOC-001",
|
|
432
|
-
data={"key": "value"}
|
|
433
|
-
)
|
|
434
|
-
```
|
|
435
|
-
|
|
436
|
-
### Task Function
|
|
437
|
-
|
|
438
|
-
```python
|
|
439
|
-
# my_app/tasks.py
|
|
440
|
-
import frappe
|
|
441
|
-
|
|
442
|
-
def process_data(customer):
|
|
443
|
-
"""Background task"""
|
|
444
|
-
frappe.init(site=frappe.local.site)
|
|
445
|
-
frappe.connect()
|
|
446
|
-
|
|
447
|
-
try:
|
|
448
|
-
# Process logic
|
|
449
|
-
doc = frappe.get_doc("Customer", customer)
|
|
450
|
-
doc.last_processed = frappe.utils.now()
|
|
451
|
-
doc.save()
|
|
452
|
-
frappe.db.commit()
|
|
453
|
-
except Exception:
|
|
454
|
-
frappe.log_error(title="Process Data Failed")
|
|
455
|
-
raise
|
|
456
|
-
finally:
|
|
457
|
-
frappe.destroy()
|
|
458
|
-
```
|
|
459
|
-
|
|
460
|
-
### Scheduled Jobs (hooks.py)
|
|
461
|
-
|
|
462
|
-
```python
|
|
463
|
-
scheduler_events = {
|
|
464
|
-
"all": [
|
|
465
|
-
"my_app.tasks.every_minute"
|
|
466
|
-
],
|
|
467
|
-
"daily": [
|
|
468
|
-
"my_app.tasks.daily_report"
|
|
469
|
-
],
|
|
470
|
-
"hourly": [
|
|
471
|
-
"my_app.tasks.hourly_sync"
|
|
472
|
-
],
|
|
473
|
-
"cron": {
|
|
474
|
-
"0 9 * * 1": [
|
|
475
|
-
"my_app.tasks.monday_morning"
|
|
476
|
-
],
|
|
477
|
-
"*/15 * * * *": [
|
|
478
|
-
"my_app.tasks.every_15_min"
|
|
479
|
-
]
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
```
|
|
483
|
-
|
|
484
|
-
## Error Handling
|
|
485
|
-
|
|
486
|
-
```python
|
|
487
|
-
from frappe import _
|
|
488
|
-
from frappe.exceptions import ValidationError, PermissionError
|
|
489
|
-
|
|
490
|
-
def my_function():
|
|
491
|
-
# Throw with message
|
|
492
|
-
frappe.throw(_("Invalid data"))
|
|
493
|
-
|
|
494
|
-
# Throw with title
|
|
495
|
-
frappe.throw(_("Cannot proceed"), title=_("Error"))
|
|
496
|
-
|
|
497
|
-
# Throw with exception type
|
|
498
|
-
frappe.throw(_("Permission denied"), exc=PermissionError)
|
|
499
|
-
|
|
500
|
-
# Message without stopping
|
|
501
|
-
frappe.msgprint(_("Warning: Check your data"))
|
|
502
|
-
|
|
503
|
-
# Log error
|
|
504
|
-
frappe.log_error(
|
|
505
|
-
title="My Error",
|
|
506
|
-
message=frappe.get_traceback()
|
|
507
|
-
)
|
|
508
|
-
|
|
509
|
-
# Try-except
|
|
510
|
-
try:
|
|
511
|
-
risky_operation()
|
|
512
|
-
except Exception:
|
|
513
|
-
frappe.log_error("Operation failed")
|
|
514
|
-
frappe.throw(_("Something went wrong"))
|
|
515
|
-
```
|
|
516
|
-
|
|
517
|
-
## Utilities
|
|
518
|
-
|
|
519
|
-
```python
|
|
520
|
-
from frappe.utils import (
|
|
521
|
-
nowdate, nowtime, now_datetime, today,
|
|
522
|
-
getdate, get_datetime,
|
|
523
|
-
add_days, add_months, add_years,
|
|
524
|
-
date_diff, time_diff_in_seconds,
|
|
525
|
-
flt, cint, cstr,
|
|
526
|
-
fmt_money, rounded,
|
|
527
|
-
strip_html, escape_html
|
|
528
|
-
)
|
|
529
|
-
|
|
530
|
-
# Date operations
|
|
531
|
-
today = nowdate() # "2024-01-15"
|
|
532
|
-
week_later = add_days(nowdate(), 7)
|
|
533
|
-
month_end = frappe.utils.get_last_day(nowdate())
|
|
534
|
-
|
|
535
|
-
# Number operations
|
|
536
|
-
amount = flt(value, 2) # Float with precision
|
|
537
|
-
count = cint(value) # Integer
|
|
538
|
-
|
|
539
|
-
# Formatting
|
|
540
|
-
money = fmt_money(1234.56, currency="USD")
|
|
541
|
-
|
|
542
|
-
# Current user
|
|
543
|
-
user = frappe.session.user
|
|
544
|
-
roles = frappe.get_roles()
|
|
545
|
-
```
|
|
546
|
-
|
|
547
|
-
## Email & Notifications
|
|
548
|
-
|
|
549
|
-
```python
|
|
550
|
-
# Send email
|
|
551
|
-
frappe.sendmail(
|
|
552
|
-
recipients=["user@example.com"],
|
|
553
|
-
subject="Subject",
|
|
554
|
-
message="Email body",
|
|
555
|
-
reference_doctype="Sales Invoice",
|
|
556
|
-
reference_name="SINV-00001"
|
|
557
|
-
)
|
|
558
|
-
|
|
559
|
-
# With template
|
|
560
|
-
frappe.sendmail(
|
|
561
|
-
recipients=["user@example.com"],
|
|
562
|
-
subject="Order Confirmation",
|
|
563
|
-
template="order_confirmation",
|
|
564
|
-
args={
|
|
565
|
-
"customer_name": "John",
|
|
566
|
-
"order_id": "ORD-001"
|
|
567
|
-
}
|
|
568
|
-
)
|
|
569
|
-
|
|
570
|
-
# Real-time notification
|
|
571
|
-
frappe.publish_realtime(
|
|
572
|
-
"msgprint",
|
|
573
|
-
{"message": "Task completed"},
|
|
574
|
-
user="user@example.com"
|
|
575
|
-
)
|
|
576
|
-
```
|
|
577
|
-
|
|
578
|
-
## Document Events via Hooks
|
|
579
|
-
|
|
580
|
-
```python
|
|
581
|
-
# hooks.py
|
|
582
|
-
doc_events = {
|
|
583
|
-
"Sales Invoice": {
|
|
584
|
-
"validate": "my_app.overrides.validate_invoice",
|
|
585
|
-
"on_submit": "my_app.overrides.on_submit_invoice",
|
|
586
|
-
"on_cancel": "my_app.overrides.on_cancel_invoice"
|
|
587
|
-
},
|
|
588
|
-
"*": {
|
|
589
|
-
"on_update": "my_app.overrides.log_all_changes"
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
```
|
|
593
|
-
|
|
594
|
-
```python
|
|
595
|
-
# my_app/overrides.py
|
|
596
|
-
import frappe
|
|
597
|
-
|
|
598
|
-
def validate_invoice(doc, method):
|
|
599
|
-
"""Called during Sales Invoice validation"""
|
|
600
|
-
if doc.grand_total > 100000:
|
|
601
|
-
if not doc.manager_approval:
|
|
602
|
-
frappe.throw(_("Manager approval required for orders above 100,000"))
|
|
603
|
-
|
|
604
|
-
def on_submit_invoice(doc, method):
|
|
605
|
-
"""Called when Sales Invoice is submitted"""
|
|
606
|
-
create_delivery_note(doc)
|
|
607
|
-
notify_warehouse(doc)
|
|
608
|
-
```
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: web-forms
|
|
3
|
-
description: Frappe Web Form development patterns — client scripts, CSS, guest access, and common gotchas.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Web Forms
|
|
7
|
-
|
|
8
|
-
Patterns for developing Frappe Web Forms (portal pages).
|
|
9
|
-
|
|
10
|
-
## When to Use
|
|
11
|
-
|
|
12
|
-
- Creating public-facing forms (customer portals, application forms)
|
|
13
|
-
- Customizing existing Web Form client scripts
|
|
14
|
-
- Styling Web Forms with custom CSS
|
|
15
|
-
- Managing Web Forms via REST API
|
|
16
|
-
|
|
17
|
-
## Key References
|
|
18
|
-
|
|
19
|
-
- `resources/web-form-patterns.md` — Complete client script patterns, CSS, and gotchas
|
|
20
|
-
|
|
21
|
-
## Critical Gotchas
|
|
22
|
-
|
|
23
|
-
1. **Use `set_values()` NOT `set_value()`** — WebForm extends FieldGroup, not Form
|
|
24
|
-
2. **Always wrap in `frappe.ready()`** — ensures form is loaded before code runs
|
|
25
|
-
3. **Read-only fields CAN be set** via `set_values()` in client scripts
|
|
26
|
-
|
|
27
|
-
## Quick Template
|
|
28
|
-
|
|
29
|
-
```javascript
|
|
30
|
-
frappe.ready(function() {
|
|
31
|
-
// Set initial values
|
|
32
|
-
frappe.web_form.set_values({
|
|
33
|
-
date: frappe.datetime.get_today()
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
// Validate before submit
|
|
37
|
-
frappe.web_form.validate = function() {
|
|
38
|
-
let values = frappe.web_form.get_values();
|
|
39
|
-
if (!values.email) {
|
|
40
|
-
frappe.msgprint(__('Email is required'));
|
|
41
|
-
return false;
|
|
42
|
-
}
|
|
43
|
-
return true;
|
|
44
|
-
};
|
|
45
|
-
});
|
|
46
|
-
```
|
|
Binary file
|