diff --git a/documentacion/api.md b/documentacion/api.md index 90ddda5..bb3fab2 100644 --- a/documentacion/api.md +++ b/documentacion/api.md @@ -128,12 +128,9 @@ Registra una factura en VeriFactu. El formato de entrada se detecta automáticam "invoice": { "number": "FA2024/001", "date": "2024-09-13T00:00:00Z", - "totalHt": 100.00, - "totalTax": 21.00, - "total": 121.00, "notePublic": "Factura de prueba", "lines": [ - {"description": "Servicio", "quantity": 1, "unitPrice": 100, "taxRate": 21, "total": 121.00} + {"description": "Servicio", "quantity": 1, "unitPrice": 100, "taxRate": 21} ] }, "client": { diff --git a/documentacion/formatos.md b/documentacion/formatos.md index e15c289..a8f8632 100644 --- a/documentacion/formatos.md +++ b/documentacion/formatos.md @@ -46,20 +46,17 @@ Formato propio de la API. Se detecta por la presencia del campo `factura`. ## Formato: `dolibarr` -Compatible con el BFF de Dolibarr. Se detecta por la presencia del campo `invoice`. Agrupa automáticamente las líneas por tipo de IVA. +Compatible con el formato que la web envía al BFF de Dolibarr. Los campos numéricos son `number` (no strings) y las fechas son ISO 8601. Se detecta por la presencia del campo `invoice`. ```json { "invoice": { "number": "FA2024/001", "date": "2024-09-13T00:00:00Z", - "totalHt": 100.00, - "totalTax": 21.00, - "total": 121.00, "notePublic": "Servicios de consultoría", "lines": [ - {"description": "Servicio A", "quantity": 1, "unitPrice": 60, "taxRate": 21, "total": 72.60}, - {"description": "Servicio B", "quantity": 1, "unitPrice": 40, "taxRate": 21, "total": 48.40} + {"description": "Servicio A", "quantity": 1, "unitPrice": 60, "taxRate": 21}, + {"description": "Servicio B", "quantity": 2, "unitPrice": 40, "taxRate": 10} ] }, "client": { @@ -83,16 +80,18 @@ Compatible con el BFF de Dolibarr. Se detecta por la presencia del campo `invoic | Dolibarr | VeriFactu | |---|---| | `invoice.number` | `num_serie` | -| `invoice.date` | `fecha_expedicion` (convierte ISO → dd-mm-yyyy) | +| `invoice.date` | `fecha_expedicion` (ISO → dd-mm-yyyy) | | `invoice.notePublic` | `descripcion` | +| `lines[].quantity × unitPrice` | calcula `base = total / (1 + rate/100)`, `cuota = total - base` | | `lines[].taxRate` | agrupa por tipo → `iva[].tipo` | -| `lines[].total` | calcula `base = total / (1 + rate/100)`, `cuota = total - base` | | `client.name` | `destinatario.nombre` | | `client.vatNumber` | `destinatario.nif` | -| `invoice.total` | `importe_total` | +| suma de líneas | `importe_total` (calculado automáticamente) | | `emisor.nif` | `emisor_nif` | | `emisor.nombre` | `emisor_nombre` | +> Los totales (`totalHt`, `totalTax`, `total`) **no se envían** — se calculan a partir de las líneas. Esto evita inconsistencias y permite validar que los números cuadran. + --- ## Añadir un nuevo formato diff --git a/internal/formats/dolibarr/format.go b/internal/formats/dolibarr/format.go index f059d32..79df925 100644 --- a/internal/formats/dolibarr/format.go +++ b/internal/formats/dolibarr/format.go @@ -18,20 +18,17 @@ type Transformer struct{} func (t *Transformer) Name() string { return "dolibarr" } type Input struct { - Invoice InvoiceInput `json:"invoice"` - Client *ClientInput `json:"client,omitempty"` - Emisor EmisorInput `json:"emisor"` - Sistema SistemaInput `json:"sistema"` + Invoice InvoiceInput `json:"invoice"` + Client *ClientInput `json:"client,omitempty"` + Emisor EmisorInput `json:"emisor"` + Sistema SistemaInput `json:"sistema"` } type InvoiceInput struct { - Number string `json:"number"` - Date string `json:"date"` - TotalHt float64 `json:"totalHt"` - TotalTax float64 `json:"totalTax"` - Total float64 `json:"total"` - NotePublic string `json:"notePublic,omitempty"` - Lines []LineInput `json:"lines"` + Number string `json:"number"` + Date string `json:"date"` + NotePublic string `json:"notePublic,omitempty"` + Lines []LineInput `json:"lines"` } type LineInput struct { @@ -39,11 +36,10 @@ type LineInput struct { Quantity float64 `json:"quantity"` UnitPrice float64 `json:"unitPrice"` TaxRate float64 `json:"taxRate"` - Total float64 `json:"total"` } type ClientInput struct { - Name string `json:"name"` + Name string `json:"name"` VatNumber string `json:"vatNumber"` } @@ -64,6 +60,10 @@ func (t *Transformer) Transform(raw json.RawMessage) (*formats.TransformResult, return nil, fmt.Errorf("invalid dolibarr format: %w", err) } + if len(in.Invoice.Lines) == 0 { + return nil, fmt.Errorf("dolibarr format: at least one line is required") + } + date, err := parseDate(in.Invoice.Date) if err != nil { return nil, fmt.Errorf("invalid invoice date: %w", err) @@ -72,8 +72,9 @@ func (t *Transformer) Transform(raw json.RawMessage) (*formats.TransformResult, ivaMap := make(map[float64]*formats.IVAData) for _, line := range in.Invoice.Lines { rate := line.TaxRate - base := line.Total / (1 + rate/100) - cuota := line.Total - base + lineTotal := line.Quantity * line.UnitPrice + base := lineTotal / (1 + rate/100) + cuota := lineTotal - base if existing, ok := ivaMap[rate]; ok { existing.Base += base @@ -88,9 +89,11 @@ func (t *Transformer) Transform(raw json.RawMessage) (*formats.TransformResult, } iva := make([]formats.IVAData, 0, len(ivaMap)) + var importeTotal float64 for _, v := range ivaMap { v.Base = round2(v.Base) v.Cuota = round2(v.Cuota) + importeTotal += v.Base + v.Cuota iva = append(iva, *v) } @@ -107,11 +110,6 @@ func (t *Transformer) Transform(raw json.RawMessage) (*formats.TransformResult, desc = "Factura" } - importeTotal := in.Invoice.Total - if importeTotal == 0 { - importeTotal = in.Invoice.TotalHt + in.Invoice.TotalTax - } - return &formats.TransformResult{ EmisorNIF: in.Emisor.NIF, EmisorNombre: in.Emisor.Nombre,