Skip to content

feat(contact): Contact model for public-ticket dedupe (Pattern B)#27

Draft
mpge wants to merge 4 commits intomainfrom
feat/public-tickets-contact-convergence
Draft

feat(contact): Contact model for public-ticket dedupe (Pattern B)#27
mpge wants to merge 4 commits intomainfrom
feat/public-tickets-contact-convergence

Conversation

@mpge
Copy link
Copy Markdown
Member

@mpge mpge commented Apr 24, 2026

Summary

WordPress port of Pattern B. Reference implementations:

Rollout strategy: https://github.com/escalated-dev/escalated/blob/feat/public-ticket-system/docs/superpowers/plans/2026-04-24-public-tickets-rollout-status.md

Changes

  • Activator — adds `contact_id` nullable column on tickets (dbDelta) + new `escalated_contacts` table (unique email, nullable user_id, metadata JSON)
  • Contact model class with static helpers: `normalize_email`, `decide_action`, `find_or_create_by_email`, `link_to_user`, `promote_to_user`
  • Test_Contact_Model — 7 pure-function WP_UnitTestCase cases

Test plan

  • CI runs 7 new tests (local WP test library unavailable)
  • Pure-function logic mirrors the equivalents green in NestJS, Adonis, .NET PRs

Backwards compatibility

Inline guest_name / guest_email / guest_token columns preserved. Follow-up PRs migrate the AJAX / inbound-email write paths.

mpge added 2 commits April 24, 2026 01:47
…ern B)

WordPress port of the Pattern B design shipped in escalated-nestjs
PR #17 and companion PRs for Laravel / Rails / Django / Adonis / .NET.

Changes:
  - class-activator.php — adds nullable contact_id column to
    escalated_tickets (via dbDelta; idempotent on re-activation)
    and a new escalated_contacts table with unique email index and
    nullable user_id.
  - includes/Models/Contact.php — class with:
      static normalize_email (trim + lowercase, pure)
      static decide_action (branch selection, pure)
      static find_by_email / find
      static find_or_create_by_email (dedupe, dual-mode name fill)
      static link_to_user
      static promote_to_user (link + back-stamp requester_id on
                              existing tickets)
  - tests/Test_Contact_Model.php — 7 WP_UnitTestCase cases
    (normalize_email ×3, decide_action ×4).

Inline guest_name / guest_email / guest_token columns preserved on
tickets for backwards compatibility; follow-up pass migrates the
AJAX handler and inbound controller to write via Contact.

Local verification: WP test library not installed in author's env;
CI on this PR will run the tests. The pure-function tests
(normalize_email, decide_action) have no WP dependencies and match
the equivalents passing in NestJS (message-id), Adonis (contact_model),
.NET (ContactModelTests) PRs.

See escalated-dev/escalated docs/superpowers/plans/2026-04-24-public-tickets-rollout-status.md
Follow-up on the Contact model added earlier on this branch.
TicketService::create_guest now resolves/creates a Contact by
email and stamps contact_id on the new ticket row.

The widget REST controller and frontend AJAX handler both
delegate here, so one change covers both submission paths.

Inline guest_name / guest_email / guest_token columns remain
populated for backwards compat.

Tests: 1 new integration case in Test_Contact_Model —
  test_create_guest_dedupes_contacts_by_email
  (two submissions with casing variants yield one Contact row,
   both tickets carry the same contact_id)

Local verification: WP test library not installed in author's
env; CI on this PR will run the tests. Pure-function coverage
(normalize_email + decide_action) mirrors equivalents passing
in NestJS/Adonis/Go/.NET/Symfony.
@mpge
Copy link
Copy Markdown
Member Author

mpge commented Apr 24, 2026

Wire-up commit pushed

TicketService::create_guest now resolves/creates a Contact by email via Contact::find_or_create_by_email and stamps contact_id on the new ticket. Both the widget REST controller and frontend AJAX handler delegate through create_guest, so one change covers both paths.

Test added: test_create_guest_dedupes_contacts_by_email — two submissions with casing variants (alice@example.com + ALICE@Example.COM) yield one Contact row, both tickets carry the same contact_id.

Inline guest_* columns preserved. CI will run the tests (local WP test library unavailable).

mpge added 2 commits April 24, 2026 02:28
Covers the wire-up added in the previous commit: two widget
submissions with casing variants on the same email yield one
Contact row, and both tickets carry the same contact_id.
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