100 lines
5.7 KiB
Markdown
100 lines
5.7 KiB
Markdown
# partner-center — nursing companies
|
|
|
|
> Client seam `client/src/services/partnerCenter/` · `USE_PARTNER_MOCK = true` (**mock is primary**) · 9 server ops
|
|
> Last verified: 2026-07-30 against commit `d3ec723` and swagger.v1.json (2026-07-29).
|
|
|
|
Nursing companies («مراکز») that sponsor nurses onto the platform. The domain with the **widest gap between
|
|
what the client wants and what the server serves** — 6 of its client calls are phantom.
|
|
|
|
## Endpoints
|
|
|
|
| Method | Path | Verdict |
|
|
| --- | --- | --- |
|
|
| GET | `/api/v1/admin/partner-centers` | wired · paginated |
|
|
| POST | `/api/v1/admin/partner-centers` | wired |
|
|
| GET | `/api/v1/admin/partner-centers/{id}` | wired |
|
|
| PATCH | `/api/v1/admin/partner-centers/{id}` | wired |
|
|
| POST | `/api/v1/admin/partner-centers/{id}/set-active` | wired — REQ-032's delivered half |
|
|
| POST | `/api/v1/admin/partner-centers/{id}/sponsor-nurse` | wired |
|
|
| POST | `/api/v1/admin/partner-centers/{id}/verify` | wired |
|
|
| GET | `/api/v1/centers/{id}/dashboard` | **unwired** — the client wants `centers/me/*` splits instead |
|
|
| GET | `/api/v1/internal/bookings/{bookingId}/center` | **unwired** — the MoR resolver, server-internal |
|
|
|
|
The seven `admin/partner-centers` routes are `DynamicPermission` + `sensitive`; `centers` is `[Authorize]`;
|
|
`internal/bookings` is `DynamicPermission`.
|
|
|
|
This domain owns **three of the four route-shape exceptions** in the API: hyphens (`partner-centers`,
|
|
`set-active`, `sponsor-nurse`), a nested `admin/` segment, and an `internal/` namespace. It also has one of
|
|
only two `PATCH` verbs. See [index.md](index.md#route-shape-exceptions).
|
|
|
|
### Phantom — 6
|
|
|
|
| Client call | REQ | Note |
|
|
| --- | --- | --- |
|
|
| `GET /api/v1/centers/me` | REQ-032 | The portal's own-center read |
|
|
| `GET /api/v1/centers/me/nurses` | REQ-032 | Split read — the server serves one aggregate instead |
|
|
| `GET /api/v1/centers/me/bookings` | REQ-032 | Split read |
|
|
| `GET /api/v1/centers/me/bookings/{id}` | REQ-064 | Partner-scoped booking detail |
|
|
| `GET /api/v1/centers/me/settlement` | REQ-033 | Per-booking commission invoices |
|
|
| `GET /api/v1/admin/partner-centers/{id}/nurses` | REQ-032 | The admin-side sponsored-nurse list |
|
|
|
|
**The shape mismatch is the point.** The server serves **one aggregate**, `GET centers/{id}/dashboard` →
|
|
`CenterDashboardDto` with `sponsoredNurses` inline. The portal wants **`/me` plus paginated splits** — it
|
|
cannot page an inline array, and it does not know its own center id without REQ-038. Until REQ-032 lands,
|
|
the portal is mock-only.
|
|
|
|
## Merchant of record
|
|
|
|
The one business rule that changes where money goes:
|
|
|
|
- **`isMerchantOfRecord = true`** → the *center* is the seller. It invoices the customer, holds the
|
|
commercial relationship, and Balinyaar's cut is a commission **against the center**.
|
|
- **`isMerchantOfRecord = false`** → the nurse is the seller and the center is a sponsor only.
|
|
|
|
`GET internal/bookings/{bookingId}/center` is the **MoR resolver** the invoice pipeline calls to decide
|
|
which entity issues the invoice — which is why `InvoiceDto.issuingEntityType` exists. See
|
|
[payment.md](payment.md). `commissionRate` on the center is a **per-center override** of the platform
|
|
default, snapshotted at compute time like every other rate.
|
|
|
|
## Shape rules the JSON does not express
|
|
|
|
- **The settlement IBAN is write-then-masked.** `settlementIbanMasked` is the only form on every read model;
|
|
the full value never comes back after the write that set it. Same pattern as
|
|
[nurse.md](nurse.md).
|
|
- **`verify` is a licence check behind a seam.** `ILicenseVerificationService` checks the eNamad code and the
|
|
MoH establishment permit; by default it returns `NeedsManualReview`, so `verify` records a **human admin
|
|
decision**. `Seams:LicenseVerification:AutoApprove` makes the mock return `Valid` to test the
|
|
auto-approve path. A real eNamad/MoH registry adapter ignores the knob.
|
|
- **`technicalDirectorNurseUserId` links to a real verified nurse**, not a free-text name — Iranian
|
|
regulation requires a named technical director («مدیر فنی») with a valid licence.
|
|
- **`set-active` is suspend/activate, not delete.** A suspended center's sponsored nurses and past bookings
|
|
stay resolvable.
|
|
- `sponsoredNurseCount` is denormalised onto both the list item and the detail so the queue needs no
|
|
per-row count.
|
|
- `legalEntityType` and `mohEstablishmentPermitNo` are the regulatory identity; `enamadCode` is the
|
|
e-commerce trust seal. All three are distinct and none substitutes for another.
|
|
|
|
## Enums
|
|
|
|
| Vocabulary | Values |
|
|
| --- | --- |
|
|
| `CenterOnboardingState` | `draft` `pending_verification` `verified` `suspended` |
|
|
| `MoadianStatus` *(on the center's invoices)* | `pending` `submitted` `registered` `failed` |
|
|
|
|
`MoadianStatus` is shared with [payment.md](payment.md) — it is the سامانه مودیان submission state, and it
|
|
is the same vocabulary on both sides.
|
|
|
|
> `CenterOnboardingState` is the client's model of the center's position in onboarding. On the wire the
|
|
> server carries the **facts** it is derived from — `isActive` and `verifiedAt` on both
|
|
> `PartnerCenterListItemDto` and `PartnerCenterDetailDto` — not the state string itself. Derive, do not
|
|
> expect a field.
|
|
|
|
## Open REQs
|
|
|
|
| REQ | Status | Effect |
|
|
| --- | --- | --- |
|
|
| REQ-032 | partially delivered | `set-active` landed. The `/me` split reads and the IBAN write-then-masked flow are deferred → 5 phantom routes. **The main reason this seam is mocked** |
|
|
| REQ-033 | partially delivered | `totalIrr` is on `InvoiceDto`; the per-booking commission invoice list is deferred → 1 phantom |
|
|
| REQ-064 | open | No partner-scoped booking detail → 1 phantom |
|
|
| REQ-038 | open | `/me` carries no signal that the caller administers a center, so the portal cannot auto-route or discover its own center id. See [auth.md](auth.md) |
|