frappe-enterprise-patterns — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited frappe-enterprise-patterns (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Architectural patterns for building production-grade enterprise applications.
Start with clear, normalized DocTypes:
Ticket (parent)
├── customer (Link: Customer)
├── assigned_to (Link: User)
├── status (Select: Open, In Progress, Resolved, Closed)
├── priority (Link: Priority)
├── sla (Link: SLA)
├── activities (Table: Ticket Activity)
└── response_by, resolution_by (Datetime)Key patterns:
Option A: Workflow DocType
Option B: docstatus for submission flow
| docstatus | Meaning |
|---|---|
| 0 | Draft |
| 1 | Submitted |
| 2 | Cancelled |
Option C: Custom status field with validation
def validate(self):
allowed = self.get_allowed_transitions()
if self.status not in allowed:
frappe.throw(f"Cannot transition to {self.status}")Row-level filtering:
Always re-check in RPC methods:
@frappe.whitelist()
def update_ticket(name, status):
doc = frappe.get_doc("Ticket", name)
if not frappe.has_permission("Ticket", "write", doc):
frappe.throw("Not permitted", frappe.PermissionError)
doc.status = status
doc.save()Track changes using Activity Log or custom child table:
def on_update(self):
if self.has_value_changed("status"):
self.append("activities", {
"action": "Status Change",
"old_value": self._doc_before_save.status,
"new_value": self.status,
"timestamp": frappe.utils.now()
})SLA DocType:
SLA
├── entity_type (Link: DocType)
├── response_time (Duration)
├── resolution_time (Duration)
└── escalation_rules (Table: Escalation Rule)Apply SLA on creation:
def after_insert(self):
sla = get_applicable_sla(self)
if sla:
self.response_by = add_to_date(self.creation, hours=sla.response_time)
self.resolution_by = add_to_date(self.creation, hours=sla.resolution_time)
self.db_update()Monitor breaches (scheduled job):
def check_sla_breaches():
tickets = frappe.get_all("Ticket",
filters={"status": ["not in", ["Resolved", "Closed"]]},
fields=["name", "resolution_by"]
)
for t in tickets:
if frappe.utils.now_datetime() > t.resolution_by:
mark_sla_breached(t.name)Round-robin assignment:
def assign_next_agent(queue):
agents = frappe.get_all("Queue Member",
filters={"queue": queue, "available": 1},
fields=["user", "current_load"],
order_by="current_load asc"
)
if agents:
return agents[0].user
return NoneAssignment Rules DocType for automatic assignment.
Configure Notification DocType for:
Escalation chain:
Level 1 (0h): Notify assigned agent
Level 2 (4h): Notify team lead
Level 3 (8h): Notify manager
Level 4 (24h): Notify department headCentralize in `integrations/` module:
# my_app/integrations/email_connector.py
def sync_emails():
# Fetch from Email Account
# Create Communications
# Link to TicketsUse background jobs for sync:
frappe.enqueue(
"my_app.integrations.email_connector.sync_emails",
queue="long",
timeout=600
)has_value_changed usagefrappe-ui-patternsfrappe-ui-patterns skill which documents app shell, navigation, list views, and form patterns from official Frappe apps. This includes sidebar layouts, quick filters, Kanban views, and detail panels.frappe.log_error() and activity logs for auditability| Mistake | Why It Fails | Fix |
|---|---|---|
| Over-complex workflows | Hard to maintain, user confusion | Keep workflows linear when possible; split complex flows |
| Missing error handling in integrations | Silent failures, data inconsistency | Wrap external calls in try/except; log errors; retry logic |
| Race conditions in document updates | Data corruption | Use frappe.db.get_value(..., for_update=True) for locks |
| SLA without timezone handling | Wrong calculations for global users | Store and compare in UTC; use frappe.utils.convert_utc_to_timezone |
| Not using queues for bulk operations | Timeouts, memory issues | Use frappe.enqueue() for operations on many records |
| Hardcoded role names | Breaks on role changes | Use constants or settings for role names |
| Custom UI patterns | Inconsistent UX, user confusion | Study and follow CRM/Helpdesk app shells |
| Using vanilla JS/jQuery for frontend | Maintenance burden, ecosystem mismatch | Always use Frappe UI with Vue 3 |
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.