2.7 KiB
US-MGN-004-005-003: Cancelar Factura de Cliente
RF Asociado: RF-MGN-004-005 Módulo: MGN-004 - Financiero Básico Epic: Facturas de Cliente Prioridad: P0 (MVP) Story Points: 3 Sprint: Sprint 10 Estado: Ready for Development Fecha: 2025-11-24
User Story
Como contador, Quiero cancelar facturas de cliente validadas, Para anular facturas erróneas creando asiento de reversión automático.
Descripción Detallada
Cancelar factura genera asiento de reversión que anula el asiento contable original. Estado cambia a 'cancelled'. Factura queda cancelada permanentemente (no se puede reactivar).
Criterios de Aceptación
Escenario 1: Cancelar factura open
Dado que factura tiene state='open', Cuando POST /customer-invoices/10/cancel con reason="Error en cliente", Entonces state='cancelled', asiento reversión creado y posteado, cancelled_date y cancelled_by asignados.
Escenario 2: Generar asiento de reversión
Dado que factura tiene asiento original con débito 1210 y créditos 1000+210, Cuando cancelo factura, Entonces asiento reversión invierte débitos ↔ créditos.
Escenario 3: No cancelar factura ya cancelled
Dado que state='cancelled', Cuando intento cancelar nuevamente, Entonces error 409 "Factura ya está cancelada".
Escenario 4: No cancelar factura paid
Dado que state='paid' (tiene pagos), Cuando intento cancelar, Entonces error 400 "No se puede cancelar factura pagada. Primero reversa los pagos".
Reglas de Negocio
- RN-1: Solo facturas open pueden cancelarse.
- RN-2: Facturas paid requieren reversar pagos primero.
- RN-3: Se genera asiento de reversión automático.
- RN-4: Motivo de cancelación es obligatorio.
- RN-5: Estado final 'cancelled' es irreversible.
Tareas Técnicas
Backend
- POST /api/v1/financial/customer-invoices/:id/cancel
- DTO: CancelInvoiceDto (reason required)
- Service: CustomerInvoiceService.cancel()
- Validar state='open', no paid
- Crear asiento de reversión
- Actualizar state='cancelled', cancelled_date, cancelled_by
- Unit tests (6 test cases)
- Integration tests (5 test cases)
Frontend
- Botón "Cancelar Factura" (si open y no paid)
- Modal: CancelInvoiceModal.tsx (reason required)
- Badge: Cancelada
- E2E test: cancel invoice
Database
- Campo: invoices.cancelled_date, cancelled_by, cancel_reason