Shipping Module Overview
Flat per-district delivery fees — admin coverage, bulk pricing, and the customer quote.
Audience: Product owners, QA, frontend and backend developers Scope: Delivery pricing and serviceability per district
Shipping Module - Overview
1. What the module is
Shipping prices one flat delivery fee per district. An admin configures each of Nepal's 77 districts with a fee and an active flag; a customer quotes a district to learn the fee and whether delivery is available there.
The module deliberately knows nothing about the address table: the quote takes a districtId, which the client reads off an address's location.district.id. Shipping does not need to — and must not — read customer_address.
2. The two surfaces
| Surface | Routes | Auth |
|---|---|---|
| Admin coverage & rates | /api/admin/shipping/* (5 routes) | JWT + Shipping_* permission |
| Customer quote | GET /api/mobile/shipping/quote?districtId= | JWT |
3. The five rules that govern everything
feeis minor units. NPR 120.00 is12000. Never a decimal.0is free delivery, not a missing value.- Unserviceable is a 200, not a 404.
{ "serviceable": false, "fee": null }is a successful answer — "we do not deliver here" is information, not an error. Only adistrictIdthat names nothing is a 404. Branch onavailability.serviceable, never on the status code. - Pausing keeps the fee; deleting discards it.
PUT … { "isActive": false }stops delivery while keeping the price for a one-toggle re-enable.DELETEremoves the configuration entirely; the district returns tounconfigured. - The rate is an upsert, keyed on the district.
district_idis unique, so there is no separate create — callingPUTtwice just replaces. The admin UI needs no create-vs-edit branch. - Bulk is all-or-nothing, and a status-only bulk cannot create a configuration (there is no fee to insert). Skipped districts are named in the response — show that list.
4. The three availability flags
The admin coverage list returns all 77 districts whether configured or not:
configured | active | serviceable | Means | Admin's next action |
|---|---|---|---|---|
false | false | false | Never priced | Set a fee |
true | false | false | Paused; the fee is kept | Re-enable — one toggle |
true | true | true | Delivering | — |
Both unserved states look identical to a customer. Only the admin screen distinguishes them.
5. How permissions get granted
Permissions have one source of truth: packages/db/src/authorization/permission-catalog.ts. Both the API's compile-time PermissionCode union and the seed's role grants derive from it, and a spec fails the build if they diverge.
Adding a module means adding its module name to that catalog, then running permissions:sync and db:seed — nothing is granted manually through a screen. The seed grants every module's permissions to admin except System_*; superadmin bypasses the check entirely.
Page guide
| Page | Read it for |
|---|---|
| Features and flows | Actor journeys, bulk semantics, edge cases |
| Backend | Architecture, schema, serviceability, cache |
| API | All six endpoints, DTOs and error codes |