feat: price home delivery with Jadlog
All checks were successful
Build and deploy / Validate source (push) Successful in 1m18s
Build and deploy / Integration suite on a real stack (push) Successful in 2m33s
Build and deploy / Secret scan and release gate (push) Successful in 9s
Build and deploy / Publish images (push) Successful in 1m42s

FREIGHT_ADAPTER=jadlog prices "Receber em casa" through Jadlog's Simulador
de Frete from the order's billed metres and value, adding production days
to Jadlog's delivery time. The package weight is a base plus a weight per
metre from the client, with no default: the adapter refuses to start
without it and without the credentials. The cart re-quotes when the package
changes, and approval quotes again from the server-priced items. The
production stack takes the Jadlog settings, so the read-only probe runs
from the worker's console.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Cauê Faleiros
2026-09-29 09:34:01 -03:00
parent 641aafc87d
commit 4de2151e9a
13 changed files with 199 additions and 26 deletions

View File

@@ -6,7 +6,7 @@ from fastapi import APIRouter, HTTPException, Request, Response
from ..core import db
from ..core.auth import client_ip, owner, new_session, rate_limit
from ..core.limits import upload_limit_bytes
from ..core.models import Freight
from ..core.models import FreightEstimate
from ..runtime import (ENVIRONMENT, GUEST_SESSION_LIMIT, PART_BYTES, freight, payment,
require_delivery_available, storage)
@@ -37,12 +37,14 @@ def session(request: Request, response: Response):
return {'environment': ENVIRONMENT, 'cart_scope': str(session_id), 'part_bytes': PART_BYTES,
'max_upload_bytes': upload_limit_bytes(), 'payment_provider': payment.name,
# Public by design: Mercado Pago's card form needs it in the browser.
'payment_public_key': os.environ.get('MP_PUBLIC_KEY', '') if payment.name == 'mercadopago' else ''}
'payment_public_key': os.environ.get('MP_PUBLIC_KEY', '') if payment.name == 'mercadopago' else '',
# The delivery service the cart quotes, or none: pickup only.
'freight_service': 'jadlog' if freight.name == 'jadlog' else 'mock-standard' if ENVIRONMENT == 'local' else None}
@router.post('/api/freight')
def quote_freight(body: Freight):
def quote_freight(body: FreightEstimate):
require_delivery_available(body.service)
try:
return freight.quote(body.service, body.postal_code)
return freight.quote(body.service, body.postal_code, body.metres, body.declared_cents)
except ValueError as exc:
raise HTTPException(422, str(exc))

View File

@@ -10,6 +10,7 @@ from psycopg.types.json import Jsonb
from ..core import db
from ..core.auth import owner
from ..core.models import QuoteRequest
from ..core.pricing import price
from .. import quote_review
from ..runtime import freight, quote_view, require_delivery_available, upload_row
from ..scanning import require_clean
@@ -24,10 +25,15 @@ def create_quote(body: QuoteRequest, session_id=Depends(owner)):
require_delivery_available(body.freight.service)
draft = body.model_dump(mode='json', exclude={'request_key'})
digest = hashlib.sha256(json.dumps(draft, sort_keys=True).encode()).hexdigest()
try:
freight.quote(body.freight.service, body.freight.postal_code)
except ValueError as exc:
raise HTTPException(422, str(exc))
reason = quote_review.review_reason(draft)
if reason is not None:
# Waits for review: check the delivery now, priced at approval.
try:
priced = [price(item.mode, str(item.metres), item.grade) for item in body.items]
freight.quote(body.freight.service, body.freight.postal_code,
sum(Decimal(i['billed_metres']) for i in priced), sum(i['total_cents'] for i in priced))
except ValueError as exc:
raise HTTPException(422, str(exc))
with db.connect() as c:
for item in body.items:
for uid in item.uploads:
@@ -41,7 +47,6 @@ def create_quote(body: QuoteRequest, session_id=Depends(owner)):
row = c.execute('SELECT * FROM dtf_local.quotes WHERE owner=%s AND request_key=%s FOR UPDATE', (session_id, body.request_key)).fetchone()
if row['request_hash'] != digest:
raise HTTPException(409, 'Request key already used for a different cart')
reason = quote_review.review_reason(draft)
if row['id'] == uid and reason is None:
quote_review.approve(c, row, body.items, quote_review.AUTO)
return {'id': row['id'], 'status': 'approved'}