se agrega fallback OWTR y calculo de fecha de vencimiento por condicion de pago
- hana_manager.get_delivery ahora busca en ODLN y, si no encuentra, en OWTR - sapb1_manager resuelve StockTransfers -> Orders (via U_IDC_NUMOV) para validar precios de flete cuando la guia es un traslado de inventario - se corrige el orden de parametros tipo/serie/numero (sin cruces) en get_delivery, get_delivery_notes y _get_delivery_entries - se agrega sapb1_manager.obtener_condicion_pago (BusinessPartners + PaymentTermsTypes) y pipeline._calcular_fecha_vencimiento para que DocDueDate del draft sea fecha de contabilizacion + dias de la condicion de pago del proveedor - se agrega variante de regex para "VALOR REFER" truncado en sunat_schema.py - se actualiza documentacion (docs/) reflejando estos cambios Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6b0ed6f519
commit
15e3b5079c
231 files changed
+21421
-475
No files matched your search
+61
-39
@@ -2,8 +2,8 @@
|
||||
|
||||
## Ubicación
|
||||
|
||||
- **Archivo:** `pipeline.py` (línea 248-370)
|
||||
- **Librería:** `sunat_schema.py` (línea 1-520)
|
||||
- **Archivo:** `pipeline.py` (línea 310-463, función anidada en `_procesar_un_correo`)
|
||||
- **Librería:** `sunat_schema.py` (línea 1-720)
|
||||
|
||||
---
|
||||
|
||||
@@ -18,14 +18,19 @@ flowchart TD
|
||||
D --> E{Hay datos?}
|
||||
E -- No --> F[ℹ️ XML sin datos]
|
||||
F --> B
|
||||
E -- Sí --> G[Construir nombre PDF canónico]
|
||||
G --> H[Buscar PDF en cache o fallback]
|
||||
H --> I[Cruzar documentos_referencia desde PDF]:::highlight
|
||||
I --> J[Cruzar guías de remisión por línea]:::highlight
|
||||
E -- Sí --> CDR{Es CDR?}
|
||||
CDR -- Sí --> CDR2[Solo renombrar, continue]
|
||||
CDR2 --> B
|
||||
CDR -- No --> G[Renombrar XML a nombre canónico]
|
||||
G --> H[Construir nombre PDF canónico y buscarlo]
|
||||
H --> RED[_copiar_a_red]:::highlight
|
||||
RED --> I[Cruzar documentos_referencia desde PDF si faltan]:::highlight
|
||||
I --> J[Asignar guías de remisión por línea]:::highlight
|
||||
J --> K[Agregar a lista de documentos]
|
||||
K --> B
|
||||
|
||||
B -- Fin --> L[return documentos]
|
||||
B -- Fin --> L[Post-proceso: resolver GRE desde guía transportista referenciada]:::highlight
|
||||
L --> M[return documentos]
|
||||
|
||||
classDef highlight fill:#fff3e0,stroke:#e65100,stroke-width:3px;
|
||||
```
|
||||
@@ -46,45 +51,43 @@ _procesar_xmls(pdf_cache) → list[dict]
|
||||
│ ├── xml_path = carpeta + adjunto
|
||||
│ │
|
||||
│ ├── sunat_schema.extraer_datos_xml(xml_path) → data | None
|
||||
│ │ └── Solo procesa Invoice tipo 01 y DespatchAdvice tipo 31
|
||||
│ │ └── Procesa Invoice tipo 01, DespatchAdvice tipo 09 y 31, y CDR (ApplicationResponse)
|
||||
│ │
|
||||
│ ├── ¿data es None? → ℹ️ XML sin datos, continue
|
||||
│ │
|
||||
│ ├── ¿data["es_cdr"]? → solo renombrar a "CDR-{ruc}-{tipo}-{serie}-{numero}.xml", continue
|
||||
│ │
|
||||
│ ├── data["ruta_xml"] = xml_path
|
||||
│ │
|
||||
│ ├── Construir nombre PDF canónico:
|
||||
│ │ └── "{ruc_emisor}-{tipo}-{serie}-{numero}.pdf"
|
||||
│ ├── Renombrar XML a nombre canónico:
|
||||
│ │ └── "{ruc_emisor}-{tipo}-{serie}-{numero}.xml"
|
||||
│ │
|
||||
│ ├── Buscar PDF asociado:
|
||||
│ │ ├── 1. pdf_cache por nombre canónico
|
||||
│ │ └── 2. Fallback: mismo nombre con extensión .pdf
|
||||
│ ├── Construir nombre PDF canónico y buscar el PDF asociado:
|
||||
│ │ ├── 1. Mismo nombre canónico con extensión .pdf
|
||||
│ │ └── 2. Fallback: mismo nombre que el XML renombrado, con extensión .pdf
|
||||
│ │ └── data["ruta_pdf"] = ruta encontrada o None
|
||||
│ │
|
||||
│ ├── _copiar_a_red(data, _log)
|
||||
│ │ └── Copia PDF/XML a la ruta de red (RUTA_RED_FACTURACION), si está configurada.
|
||||
│ │ Guarda data["ruta_red_pdf"] / data["ruta_red_xml"] (ver docs/pipeline.md).
|
||||
│ │
|
||||
│ ├── ¿data sin documentos_referencia?
|
||||
│ │ ├── Buscar en pdf_cache por nombre canónico
|
||||
│ │ │ └── pdf_utilities.extraer_referencias(items) → refs
|
||||
│ │ ├── Si no → fallback: buscar en email["datos_pdf"]
|
||||
│ │ └── Si encuentra → data["documentos_referencia"] = refs
|
||||
│ │
|
||||
│ ├── Asignar guías de remisión por línea:
|
||||
│ │ ├── refs_09 = [tipo 09] desde documentos_referencia
|
||||
│ │ ├── refs_31 = [tipo 31] desde documentos_referencia
|
||||
│ │ ├── pdf_items = pdf_cache[canonical].items[] (si existe)
|
||||
│ │ │
|
||||
│ │ └── Por cada línea en data["lineas"]:
|
||||
│ │ │
|
||||
│ │ ├── ¿Hay PDF items para esta línea?
|
||||
│ │ │ ├── Parsear guias_remision[] desde descripción PDF
|
||||
│ │ │ │ └── Regex: "{serie}-{numero}" con prefijo T/V/E
|
||||
│ │ │ └── Parsear guia_transportista desde descripción PDF
|
||||
│ │ │
|
||||
│ │ ├── ¿Hay refs_09 para esta línea?
|
||||
│ │ │ └── Asignar desde documentos_referencia ordenados
|
||||
│ │ │
|
||||
│ │ └── No → guias_remision = [], guia_transportista = None
|
||||
│ ├── Asignar guías de remisión y transportista por línea (ver prioridad abajo)
|
||||
│ │
|
||||
│ └── ✅ Agregar data a documentos[]
|
||||
│
|
||||
├── Post-procesamiento — resolver GRE desde guía de transporte referenciada:
|
||||
│ ├── guia_lookup = {(tipo, serie, numero): doc} para cada documento tipo 09/31 del correo
|
||||
│ └── Por cada factura (tipo 01), por cada documento_referencia tipo 31:
|
||||
│ ├── Buscar la guía transportista en guia_lookup
|
||||
│ ├── Tomar sus documentos_referencia tipo 09 (GRE) propios
|
||||
│ └── Asignarlos a las líneas de la factura que aún no tengan guias_remision
|
||||
│
|
||||
└── return documentos
|
||||
```
|
||||
|
||||
@@ -99,11 +102,14 @@ flowchart TD
|
||||
A[extraer_datos_xml] --> B[ET.parse xml]
|
||||
B --> C{Tag raíz?}
|
||||
|
||||
C -- ApplicationResponse --> CDR[_parse_application_response]:::highlight
|
||||
CDR --> I2[return dict con es_cdr=True]
|
||||
|
||||
C -- Invoice --> D{tipo == 01?}
|
||||
D -- Sí --> E[_parse_invoice]:::highlight
|
||||
D -- No --> F[return None]
|
||||
|
||||
C -- DespatchAdvice --> G{tipo == 31?}
|
||||
C -- DespatchAdvice --> G{tipo en 09, 31?}
|
||||
G -- Sí --> H[_parse_despatch_advice]:::highlight
|
||||
G -- No --> F
|
||||
|
||||
@@ -115,7 +121,7 @@ flowchart TD
|
||||
classDef highlight fill:#fff3e0,stroke:#e65100,stroke-width:3px;
|
||||
```
|
||||
|
||||
Solo procesa **Facturas tipo 01** y **Guías de Remisión Transportista tipo 31**.
|
||||
Procesa **Facturas tipo 01**, **Guías de Remisión Remitente tipo 09**, **Guías de Remisión Transportista tipo 31**, y **CDR** (`ApplicationResponse`, solo para renombrar el archivo, no se agrega a `documentos[]`).
|
||||
|
||||
---
|
||||
|
||||
@@ -164,10 +170,14 @@ flowchart TD
|
||||
| `cantidad` | `cbc:InvoicedQuantity` |
|
||||
| `precio_unitario` | `cac:PricingReference/cac:AlternativeConditionPrice/cbc:PriceAmount` o `cac:Price/cbc:PriceAmount` |
|
||||
| `total_linea` | `cbc:LineExtensionAmount` |
|
||||
| `guias_remision[]` / `guia_transportista` | Parseadas desde `cbc:Note` de la línea (`extraer_guias_desde_notes`) — ver prioridad de asignación más abajo |
|
||||
| `valor_referencial` | Extraído de la descripción del ítem, del `Note` de la línea, o de los `Note` a nivel de documento (`extraer_valor_referencial`) |
|
||||
|
||||
---
|
||||
|
||||
### `_parse_despatch_advice(root)` — Guía tipo 31
|
||||
### `_parse_despatch_advice(root)` — Guías tipo 09 y 31
|
||||
|
||||
La misma función procesa tanto la Guía de Remisión Remitente (09) como la Guía de Remisión Transportista (31); el `tipo` viene de `DespatchAdviceTypeCode`.
|
||||
|
||||
| Campo | Tag |
|
||||
|-------|-----|
|
||||
@@ -188,7 +198,7 @@ flowchart TD
|
||||
| `ubigeo_partida` | `Delivery/Despatch/DespatchAddress/ID` |
|
||||
| `lineas[]` | `DespatchLine` con `DeliveredQuantity` y `Item/Description` |
|
||||
|
||||
Documentos de referencia desde `AdditionalDocumentReference`.
|
||||
Documentos de referencia desde `AdditionalDocumentReference` (para la guía transportista, típicamente referencian la GRE — guía remitente — asociada).
|
||||
|
||||
---
|
||||
|
||||
@@ -205,15 +215,22 @@ pdf_cache[nombre_canonico].items[] → extraer_referencias()
|
||||
└── guia_transportista → ref tipo 31
|
||||
```
|
||||
|
||||
### 2. Asignar guías por línea
|
||||
### 2. Asignar guías por línea (dentro de `_procesar_xmls`)
|
||||
|
||||
Prioridad para asignar `guias_remision` y `guia_transportista` a cada línea:
|
||||
Prioridad para asignar `guias_remision` y `guia_transportista` a cada línea de la factura, en el orden en que realmente se aplican en el código:
|
||||
|
||||
| Prioridad | Origen | Descripción |
|
||||
|-----------|--------|-------------|
|
||||
| 1ª | PDF items | Parse desde `items[i].guias_remision[]` y `items[i].guia_transportista` con regex `{T/V/E}{serie}-{numero}` |
|
||||
| 2ª | `documentos_referencia` | Asignación ordinal: `refs_09[i]`, `refs_31[i]` |
|
||||
| 3ª | Default | `[]` y `None` |
|
||||
| 1ª | `notes_text` (ya resuelto en `sunat_schema._parse_invoice`) | Si la línea ya trae `guias_remision`/`guia_transportista` extraídos del `cbc:Note` del XML (vía `extraer_guias_desde_notes`), se conservan tal cual. |
|
||||
| 2ª | `documentos_referencia` (`refs_09`/`refs_31`) | Si la línea no trajo guías desde `notes_text`, se asigna posicionalmente desde `documentos_referencia` (`refs_09[i]`, `refs_31[i]`; la última línea recibe todas las refs restantes). |
|
||||
|
||||
### 3. Post-procesamiento — resolver GRE desde guía de transporte referenciada
|
||||
|
||||
Después de procesar todos los XML del correo, si una factura referencia una **guía transportista (31)** y esa guía (presente como documento independiente en el mismo correo) referencia a su vez una o más **GRE (09)**, esas GRE se propagan a las líneas de la factura que aún no tengan `guias_remision` asignadas.
|
||||
|
||||
### 4. `_completar_guias` (paso posterior, fuera de `_procesar_xmls`)
|
||||
|
||||
Si tras los pasos anteriores alguna línea sigue sin `guias_remision`, `_completar_guias` (ver `docs/pipeline.md`) las copia desde los `items[]` del PDF cache, parseando el texto con regex (`{T/V/E}{serie}-{numero}`).
|
||||
|
||||
---
|
||||
|
||||
@@ -246,12 +263,17 @@ Prioridad para asignar `guias_remision` y `guia_transportista` a cada línea:
|
||||
"precio_unitario": 1.3396,
|
||||
"total_linea": 468.86,
|
||||
"guias_remision": [{"tipo": "09", "serie": "T001", "numero": "127773"}],
|
||||
"guia_transportista": None
|
||||
"guia_transportista": None,
|
||||
"valor_referencial": 468.86
|
||||
}
|
||||
],
|
||||
"ruta_xml": "/path/to/20537929520-01-F002-486.xml",
|
||||
"ruta_pdf": "/path/to/20537929520-01-F002-486.pdf",
|
||||
"ruta_red_pdf": "\\\\192.168.9.66\\b1_shf\\Facturacion Electronica\\PRODUCCION\\2026\\7\\8\\01\\20100029741\\20537929520\\20100029741-01-F002-486_FormatoImpreso.pdf",
|
||||
"ruta_red_xml": "\\\\192.168.9.66\\b1_shf\\Facturacion Electronica\\PRODUCCION\\2026\\7\\8\\01\\20100029741\\20537929520\\20100029741-01-F002-486.xml",
|
||||
"tipo_descripcion": "Factura"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
`ruta_red_pdf`/`ruta_red_xml` solo aparecen si `RUTA_RED_FACTURACION` está configurada en `.env` y la copia fue exitosa; se usan luego en `_build_sap_draft` para llenar `U_VS_ANEXO_PDF`/`U_VS_ANEXO_XML` (ver `docs/pipeline.md`).
|
||||
Reference in new issue
Block a user