# 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`.
