[PRD] Supporting Create Ticket from CDP Mobile (Native + Auto-Associate)
HEADER BLOCK
| Field | Value |
|---|---|
| PM | Zhelia Alifa |
| PRD Version | 1.1 |
| Status | DRAFT |
| PRD Type | SUPPORT |
| Epic | TBD — add once Epic is created |
| Squad | CDP Squad × Mobile/CRM Squad |
| RFC Link | N/A — reuses the native Mobile Ticket Module (no new RFC) |
| Figma Master | Mobile CRM — Tickets |
| Anchor | Yes — Create Ticket & Auto-Associate from CDP (Cross-Platform) |
| Labels | epic:qontak-cdp | module:customers | feature:create-ticket-from-cdp-mobile |
| Last Updated | 2026-06-26 |
This is a SUPPORT PRD. The native ticket-create screen is owned by the Mobile Ticket Module (TF-3053 / Ticket Module in Mobile App PRD). This PRD covers the CDP-mobile contribution — adding a "Create new ticket" entry point to the contact detail, routing into that native create screen, and auto-associating the result back to the contact. CDP mobile today is associate-existing only; this closes that gap.
2. SUPPORT Context
| Field | Detail |
|---|---|
| Anchor PRD | Create Ticket & Auto-Associate from CDP (Cross-Platform) — this is the mobile track. |
| Lead-squad component | The native ticket-create screen CreateTicketScreen + create API POST /v2.8/tickets, owned by the Mobile Ticket Module (TF-3053). Note: TF-3053 §[26Q2] Ticket Association with CDP Mobile AC1.4 already describes "Create new ticket → native create page → auto-associate" — this PRD is the CDP-side detail/contract for that AC. |
| This squad's contribution (CDP-mobile) | In mobile-qontak-crm → contact detail "Others" tab: add a "From Existing / Create New" ticket bottom sheet (the "Create New" branch is the net-new part); route to CreateTicketScreen; on return, auto-associate the created ticket to the contact; refresh the association summary; pre-fill the customer in the create form. |
| Integration point (contract) | (1) Navigate navigationHelper.pushNamed(QontakAppRoute.createTicket, CreateTicketArgument(pipelineId)).then((result) => …); (2) on a non-null created ticket, call the existing associateTicket() → PUT /v2.8/tickets/{ticketId} with qontak_customer_ids = contactId. Both already exist in the repo — this PRD wires them together. |
| Handoff contract + date | Mobile Ticket Module delivers: the native CreateTicketScreen returning the created ticket on pop (already shipped). CDP-mobile delivers: the contact entry point + auto-association. Date: TBD — mobile native create already exists, so this track is NOT blocked by the CRM embed (unlike the web track). |
| Graceful degradation | If "Create New" is unavailable (e.g. flag off / no ticket-create access), the ticket bottom sheet degrades to associate-existing only — exactly today's behavior. Create is purely additive. |
3. One-liner + Problem
One-liner: Let a CDP-mobile user create a new ticket from a contact (via the existing native ticket form) and have it auto-associated to that contact — not just associate an existing one.
Problem: In mobile-qontak-crm, the contact detail "Others" tab ticket section (other_tab.dart:529–560) only offers "associate existing" (_onAddTicket() → TicketAssociationListBottomSheet). There is no "Create new ticket" option for tickets, even though that exact "From Existing / Create New" pattern already exists for companies, deals, and products. So a field/mobile agent who needs to raise a ticket on a customer must leave the contact, open the Ticket module, create the ticket, then come back and associate it manually — daily, high-frequency friction.
4. Target Users + Persona Context
| Persona | Role | Goal | Pain | Workaround |
|---|---|---|---|---|
| Primary — Field / Mobile CS Agent | Agent using mobile-qontak-crm on the move | Raise + link a ticket from the contact on a phone | No "Create new ticket" from a contact — associate-existing only | Leaves the contact → Ticket module → creates → returns → associates manually |
| Secondary — CS Supervisor (mobile) | Reviews a customer's tickets on mobile | Capture a new issue in-context during a customer interaction | Same gap; loses the customer context mid-task | Same leave-and-return workaround |
5. Non-Goals
- Not building the native ticket form —
CreateTicketScreen, pipeline selection, layout/custom fields are owned by the Mobile Ticket Module (TF-3053). This PRD only routes into it and handles the return. - Not changing the create API —
POST /v2.8/ticketsis unchanged. - Not changing associate-existing — the existing
TicketAssociationListBottomSheetflow stays. - No web scope — web is the sibling SUPPORT PRD (embed iframe).
- No ticket edit/delete from the contact — out of scope (existing module behavior).
- No offline create — requires network like the rest of the ticket module.
- No new permission system — reuses mobile's existing ticket feature flag + contact
permission.update.
Scope Changes
Engineering surfaces this PRD touches (controlled vocab: Backend · Frontend · Mobile · Infra · Data · Design · Docs · None).
- Mobile —
mobile-qontak-crm: infeatures/crm_contact/.../other_tab.dart, replace the ticket section's direct_selectExistingTickets()call with the sharedAssociationBottomSheet("From Existing" / "Create New" — same util used for companies/deals/products); on "Create New",pushNamed(QontakAppRoute.createTicket, CreateTicketArgument(pipelineId))and on a non-null return callassociateTicket()(PUT /v2.8/tickets/{id},qontak_customer_ids = contactId); pre-fill the customer/contact in the create form; refresh the association summary; gate "Create New" on the ticket feature flag +permission.update. - Design — Figma for the ticket "Create New / From Existing" bottom sheet + the post-create success/refresh state on the contact detail (align to the existing deal/product pattern).
- Backend — None (create + associate endpoints already exist; the native create keeps its existing
data_source: "Mobile Flutter"— noembed_sourceon the native path; see MD-5).
6. Constraints
| Constraint | Value |
|---|---|
| Platform | Mobile only (mobile-qontak-crm, Flutter). Web is the sibling PRD. |
| Component ID | CP-QONTAKCRM-2025-0002 (ticket features shown per the user's active package — per the Mobile Ticket Module PRD). |
| Feature gating | Mobile uses feature flags (isTicketEnabled via GetProfileBloc) + contact-level permission.update + !isTSO — not the granular web keys (tickets_general_manage, etc.). The "Create New" entry inherits this gating. |
| Create API | POST /v2.8/tickets with TicketRequestModel { id, layout_id, data_source: "Mobile Flutter", crm_properties[] } (existing). |
| Associate API | PUT /v2.8/tickets/{ticketId} with a ContactResponse carrying crm property qontak_customer_ids = contactId (UUID for C360 / numeric for CRM) (existing — contact_360_remote_data_source.dart:296). |
| Layout/custom fields | Loaded by the native form via feature flag ticketLayoutEnabled + GetTicketCRMProperties(layoutId, pipelineId) — no CDP-mobile logic. |
| C360 vs CRM contact | C360 (CDP) contact uses id360 (UUID) and Contact360AssociationBloc; CRM contact uses numeric id. Auto-association must use the right identity per mode. |
| Backward compat | Associate-existing flow unchanged; "Create New" is additive + gated. |
7. New Features
7.1 "Create New" branch in the contact ticket bottom sheet
| Element | Before | After |
|---|---|---|
Ticket section in contact "Others" tab (other_tab.dart:529–560) | _onAddTicket() → _selectExistingTickets() → TicketAssociationListBottomSheet (existing only) | _onAddTicket() → shared AssociationBottomSheet with "From Existing" + "Create New" (same pattern as companies/deals/products) |
| "Create New" action | Does not exist for tickets | pushNamed(QontakAppRoute.createTicket, CreateTicketArgument(pipelineId: defaultPipelineId)); await the popped result |
| On created ticket | n/a | Call associateTicket() (PUT /v2.8/tickets/{id}, qontak_customer_ids = contactId), then refresh the association summary + show success |
| Customer pre-fill | n/a | Pre-fill the Customer/Contact crm-property in the create form from the contact (C360: qontak_customer_ids = id360) |
Reuse map (grounded):
- Bottom sheet:
QontakAssociationFormUtils.openAssociationBottomSheet(...)(association_bottom_sheet.dart) — already returnstrue(existing) /false(create new) for other modules. - Create screen:
CreateTicketScreen+ routeQontakAppRoute.createTicket(route_generator.dart:2590). - Associate:
AssociateTicketUseCase/Contact360AssociationBloc.associateTicket()(contact360_association_bloc.dart:142).
4 UI States (Create-New branch):
- Loading: native create screen handles its own loading; on return, a brief associating spinner on the contact.
- Empty: N/A.
- Error: create fails inside the native screen (native handles it) → user returns with no ticket, nothing associated; associate failure on return → inline error + retry (ticket still exists).
- Success: ticket created + associated → association summary refreshes + success toast/snackbar.
8. API Behavior
CDP-mobile adds no new API. It composes two existing calls. Contract below.
| # | Behavior | Entity | Triggered By | Expected Behavior | Failure Behavior |
|---|---|---|---|---|---|
| 1 | Open create from contact | — | User taps Tickets "+" → "Create New" | pushNamed(QontakAppRoute.createTicket, CreateTicketArgument(pipelineId)); native form opens with the customer pre-filled | If no default pipeline resolvable → native form shows pipeline selection (its own behavior) |
| 2 | Create the ticket (native) | Ticket | User submits the native form | POST /v2.8/tickets { data_source:"Mobile Flutter", layout_id, crm_properties[] }; returns the created ticket; screen pops with the result | Native validation/network error → handled in the native screen; user returns with no created ticket |
| 3 | Auto-associate on return | Contact↔Ticket | Create screen pops with a non-null ticket | PUT /v2.8/tickets/{ticketId} with qontak_customer_ids = contactId (C360: id360); refresh association summary | Associate fails → cdp_mobile_ticket_associate_failed logged; inline error + retry; ticket still exists in CRM |
| 4 | Degrade to existing | — | Flag off / no create access | "Create New" hidden; only "From Existing" shown | — |
System Flow — CDP Mobile Create + Auto-Associate
Contact Detail (Others tab) → tap Tickets "+"
│
▼
AssociationBottomSheet ──"From Existing"──► TicketAssociationListBottomSheet (unchanged)
│
└──"Create New"──► pushNamed(createTicket, pipelineId) [customer pre-filled]
│
▼
CreateTicketScreen → POST /v2.8/tickets
│ pop(createdTicket)
▼
createdTicket != null ? ──yes──► associateTicket(): PUT /v2.8/tickets/{id}
│ (qontak_customer_ids = contactId)
│ │
│ success ► refresh summary + toast
│ fail ► log + inline retry
└──no──► return, nothing associated
9. System Flow + User Stories + ACs
9.1 System Flow
- Agent opens a CDP contact → Detail → Others tab → Tickets row (shows count + "+").
- Tap "+" → AssociationBottomSheet with From Existing / Create New (Create New gated on ticket flag +
permission.update). - Create New → navigate to native
CreateTicketScreen(default pipeline; customer pre-filled). - Agent fills + submits → native
POST /v2.8/tickets→ screen pops returning the created ticket. - On non-null return → CDP-mobile calls
associateTicket()(PUT /v2.8/tickets/{id},qontak_customer_ids = contactId) → refresh the association summary + success toast. - Failure branches: create cancelled/failed → return with nothing; associate failed → inline retry, ticket still exists.
9.2 User Stories
| User Story | Importance | Mockup | Technical Notes | Acceptance Criteria |
|---|---|---|---|---|
| [MTCK-S01] — "Create New" option in the contact ticket sheet As a mobile CS agent, I want a "Create new ticket" option from a contact, so I can raise a ticket in-context. | Must Have | Mobile CRM — Tickets (Figma) — align to the deal/product bottom sheet | Before-After: Before — _onAddTicket() → _selectExistingTickets() only (other_tab.dart:1372). After — _onAddTicket() → shared AssociationBottomSheet (openAssociationBottomSheet) with "From Existing"/"Create New", same as companies/deals/products. | — Happy Path — • AC-1: Given the ticket feature is enabled and the user has permission.update, when they tap the Tickets "+", then a bottom sheet shows From Existing and Create New.• AC-2: Given they tap "Create New", then the native CreateTicketScreen opens.— Error / Unhappy Path — • ERR-1: Given the create screen can't resolve a default pipeline, then the native pipeline selection shows (native behavior). — Permission Model — • CAN: ticket-enabled + permission.update + !isTSO.• CANNOT: otherwise → only "From Existing" shows (NEG-1). — UI States — • Loading/Empty/Error/Success per §7.1. |
| [MTCK-S02] — Pre-fill the customer in the native form As a mobile CS agent, I want the contact pre-filled in the new ticket, so I don't re-enter it. | Must Have | Figma — create form with Customer pre-filled | Before-After: Before — create opened standalone with no customer. After — pass the contact so the customer crm-property is pre-filled. C360: qontak_customer_ids = id360; CRM: numeric id (mirrors associateTicket() payload). | — Happy Path — • AC-1: Given a C360 contact, when the create form opens from it, then the Customer field is pre-filled from id360 (editable).• AC-2: Given a CRM contact, then it pre-fills from the numeric contact id. — Error / Unhappy Path — • ERR-1: Given the pre-fill can't resolve, then the form opens with the Customer empty/editable; submit still allowed. |
| [MTCK-S03] — Auto-associate the created ticket on return As a mobile CS agent, I want the new ticket linked to the contact automatically, so it shows under the contact. | Must Have | Figma — success state + refreshed association summary | Before-After: Before — n/a. After — on CreateTicketScreen pop with a non-null ticket, call associateTicket() → PUT /v2.8/tickets/{ticketId} with qontak_customer_ids = contactId via Contact360AssociationBloc (contact360_association_bloc.dart:142); refresh summary (other_tab.dart:1416 pattern). | — Happy Path — • AC-1: Given the create screen pops with a created ticket, when CDP-mobile receives it, then it calls associateTicket() and the contact's ticket count + list update.• AC-2: Given association succeeds, then a success toast shows and the new ticket appears under the contact. — Error / Unhappy Path — • ERR-1: Given the associate call fails, then cdp_mobile_ticket_associate_failed is logged, an inline retry is offered, and the ticket still exists in CRM (not lost).• ERR-2: Given the user cancels/creation fails, then the screen returns with nothing and no association is made. |
| [MTCK-S04] — Pipeline + layout inherited from the native module As a mobile CS agent, I want correct pipeline + fields in the new ticket. | Should Have | Figma — pipeline/layout (native) | Before-After: Inherited from the Mobile Ticket Module — TicketPipelineBloc (pipeline) + GetTicketCRMProperties(layoutId, pipelineId) + ticketLayoutEnabled. No CDP-mobile logic. | — Happy Path — • AC-1: Given one pipeline, then the form uses the default pipeline's layout. • AC-2: Given multiple pipelines, then pipeline selection shows first (native). (Owned by Mobile Ticket Module.) |
| [MTCK-S05-NEG] — No create without access (Guard Rail) As the system, block create-from-contact when the user lacks ticket access. | Guard Rail | — | — | • NEG-1: Given the ticket feature flag is off OR permission.update is false OR isTSO, then "Create New" is not shown — only "From Existing" remains.• NEG-2: Given no ticket view access at all, then the Tickets entry follows existing hiding behavior. |
Test Coverage Matrix — [MTCK-S03]
| Dimension | Coverage | Notes |
|---|---|---|
| Boundary values | ⚠️ partial | ⚠️ QA: create succeeds but app backgrounded before associate returns |
| State transitions | ✅ defined | created → associate → refresh; cancel → no-op |
| Data validation | ✅ defined | correct identity per mode (C360 id360 vs CRM numeric) |
| Concurrency | ⚠️ TBD | ⚠️ QA: rapid double-return / duplicate associate |
| Network/timeout | ✅ defined | ERR-1 associate failure + retry |
10. Rollout
| Field | Detail |
|---|---|
| Flag | Reuse the mobile ticket feature flag (isTicketEnabled); optionally a CDP-mobile "create from contact" sub-flag for staged rollout. |
| Stage 1 — Internal QA | Enable on a QA build; verify bottom sheet, pre-fill (C360 + CRM), auto-associate, refresh, degradation. |
| Stage 2 — Closed Beta | A few companies; monitor create + associate success. |
| Stage 3 — GA | Progressive enable. |
| Backward compat | Associate-existing unchanged; Create New additive + gated. |
| Dependency gate | Requires the Mobile Ticket Module native create (TF-3053) shipped — not blocked by the CRM embed (that blocks only the web track). |
11. Observability
| Event Name | Trigger | Properties |
|---|---|---|
cdp_mobile_ticket_create_opened | "Create New" tapped from a contact | company_sso_id, contact_id, is_c360, user_id |
cdp_mobile_ticket_created | Native create returns a ticket | contact_id, ticket_id, pipeline_id |
cdp_mobile_ticket_associated | Auto-associate succeeds | contact_id, ticket_id |
cdp_mobile_ticket_associate_failed | Auto-associate fails on return | contact_id, ticket_id, reason |
Dashboard owner: CDP Squad (Mixpanel — the Mobile Ticket Module PRD already calls for a create-ticket tracker). Alert: associate-failed > 5% → investigate.
12. Success Metrics
| Metric | Definition | Baseline | Target |
|---|---|---|---|
| ⭐ Create-from-contact adoption (mobile) | % of mobile tickets on CDP contacts created via this flow within 30 days of GA | 0 (net-new) | ≥ X% (set at beta) |
| ⭐ Auto-association success | associated / created-from-contact | N/A | ≥ 99% |
| Parity | Create-from-contact available wherever associate-existing is, on mobile | Associate-only today | 100% of mobile contact-detail surfaces |
13. Dependencies
| Dependency | Owning Team | Deliverable Needed | Blocking? |
|---|---|---|---|
| Native ticket create screen | Mobile Ticket Module (TF-3053) | CreateTicketScreen returning the created ticket on pop (shipped) | YES (already met) |
| Associate API | CRM Backend | PUT /v2.8/tickets/{id} with qontak_customer_ids (exists) | YES (already met) |
| Shared association bottom sheet | Mobile/CRM | AssociationBottomSheet "From Existing/Create New" util (exists for other modules) | YES (already met) |
| CDP-mobile integration | CDP × Mobile Squad | Wire entry point + pre-fill + auto-associate + refresh in other_tab.dart | YES (this PRD's scope) |
| Customer pre-fill into native form | Mobile Ticket Module | Accept a pre-filled customer crm-property argument on CreateTicketScreen | Confirm — small extension (OQ-M2) |
14. Key Decisions + Alternatives Rejected
14a — Decisions Made
| ID | Decision | Rationale (grounded) |
|---|---|---|
| MD-1 | Reuse the native CreateTicketScreen, not a webview/embed. | Native screen already exists; mirrors AD-3 in the anchor. Best mobile UX/perf. |
| MD-2 | Extend the existing "From Existing / Create New" bottom-sheet pattern to tickets. | The util (openAssociationBottomSheet) is already used for companies/deals/products; tickets are the missing module. |
| MD-3 | Auto-associate on return via the existing associateTicket() (PUT /v2.8/tickets/{id}, qontak_customer_ids). | Reuses the proven association path + identity handling (C360 id360 vs CRM numeric). |
| MD-4 | Reuse mobile's native gating (feature flag + permission.update). | Mobile doesn't use the granular web permission keys; consistency with the rest of the app. |
| MD-5 | Keep the native data_source: "Mobile Flutter"; no embed_source on the native path. | Mobile creates via the native CreateTicketScreen + native associate flow (not the CRM web embed), so the web embed_source allow-list mechanism does not apply. CDP-origin labeling is a web-track concern only. |
14b — Alternatives Rejected
| Alternative | Why Rejected |
|---|---|
| Embed the CRM web ticket form in a mobile webview | Regresses UX/perf; duplicates the web approach where a native one already exists. |
| A new bespoke create-from-contact flow | The shared bottom-sheet + native create + associate already exist; bespoke = needless duplication. |
| Fold this into the Mobile Ticket Module PRD | Keeps CDP-contribution ownership separate from the native-form ownership (anchor AD-5); this is the SUPPORT side of TF-3053 AC1.4. |
15. Open Questions
| # | Type | Question | Mitigation / Default | Owner | Deadline |
|---|---|---|---|---|---|
| OQ-M2 | Open Question | Does CreateTicketScreen already accept a pre-filled customer argument, or is a small extension needed (MTCK-S02)? | Confirm with Mobile Ticket Module; if missing, add an optional pre-fill arg to CreateTicketArgument. | Mobile Squad | 2026-07-04 |
| OQ-M3 | Open Question | Is auto-association best done by the existing associateTicket() (PUT), or should the created ticket carry the contact in crm_properties at create time (single call)? | Default: associate-on-return (proven). Single-call is a possible optimization later. | Mobile + CRM BE | 2026-07-11 |
| OQ-M4 | Open Question | C360 vs CRM contact: confirm the correct identity + bloc path for each on auto-associate (Contact360AssociationBloc for C360). | Default: branch by mode as the existing associate flow already does. | Mobile Squad | 2026-07-04 |
PRD CHANGELOG
| Version | Date | By | Section | Type | Summary |
|---|---|---|---|---|---|
| 1.1 | 2026-06-26 | Decision settled | 14a, 15, Scope Changes | UPDATE | Settled OQ-M1 as decision MD-5: mobile keeps native data_source: "Mobile Flutter"; no embed_source on the native path (that mechanism is web-embed only). Removed OQ-M1; Backend scope is now firmly None. |
| 1.0 | 2026-06-26 | Initial draft | All | CREATED | Created the mobile SUPPORT PRD for the cross-platform anchor. Background: CDP mobile is associate-existing only. Grounded against mobile-qontak-crm (contact "Others" tab other_tab.dart, native CreateTicketScreen + POST /v2.8/tickets, associate PUT /v2.8/tickets/{id} with qontak_customer_ids, the existing company/deal/product "From Existing/Create New" AssociationBottomSheet, and feature-flag + permission.update gating). Approach = native create + auto-associate (not webview). Stories MTCK-S01–S05-NEG with Gherkin ACs; cross-links the Mobile Ticket Module (TF-3053) AC1.4. NOT blocked by the CRM embed (that blocks only the web track). |