Shipping Features and Flows
Developer Resources Shipping Shipping Features and Flows Complete feature list, actor journeys, state flows, business rules, edge cases, and diagrams for the Shipping module.
Use this page for the shipping domain: what it does for admins and customers, and how each flow behaves from start to finish.
Source Type Files or Docs What Was Extracted API apps/api/src/modules/shipping/admin/shipping-admin.controller.ts, shipping/customer/shipping-quote-customer.controller.tsRoutes, permissions, rate limits Backend shipping-admin.service.ts, shipping-admin-bulk.service.ts, shipping-quote-customer.service.ts, shipping-serviceability.service.tsUpsert semantics, bulk transaction, availability flags Schema packages/db/src/schema/shipping/shipping-rate.tsdistrict_id unique, fee bigint minor unitsCache cache-invalidation.tags.tsshipping domainError registry apps/api/src/common/types/error-codes.ts (// SHIPPING)SHIPPING_* codes
Field Value Module shippingSubmodule N/A Primary user value A delivery fee and serviceability answer for every district, configurable in bulk Actors Admin, customer (signed in), worker/system Main entry points /api/admin/shipping/* (5 routes), GET /api/mobile/shipping/quoteMain outputs Coverage list, rate responses, bulk report, quotes Related docs API , Backend
Actor Can Do Cannot Do Auth Requirement Notes Admin View coverage, get/set/delete a district rate, bulk price Operate without the role grants the seed provides Admin JWT + Shipping_READ/Shipping_UPDATE/Shipping_DELETE — granted by the seed from the shared permission catalog Writes record activity Customer (signed in) Quote a district See admin coverage, quote without login JWT CUSTOMER_READ 60/min keyed on the accountWorker/system — — — No worker surface
Capability Surface Actor Route/Trigger State Read State Written Linked API Section Coverage list Admin Admin GET /api/admin/shipping/districtsAll 77 districts + rates — API §4 Get one rate Admin Admin GET /districts/:districtId/rateOne district + rate — API Upsert rate Admin Admin PUT /districts/:districtId/rateDistrict shipping_rateAPI Discard rate Admin Admin DELETE /districts/:districtId/rateDistrict + rate rate row removed API Bulk price Admin Admin POST /rates/bulkDistricts by scope Many rate rows API Quote Customer Customer GET /api/mobile/shipping/quote?districtId=District + rate — API
At checkout, the client reads location.district.id off the selected address and quotes it. The answer is a fee (or "not serviceable") — never a 404 for an unserved place.
Branch Condition Behavior Error/Result Active rate configured && active200 with fee serviceable: truePaused or unconfigured no active rate 200 with fee: nullserviceable: false — branch on this, not statusUnknown district uuid names nothing 404 SHIPPING_DISTRICT_NOT_FOUNDFree delivery fee = 0200 with fee 0 serviceable: true
The admin coverage screen lists all 77 districts; each unconfigured one has "set fee". Saving is an upsert — no create-vs-edit branch.
Branch Condition Behavior Error/Result Pause isActive: falseFee kept configured: true, active: falseNegative fee fee < 0400 SHIPPING_FEE_NEGATIVEMissing permission Role lacks Shipping_UPDATE 403 Add to the permission catalog + re-seed Repeat call Same district twice Second replaces first Upsert, never 409
Scopes union and de-duplicate: explicit districts, whole provinces, or all of Nepal. One transaction — an unknown id rejects the entire request. Omitting fee makes it status-only, which cannot create a configuration; those districts are skipped and named.
Covered in §5.2/§5.3. All admin writes record activity and invalidate the shipping cache domain. There is deliberately no bulk delete — use bulk isActive: false to stop delivering while keeping every configured fee.
From Event/Action To Guard Condition Side Effects unconfigured PUT with feeconfigured (active per isActive) fee >= 0 Cache invalidation configured PUT { isActive: false }paused — Fee kept paused PUT { isActive: true }active — One-toggle re-enable configured DELETEunconfigured — Fee discarded; coverage list reflects it any bulk status-only any districts without a fee are skipped, named Report
Flow DB Writes Cache Effects Jobs Realtime Analytics Notifications Upsert rate shipping_rateshipping domain — — — — Delete rate rate row removed shipping domain — — — — Bulk many rate rows (one tx) shipping domain — — — — Quote — read (serviceability cache) — — — —
Scenario Trigger User/System Experience Recovery Source Bulk partial failure impossible Unknown id Entire request rejected, nothing written Fix the scope one transaction Permission missing Role lacks Shipping_* 403 Add to the shared permission catalog + re-seed RoleGuard Rate limit Bulk 5/min 429 Build UI to submit once ADMIN_BULK_WRITECache stale Admin edit Next read fresh Invalidation after commit shipping domain
Actor capability diagram — §3/§4.
Sequence diagram per major flow — §5.1/§5.2.
Activity diagram — §5.3.
State machine diagram — §7.1.
Data side-effect diagram — §9.
Error branch diagram — §5.3/§10.
Feature Minor Behavior Actor Trigger User/System Result Backend Side Effect Source Coverage list Left-joined districts Admin GET /districtsUnconfigured visible District rows without rate rows listed service Coverage list Status filter Admin ?status=unconfiguredSubset — Coverage list Pricing filter Admin ?pricing=freefee=0 subset — Coverage list Search Admin ?search=Name match — Upsert Pause Admin isActive:falseFee kept — Upsert Repeat Admin Second call Replace — Delete Unconfigured target Admin DELETE on no rate404 SHIPPING_RATE_NOT_FOUNDBulk Scope union Admin districts + provinces + all De-duplicated — Bulk Status-only Admin fee omittedSkip unconfigured + name Report Quote Free delivery Customer fee 0 fee 0 — Quote Not serviceable Customer no active rate 200 + false —
Rule Business Reason Actor Impact Enforced In API Impact Backend Impact Tests Fee in minor units Money invariant Admin enters 12000 for NPR 120 DTO + schema Fee field bigint column spec Unserviceable = 200 Coverage is information Customer branches on flag Service Quote response — spec Pause keeps fee One-toggle re-enable Admin sees configured+inactive Service PUT semantics — District-keyed upsert No create-vs-edit Admin UI simple Unique district_id PUT replaces — Bulk all-or-nothing No half-applied price change Admin retries fixed scope One transaction 4xx on unknown — spec Status-only cannot create No fee to insert Admin sees skipped list Service Report field — Shipping never reads addresses Module boundary Quote takes districtId Service design Quote contract — reviewed
Product Decision User Benefit Engineering Benefit Alternative Tradeoff Risk Flat fee per district Simple pricing One row per district Zone/weight models No distance nuance Documented Bulk 5/min Reprice-all is atomic Matches blast radius Per-district submits Slower bulk edits UI submits once Upsert PUT No create/edit branch Stricter permission only POST+PUT pair — Permissions one-source Shipping_* come from the shared catalog Admin gets 403 only if seed not run permission-catalog.ts + spec— permissions-catalog spec
Flow Edge Case Trigger Expected Behavior User/System Feedback Source Quote Unconfigured No rate 200, fee null serviceable false Quote Paused isActive false 200, fee null serviceable false Quote Unknown district Bad uuid 404 SHIPPING_DISTRICT_NOT_FOUND Quote Free fee 0 fee 0 serviceable true Bulk Empty scope No keys 400 SHIPPING_BULK_SCOPE_EMPTY Bulk No change Neither field 400 SHIPPING_BULK_NO_CHANGE_REQUESTED Bulk > 77 distinct Impossible from correct client 409 SHIPPING_BULK_LIMIT_EXCEEDED Bulk Status-only on unconfigured No fee to insert Skipped + named skippedDistricts Delete Unconfigured No row 404 SHIPPING_RATE_NOT_FOUND
Flow Reads Writes Cache Jobs/Events Response Fields Coverage district + shipping_rate — shipping — rate response array Upsert district, rate shipping_rate invalidate — rate response Bulk districts by scope shipping_rate rows invalidate — report Quote district, rate — serviceability — quote response