# Acceptance Criteria - Bounded CRM Core

## Summary
These criteria define the observable done condition for the bounded CRM core described in `docs/prd.md`. They assume a deterministic TypeScript module with in-memory application services and no persistence.

## Assumptions
- IDs are generated by the application service layer.
- Date comparisons use a caller-supplied `asOfDate`.
- Money is represented as integer cents.

## Constraints
- The scope is limited to contacts, companies, opportunities, tasks, notes, and dashboard summaries.
- No criterion depends on HTTP, UI, database, authentication, or external integrations.
- Closed opportunities cannot be reopened.

## Structured outputs

### Companies
1. Given a non-empty `name`, when the service creates a company, then it returns a company with a stable `companyId` and the provided `name`.
2. Given an empty or whitespace-only `name`, when the service creates a company, then it fails with a validation error.
3. Given an optional `domain`, when the service creates a company, then the returned record stores that `domain`.
4. Given an existing `companyId`, when the service fetches that company, then it returns the stored record.
5. Given multiple created companies, when the service lists companies, then it returns all stored company records.

### Contacts
6. Given a non-empty `fullName`, when the service creates a contact, then it returns a contact with a stable `contactId` and the provided `fullName`.
7. Given an empty or whitespace-only `fullName`, when the service creates a contact, then it fails with a validation error.
8. Given optional `email` and `phone` values, when the service creates a contact, then the returned record stores those values.
9. Given an existing company, when the service creates a contact with that `companyId`, then the returned contact is linked to that company.
10. Given an unknown `companyId`, when the service creates a contact, then it fails with a not-found or link-validation error.
11. Given an existing `contactId`, when the service fetches that contact, then it returns the stored record.
12. Given multiple created contacts, when the service lists contacts, then it returns all stored contact records.

### Opportunities
13. Given a non-empty `title` and a valid non-negative `amountCents`, when the service creates an opportunity, then it returns an opportunity with a stable `opportunityId`.
14. Given an empty or whitespace-only `title`, when the service creates an opportunity, then it fails with a validation error.
15. Given no explicit stage, when the service creates an opportunity, then the stored stage is `Prospecting`.
16. Given `amountCents < 0`, when the service creates an opportunity, then it fails with a validation error.
17. Given an existing company, when the service creates an opportunity with that `companyId`, then the returned opportunity is linked to that company.
18. Given an existing contact, when the service creates an opportunity with that `primaryContactId`, then the returned opportunity is linked to that contact.
19. Given an unknown `companyId` or `primaryContactId`, when the service creates an opportunity, then it fails with a not-found or link-validation error.
20. Given an existing `opportunityId`, when the service fetches that opportunity, then it returns the stored record.
21. Given multiple created opportunities, when the service lists opportunities, then it returns all stored opportunity records.

### Opportunity stage transitions
22. Given an opportunity in `Prospecting`, when the service moves it to `Qualified`, then the stored stage becomes `Qualified`.
23. Given an opportunity in `Qualified`, when the service moves it to `Proposal`, then the stored stage becomes `Proposal`.
24. Given an opportunity in `Proposal`, when the service moves it to `Negotiation`, then the stored stage becomes `Negotiation`.
25. Given an opportunity in `Negotiation`, when the service moves it to `ClosedWon`, then the stored stage becomes `ClosedWon`.
26. Given an opportunity in `Prospecting`, `Qualified`, `Proposal`, or `Negotiation`, when the service moves it to `ClosedLost`, then the stored stage becomes `ClosedLost`.
27. Given an opportunity in any stage, when the service attempts a transition not listed in the PRD, then it fails with an invalid-transition error.
28. Given an opportunity in `ClosedWon`, when the service attempts any further transition, then it fails with an invalid-transition error.
29. Given an opportunity in `ClosedLost`, when the service attempts any further transition, then it fails with an invalid-transition error.
30. Given an unknown `opportunityId`, when the service moves an opportunity stage, then it fails with a not-found error.

### Tasks
31. Given a non-empty `subject` and exactly one valid parent target, when the service adds a task, then it returns a task with a stable `taskId` and `status = Open`.
32. Given an empty or whitespace-only `subject`, when the service adds a task, then it fails with a validation error.
33. Given neither parent target or both parent targets, when the service adds a task, then it fails with a validation error.
34. Given an unknown contact or opportunity target id, when the service adds a task, then it fails with a not-found or link-validation error.
35. Given an existing open task, when the service marks it done, then the stored task has `status = Done`.
36. Given an unknown `taskId`, when the service marks a task done, then it fails with a not-found error.
37. Given multiple created tasks, when the service lists all tasks, then it returns all stored task records.
38. Given tasks attached to multiple parents, when the service lists tasks for one contact or one opportunity, then it returns only tasks attached to that target.

### Notes
39. Given a non-empty `body` and exactly one valid parent target, when the service adds a note, then it returns a note with a stable `noteId`.
40. Given an empty or whitespace-only `body`, when the service adds a note, then it fails with a validation error.
41. Given neither parent target or both parent targets, when the service adds a note, then it fails with a validation error.
42. Given an unknown contact or opportunity target id, when the service adds a note, then it fails with a not-found or link-validation error.
43. Given multiple created notes, when the service lists all notes, then it returns all stored note records.
44. Given notes attached to multiple parents, when the service lists notes for one contact or one opportunity, then it returns only notes attached to that target.

### Dashboard summaries
45. Given current in-memory state, when the service computes a dashboard summary, then the summary includes totals for contacts, companies, opportunities, tasks, and notes.
46. Given opportunities across multiple stages, when the service computes a dashboard summary, then `opportunitiesByStage` contains a count for every defined stage.
47. Given open and closed opportunities with different `amountCents` values, when the service computes a dashboard summary, then `openPipelineAmountCents` equals the sum of opportunities not in `ClosedWon` or `ClosedLost`.
48. Given open tasks with due dates before, on, and after `asOfDate`, when the service computes a dashboard summary, then `openTasksDueCount` includes only open tasks with `dueDate <= asOfDate`.
49. Given tasks without a `dueDate`, when the service computes a dashboard summary, then those tasks are excluded from `openTasksDueCount`.

## Negative criteria
- The module must not read the system clock.
- The module must not require a database, API server, or UI to satisfy any acceptance criterion.
- The module must not allow a task or note to target both a contact and an opportunity at the same time.
- The module must not allow an opportunity to skip to a terminal stage from an unapproved intermediate state.

## Ready-for-test checklist
- Feature behaviors are observable through public service operations.
- Validation failures are distinguishable in tests.
- All date-sensitive behavior accepts explicit input.
- All stage transition rules are testable without hidden state.
- Dashboard summary outputs are derived only from current in-memory records.

## Open questions
- None for this bounded scope.

## Recommended next skill
- `requirements-traceability-starter`
