Slice 4: Deal pipeline - #3
Merged
Merged
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
crm_deal(004_deals):title,value DECIMAL(18,2),currency,stage ENUM(lead|qualified|proposal|negotiation),status ENUM(open|won|lost), softcontact_id/org_id.Dealsservice — field allow-list;stage/statusare write-time allow-lists (never interpolated);valueis a bounded, non-negative decimal normalised to 2 places;currencya 3-letter code;contact_id/org_idvalidated 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).Activitiesgainsdealas a valid, existence-checked subject, so activities hang off deals too.crm_deals/crm_deal_get/crm_deal_set/crm_deal_deleteon the wildcard-immunenimbuscms.crmcapability; the activity tools now accept adealsubject.crm-dealspage +deal-save/deal-deleteactions; the sharedactivity-add/activity-deletenow also servecrm-deals.Tests
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).DealsAdminTest(board escapes a hostile title, won/lost in Closed not the board, edit form + timeline, search flat list).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.crmcapability.stage/status/links are bound, allow-listed and existence-checked — no injection, no dangling refs. Money input is validated (a1000; DROP TABLEvalue 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