Architecture Explanation
Written explanation of the architecture, data flow, and design decisions for both validation tasks.
System Architecture Overview
┌─────────────────────────────────────────────────────────────────┐
│ EXTERNAL PLANNING INTERFACE │
│ (React + TypeScript SPA) │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────┐ │
│ │ Allocation │ │ Migration │ │ Reconciliation │ │
│ │ Interface │ │ Dashboard │ │ Reports │ │
│ │ (Drag&Drop) │ │ (Status) │ │ (Matrix) │ │
│ └──────┬───────┘ └──────┬───────┘ └──────────┬───────────┘ │
│ │ │ │ │
│ ┌──────┴──────────────────┴──────────────────────┴───────────┐ │
│ │ Company Context Provider │ │
│ │ (Permission Segregation Layer) │ │
│ └──────────────────────┬─────────────────────────────────────┘ │
└─────────────────────────┼───────────────────────────────────────┘
│
HTTP/REST API
(Token Auth)
│
┌─────────────────────────┼───────────────────────────────────────┐
│ FRAPPE/ERPNEXT SERVER │
│ │
│ ┌──────────────────────┴─────────────────────────────────────┐ │
│ │ Whitelisted API Methods │ │
│ │ (@frappe.whitelist() decorated) │ │
│ └──────────────────────┬─────────────────────────────────────┘ │
│ │ │
│ ┌──────────────────────┴─────────────────────────────────────┐ │
│ │ Server-Side Validation Layer │ │
│ │ • Permission checks (has_permission) │ │
│ │ • Duplicate detection (frappe.db.exists) │ │
│ │ • Capacity validation (aggregate queries) │ │
│ │ • Company segregation (user_permissions) │ │
│ └──────────────────────┬─────────────────────────────────────┘ │
│ │ │
│ ┌──────────────────────┴─────────────────────────────────────┐ │
│ │ Frappe ORM Layer │ │
│ │ • frappe.get_doc() / frappe.new_doc() │ │
│ │ • doc.insert() / doc.save() / doc.submit() │ │
│ │ • No direct SQL writes │ │
│ └──────────────────────┬─────────────────────────────────────┘ │
│ │ │
│ ┌──────────────────────┴─────────────────────────────────────┐ │
│ │ MariaDB Database │ │
│ │ • tabLine Allocation │ │
│ │ • tabProduction Line │ │
│ │ • tabSales Order (core) │ │
│ └────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────┘Custom Frappe App Structure
shangu_erp/ ├── shangu_erp/ │ ├── __init__.py │ ├── hooks.py │ ├── api.py # Whitelisted methods │ ├── shangu_manufacturing/ │ │ ├── doctype/ │ │ │ ├── production_line/ │ │ │ │ ├── production_line.json │ │ │ │ └── production_line.py │ │ │ └── line_allocation/ │ │ │ ├── line_allocation.json │ │ │ └── line_allocation.py # Validation │ │ └── __init__.py │ └── public/ │ └── js/ │ └── allocation_board.js ├── setup.py └── requirements.txt
Line Allocation DocType Definition
{
"doctype": "DocType",
"name": "Line Allocation",
"module": "Shangu Manufacturing",
"is_submittable": 1,
"fields": [
{
"fieldname": "sales_order",
"fieldtype": "Link",
"options": "Sales Order",
"reqd": 1,
"unique": 1
},
{
"fieldname": "production_line",
"fieldtype": "Link",
"options": "Production Line",
"reqd": 1
},
{
"fieldname": "allocated_quantity",
"fieldtype": "Int",
"reqd": 1
},
{
"fieldname": "company",
"fieldtype": "Link",
"options": "Company",
"reqd": 1
}
],
"permissions": [
{
"role": "Manufacturing Manager",
"read": 1, "write": 1,
"create": 1, "delete": 1,
"match": "company"
}
]
}Allocation Data Flow
User drags a Sales Order card from the unallocated panel. The frontend captures the Sales Order ID.
User drops the card onto a Production Line drop zone. Frontend sends API request with SO ID, PL code, and company context.
The whitelisted method performs 5 validation checks: (1) SO exists, (2) SO belongs to user's company, (3) PL exists, (4) PL belongs to user's company, (5) No duplicate allocation, (6) Capacity not exceeded.
If all validations pass, a new Line Allocation document is created via Frappe ORM. The document goes through the full DocType lifecycle including validation triggers and naming series.
Success response returns the created allocation. Frontend refreshes production line capacity and allocation displays. On validation failure, the specific error type and message are returned.
Key Design Decisions
The assignment explicitly requires an 'external planning interface' that integrates with ERPNext through its API framework. Building a standalone React SPA demonstrates proper API integration patterns rather than relying on Frappe's built-in page system.
While ERPNext provides a generic REST API, using @frappe.whitelist() decorated methods allows us to encapsulate business logic, enforce validation rules, and maintain clean separation of concerns. Each method is a well-defined contract.
All business rules (duplicate check, capacity check, permission check) are enforced exclusively on the server. The frontend may show warnings, but the server is the single source of truth. This prevents bypass via API calls or browser manipulation.
ERPNext's User Permission system is used to restrict data access by company. The 'match' field in DocType permissions ensures that users can only see and modify data belonging to their assigned company. This is enforced at the ORM level, not just the UI.
Making Line Allocation a submittable DocType (is_submittable: 1) enables the Draft → Submitted → Cancelled workflow. This provides audit trails, prevents accidental modifications, and aligns with ERPNext's document lifecycle patterns.
The 'unique: 1' property on the sales_order field in the Line Allocation DocType provides database-level duplicate prevention. Even if the application-level check fails, the database constraint ensures data integrity.