CRM Example
A basic CRM (Customer Relationship Management) application with Framework M.
DocTypes
Contact
# apps/crm/src/doctypes/contact/doctype.py
from __future__ import annotations
from typing import ClassVar
from framework_m import DocType, Field
class Contact(DocType):
"""Customer or lead contact."""
__doctype_name__: ClassVar[str] = "Contact"
# Name
first_name: str = Field(description="First name")
last_name: str = Field(description="Last name")
# Contact info
email: str = Field(default="", description="Email address")
phone: str = Field(default="", description="Phone number")
# Organization
company: str = Field(default="", description="Company name")
job_title: str = Field(default="", description="Job title")
# Status
type: str = Field(default="Lead", description="Contact type: Lead, Customer, Partner")
status: str = Field(default="Active", description="Status")
class Meta:
naming_rule: ClassVar[str] = "autoincrement"
api_resource: ClassVar[bool] = True
Deal
# apps/crm/src/doctypes/deal/doctype.py
from __future__ import annotations
from datetime import date
from decimal import Decimal
from typing import ClassVar
from uuid import UUID
from framework_m import DocType, Field
class Deal(DocType):
"""Sales opportunity/deal."""
__doctype_name__: ClassVar[str] = "Deal"
# Basic info
title: str = Field(description="Deal title")
contact_id: UUID = Field(description="Primary contact")
# Value
amount: Decimal = Field(default=Decimal("0"), description="Deal value")
currency: str = Field(default="USD", description="Currency")
# Pipeline
stage: str = Field(
default="Qualification",
description="Pipeline stage",
)
probability: int = Field(default=10, ge=0, le=100, description="Win probability %")
# Dates
expected_close: date | None = Field(default=None, description="Expected close date")
closed_date: date | None = Field(default=None, description="Actual close date")
class Meta:
naming_rule: ClassVar[str] = "autoincrement"
api_resource: ClassVar[bool] = True
is_submittable: ClassVar[bool] = True
Controllers
ContactController
# apps/crm/src/doctypes/contact/controller.py
from __future__ import annotations
from framework_m import Controller
from .doctype import Contact
class ContactController(Controller[Contact]):
"""Contact business logic."""
async def validate(self, context=None):
if not self.doc.first_name.strip():
raise ValueError("First name is required")
async def before_save(self, context=None):
# Normalize email
if self.doc.email:
self.doc.email = self.doc.email.lower().strip()
DealController
# apps/crm/src/doctypes/deal/controller.py
from __future__ import annotations
from datetime import date
from decimal import Decimal
from framework_m import Controller
from .doctype import Deal
class DealController(Controller[Deal]):
"""Deal business logic."""
# Pipeline stages with probabilities
STAGE_PROBABILITIES = {
"Qualification": 10,
"Proposal": 30,
"Negotiation": 60,
"Closed Won": 100,
"Closed Lost": 0,
}
async def validate(self, context=None):
if self.doc.amount < 0:
raise ValueError("Amount cannot be negative")
async def before_save(self, context=None):
# Auto-set probability based on stage
if self.doc.stage in self.STAGE_PROBABILITIES:
self.doc.probability = self.STAGE_PROBABILITIES[self.doc.stage]
# Set closed date when won/lost
if self.doc.stage in ("Closed Won", "Closed Lost"):
if not self.doc.closed_date:
self.doc.closed_date = date.today()
async def on_submit(self, context=None):
"""Called when deal is submitted (won)."""
if self.doc.stage != "Closed Won":
raise ValueError("Can only submit deals that are Closed Won")
API Usage
# Create a contact
curl -X POST http://localhost:8000/api/v1/resource/Contact \
-H "Content-Type: application/json" \
-d '{
"first_name": "John",
"last_name": "Doe",
"email": "john@example.com",
"company": "Acme Inc",
"type": "Lead"
}'
# Create a deal linked to contact
curl -X POST http://localhost:8000/api/v1/resource/Deal \
-H "Content-Type: application/json" \
-d '{
"title": "Acme Enterprise License",
"contact_id": "contact-uuid-here",
"amount": 50000,
"stage": "Proposal",
"expected_close": "2024-03-01"
}'
# Update deal stage
curl -X PUT http://localhost:8000/api/v1/resource/Deal/1 \
-H "Content-Type: application/json" \
-d '{"stage": "Negotiation"}'
# List deals by stage
curl "http://localhost:8000/api/v1/resource/Deal?filters=[{\"field\":\"stage\",\"operator\":\"eq\",\"value\":\"Proposal\"}]"
Quick Start
# Create the app
uv run m new:app crm
# Create DocTypes
cd crm
uv run m new:doctype Contact
uv run m new:doctype Deal
# Edit doctype.py and controller.py as shown above
# Run migrations
uv run m migrate init
uv run m migrate create "Add CRM doctypes" --autogenerate
uv run m migrate
# Start Studio
uv run m studio