Technical Reference
Deals & Payments

Deals & Payments — Technical Reference

This page covers the data models, Kanban architecture, and revenue calculation logic behind the Pipelines & Deals module.

Data Models

pipelines

ColumnTypeDescription
iduuidPrimary key
workspace_iduuidTenant isolation
nametextPipeline name
created_attimestamptzCreated timestamp
updated_attimestamptzLast updated timestamp

pipeline_stages

ColumnTypeDescription
iduuidPrimary key
pipeline_iduuidFK → pipelines.id (ON DELETE CASCADE)
nametextStage name
order_indexintegerSort position within the pipeline

deals

ColumnTypeDescription
iduuidPrimary key
workspace_iduuidTenant isolation
pipeline_stage_iduuidFK → pipeline_stages.id
titletextDeal name
valuenumeric(12,2)Monetary value
statustext'open', 'won', or 'lost' (CHECK constraint)
contact_iduuidFK → contacts.id (required, ON DELETE CASCADE)
company_iduuidFK → companies.id (optional, ON DELETE SET NULL)
assigned_user_iduuidFK → user_profiles.id
created_byuuidFK → user_profiles.id — creator
updated_byuuidFK → user_profiles.id — last modifier
closed_attimestamptzSet when status changes to won/lost
contract_urltextOptional URL to external contract documents

Design Note: company_id uses ON DELETE SET NULL to preserve deal revenue history even if the linked company is deleted. contact_id uses ON DELETE CASCADE since the human contact is the mandatory entity.

deal_payments

ColumnTypeDescription
iduuidPrimary key
workspace_iduuidTenant isolation — FK → workspaces.id (ON DELETE CASCADE)
deal_iduuidFK → deals.id (ON DELETE CASCADE)
labeltextMilestone name
sequence_orderintegerSort position of the payment milestone
expected_amountnumeric(12,2)Expected payment amount
paid_amountnumeric(12,2)Actual amount received
statustext'scheduled', 'paid', or 'waived' (CHECK constraint)
due_datedateExpected payment date
paid_datedateActual payment date
payment_methodtextHow payment was made (e.g., PayPal, ACH)
payment_referencetextTransaction identifier
notestextOptional internal notes for the payment
processing_feenumeric(12,2)Informational processor fee
created_byuuidCreator profile — FK → user_profiles.id
created_attimestamptzRecord creation timestamp
updated_attimestamptzLast updated timestamp

Kanban Architecture

The Kanban board (/deals/[pipelineId]) uses the @dnd-kit library for drag-and-drop interactions.

Optimistic Updates

  1. Drag Start — Shows a drag overlay with the deal title and value.
  2. Drop — The UI immediately updates the card's position locally.
  3. Background Sync — The moveDealToStage server action is called.
  4. Revert on Failure — If the action fails, the card reverts to its original stage and an error toast is shown.

Activation Constraint

A minimum drag distance of 8 pixels is required before a drag starts. This prevents accidental drags when a user intends to click the card or open the kebab menu.

Hydration Safety

Since @dnd-kit relies on browser-only APIs, the board uses a client-side mount guard to prevent Next.js hydration mismatch errors:

1. Component mounts → isMounted = false → renders skeleton columns
2. useEffect fires  → isMounted = true  → renders DndContext with full board

Terminal States

When a deal's status is changed to won or lost, it is removed from the active Kanban board, which only fetches deals where status = 'open'.

Revenue Calculations

Payments Progress Bar

The Deal Detail view includes a payments progress bar calculated as follows:

  • Total Collected = SUM of paid_amount where status = 'paid'
  • Total Expected = SUM of expected_amount where status != 'waived'
  • Progress % = Total Collected / Total Expected

Dashboard Revenue (30d)

The main dashboard Revenue (30d) metric aggregates three sources: Stripe Purchases + Event Registrations + Deal Payments

For deal payments, only the paid_amount from payments where status = 'paid' and paid_date is within the last 30 days is included. processing_fee is informational only and does not reduce the gross revenue metric.

Overdue Detection

Scheduled payments with a due_date in the past display a red "Overdue" badge. This is a UI-only detection calculated at render time. There is no cron job or automated notification system for overdue payments.

Automation Triggers

Deal transitions hook into the unified automation engine:

Trigger TypeFires WhenTrigger ID Format
deal_stage_changedA deal is moved to a new stageStage UUID
deal_status_changedA deal is marked as Won/Lost{pipelineId}_{status}

The composite {pipelineId}_{status} ID allows automation rules to be scoped to specific outcomes in specific pipelines (e.g., triggering onboarding only when a deal in the "Sales" pipeline is "won").

Performance & Hydration Optimization

Server-Side Pre-fetching & Lazy Loading

  • Server Component (pages/deals/page.tsx): Parallel-fetches getDealsDirectoryBundle (deals, pipelines, and automation rules count) server-side to support instant first paint.
  • Client Component (deals-client.tsx): Manages the Kanban board, deal searches, stages filtering, and client state.
  • Dynamic imports: The NewDealDialog and DependenciesDrawer are dynamically imported and conditionally rendered.
  • Eager Mount Mitigation: The new pipeline creation sheet is conditionally mounted so that closed state element DOM nodes do not bloat the DOM tree.
  • Sync Gates: Uses isFirstQueryRef and initialWorkspaceIdRef to guard mounts and clean filters on workspace changes.

Security (RLS)

EntitySELECTINSERTUPDATEDELETE
PipelinesAll membersAdmin+Admin+Admin+
StagesAll membersAdmin+Admin+Admin+
DealsAll membersAll membersAll membersAdmin+
Deal PaymentsAll membersAll membersAll membersAdmin+

Pipelines and Stages follow the Structural Data access model. Deals and Payments follow the Operational Data model (all members can create/edit, but only Admins can delete).