Skip to content

fix: [TESIS-152] refuse fractional order quantities with 422 instead of 500 - #105

Merged
LauAubert merged 2 commits into
masterfrom
TESIS-999017-integer-order-quantities
Oct 4, 2026
Merged

LauAubert merged 2 commits into
masterfrom
TESIS-999017-integer-order-quantities

Conversation

@LauAubert

@LauAubert LauAubert commented Oct 2, 2026 •

Copy link
Copy Markdown
Member

Ticket de Jira

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

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


Descripción

OrderItem validaba quantity > 0 sobre el valor crudo, y la columna integer lo castea recién al guardar. Con 0.5, la validación pasaba (0,5 > 0), la línea se guardaba con 0 y Catalog::DeductStock levantaba ArgumentError 'quantity must be positive', que nadie rescata: POST /api/v1/orders respondía 500. Con 2.7, la cantidad se truncaba a 2 sin ningún aviso. La edición (ReplaceOrderLines#positive_integer) ya rechazaba los dos; el alta no.

La transacción hacía rollback, así que no se corrompía nada, pero el cliente recibía un 500 por un dato de entrada.

  • Valida quantity de OrderItem como entero mayor que cero (only_integer: true). Cubre el alta manual y la ingesta por webhook; un entero que llega como texto ("3", típico de un form) sigue valiendo.
  • Specs de modelo (0,5, 2,7 y "1.5" rechazados; "3" aceptado) y de request (422 con «must be an integer», y la orden no se crea).

En el modelo y no en el PORO. Validar en CreateOrder (como hace la edición) cubría sólo el alta. En el modelo cubre todos los caminos que crean líneas, y el RecordInvalid ya se mapea a 422 en ApplicationController.


Evidencia visual

N/A


Cómo probar

  1. POST /api/v1/orders con items: [{ product_id, warehouse_id, quantity: 0.5, unit_price: 10 }] → 422 {"error": "Validation failed: Quantity must be an integer"}. En master, 500.
  2. Con quantity: 2.7 → 422, sin orden creada. En master, se crea con 2.
  3. Con quantity: "3" → 201 como siempre.

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

Dientes: sin only_integer, el spec de 0,5 vuelve a reventar con el ArgumentError del 500 (3 fallas).


Impacto y consideraciones

¿Introduce breaking changes?
Sí, acotado: una cantidad fraccionaria que antes se truncaba ahora se rechaza con 422. Ningún cliente del front manda fracciones (los inputs son step=1).

¿Requiere nuevas variables de entorno?
No

¿Afecta la arquitectura o genera un nuevo patrón?
No

🤖 Generated with Claude Code

…ad of 500

OrderItem validated the quantity as greater than zero on the raw value, and
the integer column then cast it: 0.5 passed, was stored as 0 and made
DeductStock raise ArgumentError, which nobody rescues, so POST /orders
answered 500. 2.7 was silently truncated to 2. The edition already refused
both through ReplaceOrderLines#positive_integer; the creation did not.

The quantity of an order item is now validated as an integer, so the
creation and the webhook ingestion answer 422 with the reason before any
stock moves. Integers that come as text from a form are still accepted.

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

Copy link
Copy Markdown
Contributor

Revisado. Lo veo bien para implementar.

Un POST /api/v1/orders con quantity: 0.5 devolviendo 500 es un defecto de verdad: la validación pasaba (0,5 > 0), la columna integer guardaba 0 y Catalog::DeductStock reventaba con un ArgumentError que nadie rescata. El caso de 2.7 es incluso peor que el 500, porque no avisa: guardaba 2 y seguía. El fix es de una línea y deja la respuesta en 422 con el motivo.

Lo que verifiqué

  • only_integer: true valida contra el valor crudo, antes del cast de la columna, que es justo lo que hacía falta acá.
  • El spec cubre que '3' (el entero que llega como texto desde un formulario) sigue siendo válido. Era el riesgo obvio de agregar only_integer y está contemplado.
  • La ingesta por webhook no se rompe: ProcessWebhookOrder#quantity_of hace item[:quantity].to_i antes de construir el OrderItem, así que un 1.0 que mande un canal llega como 1 a la validación. Lo verifiqué contra el código de master, no sólo contra la descripción.

Lo que queda afuera y conviene decir en la card

Ese mismo to_i de la ingesta sigue truncando: un canal que mande 2.7 va a registrar 2 unidades en silencio, que es exactamente el problema que este PR arregla para el alta manual. No lo metería acá —el criterio del webhook es otro, una venta no se descarta por un decimal—, pero dejaría la nota para que quede claro que la ingesta no quedó cubierta.

@LauAubert LauAubert changed the title fix: [TESIS-999017] refuse fractional order quantities with 422 instead of 500 fix: [TESIS-152] refuse fractional order quantities with 422 instead of 500 Oct 3, 2026
@LauAubert LauAubert closed this Oct 3, 2026
@LauAubert
LauAubert deleted the TESIS-999017-integer-order-quantities branch October 3, 2026 23:19
@LauAubert
LauAubert restored the TESIS-999017-integer-order-quantities branch October 3, 2026 23:22
@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 LoLoo03 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

Un POST /api/v1/orders con quantity: 0.5 devolviendo 500 es un defecto de verdad: la validación pasaba (0,5 > 0), la columna integer guardaba 0 y Catalog::DeductStock reventaba con un ArgumentError que nadie rescata. El caso de 2.7 es peor que el 500 porque no avisa: guardaba 2 y seguía.

Lo que verifiqué

  • only_integer: true valida contra el valor crudo, antes del cast de la columna, que es justo lo que hacía falta.
  • El spec cubre que '3' —el entero que llega como texto desde un formulario— sigue siendo válido. Era el riesgo obvio y está contemplado.
  • La ingesta por webhook no se rompe: ProcessWebhookOrder#quantity_of hace item[:quantity].to_i antes de construir el OrderItem. Lo chequeé contra el código, no contra la descripción.

🟡 Lo que queda afuera

Ese mismo to_i de la ingesta sigue truncando: un canal que mande 2.7 registra 2 unidades en silencio, que es el problema que este PR arregla para el alta manual. No lo metería acá —el criterio del webhook es otro, una venta no se descarta por un decimal— pero vale dejarlo anotado en la card.

@LauAubert
LauAubert merged commit 2cefa7b into master Oct 4, 2026
4 checks passed
LauAubert added a commit that referenced this pull request Oct 5, 2026
…of 500 (#105)

OrderItem validated the quantity as greater than zero on the raw value, and
the integer column then cast it: 0.5 passed, was stored as 0 and made
DeductStock raise ArgumentError, which nobody rescues, so POST /orders
answered 500. 2.7 was silently truncated to 2. The edition already refused
both through ReplaceOrderLines#positive_integer; the creation did not.

The quantity of an order item is now validated as an integer, so the
creation and the webhook ingestion answer 422 with the reason before any
stock moves. Integers that come as text from a form are still accepted.

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