fix: [TESIS-152] refuse fractional order quantities with 422 instead of 500 - #105
Conversation
…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>
|
Revisado. Lo veo bien para implementar. Un Lo que verifiqué
Lo que queda afuera y conviene decir en la card Ese mismo |
TomasMartin2004
left a comment
There was a problem hiding this comment.
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: truevalida 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_ofhaceitem[:quantity].to_iantes de construir elOrderItem. 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.
…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>
Ticket de Jira
https://proyectofinalfrlp.atlassian.net/browse/TESIS-152
Descripción
OrderItemvalidabaquantity > 0sobre el valor crudo, y la columna integer lo castea recién al guardar. Con0.5, la validación pasaba (0,5 > 0), la línea se guardaba con 0 yCatalog::DeductStocklevantabaArgumentError 'quantity must be positive', que nadie rescata:POST /api/v1/ordersrespondía 500. Con2.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.
quantitydeOrderItemcomo 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."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 elRecordInvalidya se mapea a 422 enApplicationController.Evidencia visual
N/A
Cómo probar
POST /api/v1/ordersconitems: [{ product_id, warehouse_id, quantity: 0.5, unit_price: 10 }]→ 422{"error": "Validation failed: Quantity must be an integer"}. Enmaster, 500.quantity: 2.7→ 422, sin orden creada. Enmaster, se crea con 2.quantity: "3"→ 201 como siempre.Verificación:
bundle exec rspec(1571 ejemplos, 0 fallas, cobertura 99.89% de línea),rubocopybrakemanlimpios.Dientes: sin
only_integer, el spec de 0,5 vuelve a reventar con elArgumentErrordel 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