# sales_service Risk Points ## Step 4 Event Outbox Status Current state after Step 4: - `sales_service` publishes domain events to the shared `event_outbox`. - Publisher: `services/sales_service/event_publisher.py` (`SalesEventPublisher`). - Registry: `services/sales_service/sales_events.py`. - Events are appended before `session.commit()`, in the same transaction as the business change. - `tenant_id` is included in event envelope payload for every sales event. Published MVP events: - `lead.entered_crm` - `lead.enrichment_completed` - `deal.stage_changed` - `deal.scenario_selected` - `deal.next_action_scheduled` - `deal.closed` - `deal.lost` - `deal.won` - `communication.started` - `communication.summary_created` - `message.received` - `message.sent` - `call.received` - `call.completed` - `offer.created` - `offer.sent` - `offer.accepted` - `offer.rejected` - `deal.conditions_confirmed` - `counterparty.completed` - `document.created` - `document.sent` - `document.confirmed` - `document.signed` - `invoice.created` - `invoice.sent` - `invoice.overdue` - `payment.received` - `invoice.paid` Remaining risks: - No sales-specific consumers/handlers yet; outbox rows are ready for the shared dispatcher, but sales automation/analytics/post-sale reactions are future steps. - The shared `event_outbox` schema has no dedicated `tenant_id`, `actor_type`, `actor_id`, or `causation_id` columns. These values are stored inside `payload_json.payload`. - Event payload schemas are registry constants plus tests, not yet formal JSON Schema files. ## DEFAULT_TENANT_ID Current state after Step 2: - Production sales API flow no longer uses `DEFAULT_TENANT_ID`. - `_tenant_id(actor)` requires tenant context from JWT/legacy dev header. - Webhooks resolve tenant from auth/header or `tenant_integrations` provider mapping. - Sales reads/writes are tenant-scoped. Remaining risk: - Tenant entity/settings are MVP-level, not full subscription/billing model. - Webhook provider signature verification is still separate from tenant resolution. ## stage_id Changes Current state after Step 3: - `sales_pipelines` and `sales_pipeline_stages` exist. - `Deal.pipeline_id` stores real `pip_*` public id. - `Deal.stage_id` stores real `pst_*` public id. - `STAGE_LABELS` is no longer the source of truth. - `_apply_stage(...)` resolves stable stage code to tenant/pipeline stage id and writes `SalesStageHistoryRow`. - Step 4 also publishes `deal.stage_changed` to `event_outbox`. Initial stage writes: - `_create_lead_and_deal_for_contact` resolves default stage code `new_qualified_lead` to real `pst_*` id and records history. - `_resolve_deal_for_inbound` creates inbound lead/deal, resolves real stage id and records history. - `create_lead` creates a lead and auto-deal, then records history with reason `lead.entered_crm`. - `create_deal` resolves stage code/id input to real `pst_*` id and records history with reason `deal.created`. Endpoint-driven stage changes: - `POST /api/v1/leads/{lead_id}/enrich` - `POST /api/v1/deals/{deal_id}/change-stage` - `POST /api/v1/deals/{deal_id}/schedule-next-action` - `POST /api/v1/deals/{deal_id}/close` - `POST /api/v1/deals/{deal_id}/escalate` - `POST /api/v1/deals/{deal_id}/communications/text` - `POST /api/v1/deals/{deal_id}/communications/voice` - `POST /api/v1/communications/{communication_id}/switch-channel` - `POST /api/v1/communications/{communication_id}/bind-external` - Offer actions: create/send/accept/reject. - Conditions upsert. - Counterparty create/patch. - Document create/send/confirm. - Invoice create/send/mark-overdue. - Payment webhook success/partial. Risk: - No transition validation/state machine yet. - Stage customization is API/MVP-level; no UI/admin migration workflow for moving active deals between stages. ## AutomationTask Creation Central helper: - `_schedule_task(session, deal, task_type, run_at, payload)` creates `SalesAutomationTaskRow`. Creation points: | Code area | task_type | |---|---| | `_start_communication` | `payload.next_action_type` | | `POST /api/v1/deals/{deal_id}/schedule-next-action` | `payload.next_action_type` | | `POST /api/v1/communications/{communication_id}/summary` | `payload.next_action_type` | | `POST /api/v1/communications/{communication_id}/switch-channel` | `switch_to_{to_channel}` | | `POST /api/v1/invoices/{invoice_id}/send` | `invoice_follow_up` | Task fields: - `status` defaults to `pending`. - `retry_count` defaults to `0`. - `last_error` exists. - Index exists on `(run_at, status)`. Risk: - No worker/dispatcher found for pending tasks. - No API found to list/update/execute/cancel/retry automation tasks. - No overdue task handling found. - Status values allow `running`, `completed`, `failed`, `canceled`, but code only creates `pending`. ## Payment Webhook Endpoint: - `POST /api/v1/payments/webhook` Input: - Required `deal_id`. - Optional `invoice_id`. - Optional `external_payment_id`. - `payment_provider`, `amount`, `currency`, `status`, `paid_at`, `payment_method`, `failure_reason`, `metadata`. Current flow: 1. Load deal by `payload.deal_id`. 2. If `invoice_id` is present, load invoice by id. 3. Create or update `SalesPaymentRow` idempotently by `(tenant_id, payment_provider, external_payment_id)` when external id is provided. 4. If invoice exists and status is `success`: - invoice -> `paid` - invoice.paid_at set - deal.status -> `won` - deal.closed_at set - deal.final_amount set to payment amount - stage -> `won` - ensure CRM customer 5. If invoice exists and status is `partial`: - invoice -> `partially_paid` - stage -> `partially_paid` 6. If invoice exists and status is `failed`: - only invoice.updated_at changes. Risk: - No provider signature verification. - Payment idempotency depends on provider sending `external_payment_id`. - Payment totals are not reconciled against invoice amount before marking paid. - Endpoint still lacks provider-specific webhook signature authentication. ## Notes Current state: - `SalesNoteRow` and `sales_notes` exist. - No `SalesNoteIn/Out` schemas found. - No notes API routes found. - `SalesWorkspaceOut` does not include notes. Risk: - Notes are table-only, not product-ready. - System/user notes cannot be created or retrieved through current sales API. ## Escalation Current state: - `POST /api/v1/deals/{deal_id}/escalate` creates `SalesEscalationRow`. - It sets `deal.assigned_human_user_id`. - It sets `deal.scenario_type = "custom_human_escalation"`. - It moves stage to `transferred_to_support`. Risk: - No resolve/reassign API found. - No integration with queue/routing SLA in sales-service. - No status transition beyond initial `open`.