Skip to content

feat: [TESIS-164] let the shipments listing be searched by tracking and order - #116

Merged
TomasMartin2004 merged 1 commit into
masterfrom
TESIS-164-shipments-search
Oct 5, 2026
Merged

TomasMartin2004 merged 1 commit into
masterfrom
TESIS-164-shipments-search

Conversation

@TomasMartin2004

Copy link
Copy Markdown
Contributor

Ticket de Jira

https://proyectofinalfrlp.atlassian.net/browse/TESIS-164


Descripción

El listado de envíos salió con pestañas por estado y paginado, pero sin buscador. Cuando llama un comprador, el operador tiene en la mano uno de dos códigos: el número de seguimiento que le dio el courier, o el id con el que el canal de venta nombra la orden. No se podía entrar por ninguno de los dos: encontrar un envío era recorrer las páginas a ojo.

GET /api/v1/shipments ahora acepta search y lo cruza contra las dos columnas. ILIKE para que la caja no importe, y el término pasa por sanitize_sql_like para que un % tipeado se busque como texto en vez de devolver la tabla entera.

Tres decisiones que quedaron escritas en el código:

  • El join con orders es left_outer y sólo entra cuando hay término. Un envío siempre tiene orden —la FK es NOT NULL—, pero un INNER acá obligaría a Postgres a resolver el join también en el listado sin buscar, que es el caso normal.
  • El término vacío corta antes. En SQL NULL ILIKE '%%' no es true sino NULL: aplicar la condición igual sacaría del listado normal a todo envío que todavía no tiene ninguno de los dos códigos (un alta manual sin despachar). Hay un spec que lo fija.
  • meta.total cuenta las filas que matchean, porque el paginador y la tabla tienen que decir lo mismo.

Fuera de alcance, igual que en el buscador de órdenes: ignorar acentos y el índice GIN con pg_trgm. Las dos piden su propia extensión y viajan juntas en su card.

  • GET /api/v1/shipments acepta search contra shipments.tracking_number y orders.external_order_id
  • El término convive con los filtros de status y order_id en vez de reemplazarlos
  • ?search[foo]=bar sale por scalar_param con 400, como el resto de los filtros del endpoint

Evidencia visual

N/A — es sólo API. El lado del front va en su propio PR de la misma card.


Cómo probar

  1. bundle exec rspec spec/requests/api/v1/shipments_spec.rb (51 ejemplos).
  2. Con la API levantada y un token de una empresa con envíos:
    • GET /api/v1/shipments?search=<parte del tracking> devuelve sólo ese envío.
    • GET /api/v1/shipments?search=<parte del external_order_id> lo encuentra aunque todavía no esté despachado y no tenga tracking.
    • GET /api/v1/shipments?search=% devuelve vacío, no la tabla entera.
    • GET /api/v1/shipments?status=pending&search=<tracking de uno in_transit> devuelve vacío: el término acota la pestaña, no la pisa.
    • GET /api/v1/shipments?search[foo]=bar responde 400 nombrando el parámetro.

Impacto y consideraciones

¿Introduce breaking changes?
No. search es opcional y sin él la respuesta es idéntica a la de hoy.

¿Requiere nuevas variables de entorno?
No.

¿Afecta la arquitectura o genera un nuevo patrón?
No. Es el mismo patrón del buscador de órdenes (TESIS-52) y del catálogo: ILIKE + sanitize_sql_like + el total contado sobre el scope filtrado (ADR-015).

Cobertura: 99,40 % línea · 95,28 % rama. RuboCop y Brakeman limpios, suite completa en 1.703 ejemplos.

🤖 Generated with Claude Code

https://claude.ai/code/session_012xAtddb53LRNVLuyTXyELp

@Sanntinat Sanntinat left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Revisión — TESIS-164 (PR #116) · Buscador del listado de envíos

Revisión de TESIS-164-shipments-search (04fd003), contra origin/master de proyecto-api.

Check Resultado
CI (GitHub Actions) lint, scan_ruby, test y validate-pr-title en verde
Rama vs master 11 commits atrás y en conflicto (spec/requests/api/v1/api_contract_spec.rb). Ver abajo
04fd003 aplicado sobre master (local) 1753 ejemplos · 0 fallas · cobertura 99,42 % línea / 95,38 % rama · RuboCop 314 archivos sin ofensas · Brakeman 0

Bloqueante para mergear, no para aprobar: la rama arrastra tres PRs que ya entraron

La rama no sale de master: trae tres commits de otros PRs con sus IDs provisorios, que en master ya entraron con squash y otro SHA.

En la rama En master
ea88536 [TESIS-999001] expose the stock status… 3be2ff7 (#99)
c1e7d25 [TESIS-999005] let a warehouse go… de07ba7 (#100)
2cefa7b [TESIS-999017] refuse fractional order quantities… cc13721 (#105)

Por eso el diff del PR muestra 18 archivos cuando el cambio de esta card son dos, y por eso choca con master en api_contract_spec.rb: ese spec cambió en master después de esos merges (TESIS-160 y TESIS-161).

Alcanza con quedarse sólo con el commit de la card:

git rebase --onto origin/master 2cefa7b TESIS-164-shipments-search

Lo probé así: 04fd003 entra limpio sobre master y los números de la tabla de arriba son los de ese resultado.

✅ El buscador

  • Las dos columnas, con ILIKE y sanitize_sql_like, el mismo patrón que órdenes y catálogo. El término viaja como parámetro (:pattern), así que no hay concatenación en el SQL.
  • El término vacío corta antes. Está bien visto que NULL ILIKE '%%' es NULL y no true: sin el corte, los envíos sin ningún código desaparecerían del listado normal.
  • El aislamiento entre empresas sigue en policy_scope(Shipment), y hay un spec que lo fija con un envío gemelo de otra empresa (never reaches the shipment of another company).
  • meta.total cuenta las filas filtradas, y el término acota la pestaña de estado en vez de pisarla.

Los dientes, probados sobre master:

Rotura Falla
Sacar sanitize_sql_like treats a literal % as text and not as a wildcard
Sacar el corte del término vacío 10, entre ellos returns every shipment when the term is blank, including the one with no codes y todos los del listado sin buscar

⚪ El motivo del left_outer en el comentario no es el que decide

El comentario de apply_search dice que un INNER «obligaría a Postgres a resolver el join también cuando no hay término». Pero con el return del término vacío, el join sólo existe cuando se busca, sea INNER o LEFT. Y como la FK es NOT NULL, los dos dan las mismas filas. left_outer está bien; lo que sobra es esa parte del motivo. El commit lo cuenta mejor: el return es lo que deja afuera al listado normal.

Los criterios de la card (parte de backend)

  • GET /api/v1/shipments acepta search contra tracking_number y orders.external_order_id.
  • ILIKE + sanitize_sql_like.
  • El join con orders es left_outer y sólo entra cuando hay término.
  • meta.total cuenta las filas que matchean.
  • ?search[foo]=bar responde 400 por scalar_param.

Veredicto

APPROVE, con el rebase como paso previo al merge.

El código de la card está bien y probado. Lo único que falta es sacar de la rama los tres commits que ya están en master, y eso no cambia ni una línea del cambio.

…nd order

The listing had tabs and pagination but no way in. When a buyer calls, the
operator holds one of two codes: the tracking number the courier issued, or
the id the sales channel gave the order. Neither was reachable, so finding a
shipment meant walking the pages by eye.

GET /api/v1/shipments now takes `search` and runs it against both columns.
ILIKE so case does not matter, and the term goes through sanitize_sql_like so
a typed % is searched as text instead of returning the whole table.

The join with orders is left_outer and only happens when there is a term. A
shipment always has an order -- the FK is NOT NULL -- but an INNER here would
make Postgres resolve the join on every unsearched listing, which is the
normal case. For the same reason the blank term returns early: `NULL ILIKE
'%%'` is NULL and not true, so applying the condition anyway would drop every
shipment that has neither code yet from the plain listing.

meta.total counts the matching rows, because the paginator and the table have
to agree. A malformed `?search[foo]=bar` leaves through scalar_param with a
400, like the other filters of the endpoint.

Out of scope, as in the orders search: ignoring accents and a GIN index with
pg_trgm. Both need their own extension and travel together in their own card.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012xAtddb53LRNVLuyTXyELp
@TomasMartin2004
TomasMartin2004 force-pushed the TESIS-164-shipments-search branch from 04fd003 to 4178eb3 Compare October 5, 2026 11:05
@TomasMartin2004
TomasMartin2004 merged commit 759bd54 into master Oct 5, 2026
4 checks passed
@TomasMartin2004
TomasMartin2004 deleted the TESIS-164-shipments-search branch October 5, 2026 14:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants