refactor(dolibarr): use typed fields matching web→BFF format
- Remove totalHt/totalTax/total from input (calculated from lines) - Remove line.total (calculated as quantity × unitPrice) - All numeric fields are proper numbers (not strings) - Dates are ISO 8601 (converted to dd-mm-yyyy internally) - Totals calculated and validated automatically
This commit is contained in:
parent
572358f96a
commit
b99d09789b
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Reference in New Issue