Skip to content

fix: [TESIS-153] validate the warehouse of each product stock row - #106

Merged
LauAubert merged 2 commits into
masterfrom
TESIS-999018-product-stock-rows-validation
Oct 4, 2026
Merged

LauAubert merged 2 commits into
masterfrom
TESIS-999018-product-stock-rows-validation

Conversation

@LauAubert

@LauAubert LauAubert commented Oct 2, 2026 •

Copy link
Copy Markdown
Member

Ticket de Jira

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

ID provisorio: se reemplaza por la clave real al cargar la card en Jira. Card: cards/018.md. Sale de una auditoría de código (TESIS-89).


Descripción

Products::Concerns::WarehouseValidation comparaba los warehouse_id de stocks[] crudos contra los de la base (owned.sort == warehouse_ids.sort). Dos fallas salían de ahí:

  • 500: una fila sin warehouse_id dejaba [id, nil], y el sort levantaba ArgumentError (comparison of Integer with nil failed), que nadie rescata. Le pasaba a POST y a PUT /api/v1/products.
  • 422 falso: si los ids llegaban como texto ("5"), [5] == ["5"] daba falso y la API respondía «One or more warehouses do not belong to this company» por un depósito que sí era de la empresa.

Orders::ReplaceOrderLines#validate_new_warehouses! tenía el mismo 422 falso cuando dos líneas nuevas nombraban el mismo depósito como "1" y como 1 (contaban como dos).

  • WarehouseValidation normaliza cada id a entero (acepta número o dígitos) y responde 422 nombrando la fila cuando falta o no es válido: stocks[1]: warehouse_id must be a positive integer.
  • La pertenencia se verifica por cantidad sobre los ids únicos (Warehouse.where(id: ids).count == ids.size); el scope de CompanyScoped sigue acotando al tenant y el mensaje para un depósito ajeno sigue siendo el genérico.
  • ReplaceOrderLines#validate_new_warehouses! compara enteros.
  • Specs de request: fila sin depósito (422 con el mensaje), id en texto (200 y stock guardado), depósito de otro tenant (mismo mensaje genérico), alta con fila sin depósito, y la edición de orden con el mismo depósito en texto y en número.

Evidencia visual

N/A


Cómo probar

  1. PUT /api/v1/products/:id con { product: { name: 'X', stocks: [{ warehouse_id: W, quantity: 1 }, { quantity: 3 }] } } → 422 stocks[1]: warehouse_id must be a positive integer. En master, 500.
  2. Con stocks: [{ warehouse_id: "W", quantity: 4 }] (texto) → 200 y el stock queda en 4. En master, 422.
  3. Con un depósito de otra empresa → 422 «One or more warehouses do not belong to this company», como siempre.

Verificación: bundle exec rspec (1572 ejemplos, 0 fallas, cobertura 99.89% de línea), rubocop y brakeman limpios.

Dientes: con la versión de master del concern, fallan 2 de los 4 specs nuevos de productos.


Impacto y consideraciones

¿Introduce breaking changes?
No. Un request que antes fallaba con 500 o con un 422 equivocado ahora responde lo correcto.

¿Requiere nuevas variables de entorno?
No

¿Afecta la arquitectura o genera un nuevo patrón?
No. Mismo criterio de positive_integer que ya usaba ReplaceOrderLines.

🤖 Generated with Claude Code

The products POROs compared the warehouse ids of the stock rows raw against
the ids in the database. A row without warehouse_id made the sort raise
ArgumentError (nil against Integer), so POST and PUT /products answered 500;
ids that came as text ("5") never matched and gave a false 422 saying the
warehouse did not belong to the company. The order edition had the same
false 422 with the same warehouse sent as "1" and 1.

The rows now need a positive integer warehouse_id, as a number or as digits,
and are compared as unique integers by count. A missing one answers 422
naming the row. Warehouses of another company are still refused with the
same generic message.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@TomasMartin2004

Copy link
Copy Markdown
Contributor

Revisado. Lo veo bien para implementar.

Las dos fallas son reales y las dos se alcanzan desde la UI:

  • una fila de stocks sin warehouse_id dejaba [id, nil] y el sort levantaba ArgumentError, o sea 500 en POST y en PUT /api/v1/products;
  • un id que llega como texto ("5") no coincidía con el entero de la base y la API respondía un 422 de «One or more warehouses do not belong to this company» que era mentira. Ese es el peor de los dos: acusa a un depósito propio de ser de otra empresa.

Lo que verifiqué

  • El cambio de owned.sort == warehouse_ids.sort a Warehouse.where(id: ids).count == ids.size es equivalente y además no trae ids a memoria para después ordenarlos.
  • warehouse_id_of! acepta Integer y String de sólo dígitos, y rechaza todo lo demás nombrando la fila (stocks[1]: warehouse_id must be a positive integer). Nombrarla está bien: es un dato del request del propio usuario, no filtra nada de otro tenant.
  • En ReplaceOrderLines#validate_new_warehouses!, item[:warehouse_id].to_s.to_i convierte nil en 0, que no existe y cae igual en el 422. Es el mismo resultado que antes (Warehouse.where(id: [nil]) tampoco encontraba nada), y de todas formas validate_lines_without_warehouse! corre antes para las líneas que mueven stock. No cambia comportamiento.
  • El spec cubre que un depósito de otra empresa sigue dando el 422 genérico. Era lo importante: al normalizar los ids no había que aflojar la validación de tenant, y no se aflojó.

Antes de mergear

scan_ruby en rojo es la corrida vieja del 02/10 (falla igual en toda la tanda y master está verde). Rebasá y vuelve a correr.

@LauAubert LauAubert changed the title fix: [TESIS-999018] validate the warehouse of each product stock row fix: [TESIS-153] validate the warehouse of each product stock row Oct 3, 2026
@LauAubert LauAubert closed this Oct 3, 2026
@LauAubert
LauAubert deleted the TESIS-999018-product-stock-rows-validation branch October 3, 2026 23:19
@LauAubert
LauAubert restored the TESIS-999018-product-stock-rows-validation branch October 3, 2026 23:23
@LauAubert LauAubert reopened this Oct 3, 2026
@LauAubert
LauAubert marked this pull request as ready for review October 3, 2026 23:25
@LauAubert
LauAubert requested a review from a team as a code owner October 3, 2026 23:25
@LauAubert
LauAubert requested a review from Sanntinat October 3, 2026 23:25

@TomasMartin2004 TomasMartin2004 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 del diff completo. El código es idéntico al que leí cuando los PRs estaban en draft —ningún commit nuevo—, así que lo que sigue es el veredicto formal.

✅ Aprobado

Las dos fallas son reales y las dos se alcanzan desde la UI:

  • una fila de stocks sin warehouse_id dejaba [id, nil] y el sort levantaba ArgumentError: 500 en POST y en PUT /api/v1/products;
  • un id que llega como texto ("5") no coincidía con el entero de la base y la API respondía un 422 de «One or more warehouses do not belong to this company» que era mentira. Ese es el peor de los dos: acusa a un depósito propio de ser de otra empresa.

Lo que verifiqué

  • owned.sort == warehouse_ids.sort → Warehouse.where(id: ids).count == ids.size es equivalente y además no trae ids a memoria para ordenarlos.
  • warehouse_id_of! acepta Integer y String de sólo dígitos y rechaza el resto nombrando la fila. Nombrarla está bien: es un dato del request del propio usuario, no filtra nada de otro tenant.
  • En ReplaceOrderLines, item[:warehouse_id].to_s.to_i convierte nil en 0, que no existe y cae igual en el 422 — el mismo resultado que antes. Y validate_lines_without_warehouse! corre antes para las líneas que mueven stock. No cambia comportamiento.
  • El spec cubre que un depósito de otra empresa sigue dando el 422 genérico. Era lo importante: al normalizar los ids no había que aflojar la validación de tenant, y no se aflojó.

@LauAubert
LauAubert merged commit 34a6aab into master Oct 4, 2026
4 checks passed
LauAubert added a commit that referenced this pull request Oct 5, 2026
The products POROs compared the warehouse ids of the stock rows raw against
the ids in the database. A row without warehouse_id made the sort raise
ArgumentError (nil against Integer), so POST and PUT /products answered 500;
ids that came as text ("5") never matched and gave a false 422 saying the
warehouse did not belong to the company. The order edition had the same
false 422 with the same warehouse sent as "1" and 1.

The rows now need a positive integer warehouse_id, as a number or as digits,
and are compared as unique integers by count. A missing one answers 422
naming the row. Warehouses of another company are still refused with the
same generic message.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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