Skip to content

Slice 4: Deal pipeline - #3

Merged
DanMat merged 1 commit into
mainfrom
feat/deals
Sep 4, 2026
Merged

DanMat merged 1 commit into
mainfrom
feat/deals

Conversation

@DanMat

@DanMat DanMat commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Fourth CRM slice: the deal pipeline — the piece that keeps this a general CRM (agencies, B2B, membership, a restaurant's guests), not a restaurant-shaped one. Same discipline as Slices 1–3.

What's here

  • Schema — crm_deal (004_deals): title, value DECIMAL(18,2), currency, stage ENUM(lead|qualified|proposal|negotiation), status ENUM(open|won|lost), soft contact_id/org_id.
  • Deals service — field allow-list; stage/status are write-time allow-lists (never interpolated); value is a bounded, non-negative decimal normalised to 2 places; currency a 3-letter code; contact_id/org_id validated to exist at write; bound + wildcard-escaped title search; status filter; board ordered along the pipeline; total delete that takes the deal's activity timeline with it (transaction).
  • Cross-entity delete — deleting a contact or organization now also NULLs any deal's link to it (the deal is kept, never orphaned or cascaded). Activities gains deal as a valid, existence-checked subject, so activities hang off deals too.
  • MCP — crm_deals / crm_deal_get / crm_deal_set / crm_deal_delete on the wildcard-immune nimbuscms.crm capability; the activity tools now accept a deal subject.
  • Admin — a pipeline board: a column per stage for open deals with a per-column count and value total, won/lost in a Closed section; a create/edit form with contact/org selects; and the inline activity timeline on a deal. Reflows to a single column under ~52rem (phone-friendly). New crm-deals page + deal-save/deal-delete actions; the shared activity-add/activity-delete now also serve crm-deals.
  • Guide — deals section.

Tests

  • New: DealsTest (defaults, title required, money normalised/bounded, negative & non-numeric rejected, stage/status allow-lists, links must exist, over-post ignored, bound search + status filter, pipeline ordering, delete takes the timeline).
  • New: DealsAdminTest (board escapes a hostile title, won/lost in Closed not the board, edit form + timeline, search flat list).
  • Extended: ContactsTest / OrganizationsTest (deleting one keeps its deals, clears the link); ActivitiesTest (a deal subject is valid); CrmToolsetTest (deal tools listed, content token can't reach them, money/currency/link round-trip, status filter, title/value-as-data). All setups build + truncate the deal table.

Security posture

Every deal surface gates on the wildcard-immune nimbuscms.crm capability. stage/status/links are bound, allow-listed and existence-checked — no injection, no dangling refs. Money input is validated (a 1000; DROP TABLE value is rejected as data). Store-raw/escape-on-render (XSS test on the board). Delete leaves no residue.

cs-fixer + PHPStan level 6 green locally (borrowed-vendor); phpunit runs in CI.

🤖 Generated with Claude Code

Adds the deal pipeline — the piece that keeps this a general CRM.

- Schema: crm_deal table + 004_deals migration. title, value
  DECIMAL(18,2), currency, stage ENUM(lead|qualified|proposal|
  negotiation), status ENUM(open|won|lost), soft contact_id/org_id.
- Deals service: field allow-list; stage/status are write-time
  allow-lists (never interpolated); value is a bounded, non-negative
  decimal normalised to 2 places; currency a 3-letter code; contact_id/
  org_id validated to exist at write; bound + wildcard-escaped title
  search; status filter; board ordered along the pipeline; total delete
  that takes the deal's activity timeline with it (transaction).
- Cross-entity delete: deleting a contact or organization now also NULLs
  the deal's link (kept, never orphaned/cascaded); Activities gains
  'deal' as a valid subject (existence-checked), so activities hang off
  deals too.
- MCP: crm_deals / _get / _set / _delete on the same wildcard-immune
  capability; activity tools now accept a deal subject.
- Admin: a pipeline board (a column per stage for open deals, per-column
  count + value total, won/lost in a Closed section), create/edit form
  with contact/org selects, and the inline activity timeline on a deal.
  Reflows to one column on a phone. New crm-deals page + deal-save/
  deal-delete actions; activity-add/-delete now also serve crm-deals.
- Guide: deals section.
- Tests: DealsTest, DealsAdminTest; Contacts/Organizations deal-link-
  cleared tests; Activities deal-subject test; CrmToolset deal tools +
  gating + money/link/status coverage; all setups build+truncate the
  deal table.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@DanMat
DanMat merged commit 0c83664 into main Sep 4, 2026
2 checks passed
@DanMat
DanMat deleted the feat/deals branch September 4, 2026 20:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant