# # NexGate Shipping Service — Build Plan Development

How we build `docs/shipping/SHIPPING_SERVICE_DESIGN.md` (the "design", § numbers below point to it).
Status: **plan agreed to start, nothing built yet.** Built on the Location Service (done: wards, shop locations,
user addresses, frozen places).

## Ground rules for every step

- **Talk first, then build** — each step starts with a short talk on its details, then code.
- **Code standard**: new top-level package `shipping_service/` (same level as `location_service/`), the project's
  controller / service / service/impl / payload / entity / repo shape, no boilerplate, comments only where needed.
- **Existing responses**: add fields; reshape only when agreed (dev phase) and always recorded.
- **Every change recorded** in `commerce_logistics_changes.md` — before / after, examples, app to-do.
- **Design doc kept true** — any decision that changes during a step is updated in the design.
- **Settings**: fixed rules in properties (`shipping.*`), per-shipper agreement settings in the database (§6.7.1).
- **Money**: every money move goes through the ledger; every step that moves money ends with the ledger balance check.
- **Tested live** locally after each step (like the location steps), using the worked scenarios (§17) as test cases.
- **Phase-0 hooks** (§15b) are built in from the start: attempt records, failure reason codes, the extra statuses,
  timers as data, reverse legs allowed — even though their rules come later.

## The order at a glance

| Step | Delivers | Real-world result after the step |
|---|---|---|
| **0** | Foundations | nothing visible; ground ready |
| **1** | Pickup, end to end (with codes and "✓ It's what I ordered") | **buyers can buy and collect at the shop** |
| **2** | Native shipper registration (KYC + Service Setup basics) | delivery businesses can sign up |
| **3** | Door coverage, pricing, ETA | businesses describe where / how much |
| **4** | Engine: estimation + quotation (single door leg) | buyers see real delivery costs |
| **5** | Cart / checkout / agreement, escrow per part | **buyers pay real delivery** |
| **6** | Job flow: offers, codes, statuses, release | **first real door-to-door deliveries** |
| **7** | Reassignment, switching, cancelling, bulky mismatch | unhappy-but-common cases handled |
| **8** | Reliability, reviews, auto-pause, upcoming orders | trust and planning |
| **9** | Stations (single leg) | intercity: collect at a station |
| **10** | Multi-shipper chains | shop → bus → door |
| **11** | Group purchase and installments | shipping inside group / installment buys |
| **12** | Free shipping (seller rules + campaigns) | "Free delivery" badges and filter |
| **13** | Bolt integration | boda on demand in town |
| **14** | Admin dashboard (screens) | ops tooling — **last** |
| later | Notifications set · unhappy paths (§15a/b) · staging deploy | after the build |

---

## Step 0 — Foundations
**Goal:** everything later steps stand on, with no behaviour change.

- Package `shipping_service/` with sub-packages from §2 (empty shells added as each step needs them).
- `shipping.*` properties with every value from §6.7.1 (read through one settings class).
- **Shared opening hours** (§6.3): weekly ranges, special days, temporarily closed, cut-off, EAT, overnight;
  "is open at", "next opening", "add opening hours" helpers. Attached to **shops** now (§6.4): shop hours endpoints,
  "Open now / Opens Sat 09:00" on shop responses; shop **pause modes** (keep selling / stop orders, `SHOP_CLOSED`).
- **Escrow table**: `part` (ITEMS / LEG), `legId`, `orderId` filled, **provider payee** (§12.1) — new columns only,
  current payments keep working (they are "ITEMS" parts).
- **Product `bulky`** field (§8.1) — default false; category pre-tick later.
- **File Thunder**: new upload types *business document* (private) and *business logo* (separate repo, small).
- **Codes service**: 4 random digits, hashed, never logged, 5 tries → locked, regenerate (§11). Replaces the old
  `DeliveryConfirmation` code generator (and its plain-code log line).

**Check:** existing checkout / orders / cart-escrow tests still pass; shop hours + pause visible; a code can be
generated, verified, locked after 5 wrong tries, and never appears in logs.

## Step 1 — Pickup, end to end
**Goal:** a buyer can buy, the seller prepares, the buyer collects with a code, money is released on confirmation.

- Move Step-4 `e_commerce/fulfillment` (JSON) into `shipping_service/fulfillment` as a **table**; orders link to it
  (§2, §3.5). Frozen places stay inside the record.
- **Shipping agreement entity** (§4.8): `shipping_agreement` + parties + changes; states DRAFT → PENDING → AGREED →
  DONE / CANCELLED. For pickup the only parties are the buyer and the seller. **Escrow parts move only on agreement
  events** from here on.
- Order flow: seller **accepts** (24 opening hours, else auto-cancel + refund) → **Ready** → buyer told → **pickup at
  the counter**: buyer's code, seller enters it → *collected* (§3.1, §10).
- **"✓ It's what I ordered"** releases **all** escrow parts in one transaction; **"⚠ Report a problem"** freezes them (§12.2).
- Account setting: **code or "received" button** (§11).
- Pickup statuses `placed → accepted → ready → collected → completed` (+ cancelled / expired); optional **pickup
  time** (shop opening hours, inside the window; reminder that morning).
- **14-day collection** expiry with reminders (§3.1). **Cancel before the seller accepts** (§3.4a). **Seller cancel** at once
  (pop-up "I have already informed the buyer"; pickup before collected, delivery before Ready).
- Recent pins recorded **after payment**, not at checkout (§4.4).
- Admin endpoints needed from now: manual **release / refund** of an escrow part (§6.7.2) — `SUPER_ADMIN`, reason, audit log.

**Check:** scenario **A** (§17) live: pay → accept → ready → code → collected → confirm → seller paid; 14-day expiry; seller cancel
refunds; cancel before accept refunds; problem report freezes; admin release works and is logged.

## Step 2 — Native shipper registration
**Goal:** a delivery business can sign up and get approved automatically.

- **KYC** (§6.2 part 1): name, contact phone, **office location** (pin), business licence (file + number + expiry,
  unique number), director's NIDA (file + number), LATRA (file + number + expiry) → auto-approved `KYC_APPROVED`.
- **Service Setup basics** (§6.2 part 2): profile (logo, description, WhatsApp, email, badge), service type
  (door / station), **hours** (shared piece, default Mon–Sat 08:00–18:00), limits (max value, bulky + extra,
  response window, heads-up). Setup checklist endpoint.
- Statuses `KYC_APPROVED → LIVE ⇄ PAUSED`, `SUSPENDED`; documents private (owner + admin).
- "Delivery business" mode in the app (owner only, §6.6) — endpoints for the owner's own business.
- Admin endpoints: **suspend / lift** (§6.7.2).

**Check:** register with all documents → auto-approved; duplicate licence number refused; documents not visible to
others; checklist shows what's missing; suspend / lift logged.

## Step 3 — Door coverage, pricing, ETA
**Goal:** a business says where it works, how much, how long — and goes LIVE.

- **Location helpers** (in `location_service`): regions → districts → wards lists + search; coverage outline for the map.
- **Coverage** (§7.1): ticked areas (region / district / ward), trips `WITHIN_EACH` / `WITHIN_AND_ACROSS`,
  "both ends inside", fast **area-code matching** (the spec's second core function, `areaMatch`).
- **Pricing** (§8.1): distance bands (price + time each, ×1.3, last band = max distance), flat, value add-on ladder
  + max value, bulky extra, rounding up to 100.
- Goes **LIVE automatically** when service type + coverage + pricing exist.
- Delivery commission fee type (0%) in the fee table + per-shipper override with start date (§6.7.1).

**Check:** the §7.1 crossing table (Sinza → Mikocheni with WITHIN_EACH vs ACROSS…) as tests; band and add-on maths
incl. rounding; business flips to LIVE when complete.

## Step 4 — Engine: estimation + quotation (single door leg)
**Goal:** buyers see real delivery options and costs.

- Engine (§9): where → who (coverage) → filter (distance, value, bulky, hours / cut-off, paused) → cost + ETA →
  clean-up (dominated, outliers 3×) → labels 💰 ⚡ ⭐ (⭐ = price + time at launch) → order, top 5, fairness.
- **ETA** (§8.2): from "shop ready", ranges, closed hours.
- **Estimation** `POST /api/v1/shipping/estimations` (§4.5): `cost {from,to}`, `eta {from,to}`, pickup area;
  Redis cache shop-area → buyer-area (1 h).
- **Quotation** (§4.1): live options for a shop's parcel and a destination (§4.2: saved / recent / pin; station
  recognition comes with step 9).
- Product detail: live quotation for the viewer's address.

**Check:** scenario **L** (cards) and the engine walk-throughs from the master guide (A, B, E) reproduce; cache hit
on the second call; closed shipper shifts ETA instead of disappearing.

## Step 5 — Cart, checkout, agreement, escrow per part
**Goal:** buyers pay the real delivery cost; money split into items + leg.

- Cart (§4.3, §4.4): one destination per cart, per-shop quotation **held 5 days**, add-to-cart `fulfillment` +
  `dropoff`, defaults for a new shop, unsaved pin on the cart, re-quote on changes.
- Checkout → **agreement** (§4.1, §4.8) with its **legs** (each leg escrow part points to one): editable on the checkout screen until paid (re-quote, wallet re-check), **locked
  once paid**. Buy-now uses the sheet's quotation.
- **Escrow per part** (§12.1): items part → seller, leg part → shipper owner; release all on confirm.
- **Seller fee on items + tax only**; delivery commission from the leg (§8.3).
- `shippingByShop` now real costs (replaces the 0 placeholder).
- What the app keeps vs the server (§4.6) honoured.

**Check:** scenario **B** up to payment: escrows created per part with the right payees and fees; quotation older
than 5 days refreshed; edit at checkout re-quotes; ledger balance check passes.

## Step 6 — Job flow, codes, delivery
**Goal:** the first real door-to-door delivery from offer to release.

- Seller accepts → **heads-up**; **Ready** → **offer** with response window (counted while open), **closed-shipper
  4 h rule** (§10).
- **Ready pop-up**: "I have packed it well" + "label / number is on the package" (recorded).
- **Parcel number** created when the seller accepts (QR label generated by the app, or written on the box); scanned / typed at pickup → "✓ yours / ✗ wrong parcel";
  shippers see the parcel, never the order (§11, §6.6).
- Shipper: accept / reject (reason), timeout; active job steps; **pickup code** (seller verifies) and **delivery
  code** (shipper submits) or the buyer's button (§11); optional handover photos.
- Shipper accept = **signs the agreement**; all signed → AGREED, parcel moves (§4.8).
- Statuses door ladder (§3.2) incl. **heading to the shop** + phase-0 statuses.
- Release on "✓ It's what I ordered": items → seller, leg → shipper (one transaction).
- Shipper app endpoints for offers / jobs / earnings (§6.6), live push.

**Check:** scenario **B** end to end; scenario **M** (a shipper's day) step by step; timeout counted; codes locked
after 5 tries.

## Step 7 — Reassignment, switching, cancelling, bulky mismatch
**Goal:** the common unhappy-but-normal cases.

- **Reassignment** (§10a): rejection / timeout → that leg only; tiers **1 km → same district → buyer**; same-or-cheaper
  and not slower = automatic (escrow re-pointed, difference refunded); dearer / none → buyer decides; **loop guard
  3 per order**; **buyer silent 24 h → pickup**; **chain safety net 24 h**; free-shipping caps recalculated; history kept.
- **Switching** (§3.4): pickup → delivery (fresh quotation, pay, offer at once if Ready); delivery → pickup (before any
  shipper accepted, refund).
- **Cancelling** (§3.4a): shipping-only until pickup — free before **heading to the shop**, wasted-trip fee (50% / Bolt's
  charge) after; whole order when the chain failed.
- **Agreement changes** (§4.8): propose → affected parties approve → default on silence; **destination change**
  (accept new cost / keep old price / decline; no withdraw after pickup).
- **Bulky mismatch** (§8.4): report before pickup, bulky-only reassignment, seller pays difference + 50% wasted trip,
  auto-flag after 2.
- **Badly packed** refusal (§8.5): before pickup, photo, seller repacks + Ready again, seller pays 50% wasted trip.
- Admin endpoints: force **reassign / cancel** (§6.7.2).

**Check:** scenarios **D, F, G, H, N, O, P**; loop guard; silent buyer → pickup after 24 h; ledger balanced after every
adjustment.

## Step 8 — Reliability, reviews, auto-pause, upcoming orders
- **Reliability counters** (acceptance, on time, failed, broke binding) and **auto-pause** levels with the §14 table,
  grace period 10 offers, reasons shown.
- **Reviews** like shops, verified only, owner replies, moderation (§14); shown in quotations.
- **Two-way private ratings** (shipper rates buyer / seller).
- **Upcoming orders** on the shipper dashboard, area-only until accepted (§6.6).
- Real delivery times recorded (for after-launch ETA).

**Check:** the §14 trigger table as tests (2 / 3 missed, 3rd pause in 7 days → suspended…); new shipper not paused
inside the grace period; upcoming card hides the exact pin until accepted.

## Step 9 — Stations (reached by a door leg)
- Stations (§7.2): pins with counter name, phone, hours; ≥ 1 per ticked area; **lines**; **reach 30 km**
  (per business override).
- **Station-pair table** (price + time, both ways, pre-filled pairs) (§8.1).
- Handover points (§7.2a): leg 1 always starts with a collection at the shop (option A) — either the station
  business **collects from shops** (areas + flat fee, 0 = free; one leg) or a door shipper brings it to the station
  (the **two-leg chain** door → station, built here); buyer collects at the destination station (station staff enter the
  buyer's code). Step 10 extends it to 3 legs, editing and re-confirms.
- Station ladder (§3.3); quotation **meeting-point recognition** ("collect at Mbeya Bus Stand", §4.2).

**Check:** a Mbeya → Morogoro trip as boda → bus (two legs) and as a bus company collecting free (one leg, ranked
cheapest); no door shipper at the shop → no station route; pin within 1 km of a station
offers "collect there"; empty pair = not offered.

## Step 10 — Multi-shipper chains
- Chain building (§4.7): up to 3 legs, meeting points within 1 km, always next to single-leg options.
- Quotation editing per leg: stop early, swap a leg's shipper, change a meeting point.
- **All legs offered at Ready, nothing moves until all accept**; **binding after accept**; **re-confirm** when an
  accepted leg's time moves > 4 h (§10a 7a).
- Shipper → shipper handover: "handed over" / "received" + that handover's code, optional photo (§11).
- One escrow part **per leg**; release all together (§12).

**Check:** scenarios **C, D, E** with three legs; one leg rejecting while others accepted; re-confirm triggered by a
> 4 h move.

## Step 11 — Group purchase and installments
- Group (§5): each member's own fulfillment and fee with their payment; refunds on fail / withdraw; moves with a
  transferred payment; price frozen at payment.
- Installments (§5): immediate = fee with the down payment; layaway = choose and pay after the last instalment
  (reuses switch-to-delivery).

**Check:** scenarios **J** and **K**.

## Step 12 — Free shipping
- Seller rules (areas, minimum, cap, product override) and NexGate campaigns (areas, dates, cap, audience, shops,
  budget reserved at payment, auto-stop) (§13).
- Stacking, the cheapest qualifying option free, faster pays the difference; campaign ledger account; seller share
  from the items part at release.
- Estimation `freeDelivery` + card badge + "Free delivery to my area" search filter.

**Check:** scenario **I**; campaign budget never overspent under parallel checkouts; refund returns budget.

## Step 13 — Bolt integration
- Adapter (§6.5): OAuth2 token cache, estimations (boda, non-bulky, in-city), book at Ready (re-estimate, held price
  = max + 2%), cancel, webhooks (3 IPs + token) and polling fallback, state mapping, tracking link + driver details.
- **Bolt ledger account**, monthly settlement record, receipt differences; **kill-switch** and **daily spend limit**
  (§6.7); seller pays a shop no-show.
- Built and tested in Bolt's **sandbox** first.

**Check:** sandbox flow (estimate → create → states → completed → receipt); NO_DRIVER_FOUND → reassignment; spend
limit blocks new Bolt quotations; kill-switch hides Bolt immediately.

## Step 14 — Admin dashboard (last)
Screens on top of the existing admin endpoints (§6.7.3): shippers + KYC documents, Bolt status and settlement,
operations board, campaigns, reports (by area), audit log, `STAFF_ADMIN` / `SUPER_ADMIN` split.

## After the build
- **New dedicated shipping notifications** (one set, after the shipping service).
- **Unhappy paths** (§15a / §15b): auto-confirm window, delivery attempts + office hold, returns, disputes.
- **Staging / production**: PostGIS image + `CREATE EXTENSION`, ward file, settings — at deployment.
- After-launch items (§16).

## Dependencies

```
0 ─► 1 ─► 2 ─► 3 ─► 4 ─► 5 ─► 6 ─► 7 ─► 8
                         │         ├─► 9 ─► 10
                         │         ├─► 11
                         │         ├─► 12
                         │         └─► 13
                         └─────────────────────► 14 (last)
```
Steps 9, 11, 12, 13 can be reordered after step 6 (each only needs the single-leg flow). 10 needs 9.