sigma-audio-crm
Use this skill for ALL development, debugging, review, refactoring, feature building, and integration work on the Sigma Audio Dealer & Sales Intelligence Platform. Trigger whenever the user mentions leads, dealers, follow-ups, RBAC, CRM, audit logs, Kanban pipeline, analytics, sales executive, Djang
Install
mkdir -p .claude/skills/sigma-audio-crm && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/15213" && unzip -o skill.zip -d .claude/skills/sigma-audio-crm && rm skill.zipInstalls to .claude/skills/sigma-audio-crm
Activation
This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.
Use this skill for ALL development, debugging, review, refactoring, feature building, and integration work on the Sigma Audio Dealer & Sales Intelligence Platform. Trigger whenever the user mentions leads, dealers, follow-ups, RBAC, CRM, audit logs, Kanban pipeline, analytics, sales executive, Django backend, React frontend, or anything related to Sigma Audio's operational system. Also trigger for tasks like "add a feature", "fix this bug", "write a migration", "add an endpoint", "wire the frontend", "check permissions", or "optimise this query" when working in this codebase. When in doubt, use this skill — it encodes years of project-specific decisions that general knowledge does not.About this skill
Sigma Audio CRM — Agent Skill Enterprise-grade CRM for Sigma Audio's automotive audio distribution network. Django REST backend + React/Vite frontend. Every task follows a strict read → plan → implement → verify → report cycle.
Shell command rules
- Always use
python3for Django/venv commands; never use barepython. - Keep
.envlocal only and never commit it. - Verify
.envis not in history with:git log --all --full-history -- "backend/.env".
Codebase Map backend/ backend/ settings.py · urls.py · wsgi.py · asgi.py apps/ api/ AUTH_USER_MODEL=api.User · legacy models (do not add new models here) accounts/ Role model · UserViewSet leads/ Lead · LeadNote · LeadTimelineEvent · LeadViewSet dealers/ Dealer (live-annotated counts) · DealerViewSet products/ Product · ProductViewSet tasks/ Task · TaskViewSet followups/ FollowUp · FollowUpViewSet notifications/ Notification · create_notification() audit/ AuditLog · log_action() files/ Attachment (GenericForeignKey + FileField) analytics/ AnalyticsOverviewAPIView (5-min cache) integrations/ Third-party stubs shared/ models.py TimestampedModel · OwnedModel permissions/rbac.py SigmaRolePermission · LeadPermission · get_role_slug() authentication/ CookieJWTAuthentication · cookie token views pagination/ SigmaPageNumberPagination
frontend/ src/ main.jsx Router · lazy imports · route tree context/AuthContext.jsx Cookie-based auth · no localStorage services/api/client.js Axios · withCredentials:true · no Auth header services/api/crm.js All API functions modules/leads/services/ leadsApi.js (lead-scoped wrappers) app/routes/ProtectedRoute RBAC route guard app/store/uiStore.js Zustand UI state components/ui/ SigmaForm · SigmaModal · StatusBadge · EmptyState
Domain Reference Lead.STATUS_CHOICES (exact slugs — validate against these): new | attempted_contact | contacted | interested | negotiation | dealer_assigned | converted | lost | closed Lead.SOURCE_CHOICES: website_form | whatsapp | dealer_inquiry | distributor | direct_call | manual_entry | social_media Lead.INQUIRY_TYPE_CHOICES: product | dealer | distributor | support | brochure Lead.PRIORITY_CHOICES: low | medium | high | urgent Dealer.STATUS_CHOICES: prospect | pending_approval | active | inactive | suspended Dealer.TIER_CHOICES: platinum | gold | silver | bronze | prospect FollowUp.STATUS_CHOICES: scheduled | completed | missed | cancelled | rescheduled FollowUp.CHANNEL_CHOICES: call | whatsapp | email | meeting | site_visit Task.STATUS_CHOICES: todo | in_progress | blocked | done | cancelled RBAC roles (exact slugs): super-admin full access · is_superuser admin full access · is_staff sales-manager leads + dealers + analytics + users + audit dealer-manager dealers + assigned leads sales-executive OWN assigned leads ONLY support-staff read-only on leads + notifications Key permission classes: pythonLeadPermission shared/permissions/rbac.py DealerPermission shared/permissions/rbac.py TaskPermission shared/permissions/rbac.py AnalyticsPermission shared/permissions/rbac.py AuditReadPermission apps/audit/views.py ManagementReadPermission apps/accounts/views.py URL contracts: POST /api/token/ login → sets httpOnly cookies POST /api/token/refresh/ refresh → rotates cookies POST /api/token/logout/ logout → clears cookies GET /api/auth/me/ current user profile GET/POST /api/v1/leads/ list + create POST /api/v1/leads/{id}/move/ Kanban status change GET/POST /api/v1/leads/{id}/notes/ lead notes GET /api/v1/leads/{id}/timeline/ lead timeline GET /api/v1/dealers/ annotated list GET /api/v1/products/ GET /api/v1/tasks/ GET /api/v1/followups/ GET /api/v1/notifications/ GET /api/v1/audit-logs/ admin + manager only GET /api/v1/files/ filter by content_type + object_id GET /api/v1/analytics/overview/ cached 5 min GET /api/v1/users/ admin + manager only
Aliases active: /api/v1/accounts/users/ → /api/v1/users/ /api/v1/accounts/roles/ → /api/auth/roles/ /api/v1/audit/ → /api/v1/audit-logs/
Task Execution Protocol Every task follows these 5 steps. Do not skip any. Step 1 — Understand Before writing a single line:
Restate the task in plain English List every file that will be touched List every side effect: migrations · new routes · audit hooks · timeline events · notifications If anything is ambiguous: ask ONE focused question, then wait
Step 2 — Plan Write a numbered plan. Example:
- Read apps/leads/views.py in full
- Add @action move() to LeadViewSet
- Validate status against Lead.STATUS_CHOICES → 400 on invalid
- Save lead with update_fields=["status", "updated_by"]
- create_timeline_event() for status change
- log_action() for audit trail
- Return LeadSerializer(lead).data
- Run manage.py check + test + npm lint Step 3 — Implement Execute the plan one numbered step at a time. After each file edit, state: what changed · why · what it affects. Step 4 — Verify Run these checks in exact order. All must pass before reporting done: bash# Backend python3 manage.py check # must be 0 issues python3 manage.py makemigrations --check # must be clean python3 manage.py test # must pass all
Frontend
cd frontend && npm run lint # must be 0 errors cd frontend && npm run build # must compile clean For any RBAC-touching change, run the boundary probe: python# These must always hold exec | GET /api/v1/audit-logs/ → 403 exec | GET /api/v1/users/ → 403 exec | GET /api/v1/leads/ → 200 (own leads only) admin| GET /api/v1/leads/ → 200 (all leads) For any queryset change, verify no N+1: pythonwith CaptureQueriesContext(connection) as ctx: resp = client.get("/api/v1/leads/") assert len(ctx) <= 5, f"N+1 detected: {len(ctx)} queries" Step 5 — Report Summary: one sentence — what changed and why Files: every file created or modified Migrations: every migration generated (name + app) Hooks: audit ✓/✗ · timeline ✓/✗ · notification ✓/✗ Checks: manage.py check ✓ · makemigrations ✓ · test ✓ · lint ✓ · build ✓ Gaps: any known limitations or follow-up work
Required Hooks — Never Skip These Every write operation must include ALL applicable hooks. A task is not done until every required hook is present. A — Audit log (every create, update, delete) pythonfrom apps.audit.services import log_action log_action(request.user, "entity.verb", instance, request=request)
Action string format: "<model>.<verb>"
Examples: "lead.created" · "lead.updated" · "lead.status_changed"
"dealer.approved" · "task.completed" · "followup.rescheduled"
B — Timeline event (every Lead status change) pythonfrom apps.leads.services import create_timeline_event create_timeline_event( lead=lead, action="lead.status_changed", message=f"Status changed from {old_status} to {lead.status}.", metadata={"from": old_status, "to": lead.status}, user=request.user, ) C — Timeline event (every Lead assignment change) pythoncreate_timeline_event( lead=lead, action="lead.reassigned", message=f"Lead reassigned to {lead.assigned_executive}.", metadata={"assignee_id": str(lead.assigned_executive_id)}, user=request.user, ) D — notify_assignment (every Lead assignment or reassignment) pythonfrom apps.leads.services import notify_assignment notify_assignment(lead) E — create_notification (changes affecting other users) pythonfrom apps.notifications.services import create_notification create_notification( user=affected_user, notification_type="task_assigned", # or "lead_assignment", "dealer_update" title="...", message="...", payload={"entity_id": str(instance.id)}, )
Coding Rules Backend python# 1. All new models extend OwnedModel or TimestampedModel class MyModel(OwnedModel): # gets created_at, updated_at, created_by, updated_by ...
2. All views require permission_classes
class MyViewSet(viewsets.ModelViewSet): permission_classes = [IsAuthenticated, MyPermission]
3. Role-scoped querysets — never Lead.objects.all() in a view
def get_queryset(self): qs = Lead.objects.select_related("assigned_executive", "interested_product") if get_role_slug(self.request.user) == "sales-executive": qs = qs.filter(assigned_executive=self.request.user) return qs
4. Targeted saves only
lead.save(update_fields=["status", "updated_by", "updated_at"])
5. Action-scoped prefetch
def get_queryset(self): qs = Lead.objects.select_related(...) if self.action == "retrieve": qs = qs.prefetch_related("followups", "lead_notes", "timeline") return qs
6. perform_create / perform_update pattern
def perform_create(self, serializer): instance = serializer.save(created_by=self.request.user, updated_by=self.request.user) log_action(self.request.user, "entity.created", instance, request=self.request)
7. New apps go in settings.INSTALLED_APPS
8. New model changes get a migration
python3 manage.py makemigrations <app_name>
Frontend typescript// 1. All API calls through the axios instance import apiClient from '@/services/api/client' // Never: fetch(), new axios.create(), raw XMLHttpRequest
// 2. Server state = React Query, staleTime always set const { data } = useQuery({ queryKey: ['leads'], queryFn: () => apiClient.get('/v1/leads/'), staleTime: 30_000, // standard data // staleTime: 300_000 // analytics data // staleTime: 60_000 // user/role data })
// 3. UI state = Zustand import { useUIStore } from '@/app/store/uiStore'
// 4. All pages lazy-loa
Content truncated.