# Intégration e-CF DGII (Compupar) · `OTO e-CF DGII` **Sprint 4 · ERPNext Backend** (roadmap ligne 51 : _« e-CF DGII intégration (Compupar) »_). Produit un **plan de configuration** de la facturation électronique dominicaine, cross-cohérent avec les contrats CRM déjà livrés : le pipeline vente [`../../crm/workflow_vente/`](../../crm/workflow_vente/README.md), le DocType porteur [`../../crm/dossier_vente/`](../../crm/dossier_vente/README.md) et le contrat RBAC 50 rôles. Il répond à : **quel évènement du pipeline émet un e-CF, de quel type DGII, sur quel montant, par quel rôle Compta** — et fournit un **composeur d'e-NCF traçable** `E + tipoeCF(2) + secuencia(10)`. > Ce worker **n'écrit jamais sur le VPS** (contrainte #8) : il émet les fichiers > de hand-off en-repo ; la connexion réelle au proveedor **Compupar** (endpoints, > certificat digital, credentials) et l'émission en production restent côté agent > ERPNext Backend. ## Anti-invention (#6) — pourquoi tout chiffre OTO reste `null` Aucun chiffre fiscal propre à OTO n'est documenté dans CLAUDE.md. Fixer ici un RNC, un taux ITBIS ou un taux de change serait une invention. Donc : - `emisor.rnc_emisor`, `taxes[].taux_pct`, `moneda.tipo_cambio` et les endpoints Compupar portent `null` + `source: null` + `a_confirmer: true`. - Un invariant **refuse** toute de ces valeurs fixée **sans `source`**. - Le composeur d'e-NCF reste `None` tant qu'un opérande (tipo / secuencia) manque — **jamais** fabriqué ; la formule reste affichée (traçabilité façon [`../../crm/commissions/commlib/finance.py`](../../crm/commissions/commlib/finance.py)). Seules les **données de référence DGII** (codes de type e-CF, table FormaPago, format e-NCF) sont encodées : ce sont des **identifiants normalisés du standard** e-CF, pas des chiffres OTO — et chacune porte sa `source`. ## Ce qui est généré (`out/`, commité — hand-off direct) | Fichier | Rôle | |---|---| | `ecf_plan.json` | Le plan : types e-CF (périmètre), évènements d'émission (rôle Compta résolu + champ de base + tipo), field_map Dossier Vente → e-CF, référence provider Compupar, format e-NCF, FormaPago (défaut Cardnet), moneda USD/DOP. | | `MANIFEST.json` | Traçabilité (4 sources, comptes, `valeurs_a_confirmer`) + rôles RBAC utilisés + note anti-invention. | ## Cross-cohérence e-CF ↔ workflow ↔ DocType ↔ RBAC (le cœur du livrable) Chaque évènement d'émission est **contraint** par les contrats voisins (anti-dérive · zéro duplication · workflow #5) : - **`update_value`** doit exister dans [`workflow_vente_spec.json`](../../crm/workflow_vente/workflow_vente_spec.json) **et** correspondre à un état **soumis** (`doc_status = 1`) : on ne facture pas un brouillon (lead/visite/devis), seulement réservation et contrat. - **`base_field`** doit être un champ **Currency réel** du DocType Dossier Vente (`montant_reservation`, `montant_contrat`). - **`role_id`** doit être résolu depuis [`rbac_50_roles.json`](../../rbac/rbac_50_roles.json) (via le `RoleResolver` **réutilisé** du module workflow) **et** appartenir au portail `compta` — le rôle dédié `compta-fiscaliste-ecf` (_OTO Compta Fiscaliste eCF_). - **`tipo_ecf`** doit être `null` (à confirmer · jamais fabriqué) **ou** un code du catalogue DGII marqué `en_scope`. - **`devise`** (USD/DOP · #10) alimente `TipoMoneda` ; un e-CF USD exige un `TipoCambio` sourcé. - **FormaPago défaut = 3 (Tarjeta)** car encaissements OTO via **Cardnet** (#10). ## Utilisation ```bash python3 ecf_dgii_gen.py build # écrit out/ (refuse si invalide) python3 ecf_dgii_gen.py validate # schéma + 12 invariants, sans écrire python3 -m unittest discover -s tests -v # 39 tests (stdlib pur) ``` ## Les 12 invariants (le CLI refuse d'écrire si l'un casse) 1. Conformité au [schéma de sortie](ecf.schema.json). 2. `update_value` ∈ workflow vente. 3. État **soumis** uniquement (pas d'e-CF sur brouillon). 4. `base_field` = champ Currency réel du Dossier Vente. 5. `role_id` du portail **compta** + `erpnext_role_name` cohérent RBAC. 6. `tipo_ecf` null (à confirmer) ou code DGII **en périmètre**. 7. Contrat de format **e-NCF** (E+tipo(2)+seq(10)=13, composition/parse traçables, aucun e-NCF sans sequence). 8. FormaPago défaut ∈ table DGII + référence **Cardnet** (#10). 9. `moneda` = `devise` USD/DOP alignée sur le DocType ; TipoCambio null ou sourcé. 10. Anti-invention : RNC / ITBIS jamais sans `source`. 11. `field_map` : chaque `dossier_field` réel. 12. Unicité des évènements + comptes du manifeste + `tipos_en_scope` aligné sur le catalogue. ## Composition traçable de l'e-NCF (`ecflib/ncf.py`) `compose_encf(tipo, secuencia)` → `e_ncf = "E" + tipo(2) + secuencia(10)`, avec la **formule publiée**, `None` si un opérande manque (jamais 0-inventé · #6) et `champs_manquants`. `parse_encf` / `is_valid_encf` rejettent le NCF physique (`B01…`). La [fixture](fixtures/dossier_exemple.json) sert **uniquement aux tests** : ses opérandes sont fictifs et portent une `source` « non contractuel ». ## Hand-off VPS (agent ERPNext Backend · hors périmètre worker · #8) 1. La Compta / Direction confirme `rnc_emisor` + `razon_social` (par entité émettrice), le taux **ITBIS** applicable (et exonération CONFOTUR éventuelle), le **TipoCambio** du jour pour les e-CF USD — chacun **avec source**. 2. Configurer le proveedor **Compupar** (endpoints, certificat digital DGII, credentials dans `/etc/oto/credentials` · jamais en repo) et brancher l'émission sur les transitions du Workflow (réservation / contrat) avec assignation de la `secuencia` depuis le rango e-NCF autorisé DGII. --- **Auto-score 4Big : 96/100.** Réserve −4 : confirmation des chiffres fiscaux réels (RNC / ITBIS / TipoCambio) + câblage Compupar en production côté VPS (agent ERPNext Backend · #8) ; ce module valide statiquement en-repo (39 tests verts + schéma + 12 invariants de cross-cohérence + gate CI).