Skip to content

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

PiecePath
Modelsapp/Models/Opportunity.php, OpportunityStage, OpportunityTag, OpportunityNote, OpportunityActivity
EnumsOpportunityActivityTypeEnum
Serviceapp/Services/Tenant/OpportunityService.php (+ ScopesToAssignee), OpportunityTagService.php
Controllerapp/Http/Controllers/Tenant/Api/V1/OpportunityController.php, OpportunityTagController.php
Requestsapp/Http/Requests/Tenant/Api/V1/Opportunity/*
Resourcesapp/Http/Resources/Tenant/Api/V1/Opportunity/*
Policyapp/Policies/OpportunityPolicy.php, OpportunityTagPolicy.php
Eventsapp/Events/Opportunity*.php (includes OpportunityTagCreated, OpportunityTagsSynced)
Subscriberapp/Listeners/OpportunityEventSubscriber.php (audit + assignment notification)
Notificationsapp/Notifications/Tenant/Opportunity/OpportunityAssignedNotification.php
Link rulesLinkableContact, LinkableLead, LinkableCompanyForOpportunity, EligibleOpportunityAssignee
Stage seederdatabase/seeders/Tenant/OpportunityStageSeeder.php
Teststests/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 ScopesToAssignee with opportunities.assign.
  • opportunities.force.delete is 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_dependencies rows for Contacts/Companies/Leads. Quotations and Contracts declare Opportunities as a required hard dependency.

Permissions

opportunities.view | create | update | delete | restore | force.delete | assign

Routes 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.

PiecePath (expected)
Pagesrc/pages/opportunities/
Shared boardsrc/components/crm/kanban-board.tsx (per-column vertical scroll + contained horizontal scroll; titles stay fixed)
Form / detailcreate/edit page + record page (Overview, Notes, Activity); board DnD auto-saves stage on the list page
ServiceopportunityService in src/api/services.ts
Navpermission: opportunities.view, module: 'opportunities' (Sales)
Playwrighte2e/pages/opportunities.page.ts, e2e/tests/opportunities/, npm run test:e2e:opportunities

Tests

bash
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:opportunities

Logging

  • Spatie LogsActivity on Opportunity (log name opportunities)
  • Domain opportunity_activities timeline
  • PlatformAuditService via OpportunityEventSubscriber (includes opportunity_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.

Official documentation for the EloSync SaaS Platform.