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": {
|
"invoice": {
|
||||||
"number": "FA2024/001",
|
"number": "FA2024/001",
|
||||||
"date": "2024-09-13T00:00:00Z",
|
"date": "2024-09-13T00:00:00Z",
|
||||||
"totalHt": 100.00,
|
|
||||||
"totalTax": 21.00,
|
|
||||||
"total": 121.00,
|
|
||||||
"notePublic": "Factura de prueba",
|
"notePublic": "Factura de prueba",
|
||||||
"lines": [
|
"lines": [
|
||||||
{"description": "Servicio", "quantity": 1, "unitPrice": 100, "taxRate": 21, "total": 121.00}
|
{"description": "Servicio", "quantity": 1, "unitPrice": 100, "taxRate": 21}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"client": {
|
"client": {
|
||||||
|
|
|
||||||
|
|
@ -46,20 +46,17 @@ Formato propio de la API. Se detecta por la presencia del campo `factura`.
|
||||||
|
|
||||||
## Formato: `dolibarr`
|
## 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
|
```json
|
||||||
{
|
{
|
||||||
"invoice": {
|
"invoice": {
|
||||||
"number": "FA2024/001",
|
"number": "FA2024/001",
|
||||||
"date": "2024-09-13T00:00:00Z",
|
"date": "2024-09-13T00:00:00Z",
|
||||||
"totalHt": 100.00,
|
|
||||||
"totalTax": 21.00,
|
|
||||||
"total": 121.00,
|
|
||||||
"notePublic": "Servicios de consultoría",
|
"notePublic": "Servicios de consultoría",
|
||||||
"lines": [
|
"lines": [
|
||||||
{"description": "Servicio A", "quantity": 1, "unitPrice": 60, "taxRate": 21, "total": 72.60},
|
{"description": "Servicio A", "quantity": 1, "unitPrice": 60, "taxRate": 21},
|
||||||
{"description": "Servicio B", "quantity": 1, "unitPrice": 40, "taxRate": 21, "total": 48.40}
|
{"description": "Servicio B", "quantity": 2, "unitPrice": 40, "taxRate": 10}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"client": {
|
"client": {
|
||||||
|
|
@ -83,16 +80,18 @@ Compatible con el BFF de Dolibarr. Se detecta por la presencia del campo `invoic
|
||||||
| Dolibarr | VeriFactu |
|
| Dolibarr | VeriFactu |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `invoice.number` | `num_serie` |
|
| `invoice.number` | `num_serie` |
|
||||||
| `invoice.date` | `fecha_expedicion` (convierte ISO → dd-mm-yyyy) |
|
| `invoice.date` | `fecha_expedicion` (ISO → dd-mm-yyyy) |
|
||||||
| `invoice.notePublic` | `descripcion` |
|
| `invoice.notePublic` | `descripcion` |
|
||||||
|
| `lines[].quantity × unitPrice` | calcula `base = total / (1 + rate/100)`, `cuota = total - base` |
|
||||||
| `lines[].taxRate` | agrupa por tipo → `iva[].tipo` |
|
| `lines[].taxRate` | agrupa por tipo → `iva[].tipo` |
|
||||||
| `lines[].total` | calcula `base = total / (1 + rate/100)`, `cuota = total - base` |
|
|
||||||
| `client.name` | `destinatario.nombre` |
|
| `client.name` | `destinatario.nombre` |
|
||||||
| `client.vatNumber` | `destinatario.nif` |
|
| `client.vatNumber` | `destinatario.nif` |
|
||||||
| `invoice.total` | `importe_total` |
|
| suma de líneas | `importe_total` (calculado automáticamente) |
|
||||||
| `emisor.nif` | `emisor_nif` |
|
| `emisor.nif` | `emisor_nif` |
|
||||||
| `emisor.nombre` | `emisor_nombre` |
|
| `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
|
## Añadir un nuevo formato
|
||||||
|
|
|
||||||
|
|
@ -18,20 +18,17 @@ type Transformer struct{}
|
||||||
func (t *Transformer) Name() string { return "dolibarr" }
|
func (t *Transformer) Name() string { return "dolibarr" }
|
||||||
|
|
||||||
type Input struct {
|
type Input struct {
|
||||||
Invoice InvoiceInput `json:"invoice"`
|
Invoice InvoiceInput `json:"invoice"`
|
||||||
Client *ClientInput `json:"client,omitempty"`
|
Client *ClientInput `json:"client,omitempty"`
|
||||||
Emisor EmisorInput `json:"emisor"`
|
Emisor EmisorInput `json:"emisor"`
|
||||||
Sistema SistemaInput `json:"sistema"`
|
Sistema SistemaInput `json:"sistema"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type InvoiceInput struct {
|
type InvoiceInput struct {
|
||||||
Number string `json:"number"`
|
Number string `json:"number"`
|
||||||
Date string `json:"date"`
|
Date string `json:"date"`
|
||||||
TotalHt float64 `json:"totalHt"`
|
NotePublic string `json:"notePublic,omitempty"`
|
||||||
TotalTax float64 `json:"totalTax"`
|
Lines []LineInput `json:"lines"`
|
||||||
Total float64 `json:"total"`
|
|
||||||
NotePublic string `json:"notePublic,omitempty"`
|
|
||||||
Lines []LineInput `json:"lines"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type LineInput struct {
|
type LineInput struct {
|
||||||
|
|
@ -39,11 +36,10 @@ type LineInput struct {
|
||||||
Quantity float64 `json:"quantity"`
|
Quantity float64 `json:"quantity"`
|
||||||
UnitPrice float64 `json:"unitPrice"`
|
UnitPrice float64 `json:"unitPrice"`
|
||||||
TaxRate float64 `json:"taxRate"`
|
TaxRate float64 `json:"taxRate"`
|
||||||
Total float64 `json:"total"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ClientInput struct {
|
type ClientInput struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
VatNumber string `json:"vatNumber"`
|
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)
|
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)
|
date, err := parseDate(in.Invoice.Date)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("invalid invoice date: %w", err)
|
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)
|
ivaMap := make(map[float64]*formats.IVAData)
|
||||||
for _, line := range in.Invoice.Lines {
|
for _, line := range in.Invoice.Lines {
|
||||||
rate := line.TaxRate
|
rate := line.TaxRate
|
||||||
base := line.Total / (1 + rate/100)
|
lineTotal := line.Quantity * line.UnitPrice
|
||||||
cuota := line.Total - base
|
base := lineTotal / (1 + rate/100)
|
||||||
|
cuota := lineTotal - base
|
||||||
|
|
||||||
if existing, ok := ivaMap[rate]; ok {
|
if existing, ok := ivaMap[rate]; ok {
|
||||||
existing.Base += base
|
existing.Base += base
|
||||||
|
|
@ -88,9 +89,11 @@ func (t *Transformer) Transform(raw json.RawMessage) (*formats.TransformResult,
|
||||||
}
|
}
|
||||||
|
|
||||||
iva := make([]formats.IVAData, 0, len(ivaMap))
|
iva := make([]formats.IVAData, 0, len(ivaMap))
|
||||||
|
var importeTotal float64
|
||||||
for _, v := range ivaMap {
|
for _, v := range ivaMap {
|
||||||
v.Base = round2(v.Base)
|
v.Base = round2(v.Base)
|
||||||
v.Cuota = round2(v.Cuota)
|
v.Cuota = round2(v.Cuota)
|
||||||
|
importeTotal += v.Base + v.Cuota
|
||||||
iva = append(iva, *v)
|
iva = append(iva, *v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -107,11 +110,6 @@ func (t *Transformer) Transform(raw json.RawMessage) (*formats.TransformResult,
|
||||||
desc = "Factura"
|
desc = "Factura"
|
||||||
}
|
}
|
||||||
|
|
||||||
importeTotal := in.Invoice.Total
|
|
||||||
if importeTotal == 0 {
|
|
||||||
importeTotal = in.Invoice.TotalHt + in.Invoice.TotalTax
|
|
||||||
}
|
|
||||||
|
|
||||||
return &formats.TransformResult{
|
return &formats.TransformResult{
|
||||||
EmisorNIF: in.Emisor.NIF,
|
EmisorNIF: in.Emisor.NIF,
|
||||||
EmisorNombre: in.Emisor.Nombre,
|
EmisorNombre: in.Emisor.Nombre,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue