Skip to main content

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