Quote API: return service limits in the 422 error for overweight parcels

Summary

Overweight parcels now get a structured 422. error: "unserviceable" and message are kept, so existing clients don't break. The body adds:

  • reason: "overweight"
  • service
  • weight_kg
  • max_weight_kg, read from config
  • suggested_service: null or {service, label, max_weight_kg}

suggested_service is the first service in config order (SDS, NXT, REG) that accepts the weight and serves the lane. This rule is a judgement call, since the issue doesn't specify one, and can be adjusted in review.

The lane check still runs first, so an unserved lane keeps the lane error and gets no weight fields. Validation errors, the X-LastMile-Version header and the lastmile_quotes_rejected_total{service} counter are unchanged.

Changes

  • New OverweightParcel, which extends UnserviceableLane (no longer final). It carries the service, weight, max weight and suggestion.
  • QuoteCalculator throws it from the weight check. A new pure suggestService() helper reads the services from config.
  • QuoteController adds the fields to the 422 body.
  • No changes to ETA or price computation, invariants, config or policy. The rejection happens before a Quote exists.

Risk

Low. It only changes the body of an existing 4xx rejection.

Test evidence

php artisan test passed: 51 tests, 1619 assertions. vendor/bin/pint passed.

  • Overweight 422 for SDS 20.5 kg, NXT 50.5 kg (including a long lane) and REG 100.5 kg, with limit and suggestion checked.
  • 20, 50 and 100 kg are still accepted with X-LastMile-Invariants: pass.
  • The version header and the rejected-quotes metric are checked on the overweight 422.
  • The limit comes from config, not a hard-coded value.
  • A lane error carries no weight fields.
  • Unit tests cover the exception payload and the suggestService() boundaries.

Closes #3 (closed)

Merge request reports

Loading
Loading