$npx -y skills add lubusIN/frappe-skills --skill frappe-enterprise-patternsProduction-grade architectural patterns for building enterprise Frappe apps like CRM, Helpdesk, and HRMS. Use when designing complex multi-entity systems with workflows, SLAs, and integrations.
| 1 | # Frappe Enterprise Patterns |
| 2 | |
| 3 | Architectural patterns for building production-grade enterprise applications. |
| 4 | |
| 5 | ## When to use |
| 6 | |
| 7 | - Building CRM, Helpdesk, HRMS, or similar multi-entity systems |
| 8 | - Designing SLA-driven workflows |
| 9 | - Implementing assignment and queue management |
| 10 | - Building audit trails and activity logs |
| 11 | - Integrating with external systems (email, telephony, CRM) |
| 12 | |
| 13 | ## Inputs required |
| 14 | |
| 15 | - System type (CRM/Helpdesk/custom) |
| 16 | - Core entities and relationships |
| 17 | - SLA requirements |
| 18 | - Workflow states and transitions |
| 19 | - Integration points |
| 20 | |
| 21 | ## Procedure |
| 22 | |
| 23 | ### 0) Design data model |
| 24 | |
| 25 | Start with clear, normalized DocTypes: |
| 26 | |
| 27 | ``` |
| 28 | Ticket (parent) |
| 29 | ├── customer (Link: Customer) |
| 30 | ├── assigned_to (Link: User) |
| 31 | ├── status (Select: Open, In Progress, Resolved, Closed) |
| 32 | ├── priority (Link: Priority) |
| 33 | ├── sla (Link: SLA) |
| 34 | ├── activities (Table: Ticket Activity) |
| 35 | └── response_by, resolution_by (Datetime) |
| 36 | ``` |
| 37 | |
| 38 | **Key patterns:** |
| 39 | - Use Link fields for relationships |
| 40 | - Use child tables for activities, timelines, line items |
| 41 | - Use Dynamic Link when target DocType varies |
| 42 | |
| 43 | ### 1) Implement state machine |
| 44 | |
| 45 | **Option A: Workflow DocType** |
| 46 | - Create Workflow with states and role-based transitions |
| 47 | - Link to your DocType |
| 48 | |
| 49 | **Option B: docstatus for submission flow** |
| 50 | | docstatus | Meaning | |
| 51 | |-----------|---------| |
| 52 | | 0 | Draft | |
| 53 | | 1 | Submitted | |
| 54 | | 2 | Cancelled | |
| 55 | |
| 56 | **Option C: Custom status field with validation** |
| 57 | ```python |
| 58 | def validate(self): |
| 59 | allowed = self.get_allowed_transitions() |
| 60 | if self.status not in allowed: |
| 61 | frappe.throw(f"Cannot transition to {self.status}") |
| 62 | ``` |
| 63 | |
| 64 | ### 2) Set up permissions |
| 65 | |
| 66 | **Row-level filtering:** |
| 67 | - Use User Permissions to restrict by entity |
| 68 | - Combine with Role Permissions |
| 69 | |
| 70 | **Always re-check in RPC methods:** |
| 71 | ```python |
| 72 | @frappe.whitelist() |
| 73 | def update_ticket(name, status): |
| 74 | doc = frappe.get_doc("Ticket", name) |
| 75 | if not frappe.has_permission("Ticket", "write", doc): |
| 76 | frappe.throw("Not permitted", frappe.PermissionError) |
| 77 | doc.status = status |
| 78 | doc.save() |
| 79 | ``` |
| 80 | |
| 81 | ### 3) Build activity trail |
| 82 | |
| 83 | Track changes using Activity Log or custom child table: |
| 84 | |
| 85 | ```python |
| 86 | def on_update(self): |
| 87 | if self.has_value_changed("status"): |
| 88 | self.append("activities", { |
| 89 | "action": "Status Change", |
| 90 | "old_value": self._doc_before_save.status, |
| 91 | "new_value": self.status, |
| 92 | "timestamp": frappe.utils.now() |
| 93 | }) |
| 94 | ``` |
| 95 | |
| 96 | ### 4) Implement SLA |
| 97 | |
| 98 | **SLA DocType:** |
| 99 | ``` |
| 100 | SLA |
| 101 | ├── entity_type (Link: DocType) |
| 102 | ├── response_time (Duration) |
| 103 | ├── resolution_time (Duration) |
| 104 | └── escalation_rules (Table: Escalation Rule) |
| 105 | ``` |
| 106 | |
| 107 | **Apply SLA on creation:** |
| 108 | ```python |
| 109 | def after_insert(self): |
| 110 | sla = get_applicable_sla(self) |
| 111 | if sla: |
| 112 | self.response_by = add_to_date(self.creation, hours=sla.response_time) |
| 113 | self.resolution_by = add_to_date(self.creation, hours=sla.resolution_time) |
| 114 | self.db_update() |
| 115 | ``` |
| 116 | |
| 117 | **Monitor breaches (scheduled job):** |
| 118 | ```python |
| 119 | def check_sla_breaches(): |
| 120 | tickets = frappe.get_all("Ticket", |
| 121 | filters={"status": ["not in", ["Resolved", "Closed"]]}, |
| 122 | fields=["name", "resolution_by"] |
| 123 | ) |
| 124 | for t in tickets: |
| 125 | if frappe.utils.now_datetime() > t.resolution_by: |
| 126 | mark_sla_breached(t.name) |
| 127 | ``` |
| 128 | |
| 129 | ### 5) Assignment and queues |
| 130 | |
| 131 | **Round-robin assignment:** |
| 132 | ```python |
| 133 | def assign_next_agent(queue): |
| 134 | agents = frappe.get_all("Queue Member", |
| 135 | filters={"queue": queue, "available": 1}, |
| 136 | fields=["user", "current_load"], |
| 137 | order_by="current_load asc" |
| 138 | ) |
| 139 | if agents: |
| 140 | return agents[0].user |
| 141 | return None |
| 142 | ``` |
| 143 | |
| 144 | **Assignment Rules DocType** for automatic assignment. |
| 145 | |
| 146 | ### 6) Notifications and escalations |
| 147 | |
| 148 | **Configure Notification DocType for:** |
| 149 | - SLA approaching breach |
| 150 | - Assignment changes |
| 151 | - Status transitions |
| 152 | - Customer replies |
| 153 | |
| 154 | **Escalation chain:** |
| 155 | ``` |
| 156 | Level 1 (0h): Notify assigned agent |
| 157 | Level 2 (4h): Notify team lead |
| 158 | Level 3 (8h): Notify manager |
| 159 | Level 4 (24h): Notify department head |
| 160 | ``` |
| 161 | |
| 162 | ### 7) External integrations |
| 163 | |
| 164 | **Centralize in `integrations/` module:** |
| 165 | ```python |
| 166 | # my_app/integrations/email_connector.py |
| 167 | def sync_emails(): |
| 168 | # Fetch from Email Account |
| 169 | # Create Communications |
| 170 | # Link to Tickets |
| 171 | ``` |
| 172 | |
| 173 | **Use background jobs for sync:** |
| 174 | ```python |
| 175 | frappe.enqueue( |
| 176 | "my_app.integrations.email_connector.sync_emails", |
| 177 | queue="long", |
| 178 | timeout=600 |
| 179 | ) |
| 180 | ``` |
| 181 | |
| 182 | ## Verification |
| 183 | |
| 184 | - [ ] Workflow transitions work for all roles |
| 185 | - [ ] Permissions enforced at API level |
| 186 | - [ ] Activity log captures all changes |
| 187 | - [ ] SLA calculation correct |
| 188 | - [ ] Notifications fire appropriately |
| 189 | - [ ] Integration sync runs without errors |
| 190 | |
| 191 | ## Failure modes / debugging |
| 192 | |
| 193 | - **Permission bypass**: Check RPC methods have explicit permission checks |
| 194 | - **SLA not applying**: Verify scheduled job is running |
| 195 | - **A |