# Services — SlpSoftware Production API ## `IOfferingsService` **Layer**: Service (business orchestration), between `OfferingsController` and `IOfferingRepository`. **Why this service exists** (per decision Q2 = A, mirroring `ICmsInstanceService` in `Modules.Master`): the repository alone can't own the rules that span more than a single row — the "at most one featured offering" exclusivity check (US-10) reads and writes two rows in one logical operation, and both reorder interactions (US-08 full reorder, US-09 adjacent swap) recompute `DisplayOrder` across multiple rows. A controller calling the repository directly would either duplicate this orchestration or risk skipping it. **Orchestration patterns**: 1. **Create/Update with featured exclusivity** (US-10): when a request sets `Featured = true`, the service first calls `GetFeaturedAsync()`; if a different offering currently holds it, that offering is un-featured (via `UpdateAsync` on it) in the same logical operation before the requested offering is saved as featured. Exact transactional boundaries (single DB transaction vs. sequential saves) are a Functional Design decision for the Offerings unit, not decided here. 2. **Full reorder** (US-08): `ReorderAsync` receives the complete ordered list of IDs from the drag-and-drop UI and reassigns `DisplayOrder` sequentially (0, 1, 2, ...) to match. 3. **Adjacent swap** (US-09): `MoveUpAsync`/`MoveDownAsync` locate the neighboring offering by `DisplayOrder` and swap the two `DisplayOrder` values. A no-op (or a clearly-defined error) at the boundaries (first item moving up, last item moving down) — exact behavior for an out-of-bounds call is a Functional Design detail. 4. **Soft delete** (US-06/US-07, Q5 = B): `DeleteAsync` sets `IsDeleted = true` / `DeletedAt = now` rather than removing the row. The repository's read methods (`GetAllAsync`, `GetByIdAsync`, `GetFeaturedAsync`) always exclude soft-deleted rows, so callers never need to remember to filter — deleting the last remaining offering (US-07) is unaffected by this and remains always allowed. 5. **Public vs. admin projections**: `GetPublicOfferingsAsync` returns `OfferingDto` (the exact FR-6 contract shape: `id`, `title`, `description`, `price`, `priceNote`, `features`, `ctaLabel`, `featured`). `GetAllForAdminAsync` returns `OfferingAdminDto`, which additionally exposes `DisplayOrder` (and, if useful in the admin UI, `IsDeleted`/`DeletedAt` are **not** exposed since deleted rows are never returned to any caller). **No other services are introduced by this feature.** `CmsHost` (components.md #1) is a static composition helper, not a service in the DI/business-orchestration sense — it has no business rules, only infrastructure wiring, so it is documented under Components/Component Methods rather than here.