Enforce lead disqualification reason, group campaign/task fields, unify priority_rank - #577
Merged
Merged
Conversation
…fy priority_rank Refs #575 (A2, A3, A4). A2 — crm_lead.disqualification_reason promised "Required when status is Unqualified" in its own description and nothing enforced it: no validation, no hook, and no form that rendered the field. Adds the disqualification_reason_required script validation (same shape as crm_case.escalation_reason_required), puts the field on every crm_lead form that exposes an editable status (shown only when the status is unqualified) and on the lead detail page, and gives the generated unqualified seed leads a reason with a matching note. A3 — crm_campaign and crm_task were the last two detail-page business objects with zero fieldGroups, so both rendered as one flat grid. Both now declare groups mirroring the sections their forms already use, with every field assigned. Line items stay ungrouped: they are edited inline in the parent's grid and have no detail page to section. A4 — priority_rank's unknown-priority fallback was 1 on crm_case and 2 on crm_task, with field defaults to match, so the same unrecognised priority sorted differently on the two objects and on each was indistinguishable from a real priority. Both now use 0, an unranked sentinel below every real rank on the priority_rank desc queues; the known ranks 1-4 are unchanged. The two rank maps stay hand-copied because L2 hook bodies run body-only in the QuickJS sandbox, so the new parity test is what keeps the copies in agreement. New guards live in three new test files rather than being appended to test/metadata-references.test.ts. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KSmk2xtzFcv9TbW9PuYBUe
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
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.
Description
Items A2, A3 and A4 from #575 — the three A-group items scoped here. Nothing from the B group is touched. All three were re-verified against
main(3ed27b26) before any code was written; all three still existed.Type of Change
Related Issues
Related to #575
Deliberately not
Fixes/Closes: this PR covers A2/A3/A4 only, and #575 still carries A1 and the whole B group.Changes Made
A2 —
crm_lead.disqualification_reasonpromised required and enforced nothing.The field's own description has read "Required when status is Unqualified" since it was added, with no validation, no hook, and no form that rendered the field at all.
disqualification_reason_requiredscript validation oncrm_lead, modelled on the repo's existingcrm_case.escalation_reason_required.crm_leadform that exposes an editablestatus(default form,quick_create,detail_form,lead_conversion_wizard,split_edit,quick_edit_drawer,status_update_modal,advanced_conditional), shown only when the status isunqualified, and on the lead detail page beside the status.unqualifiedseed leads now carry a reason (rotated across four values, each with a matching note) instead of a budget-flavoured note and no reason.A3 —
crm_campaignandcrm_taskhad nofieldGroups.They were the last two business objects with a full detail page and zero groups, so both rendered as one flat grid — the campaign's ROI formulas inline with its name, the task's five polymorphic
related_to_*lookups and recurrence machinery inline with its subject. Both now declare groups mirroring the sections their forms already use, with every field assigned. The two line-item objects stay ungrouped, as the issue allows: they are edited inline in the parent's grid and have no detail page to section.A4 —
priority_rankdiverged between the two objects that use it.The unknown-priority fallback was
1oncrm_caseand2oncrm_task, with field defaults to match — so the same unrecognised priority sorted differently on the two objects, and on each it was indistinguishable from a genuine priority (low/normalrespectively). Both now use0, an unranked sentinel that sorts below every real rank on thepriority_rank descqueues.test/metadata-references.test.tsandtest/hooks-runtime-service.test.ts, which pin this area, are untouched and still pass.undefined(see_line-item-price-fill.ts). Since the duplication is forced, the new parity test is what keeps the copies honest — it drives both hooks and asserts they rank an unknown priority identically and consistently with each object's field default.Testing
pnpm test) — 515 passed, 2 skipped, 28 filespnpm lint)pnpm build)Full
pnpm verify(validate → typecheck → lint → hygiene → build → test) is green. The two expression warnings the build emits are pre-existing (campaign_enrollmentflow variable) and unrelated.New guards live in three new test files, per the issue's convention, rather than being appended to
test/metadata-references.test.ts:test/lead-disqualification.test.ts— the rule exists and fires on exactly the right condition, the field still carries the promise the rule keeps, every form that can setstatusalso offers the reason, and every seeded unqualified lead carries a legal reason value.test/field-groups-coverage.test.ts— every detail-page object declares groups, plus the silent failures around them: a field pointing at an undeclared group key vanishes from the layout, and a declared group with no fields renders an empty section header.test/priority-rank-parity.test.ts— both hooks rank the known priorities 1–4, fall back to the same sentinel for an unknown priority, and agree with their object's field default.Checklist
.changeset/lead-disqualification-field-groups-priority-rank.mdAdditional Notes
Per the issue's convention #5,
pnpm verifyshould be re-run onmainafter merge — three "green at merge, red on main" incidents happened the day the issue was filed.Generated by Claude Code