Opportunities — Developer Guide
Mirror of the Leads developer guide (pipeline board) and Activities developer guide (soft related FKs, notes, assignee scope). Prefer copying those patterns over inventing new ones.
Backend layout
| Piece | Path |
|---|---|
| Models | app/Models/Opportunity.php, OpportunityStage, OpportunityTag, OpportunityNote, OpportunityActivity |
| Enums | OpportunityActivityTypeEnum |
| Service | app/Services/Tenant/OpportunityService.php (+ ScopesToAssignee), OpportunityTagService.php |
| Controller | app/Http/Controllers/Tenant/Api/V1/OpportunityController.php, OpportunityTagController.php |
| Requests | app/Http/Requests/Tenant/Api/V1/Opportunity/* |
| Resources | app/Http/Resources/Tenant/Api/V1/Opportunity/* |
| Policy | app/Policies/OpportunityPolicy.php, OpportunityTagPolicy.php |
| Events | app/Events/Opportunity*.php (includes OpportunityTagCreated, OpportunityTagsSynced) |
| Subscriber | app/Listeners/OpportunityEventSubscriber.php (audit + assignment notification) |
| Notifications | app/Notifications/Tenant/Opportunity/OpportunityAssignedNotification.php |
| Link rules | LinkableContact, LinkableLead, LinkableCompanyForOpportunity, EligibleOpportunityAssignee |
| Stage seeder | database/seeders/Tenant/OpportunityStageSeeder.php |
| Tests | tests/Feature/Tenant/Opportunity/OpportunityTest.php, OpportunityTagTest.php |
Domain notes
- Sales Pipeline is not a separate module —
opportunity_stages+ board endpoints live inside Opportunities. - Assignee scoping via
ScopesToAssigneewithopportunities.assign. opportunities.force.deleteis not granted to any default role — owner/superadmin only.- Related FKs (
contact_id/company_id/lead_id) are optional; each is validated for module entitlement + assignee scope on the related record when set. - Default stages are ensured idempotently (
OpportunityStageSeeder/ensureDefaultStages()): Prospecting → Qualification → Proposal → Negotiation → Won / Lost. - Soft delete; stage changes via
POST .../stage(can:opportunities.update). - No hard
module_dependenciesrows for Contacts/Companies/Leads. Quotations and Contracts declare Opportunities as a required hard dependency.
Permissions
opportunities.view | create | update | delete | restore | force.delete | assignRoutes use module:opportunities then can:opportunities.* / policies.
Catalog: slug opportunities, category sales, is_default_included = false, is_billable = false, sort_order = 40. Registered via DefaultModuleRegistrar migration (migrate-only).
API (tenant)
Base: /api/tenant/v1 — full reference tenant-v1-opportunities.md.
Colored tags are create-only for MVP (GET/POST /opportunity-tags, assign via tag_ids / PUT …/tags, filter tag_id). No rename/delete/reorder tag routes.
Frontend
SPA should mirror Leads (board default + table, create/edit page, record page) under the existing AppLayout — do not invent a parallel shell.
| Piece | Path (expected) |
|---|---|
| Page | src/pages/opportunities/ |
| Shared board | src/components/crm/kanban-board.tsx (per-column vertical scroll + contained horizontal scroll; titles stay fixed) |
| Form / detail | create/edit page + record page (Overview, Notes, Activity); board DnD auto-saves stage on the list page |
| Service | opportunityService in src/api/services.ts |
| Nav | permission: opportunities.view, module: 'opportunities' (Sales) |
| Playwright | e2e/pages/opportunities.page.ts, e2e/tests/opportunities/, npm run test:e2e:opportunities |
Tests
php artisan test --compact tests/Feature/Tenant/Opportunity/OpportunityTest.php
php artisan test --compact tests/Feature/Tenant/Opportunity/OpportunityTagTest.php
npm run typecheck && npm run lint && npm run build
npm run test:e2e:opportunitiesLogging
- Spatie
LogsActivityonOpportunity(log nameopportunities) - Domain
opportunity_activitiestimeline PlatformAuditServiceviaOpportunityEventSubscriber(includesopportunity_tag_created,opportunity_tags_synced)
Ask EloSync
Ask EloSync Opportunity tools (get_opportunity, get_opportunity_stages, confirmed stage/assign/note writes, plus existing search_opportunities / get_pipeline_summary) are registered in AIToolRegistry and confirmed via PendingAiActionService. Search rows include stage_id and assignee id. HTTP POST …/stage validates stage_id in the current tenant. See AI tools and AI Opportunity triage production readiness.