@factpulse/sdk 2.0.11 → 2.0.49
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/SETUP_GITHUB_ACTIONS.md +70 -8
- package/.github/workflows/publish-npm.yml +5 -7
- package/.openapi-generator/FILES +58 -26
- package/CHANGELOG.md +3 -3
- package/README.md +165 -116
- package/api/afnorpdppaapi.ts +149 -0
- package/api/chorus-pro-api.ts +151 -181
- package/api/traitement-facture-api.ts +12 -12
- package/api/vrification-pdfxmlapi.ts +536 -0
- package/api.ts +1 -0
- package/dist/api/afnorpdppaapi.d.ts +65 -0
- package/dist/api/afnorpdppaapi.js +142 -1
- package/dist/api/afnorpdppadirectory-service-api.js +4 -1
- package/dist/api/afnorpdppaflow-service-api.js +4 -1
- package/dist/api/chorus-pro-api.d.ts +168 -103
- package/dist/api/chorus-pro-api.js +155 -160
- package/dist/api/sant-api.js +4 -1
- package/dist/api/traitement-facture-api.d.ts +12 -12
- package/dist/api/traitement-facture-api.js +16 -13
- package/dist/api/utilisateur-api.js +4 -1
- package/dist/api/vrification-pdfxmlapi.d.ts +237 -0
- package/dist/api/vrification-pdfxmlapi.js +514 -0
- package/dist/api.d.ts +1 -0
- package/dist/api.js +1 -0
- package/dist/base.js +4 -1
- package/dist/esm/api/afnorpdppaapi.d.ts +65 -0
- package/dist/esm/api/afnorpdppaapi.js +139 -1
- package/dist/esm/api/chorus-pro-api.d.ts +168 -103
- package/dist/esm/api/chorus-pro-api.js +151 -159
- package/dist/esm/api/traitement-facture-api.d.ts +12 -12
- package/dist/esm/api/traitement-facture-api.js +12 -12
- package/dist/esm/api/vrification-pdfxmlapi.d.ts +237 -0
- package/dist/esm/api/vrification-pdfxmlapi.js +504 -0
- package/dist/esm/api.d.ts +1 -0
- package/dist/esm/api.js +1 -0
- package/dist/esm/models/{utilisateur.d.ts → apierror.d.ts} +12 -15
- package/dist/esm/models/beneficiaire.d.ts +26 -0
- package/dist/esm/models/bounding-box-schema.d.ts +44 -0
- package/dist/esm/models/cadre-de-facturation.d.ts +3 -1
- package/dist/esm/models/champ-verifie-schema.d.ts +42 -0
- package/dist/esm/models/code-cadre-facturation.d.ts +3 -0
- package/dist/esm/models/code-cadre-facturation.js +3 -0
- package/dist/esm/models/destinataire.d.ts +1 -1
- package/dist/esm/models/dimension-page-schema.d.ts +24 -0
- package/dist/esm/models/{quota-info.d.ts → direction-flux.d.ts} +6 -8
- package/dist/esm/models/direction-flux.js +20 -0
- package/dist/esm/models/error-level.d.ts +16 -0
- package/dist/esm/models/error-level.js +17 -0
- package/dist/esm/models/{body-ajouter-fichier-api-v1-chorus-pro-transverses-ajouter-fichier-post.d.ts → error-source.d.ts} +11 -9
- package/dist/esm/models/error-source.js +23 -0
- package/dist/esm/models/facture-entrante.d.ts +68 -0
- package/dist/esm/models/facture-factur-x.d.ts +2 -0
- package/dist/esm/models/{body-rechercher-factures-destinataire-api-v1-chorus-pro-factures-rechercher-destinataire-post.d.ts → flux-resume.d.ts} +12 -8
- package/dist/esm/models/format-facture.d.ts +20 -0
- package/dist/esm/models/format-facture.js +21 -0
- package/dist/esm/models/fournisseur-entrant.d.ts +29 -0
- package/dist/esm/models/fournisseur.d.ts +1 -1
- package/dist/esm/models/index.d.ts +28 -13
- package/dist/esm/models/index.js +28 -13
- package/dist/esm/models/ligne-de-tva.d.ts +3 -1
- package/dist/esm/models/nature-operation.d.ts +31 -0
- package/dist/esm/models/nature-operation.js +32 -0
- package/dist/esm/models/note-obligatoire-schema.d.ts +34 -0
- package/dist/esm/models/note.d.ts +2 -2
- package/dist/esm/models/pdpcredentials.d.ts +33 -0
- package/dist/esm/models/profil-flux.d.ts +20 -0
- package/dist/esm/models/profil-flux.js +21 -0
- package/dist/esm/models/{body-completer-facture-api-v1-chorus-pro-factures-completer-post.d.ts → reponse-healthcheck-afnor.d.ts} +16 -8
- package/dist/esm/models/reponse-recherche-flux.d.ts +33 -0
- package/dist/esm/models/reponse-soumission-flux.d.ts +38 -0
- package/dist/esm/models/reponse-verification-succes.d.ts +56 -0
- package/dist/esm/models/reponse-verification-succes.js +14 -0
- package/dist/esm/models/requete-recherche-flux.d.ts +34 -0
- package/dist/esm/models/requete-recherche-flux.js +14 -0
- package/dist/esm/models/requete-soumission-flux.d.ts +31 -0
- package/dist/esm/models/requete-soumission-flux.js +14 -0
- package/dist/esm/models/statut-acquittement.d.ts +20 -0
- package/dist/esm/models/statut-acquittement.js +21 -0
- package/dist/esm/models/statut-champ-api.d.ts +22 -0
- package/dist/esm/models/statut-champ-api.js +23 -0
- package/dist/esm/models/syntaxe-flux.d.ts +22 -0
- package/dist/esm/models/syntaxe-flux.js +23 -0
- package/dist/esm/models/type-document.d.ts +37 -0
- package/dist/esm/models/type-document.js +38 -0
- package/dist/esm/models/type-facture.d.ts +65 -3
- package/dist/esm/models/type-facture.js +65 -3
- package/dist/esm/models/type-flux.d.ts +22 -0
- package/dist/esm/models/type-flux.js +23 -0
- package/dist/esm/models/validation-error-detail.d.ts +32 -0
- package/dist/esm/models/validation-error-detail.js +14 -0
- package/dist/esm/src/helpers/client.d.ts +265 -0
- package/dist/esm/src/helpers/client.js +817 -0
- package/dist/esm/src/helpers/exceptions.d.ts +23 -0
- package/dist/esm/src/helpers/exceptions.js +27 -0
- package/dist/esm/src/helpers/index.d.ts +3 -0
- package/dist/esm/src/helpers/index.js +6 -0
- package/dist/models/{utilisateur.d.ts → apierror.d.ts} +12 -15
- package/dist/models/beneficiaire.d.ts +26 -0
- package/dist/models/bounding-box-schema.d.ts +44 -0
- package/dist/models/cadre-de-facturation.d.ts +3 -1
- package/dist/models/champ-verifie-schema.d.ts +42 -0
- package/dist/models/code-cadre-facturation.d.ts +3 -0
- package/dist/models/code-cadre-facturation.js +3 -0
- package/dist/models/destinataire.d.ts +1 -1
- package/dist/models/dimension-page-schema.d.ts +24 -0
- package/dist/models/{quota-info.d.ts → direction-flux.d.ts} +6 -8
- package/dist/models/direction-flux.js +23 -0
- package/dist/models/error-level.d.ts +16 -0
- package/dist/models/error-level.js +20 -0
- package/dist/models/error-source.d.ts +22 -0
- package/dist/models/error-source.js +26 -0
- package/dist/models/facture-entrante.d.ts +68 -0
- package/dist/models/facture-factur-x.d.ts +2 -0
- package/dist/models/flux-resume.d.ts +24 -0
- package/dist/models/format-facture.d.ts +20 -0
- package/dist/models/format-facture.js +24 -0
- package/dist/models/fournisseur-entrant.d.ts +29 -0
- package/dist/models/fournisseur.d.ts +1 -1
- package/dist/models/index.d.ts +28 -13
- package/dist/models/index.js +28 -13
- package/dist/models/ligne-de-tva.d.ts +3 -1
- package/dist/models/nature-operation.d.ts +31 -0
- package/dist/models/nature-operation.js +35 -0
- package/dist/models/note-obligatoire-schema.d.ts +34 -0
- package/dist/models/note.d.ts +2 -2
- package/dist/models/pdpcredentials.d.ts +33 -0
- package/dist/models/profil-flux.d.ts +20 -0
- package/dist/models/profil-flux.js +24 -0
- package/dist/{esm/models/body-recycler-facture-api-v1-chorus-pro-factures-recycler-post.d.ts → models/reponse-healthcheck-afnor.d.ts} +16 -8
- package/dist/models/reponse-recherche-flux.d.ts +33 -0
- package/dist/models/reponse-soumission-flux.d.ts +38 -0
- package/dist/models/reponse-verification-succes.d.ts +56 -0
- package/dist/models/reponse-verification-succes.js +15 -0
- package/dist/models/requete-recherche-flux.d.ts +34 -0
- package/dist/models/requete-recherche-flux.js +15 -0
- package/dist/models/requete-soumission-flux.d.ts +31 -0
- package/dist/models/requete-soumission-flux.js +15 -0
- package/dist/models/statut-acquittement.d.ts +20 -0
- package/dist/models/statut-acquittement.js +24 -0
- package/dist/models/statut-champ-api.d.ts +22 -0
- package/dist/models/statut-champ-api.js +26 -0
- package/dist/models/syntaxe-flux.d.ts +22 -0
- package/dist/models/syntaxe-flux.js +26 -0
- package/dist/models/type-document.d.ts +37 -0
- package/dist/models/type-document.js +41 -0
- package/dist/models/type-facture.d.ts +65 -3
- package/dist/models/type-facture.js +65 -3
- package/dist/models/type-flux.d.ts +22 -0
- package/dist/models/type-flux.js +26 -0
- package/dist/models/validation-error-detail.d.ts +32 -0
- package/dist/models/validation-error-detail.js +15 -0
- package/dist/src/helpers/client.d.ts +265 -0
- package/dist/src/helpers/client.js +866 -0
- package/dist/src/helpers/exceptions.d.ts +23 -0
- package/dist/src/helpers/exceptions.js +35 -0
- package/dist/src/helpers/index.d.ts +3 -0
- package/dist/src/helpers/index.js +23 -0
- package/docs/AFNORPDPPAApi.md +108 -0
- package/docs/APIError.md +25 -0
- package/docs/Beneficiaire.md +31 -0
- package/docs/BoundingBoxSchema.md +33 -0
- package/docs/CadreDeFacturation.md +3 -1
- package/docs/ChampVerifieSchema.md +37 -0
- package/docs/ChorusProApi.md +54 -69
- package/docs/CodeCadreFacturation.md +1 -0
- package/docs/DimensionPageSchema.md +23 -0
- package/docs/DirectionFlux.md +11 -0
- package/docs/ErrorLevel.md +10 -0
- package/docs/ErrorSource.md +22 -0
- package/docs/FactureEntrante.md +57 -0
- package/docs/FactureFacturX.md +2 -0
- package/docs/FluxResume.md +35 -0
- package/docs/FormatFacture.md +13 -0
- package/docs/FournisseurEntrant.md +35 -0
- package/docs/LigneDeTVA.md +5 -1
- package/docs/NatureOperation.md +35 -0
- package/docs/Note.md +4 -4
- package/docs/NoteObligatoireSchema.md +33 -0
- package/docs/PDPCredentials.md +29 -0
- package/docs/ProfilFlux.md +13 -0
- package/docs/ReponseHealthcheckAFNOR.md +25 -0
- package/docs/ReponseRechercheFlux.md +27 -0
- package/docs/ReponseSoumissionFlux.md +33 -0
- package/docs/ReponseVerificationSucces.md +39 -0
- package/docs/RequeteRechercheFlux.md +37 -0
- package/docs/RequeteSoumissionFlux.md +31 -0
- package/docs/StatutAcquittement.md +13 -0
- package/docs/StatutChampAPI.md +17 -0
- package/docs/SyntaxeFlux.md +17 -0
- package/docs/TraitementFactureApi.md +3 -3
- package/docs/TypeDocument.md +17 -0
- package/docs/TypeFacture.md +31 -3
- package/docs/TypeFlux.md +17 -0
- package/docs/ValidationErrorDetail.md +29 -0
- package/docs/VrificationPDFXMLApi.md +335 -0
- package/models/{utilisateur.ts → apierror.ts} +12 -15
- package/models/beneficiaire.ts +34 -0
- package/models/bounding-box-schema.ts +50 -0
- package/models/cadre-de-facturation.ts +5 -1
- package/models/champ-verifie-schema.ts +54 -0
- package/models/code-cadre-facturation.ts +3 -0
- package/models/destinataire.ts +1 -1
- package/models/dimension-page-schema.ts +30 -0
- package/models/{quota-info.ts → direction-flux.ts} +10 -8
- package/models/error-level.ts +26 -0
- package/models/error-source.ts +32 -0
- package/models/facture-entrante.ts +82 -0
- package/models/facture-factur-x.ts +4 -0
- package/models/flux-resume.ts +30 -0
- package/models/format-facture.ts +30 -0
- package/models/fournisseur-entrant.ts +39 -0
- package/models/fournisseur.ts +1 -1
- package/models/index.ts +28 -13
- package/models/ligne-de-tva.ts +3 -1
- package/models/nature-operation.ts +41 -0
- package/models/{body-lister-services-structure-api-v1-chorus-pro-structures-id-structure-cpp-services-get.ts → note-obligatoire-schema.ts} +24 -5
- package/models/note.ts +2 -2
- package/models/pdpcredentials.ts +39 -0
- package/models/profil-flux.ts +30 -0
- package/models/reponse-healthcheck-afnor.ts +34 -0
- package/models/{body-ajouter-fichier-api-v1-chorus-pro-transverses-ajouter-fichier-post.ts → reponse-recherche-flux.ts} +21 -8
- package/models/{body-completer-facture-api-v1-chorus-pro-factures-completer-post.ts → reponse-soumission-flux.ts} +26 -10
- package/models/reponse-verification-succes.ts +68 -0
- package/models/requete-recherche-flux.ts +48 -0
- package/models/{body-rechercher-factures-destinataire-api-v1-chorus-pro-factures-rechercher-destinataire-post.ts → requete-soumission-flux.ts} +23 -6
- package/models/statut-acquittement.ts +30 -0
- package/models/statut-champ-api.ts +32 -0
- package/models/syntaxe-flux.ts +32 -0
- package/models/type-document.ts +47 -0
- package/models/type-facture.ts +65 -3
- package/models/type-flux.ts +32 -0
- package/models/validation-error-detail.ts +44 -0
- package/package.json +2 -2
- package/src/helpers/client.ts +882 -0
- package/src/helpers/exceptions.ts +32 -0
- package/src/helpers/index.ts +6 -0
- package/tsconfig.esm.json +1 -0
- package/tsconfig.json +1 -0
- package/dist/esm/models/body-lister-services-structure-api-v1-chorus-pro-structures-id-structure-cpp-services-get.d.ts +0 -17
- package/dist/esm/models/body-rechercher-factures-fournisseur-api-v1-chorus-pro-factures-rechercher-fournisseur-post.d.ts +0 -20
- package/dist/esm/models/body-telecharger-groupe-factures-api-v1-chorus-pro-factures-telecharger-groupe-post.d.ts +0 -20
- package/dist/esm/models/body-traiter-facture-recue-api-v1-chorus-pro-factures-traiter-facture-recue-post.d.ts +0 -20
- package/dist/esm/models/body-valideur-consulter-facture-api-v1-chorus-pro-factures-valideur-consulter-post.d.ts +0 -20
- package/dist/esm/models/body-valideur-rechercher-factures-api-v1-chorus-pro-factures-valideur-rechercher-post.d.ts +0 -20
- package/dist/esm/models/body-valideur-traiter-facture-api-v1-chorus-pro-factures-valideur-traiter-post.d.ts +0 -20
- package/dist/models/body-ajouter-fichier-api-v1-chorus-pro-transverses-ajouter-fichier-post.d.ts +0 -20
- package/dist/models/body-completer-facture-api-v1-chorus-pro-factures-completer-post.d.ts +0 -20
- package/dist/models/body-lister-services-structure-api-v1-chorus-pro-structures-id-structure-cpp-services-get.d.ts +0 -17
- package/dist/models/body-rechercher-factures-destinataire-api-v1-chorus-pro-factures-rechercher-destinataire-post.d.ts +0 -20
- package/dist/models/body-rechercher-factures-fournisseur-api-v1-chorus-pro-factures-rechercher-fournisseur-post.d.ts +0 -20
- package/dist/models/body-recycler-facture-api-v1-chorus-pro-factures-recycler-post.d.ts +0 -20
- package/dist/models/body-telecharger-groupe-factures-api-v1-chorus-pro-factures-telecharger-groupe-post.d.ts +0 -20
- package/dist/models/body-traiter-facture-recue-api-v1-chorus-pro-factures-traiter-facture-recue-post.d.ts +0 -20
- package/dist/models/body-valideur-consulter-facture-api-v1-chorus-pro-factures-valideur-consulter-post.d.ts +0 -20
- package/dist/models/body-valideur-rechercher-factures-api-v1-chorus-pro-factures-valideur-rechercher-post.d.ts +0 -20
- package/dist/models/body-valideur-traiter-facture-api-v1-chorus-pro-factures-valideur-traiter-post.d.ts +0 -20
- package/docs/BodyAjouterFichierApiV1ChorusProTransversesAjouterFichierPost.md +0 -24
- package/docs/BodyCompleterFactureApiV1ChorusProFacturesCompleterPost.md +0 -24
- package/docs/BodyListerServicesStructureApiV1ChorusProStructuresIdStructureCppServicesGet.md +0 -22
- package/docs/BodyRechercherFacturesDestinataireApiV1ChorusProFacturesRechercherDestinatairePost.md +0 -24
- package/docs/BodyRechercherFacturesFournisseurApiV1ChorusProFacturesRechercherFournisseurPost.md +0 -24
- package/docs/BodyRecyclerFactureApiV1ChorusProFacturesRecyclerPost.md +0 -24
- package/docs/BodyTelechargerGroupeFacturesApiV1ChorusProFacturesTelechargerGroupePost.md +0 -24
- package/docs/BodyTraiterFactureRecueApiV1ChorusProFacturesTraiterFactureRecuePost.md +0 -24
- package/docs/BodyValideurConsulterFactureApiV1ChorusProFacturesValideurConsulterPost.md +0 -24
- package/docs/BodyValideurRechercherFacturesApiV1ChorusProFacturesValideurRechercherPost.md +0 -24
- package/docs/BodyValideurTraiterFactureApiV1ChorusProFacturesValideurTraiterPost.md +0 -24
- package/docs/QuotaInfo.md +0 -29
- package/docs/Utilisateur.md +0 -43
- package/models/body-rechercher-factures-fournisseur-api-v1-chorus-pro-factures-rechercher-fournisseur-post.ts +0 -28
- package/models/body-recycler-facture-api-v1-chorus-pro-factures-recycler-post.ts +0 -28
- package/models/body-telecharger-groupe-factures-api-v1-chorus-pro-factures-telecharger-groupe-post.ts +0 -28
- package/models/body-traiter-facture-recue-api-v1-chorus-pro-factures-traiter-facture-recue-post.ts +0 -28
- package/models/body-valideur-consulter-facture-api-v1-chorus-pro-factures-valideur-consulter-post.ts +0 -28
- package/models/body-valideur-rechercher-factures-api-v1-chorus-pro-factures-valideur-rechercher-post.ts +0 -28
- package/models/body-valideur-traiter-facture-api-v1-chorus-pro-factures-valideur-traiter-post.ts +0 -28
- /package/dist/esm/models/{body-ajouter-fichier-api-v1-chorus-pro-transverses-ajouter-fichier-post.js → apierror.js} +0 -0
- /package/dist/esm/models/{body-completer-facture-api-v1-chorus-pro-factures-completer-post.js → beneficiaire.js} +0 -0
- /package/dist/esm/models/{body-lister-services-structure-api-v1-chorus-pro-structures-id-structure-cpp-services-get.js → bounding-box-schema.js} +0 -0
- /package/dist/esm/models/{body-rechercher-factures-destinataire-api-v1-chorus-pro-factures-rechercher-destinataire-post.js → champ-verifie-schema.js} +0 -0
- /package/dist/esm/models/{body-rechercher-factures-fournisseur-api-v1-chorus-pro-factures-rechercher-fournisseur-post.js → dimension-page-schema.js} +0 -0
- /package/dist/esm/models/{body-recycler-facture-api-v1-chorus-pro-factures-recycler-post.js → facture-entrante.js} +0 -0
- /package/dist/esm/models/{body-telecharger-groupe-factures-api-v1-chorus-pro-factures-telecharger-groupe-post.js → flux-resume.js} +0 -0
- /package/dist/esm/models/{body-traiter-facture-recue-api-v1-chorus-pro-factures-traiter-facture-recue-post.js → fournisseur-entrant.js} +0 -0
- /package/dist/esm/models/{body-valideur-consulter-facture-api-v1-chorus-pro-factures-valideur-consulter-post.js → note-obligatoire-schema.js} +0 -0
- /package/dist/esm/models/{body-valideur-rechercher-factures-api-v1-chorus-pro-factures-valideur-rechercher-post.js → pdpcredentials.js} +0 -0
- /package/dist/esm/models/{body-valideur-traiter-facture-api-v1-chorus-pro-factures-valideur-traiter-post.js → reponse-healthcheck-afnor.js} +0 -0
- /package/dist/esm/models/{quota-info.js → reponse-recherche-flux.js} +0 -0
- /package/dist/esm/models/{utilisateur.js → reponse-soumission-flux.js} +0 -0
- /package/dist/models/{body-ajouter-fichier-api-v1-chorus-pro-transverses-ajouter-fichier-post.js → apierror.js} +0 -0
- /package/dist/models/{body-completer-facture-api-v1-chorus-pro-factures-completer-post.js → beneficiaire.js} +0 -0
- /package/dist/models/{body-lister-services-structure-api-v1-chorus-pro-structures-id-structure-cpp-services-get.js → bounding-box-schema.js} +0 -0
- /package/dist/models/{body-rechercher-factures-destinataire-api-v1-chorus-pro-factures-rechercher-destinataire-post.js → champ-verifie-schema.js} +0 -0
- /package/dist/models/{body-rechercher-factures-fournisseur-api-v1-chorus-pro-factures-rechercher-fournisseur-post.js → dimension-page-schema.js} +0 -0
- /package/dist/models/{body-recycler-facture-api-v1-chorus-pro-factures-recycler-post.js → facture-entrante.js} +0 -0
- /package/dist/models/{body-telecharger-groupe-factures-api-v1-chorus-pro-factures-telecharger-groupe-post.js → flux-resume.js} +0 -0
- /package/dist/models/{body-traiter-facture-recue-api-v1-chorus-pro-factures-traiter-facture-recue-post.js → fournisseur-entrant.js} +0 -0
- /package/dist/models/{body-valideur-consulter-facture-api-v1-chorus-pro-factures-valideur-consulter-post.js → note-obligatoire-schema.js} +0 -0
- /package/dist/models/{body-valideur-rechercher-factures-api-v1-chorus-pro-factures-valideur-rechercher-post.js → pdpcredentials.js} +0 -0
- /package/dist/models/{body-valideur-traiter-facture-api-v1-chorus-pro-factures-valideur-traiter-post.js → reponse-healthcheck-afnor.js} +0 -0
- /package/dist/models/{quota-info.js → reponse-recherche-flux.js} +0 -0
- /package/dist/models/{utilisateur.js → reponse-soumission-flux.js} +0 -0
|
@@ -0,0 +1,882 @@
|
|
|
1
|
+
import axios, { AxiosInstance, AxiosError } from 'axios';
|
|
2
|
+
import FormData from 'form-data';
|
|
3
|
+
import * as fs from 'fs';
|
|
4
|
+
import * as path from 'path';
|
|
5
|
+
import { FactPulseAuthError, FactPulsePollingTimeout, FactPulseValidationError, ValidationErrorDetail } from './exceptions';
|
|
6
|
+
|
|
7
|
+
// =============================================================================
|
|
8
|
+
// Credentials interfaces - pour une configuration simplifiée
|
|
9
|
+
// =============================================================================
|
|
10
|
+
|
|
11
|
+
/** Credentials Chorus Pro pour le mode Zero-Trust. */
|
|
12
|
+
export interface ChorusProCredentials {
|
|
13
|
+
pisteClientId: string;
|
|
14
|
+
pisteClientSecret: string;
|
|
15
|
+
chorusProLogin: string;
|
|
16
|
+
chorusProPassword: string;
|
|
17
|
+
sandbox?: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Credentials AFNOR PDP pour le mode Zero-Trust. L'API FactPulse utilise ces credentials pour s'authentifier auprès de la PDP AFNOR. */
|
|
21
|
+
export interface AFNORCredentials {
|
|
22
|
+
flowServiceUrl: string;
|
|
23
|
+
tokenUrl: string;
|
|
24
|
+
clientId: string;
|
|
25
|
+
clientSecret: string;
|
|
26
|
+
directoryServiceUrl?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface FactPulseClientConfig {
|
|
30
|
+
email: string;
|
|
31
|
+
password: string;
|
|
32
|
+
apiUrl?: string;
|
|
33
|
+
clientUid?: string;
|
|
34
|
+
chorusCredentials?: ChorusProCredentials;
|
|
35
|
+
afnorCredentials?: AFNORCredentials;
|
|
36
|
+
pollingInterval?: number;
|
|
37
|
+
pollingTimeout?: number;
|
|
38
|
+
maxRetries?: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// =============================================================================
|
|
42
|
+
// Helpers pour les types anyOf - évite la verbosité des wrappers générés
|
|
43
|
+
// =============================================================================
|
|
44
|
+
|
|
45
|
+
type MontantValue = string | number | null | undefined;
|
|
46
|
+
|
|
47
|
+
/** Convertit une valeur en string de montant pour l'API. */
|
|
48
|
+
export function montant(value: MontantValue): string {
|
|
49
|
+
if (value === null || value === undefined) return '0.00';
|
|
50
|
+
if (typeof value === 'number') return value.toFixed(2);
|
|
51
|
+
if (typeof value === 'string') return value;
|
|
52
|
+
return '0.00';
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Crée un objet MontantTotal simplifié. */
|
|
56
|
+
export function montantTotal(
|
|
57
|
+
ht: MontantValue, tva: MontantValue, ttc: MontantValue, aPayer: MontantValue,
|
|
58
|
+
options?: { remiseTtc?: MontantValue; motifRemise?: string; acompte?: MontantValue }
|
|
59
|
+
): Record<string, unknown> {
|
|
60
|
+
const result: Record<string, unknown> = {
|
|
61
|
+
montantHtTotal: montant(ht), montantTva: montant(tva),
|
|
62
|
+
montantTtcTotal: montant(ttc), montantAPayer: montant(aPayer),
|
|
63
|
+
};
|
|
64
|
+
if (options?.remiseTtc !== undefined) result.montantRemiseGlobaleTtc = montant(options.remiseTtc);
|
|
65
|
+
if (options?.motifRemise !== undefined) result.motifRemiseGlobaleTtc = options.motifRemise;
|
|
66
|
+
if (options?.acompte !== undefined) result.acompte = montant(options.acompte);
|
|
67
|
+
return result;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Crée une ligne de poste (aligné sur LigneDePoste de models.py).
|
|
71
|
+
* Pour le taux TVA: soit tauxTva (code ex: "TVA20") soit tauxTvaManuel (valeur ex: 20.00) */
|
|
72
|
+
export function ligneDePoste(
|
|
73
|
+
numero: number, denomination: string, quantite: MontantValue, montantUnitaireHt: MontantValue, montantTotalLigneHt: MontantValue,
|
|
74
|
+
options?: { tauxTva?: string; tauxTvaManuel?: MontantValue; categorieTva?: string; unite?: string; reference?: string;
|
|
75
|
+
montantRemiseHt?: MontantValue; codeRaisonReduction?: string; raisonReduction?: string;
|
|
76
|
+
dateDebutPeriode?: string; dateFinPeriode?: string }
|
|
77
|
+
): Record<string, unknown> {
|
|
78
|
+
const result: Record<string, unknown> = {
|
|
79
|
+
numero, denomination, quantite: montant(quantite), montantUnitaireHt: montant(montantUnitaireHt),
|
|
80
|
+
montantTotalLigneHt: montant(montantTotalLigneHt),
|
|
81
|
+
categorieTva: options?.categorieTva ?? 'S', unite: options?.unite ?? 'FORFAIT',
|
|
82
|
+
};
|
|
83
|
+
// Soit tauxTva (code) soit tauxTvaManuel (valeur)
|
|
84
|
+
if (options?.tauxTva) result.tauxTva = options.tauxTva;
|
|
85
|
+
else result.tauxTvaManuel = montant(options?.tauxTvaManuel ?? '20.00');
|
|
86
|
+
if (options?.reference) result.reference = options.reference;
|
|
87
|
+
if (options?.montantRemiseHt !== undefined) result.montantRemiseHt = montant(options.montantRemiseHt);
|
|
88
|
+
if (options?.codeRaisonReduction) result.codeRaisonReduction = options.codeRaisonReduction;
|
|
89
|
+
if (options?.raisonReduction) result.raisonReduction = options.raisonReduction;
|
|
90
|
+
if (options?.dateDebutPeriode) result.dateDebutPeriode = options.dateDebutPeriode;
|
|
91
|
+
if (options?.dateFinPeriode) result.dateFinPeriode = options.dateFinPeriode;
|
|
92
|
+
return result;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Crée une ligne de TVA (aligné sur LigneDeTVA de models.py).
|
|
96
|
+
* Pour le taux: soit taux (code ex: "TVA20") soit tauxManuel (valeur ex: 20.00) */
|
|
97
|
+
export function ligneDeTva(
|
|
98
|
+
montantBaseHt: MontantValue, montantTva: MontantValue,
|
|
99
|
+
options?: { taux?: string; tauxManuel?: MontantValue; categorie?: string }
|
|
100
|
+
): Record<string, unknown> {
|
|
101
|
+
const result: Record<string, unknown> = {
|
|
102
|
+
montantBaseHt: montant(montantBaseHt), montantTva: montant(montantTva), categorie: options?.categorie ?? 'S',
|
|
103
|
+
};
|
|
104
|
+
// Soit taux (code) soit tauxManuel (valeur)
|
|
105
|
+
if (options?.taux) result.taux = options.taux;
|
|
106
|
+
else result.tauxManuel = montant(options?.tauxManuel ?? '20.00');
|
|
107
|
+
return result;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Crée une adresse postale pour l'API FactPulse. */
|
|
111
|
+
export function adressePostale(
|
|
112
|
+
ligne1: string, codePostal: string, ville: string,
|
|
113
|
+
options?: { pays?: string; ligne2?: string; ligne3?: string }
|
|
114
|
+
): Record<string, unknown> {
|
|
115
|
+
const result: Record<string, unknown> = { ligneUn: ligne1, codePostal, nomVille: ville, paysCodeIso: options?.pays ?? 'FR' };
|
|
116
|
+
if (options?.ligne2) result.ligneDeux = options.ligne2;
|
|
117
|
+
if (options?.ligne3) result.ligneTrois = options.ligne3;
|
|
118
|
+
return result;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Crée une adresse électronique pour l'API FactPulse. schemeId: "0009"=SIREN, "0225"=SIRET */
|
|
122
|
+
export function adresseElectronique(identifiant: string, schemeId = '0009'): Record<string, unknown> {
|
|
123
|
+
return { identifiant, schemeId };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** Calcule le numéro TVA intracommunautaire français depuis un SIREN. */
|
|
127
|
+
function calculerTvaIntra(siren: string): string | null {
|
|
128
|
+
if (siren.length !== 9 || !/^\d+$/.test(siren)) return null;
|
|
129
|
+
const cle = (12 + 3 * (parseInt(siren, 10) % 97)) % 97;
|
|
130
|
+
return `FR${cle.toString().padStart(2, '0')}${siren}`;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Crée un fournisseur (émetteur) avec auto-calcul SIREN, TVA intracommunautaire et adresses. */
|
|
134
|
+
export function fournisseur(
|
|
135
|
+
nom: string, siret: string, adresseLigne1: string, codePostal: string, ville: string,
|
|
136
|
+
options?: { idFournisseur?: number; siren?: string; numeroTvaIntra?: string; iban?: string; pays?: string; adresseLigne2?: string; codeService?: number; codeCoordonnesBancaires?: number }
|
|
137
|
+
): Record<string, unknown> {
|
|
138
|
+
const opts = options ?? {};
|
|
139
|
+
const siren = opts.siren ?? (siret.length === 14 ? siret.slice(0, 9) : undefined);
|
|
140
|
+
const numeroTvaIntra = opts.numeroTvaIntra ?? (siren ? calculerTvaIntra(siren) : null);
|
|
141
|
+
const result: Record<string, unknown> = {
|
|
142
|
+
nom, idFournisseur: opts.idFournisseur ?? 0, siret,
|
|
143
|
+
adresseElectronique: adresseElectronique(siret, '0225'),
|
|
144
|
+
adressePostale: adressePostale(adresseLigne1, codePostal, ville, { pays: opts.pays, ligne2: opts.adresseLigne2 }),
|
|
145
|
+
};
|
|
146
|
+
if (siren) result.siren = siren;
|
|
147
|
+
if (numeroTvaIntra) result.numeroTvaIntra = numeroTvaIntra;
|
|
148
|
+
if (opts.iban) result.iban = opts.iban;
|
|
149
|
+
if (opts.codeService) result.idServiceFournisseur = opts.codeService;
|
|
150
|
+
if (opts.codeCoordonnesBancaires) result.codeCoordonnesBancairesFournisseur = opts.codeCoordonnesBancaires;
|
|
151
|
+
return result;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** Crée un destinataire (client) avec auto-calcul SIREN et adresses. */
|
|
155
|
+
export function destinataire(
|
|
156
|
+
nom: string, siret: string, adresseLigne1: string, codePostal: string, ville: string,
|
|
157
|
+
options?: { siren?: string; pays?: string; adresseLigne2?: string; codeServiceExecutant?: string }
|
|
158
|
+
): Record<string, unknown> {
|
|
159
|
+
const opts = options ?? {};
|
|
160
|
+
const siren = opts.siren ?? (siret.length === 14 ? siret.slice(0, 9) : undefined);
|
|
161
|
+
const result: Record<string, unknown> = {
|
|
162
|
+
nom, siret,
|
|
163
|
+
adresseElectronique: adresseElectronique(siret, '0225'),
|
|
164
|
+
adressePostale: adressePostale(adresseLigne1, codePostal, ville, { pays: opts.pays, ligne2: opts.adresseLigne2 }),
|
|
165
|
+
};
|
|
166
|
+
if (siren) result.siren = siren;
|
|
167
|
+
if (opts.codeServiceExecutant) result.codeServiceExecutant = opts.codeServiceExecutant;
|
|
168
|
+
return result;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Crée un bénéficiaire (factor) pour l'affacturage.
|
|
173
|
+
*
|
|
174
|
+
* Le bénéficiaire (BG-10 / PayeeTradeParty) est utilisé lorsque le paiement
|
|
175
|
+
* doit être effectué à un tiers différent du fournisseur, typiquement un
|
|
176
|
+
* factor (société d'affacturage).
|
|
177
|
+
*
|
|
178
|
+
* Pour les factures affacturées, il faut aussi:
|
|
179
|
+
* - Utiliser un type de document affacturé (393, 396, 501, 502, 472, 473)
|
|
180
|
+
* - Ajouter une note ACC avec la mention de subrogation
|
|
181
|
+
* - L'IBAN du bénéficiaire sera utilisé pour le paiement
|
|
182
|
+
*
|
|
183
|
+
* @param nom Raison sociale du factor (BT-59)
|
|
184
|
+
* @param options Options: siret (BT-60), siren (BT-61), iban, bic
|
|
185
|
+
* @returns Dict prêt à être utilisé dans une facture affacturée
|
|
186
|
+
*
|
|
187
|
+
* @example
|
|
188
|
+
* const factor = beneficiaire('FACTOR SAS', {
|
|
189
|
+
* siret: '30000000700033',
|
|
190
|
+
* iban: 'FR76 3000 4000 0500 0012 3456 789',
|
|
191
|
+
* });
|
|
192
|
+
*/
|
|
193
|
+
export function beneficiaire(
|
|
194
|
+
nom: string,
|
|
195
|
+
options?: { siret?: string; siren?: string; iban?: string; bic?: string }
|
|
196
|
+
): Record<string, unknown> {
|
|
197
|
+
const opts = options ?? {};
|
|
198
|
+
// Auto-calcul SIREN depuis SIRET
|
|
199
|
+
const siren = opts.siren ?? (opts.siret && opts.siret.length === 14 ? opts.siret.slice(0, 9) : undefined);
|
|
200
|
+
|
|
201
|
+
const result: Record<string, unknown> = { nom };
|
|
202
|
+
if (opts.siret) result.siret = opts.siret;
|
|
203
|
+
if (siren) result.siren = siren;
|
|
204
|
+
if (opts.iban) result.iban = opts.iban;
|
|
205
|
+
if (opts.bic) result.bic = opts.bic;
|
|
206
|
+
return result;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// =============================================================================
|
|
210
|
+
// Client principal
|
|
211
|
+
// =============================================================================
|
|
212
|
+
|
|
213
|
+
const DEFAULT_API_URL = 'https://factpulse.fr';
|
|
214
|
+
const DEFAULT_POLLING_INTERVAL = 2000;
|
|
215
|
+
const DEFAULT_POLLING_TIMEOUT = 120000;
|
|
216
|
+
const DEFAULT_MAX_RETRIES = 1;
|
|
217
|
+
|
|
218
|
+
interface InternalConfig {
|
|
219
|
+
email: string; password: string; apiUrl: string; clientUid: string;
|
|
220
|
+
chorusCredentials?: ChorusProCredentials; afnorCredentials?: AFNORCredentials;
|
|
221
|
+
pollingInterval: number; pollingTimeout: number; maxRetries: number;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export class FactPulseClient {
|
|
225
|
+
private config: InternalConfig;
|
|
226
|
+
private httpClient: AxiosInstance;
|
|
227
|
+
private accessToken: string | null = null;
|
|
228
|
+
private refreshToken: string | null = null;
|
|
229
|
+
private tokenExpiresAt: number | null = null;
|
|
230
|
+
|
|
231
|
+
public readonly chorusCredentials?: ChorusProCredentials;
|
|
232
|
+
public readonly afnorCredentials?: AFNORCredentials;
|
|
233
|
+
|
|
234
|
+
constructor(config: FactPulseClientConfig) {
|
|
235
|
+
this.config = {
|
|
236
|
+
email: config.email, password: config.password,
|
|
237
|
+
apiUrl: (config.apiUrl || DEFAULT_API_URL).replace(/\/$/, ''),
|
|
238
|
+
clientUid: config.clientUid || '',
|
|
239
|
+
chorusCredentials: config.chorusCredentials,
|
|
240
|
+
afnorCredentials: config.afnorCredentials,
|
|
241
|
+
pollingInterval: config.pollingInterval || DEFAULT_POLLING_INTERVAL,
|
|
242
|
+
pollingTimeout: config.pollingTimeout || DEFAULT_POLLING_TIMEOUT,
|
|
243
|
+
maxRetries: config.maxRetries ?? DEFAULT_MAX_RETRIES,
|
|
244
|
+
};
|
|
245
|
+
this.chorusCredentials = config.chorusCredentials;
|
|
246
|
+
this.afnorCredentials = config.afnorCredentials;
|
|
247
|
+
this.httpClient = axios.create({ timeout: 30000, headers: { 'Content-Type': 'application/json' } });
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
getChorusCredentialsForApi(): Record<string, unknown> | undefined {
|
|
251
|
+
if (!this.chorusCredentials) return undefined;
|
|
252
|
+
return {
|
|
253
|
+
piste_client_id: this.chorusCredentials.pisteClientId,
|
|
254
|
+
piste_client_secret: this.chorusCredentials.pisteClientSecret,
|
|
255
|
+
chorus_pro_login: this.chorusCredentials.chorusProLogin,
|
|
256
|
+
chorus_pro_password: this.chorusCredentials.chorusProPassword,
|
|
257
|
+
sandbox: this.chorusCredentials.sandbox ?? true,
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
getAfnorCredentialsForApi(): Record<string, unknown> | undefined {
|
|
262
|
+
if (!this.afnorCredentials) return undefined;
|
|
263
|
+
return {
|
|
264
|
+
client_id: this.afnorCredentials.clientId,
|
|
265
|
+
client_secret: this.afnorCredentials.clientSecret,
|
|
266
|
+
flow_service_url: this.afnorCredentials.flowServiceUrl,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Alias plus courts
|
|
271
|
+
getChorusProCredentials(): Record<string, unknown> | undefined { return this.getChorusCredentialsForApi(); }
|
|
272
|
+
getAfnorCredentials(): Record<string, unknown> | undefined { return this.getAfnorCredentialsForApi(); }
|
|
273
|
+
|
|
274
|
+
private async obtainToken(): Promise<{ access: string; refresh: string }> {
|
|
275
|
+
const payload: Record<string, string> = { username: this.config.email, password: this.config.password };
|
|
276
|
+
if (this.config.clientUid) payload.client_uid = this.config.clientUid;
|
|
277
|
+
try {
|
|
278
|
+
const response = await this.httpClient.post(`${this.config.apiUrl}/api/token/`, payload);
|
|
279
|
+
return response.data;
|
|
280
|
+
} catch (error) {
|
|
281
|
+
const axiosError = error as AxiosError<{ detail?: string }>;
|
|
282
|
+
throw new FactPulseAuthError(`Impossible d'obtenir le token JWT: ${axiosError.response?.data?.detail || axiosError.message}`);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
async ensureAuthenticated(forceRefresh: boolean = false): Promise<void> {
|
|
287
|
+
const now = Date.now();
|
|
288
|
+
if (forceRefresh || !this.accessToken || (this.tokenExpiresAt && now >= this.tokenExpiresAt)) {
|
|
289
|
+
const tokens = await this.obtainToken();
|
|
290
|
+
this.accessToken = tokens.access; this.refreshToken = tokens.refresh;
|
|
291
|
+
this.tokenExpiresAt = now + 28 * 60 * 1000;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
resetAuth(): void { this.accessToken = this.refreshToken = null; this.tokenExpiresAt = null; }
|
|
296
|
+
|
|
297
|
+
async pollTask(taskId: string, timeout?: number, interval?: number): Promise<Record<string, unknown>> {
|
|
298
|
+
const timeoutMs = timeout ?? this.config.pollingTimeout;
|
|
299
|
+
const intervalMs = interval ?? this.config.pollingInterval;
|
|
300
|
+
const startTime = Date.now();
|
|
301
|
+
let currentInterval = intervalMs;
|
|
302
|
+
|
|
303
|
+
while (true) {
|
|
304
|
+
if (Date.now() - startTime > timeoutMs) throw new FactPulsePollingTimeout(taskId, timeoutMs);
|
|
305
|
+
await this.ensureAuthenticated();
|
|
306
|
+
try {
|
|
307
|
+
const response = await this.httpClient.get(`${this.config.apiUrl}/api/v1/traitement/taches/${taskId}/statut`, {
|
|
308
|
+
headers: { Authorization: `Bearer ${this.accessToken}` },
|
|
309
|
+
});
|
|
310
|
+
const { statut, resultat } = response.data;
|
|
311
|
+
if (statut === 'SUCCESS') return (resultat as Record<string, unknown>) || {};
|
|
312
|
+
if (statut === 'FAILURE') {
|
|
313
|
+
// Format AFNOR: errorMessage, details
|
|
314
|
+
const result = resultat as Record<string, unknown> | undefined;
|
|
315
|
+
const errors: ValidationErrorDetail[] = Array.isArray(result?.details) ? result.details.filter((e): e is ValidationErrorDetail => typeof e === 'object' && e !== null) : [];
|
|
316
|
+
throw new FactPulseValidationError(`La tâche ${taskId} a échoué: ${result?.errorMessage || 'Erreur inconnue'}`, errors);
|
|
317
|
+
}
|
|
318
|
+
await new Promise(resolve => setTimeout(resolve, currentInterval));
|
|
319
|
+
currentInterval = Math.min(currentInterval * 1.5, 10000);
|
|
320
|
+
} catch (error) {
|
|
321
|
+
if (error instanceof FactPulseValidationError || error instanceof FactPulsePollingTimeout) throw error;
|
|
322
|
+
const axiosError = error as AxiosError;
|
|
323
|
+
if (axiosError.response?.status === 401) { this.resetAuth(); continue; }
|
|
324
|
+
throw new FactPulseValidationError(`Erreur API: ${axiosError.message}`);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
async genererFacturx(factureData: Record<string, unknown> | string, pdfPath: string, profil = 'EN16931', formatSortie = 'pdf', sync = true, timeout?: number): Promise<Buffer | string> {
|
|
330
|
+
const jsonData = typeof factureData === 'string' ? factureData : JSON.stringify(factureData);
|
|
331
|
+
let taskId: string | null = null;
|
|
332
|
+
|
|
333
|
+
for (let attempt = 0; attempt <= this.config.maxRetries; attempt++) {
|
|
334
|
+
await this.ensureAuthenticated();
|
|
335
|
+
const form = new FormData();
|
|
336
|
+
form.append('donnees_facture', jsonData);
|
|
337
|
+
form.append('profil', profil);
|
|
338
|
+
form.append('format_sortie', formatSortie);
|
|
339
|
+
form.append('source_pdf', fs.createReadStream(pdfPath), { filename: path.basename(pdfPath), contentType: 'application/pdf' });
|
|
340
|
+
try {
|
|
341
|
+
const response = await this.httpClient.post(`${this.config.apiUrl}/api/v1/traitement/generer-facture`, form, {
|
|
342
|
+
headers: { ...form.getHeaders(), Authorization: `Bearer ${this.accessToken}` }, timeout: 60000,
|
|
343
|
+
});
|
|
344
|
+
taskId = response.data.id_tache; break;
|
|
345
|
+
} catch (error) {
|
|
346
|
+
const axiosError = error as AxiosError<{ detail?: unknown; errorMessage?: string }>;
|
|
347
|
+
if (axiosError.response?.status === 401 && attempt < this.config.maxRetries) { this.resetAuth(); continue; }
|
|
348
|
+
|
|
349
|
+
// Extraire les détails d'erreur du corps de la réponse
|
|
350
|
+
const responseData = axiosError.response?.data;
|
|
351
|
+
let errorMsg = `Erreur API (${axiosError.response?.status || 'unknown'}): ${axiosError.message}`;
|
|
352
|
+
const errors: ValidationErrorDetail[] = [];
|
|
353
|
+
|
|
354
|
+
if (responseData) {
|
|
355
|
+
// Format FastAPI/Pydantic: {"detail": [{"loc": [...], "msg": "...", "type": "..."}]}
|
|
356
|
+
if (Array.isArray(responseData.detail)) {
|
|
357
|
+
errorMsg = 'Erreur de validation';
|
|
358
|
+
for (const err of responseData.detail) {
|
|
359
|
+
if (typeof err === 'object' && err !== null) {
|
|
360
|
+
const loc = (err as { loc?: unknown[] }).loc || [];
|
|
361
|
+
errors.push({
|
|
362
|
+
level: 'ERROR',
|
|
363
|
+
item: loc.map(String).join(' -> '),
|
|
364
|
+
reason: (err as { msg?: string }).msg || String(err),
|
|
365
|
+
source: 'validation',
|
|
366
|
+
code: (err as { type?: string }).type,
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
} else if (typeof responseData.detail === 'string') {
|
|
371
|
+
errorMsg = responseData.detail;
|
|
372
|
+
} else if (responseData.errorMessage) {
|
|
373
|
+
errorMsg = responseData.errorMessage;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
console.error(`Erreur API ${axiosError.response?.status}:`, responseData);
|
|
378
|
+
throw new FactPulseValidationError(errorMsg, errors);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
if (!taskId) throw new FactPulseValidationError("Pas d'ID de tâche");
|
|
382
|
+
if (!sync) return taskId;
|
|
383
|
+
const result = await this.pollTask(taskId, timeout);
|
|
384
|
+
if (result.contenu_b64) return Buffer.from(result.contenu_b64 as string, 'base64');
|
|
385
|
+
throw new FactPulseValidationError('Pas de contenu');
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
static formatMontant(m: unknown): string {
|
|
389
|
+
if (m === null || m === undefined) return '0.00';
|
|
390
|
+
if (typeof m === 'number') return m.toFixed(2);
|
|
391
|
+
if (typeof m === 'string') return m;
|
|
392
|
+
return '0.00';
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// =========================================================================
|
|
396
|
+
// AFNOR - Authentication et helpers internes
|
|
397
|
+
// =========================================================================
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Recupere les credentials AFNOR (mode stored ou zero-trust).
|
|
401
|
+
* Mode zero-trust: Retourne les afnorCredentials fournis au constructeur.
|
|
402
|
+
* Mode stored: Recupere les credentials via GET /api/v1/afnor/credentials.
|
|
403
|
+
*/
|
|
404
|
+
private async getAfnorCredentialsInternal(): Promise<AFNORCredentials> {
|
|
405
|
+
// Mode zero-trust : credentials fournis au constructeur
|
|
406
|
+
if (this.afnorCredentials) {
|
|
407
|
+
return this.afnorCredentials;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// Mode stored : recuperer les credentials via l'API
|
|
411
|
+
await this.ensureAuthenticated();
|
|
412
|
+
|
|
413
|
+
try {
|
|
414
|
+
const response = await this.httpClient.get(`${this.config.apiUrl}/api/v1/afnor/credentials`, {
|
|
415
|
+
headers: { Authorization: `Bearer ${this.accessToken}` },
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
const creds = response.data;
|
|
419
|
+
return {
|
|
420
|
+
flowServiceUrl: creds.flow_service_url,
|
|
421
|
+
tokenUrl: creds.token_url,
|
|
422
|
+
clientId: creds.client_id,
|
|
423
|
+
clientSecret: creds.client_secret,
|
|
424
|
+
directoryServiceUrl: creds.directory_service_url,
|
|
425
|
+
};
|
|
426
|
+
} catch (error) {
|
|
427
|
+
const axiosError = error as AxiosError<{ detail?: { error?: string; message?: string } }>;
|
|
428
|
+
if (axiosError.response?.status === 400) {
|
|
429
|
+
const detail = axiosError.response.data?.detail;
|
|
430
|
+
if (typeof detail === 'object' && detail?.error === 'NO_CLIENT_UID') {
|
|
431
|
+
throw new FactPulseAuthError(
|
|
432
|
+
"Aucun client_uid dans le JWT. Pour utiliser les endpoints AFNOR, soit:\n" +
|
|
433
|
+
"1. Generez un token avec un client_uid (mode stored)\n" +
|
|
434
|
+
"2. Fournissez AFNORCredentials au constructeur du client (mode zero-trust)"
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
throw new FactPulseAuthError(`Echec recuperation credentials AFNOR: ${axiosError.message}`);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Obtient le token OAuth2 AFNOR et l'URL de la PDP.
|
|
444
|
+
* Cette methode:
|
|
445
|
+
* 1. Recupere les credentials AFNOR (mode stored ou zero-trust)
|
|
446
|
+
* 2. Fait l'OAuth AFNOR pour obtenir un token
|
|
447
|
+
* 3. Retourne le token et l'URL de la PDP
|
|
448
|
+
*/
|
|
449
|
+
private async getAfnorTokenAndUrl(): Promise<{ token: string; pdpBaseUrl: string }> {
|
|
450
|
+
// Etape 1: Obtenir les credentials AFNOR
|
|
451
|
+
const credentials = await this.getAfnorCredentialsInternal();
|
|
452
|
+
|
|
453
|
+
// Etape 2: Faire l'OAuth AFNOR via le proxy FactPulse
|
|
454
|
+
const oauthData = new URLSearchParams({
|
|
455
|
+
grant_type: 'client_credentials',
|
|
456
|
+
client_id: credentials.clientId,
|
|
457
|
+
client_secret: credentials.clientSecret,
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
try {
|
|
461
|
+
const response = await this.httpClient.post(
|
|
462
|
+
`${this.config.apiUrl}/api/v1/afnor/oauth/token`,
|
|
463
|
+
oauthData.toString(),
|
|
464
|
+
{
|
|
465
|
+
headers: {
|
|
466
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
467
|
+
'X-PDP-Token-URL': credentials.tokenUrl,
|
|
468
|
+
},
|
|
469
|
+
}
|
|
470
|
+
);
|
|
471
|
+
|
|
472
|
+
const tokenData = response.data;
|
|
473
|
+
if (!tokenData.access_token) {
|
|
474
|
+
throw new FactPulseAuthError('Reponse OAuth2 AFNOR invalide: access_token manquant');
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
return {
|
|
478
|
+
token: tokenData.access_token,
|
|
479
|
+
pdpBaseUrl: credentials.flowServiceUrl,
|
|
480
|
+
};
|
|
481
|
+
} catch (error) {
|
|
482
|
+
const axiosError = error as AxiosError;
|
|
483
|
+
throw new FactPulseAuthError(`Echec OAuth2 AFNOR: ${axiosError.message}`);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* Effectue une requete vers l'API AFNOR avec gestion d'auth et d'erreurs.
|
|
489
|
+
* IMPORTANT: Cette methode utilise le token OAuth AFNOR, PAS le JWT FactPulse!
|
|
490
|
+
*/
|
|
491
|
+
private async makeAfnorRequest<T = unknown>(
|
|
492
|
+
method: 'GET' | 'POST',
|
|
493
|
+
endpoint: string,
|
|
494
|
+
options?: { data?: unknown; files?: FormData; params?: Record<string, string> }
|
|
495
|
+
): Promise<T> {
|
|
496
|
+
// Obtenir le token AFNOR et l'URL de la PDP
|
|
497
|
+
const { token: afnorToken, pdpBaseUrl } = await this.getAfnorTokenAndUrl();
|
|
498
|
+
|
|
499
|
+
const url = `${this.config.apiUrl}/api/v1/afnor${endpoint}`;
|
|
500
|
+
|
|
501
|
+
// TOUJOURS utiliser le token AFNOR + header X-PDP-Base-URL
|
|
502
|
+
const headers: Record<string, string> = {
|
|
503
|
+
'Authorization': `Bearer ${afnorToken}`,
|
|
504
|
+
'X-PDP-Base-URL': pdpBaseUrl,
|
|
505
|
+
};
|
|
506
|
+
|
|
507
|
+
try {
|
|
508
|
+
let response;
|
|
509
|
+
if (options?.files) {
|
|
510
|
+
response = await this.httpClient.request({
|
|
511
|
+
method,
|
|
512
|
+
url,
|
|
513
|
+
data: options.files,
|
|
514
|
+
headers: { ...options.files.getHeaders(), ...headers },
|
|
515
|
+
params: options?.params,
|
|
516
|
+
timeout: 60000,
|
|
517
|
+
});
|
|
518
|
+
} else {
|
|
519
|
+
response = await this.httpClient.request({
|
|
520
|
+
method,
|
|
521
|
+
url,
|
|
522
|
+
data: options?.data,
|
|
523
|
+
headers: { ...headers, 'Content-Type': 'application/json' },
|
|
524
|
+
params: options?.params,
|
|
525
|
+
timeout: 30000,
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
return response.data;
|
|
529
|
+
} catch (error) {
|
|
530
|
+
const axiosError = error as AxiosError<{ errorMessage?: string; detail?: string }>;
|
|
531
|
+
const errorMsg = axiosError.response?.data?.errorMessage ||
|
|
532
|
+
axiosError.response?.data?.detail ||
|
|
533
|
+
axiosError.message;
|
|
534
|
+
throw new FactPulseValidationError(`Erreur AFNOR: ${errorMsg}`);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// ==================== AFNOR Directory ====================
|
|
539
|
+
|
|
540
|
+
async rechercherSiretAfnor(siret: string): Promise<Record<string, unknown>> {
|
|
541
|
+
return this.makeAfnorRequest('GET', `/directory/siret/${siret}`);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
async rechercherSirenAfnor(siren: string): Promise<Record<string, unknown>> {
|
|
545
|
+
return this.makeAfnorRequest('GET', `/directory/siren/${siren}`);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
async listerCodesRoutageAfnor(siren: string): Promise<unknown[]> {
|
|
549
|
+
return this.makeAfnorRequest('GET', `/directory/siren/${siren}/routing-codes`);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// ==================== AFNOR Flow ====================
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* Soumet une facture a une PDP via l'API AFNOR.
|
|
556
|
+
* L'authentification utilise le token OAuth AFNOR (obtenu automatiquement),
|
|
557
|
+
* soit via les credentials stockes (mode stored), soit via les afnorCredentials
|
|
558
|
+
* fournis au constructeur (mode zero-trust).
|
|
559
|
+
*
|
|
560
|
+
* @param pdfBuffer Buffer du PDF Factur-X a soumettre
|
|
561
|
+
* @param flowName Nom du flux (ex: "Facture FAC-2025-001")
|
|
562
|
+
* @param options Options: trackingId, flowSyntax (CII/UBL), flowProfile
|
|
563
|
+
*/
|
|
564
|
+
async soumettreFactureAfnor(
|
|
565
|
+
pdfBuffer: Buffer,
|
|
566
|
+
flowName: string,
|
|
567
|
+
options: { trackingId?: string; flowSyntax?: string; flowProfile?: string } = {}
|
|
568
|
+
): Promise<Record<string, unknown>> {
|
|
569
|
+
const { trackingId, flowSyntax = 'CII', flowProfile = 'EN16931' } = options;
|
|
570
|
+
|
|
571
|
+
// Calculer SHA-256
|
|
572
|
+
const crypto = require('crypto');
|
|
573
|
+
const sha256 = crypto.createHash('sha256').update(pdfBuffer).digest('hex');
|
|
574
|
+
|
|
575
|
+
// Preparer flowInfo
|
|
576
|
+
const flowInfo: Record<string, unknown> = { name: flowName, flowSyntax, flowProfile, sha256 };
|
|
577
|
+
if (trackingId) flowInfo.trackingId = trackingId;
|
|
578
|
+
|
|
579
|
+
const form = new FormData();
|
|
580
|
+
form.append('file', pdfBuffer, { filename: 'facture.pdf', contentType: 'application/pdf' });
|
|
581
|
+
form.append('flowInfo', JSON.stringify(flowInfo), { contentType: 'application/json' });
|
|
582
|
+
|
|
583
|
+
return this.makeAfnorRequest('POST', '/flow/v1/flows', { files: form });
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
async rechercherFluxAfnor(criteria: { trackingId?: string; status?: string; offset?: number; limit?: number } = {}): Promise<Record<string, unknown>> {
|
|
587
|
+
const searchBody: Record<string, unknown> = {
|
|
588
|
+
offset: criteria.offset ?? 0,
|
|
589
|
+
limit: criteria.limit ?? 25,
|
|
590
|
+
where: {},
|
|
591
|
+
};
|
|
592
|
+
if (criteria.trackingId) (searchBody.where as Record<string, unknown>).trackingId = criteria.trackingId;
|
|
593
|
+
if (criteria.status) (searchBody.where as Record<string, unknown>).status = criteria.status;
|
|
594
|
+
|
|
595
|
+
return this.makeAfnorRequest('POST', '/flow/v1/flows/search', { data: searchBody });
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
async telechargerFluxAfnor(flowId: string): Promise<Buffer> {
|
|
599
|
+
// Pour le telechargement, on doit gerer le type de reponse differemment
|
|
600
|
+
const { token: afnorToken, pdpBaseUrl } = await this.getAfnorTokenAndUrl();
|
|
601
|
+
|
|
602
|
+
const url = `${this.config.apiUrl}/api/v1/afnor/flow/v1/flows/${flowId}`;
|
|
603
|
+
const response = await this.httpClient.get(url, {
|
|
604
|
+
headers: {
|
|
605
|
+
'Authorization': `Bearer ${afnorToken}`,
|
|
606
|
+
'X-PDP-Base-URL': pdpBaseUrl,
|
|
607
|
+
},
|
|
608
|
+
responseType: 'arraybuffer',
|
|
609
|
+
});
|
|
610
|
+
return Buffer.from(response.data);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
/**
|
|
614
|
+
* Récupère les métadonnées JSON d'un flux entrant (facture fournisseur).
|
|
615
|
+
* Télécharge un flux entrant depuis la PDP AFNOR et extrait les métadonnées
|
|
616
|
+
* de la facture vers un format JSON unifié. Supporte Factur-X, CII et UBL.
|
|
617
|
+
*
|
|
618
|
+
* Note: Cet endpoint utilise l'authentification JWT FactPulse (pas OAuth AFNOR).
|
|
619
|
+
* Le serveur FactPulse se charge d'appeler la PDP avec les credentials stockés.
|
|
620
|
+
*
|
|
621
|
+
* @param flowId Identifiant du flux (UUID)
|
|
622
|
+
* @param includeDocument Si true, inclut le document original encodé en base64
|
|
623
|
+
* @returns Métadonnées de la facture (fournisseur, montants, dates, etc.)
|
|
624
|
+
*
|
|
625
|
+
* @example
|
|
626
|
+
* const facture = await client.obtenirFactureEntranteAfnor("550e8400-...");
|
|
627
|
+
* console.log(`Fournisseur: ${facture.fournisseur.nom}`);
|
|
628
|
+
* console.log(`Montant TTC: ${facture.montant_ttc} ${facture.devise}`);
|
|
629
|
+
*/
|
|
630
|
+
async obtenirFactureEntranteAfnor(
|
|
631
|
+
flowId: string,
|
|
632
|
+
includeDocument: boolean = false
|
|
633
|
+
): Promise<Record<string, unknown>> {
|
|
634
|
+
await this.ensureAuthenticated();
|
|
635
|
+
|
|
636
|
+
const url = `${this.config.apiUrl}/api/v1/afnor/flux-entrants/${flowId}`;
|
|
637
|
+
const params: Record<string, string> = {};
|
|
638
|
+
if (includeDocument) {
|
|
639
|
+
params.include_document = 'true';
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
try {
|
|
643
|
+
const response = await this.httpClient.get(url, {
|
|
644
|
+
headers: { 'Authorization': `Bearer ${this.accessToken}` },
|
|
645
|
+
params: Object.keys(params).length > 0 ? params : undefined,
|
|
646
|
+
timeout: 60000,
|
|
647
|
+
});
|
|
648
|
+
return response.data;
|
|
649
|
+
} catch (error) {
|
|
650
|
+
const axiosError = error as AxiosError<{ detail?: string }>;
|
|
651
|
+
throw new FactPulseValidationError(
|
|
652
|
+
`Erreur flux entrant: ${axiosError.response?.data?.detail || axiosError.message}`
|
|
653
|
+
);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
async healthcheckAfnor(): Promise<Record<string, unknown>> {
|
|
658
|
+
return this.makeAfnorRequest('GET', '/flow/v1/healthcheck');
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
// ==================== Chorus Pro ====================
|
|
662
|
+
|
|
663
|
+
async rechercherStructureChorus(criteria: Record<string, unknown>): Promise<unknown[]> {
|
|
664
|
+
await this.ensureAuthenticated();
|
|
665
|
+
const response = await this.httpClient.post(`${this.config.apiUrl}/api/v1/chorus-pro/structures/rechercher`, criteria, {
|
|
666
|
+
headers: { Authorization: `Bearer ${this.accessToken}`, 'Content-Type': 'application/json' },
|
|
667
|
+
});
|
|
668
|
+
return response.data;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
async consulterStructureChorus(idStructureCpp: number): Promise<Record<string, unknown>> {
|
|
672
|
+
await this.ensureAuthenticated();
|
|
673
|
+
const response = await this.httpClient.post(`${this.config.apiUrl}/api/v1/chorus-pro/structures/consulter`, { id_structure_cpp: idStructureCpp }, {
|
|
674
|
+
headers: { Authorization: `Bearer ${this.accessToken}`, 'Content-Type': 'application/json' },
|
|
675
|
+
});
|
|
676
|
+
return response.data;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
/**
|
|
680
|
+
* Liste les services d'une structure Chorus Pro.
|
|
681
|
+
* @param idStructureCpp ID Chorus Pro de la structure
|
|
682
|
+
* @returns Objet avec listeServices, total, codeRetour, libelle
|
|
683
|
+
*/
|
|
684
|
+
async listerServicesStructureChorus(idStructureCpp: number): Promise<Record<string, unknown>> {
|
|
685
|
+
await this.ensureAuthenticated();
|
|
686
|
+
const response = await this.httpClient.get(`${this.config.apiUrl}/api/v1/chorus-pro/structures/${idStructureCpp}/services`, {
|
|
687
|
+
headers: { Authorization: `Bearer ${this.accessToken}` },
|
|
688
|
+
});
|
|
689
|
+
return response.data;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
async obtenirIdChorusDepuisSiret(siret: string): Promise<number | null> {
|
|
693
|
+
const results = await this.rechercherStructureChorus({ identifiant_structure: siret, type_identifiant: 'SIRET' }) as Record<string, unknown>[];
|
|
694
|
+
if (results.length > 0 && results[0].id_structure_cpp) return results[0].id_structure_cpp as number;
|
|
695
|
+
return null;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
async soumettreFactureChorus(factureData: Record<string, unknown>): Promise<Record<string, unknown>> {
|
|
699
|
+
await this.ensureAuthenticated();
|
|
700
|
+
const response = await this.httpClient.post(`${this.config.apiUrl}/api/v1/chorus/factures/soumettre`, factureData, {
|
|
701
|
+
headers: { Authorization: `Bearer ${this.accessToken}`, 'Content-Type': 'application/json' },
|
|
702
|
+
});
|
|
703
|
+
return response.data;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
async consulterFactureChorus(identifiantFactureCpp: number): Promise<Record<string, unknown>> {
|
|
707
|
+
await this.ensureAuthenticated();
|
|
708
|
+
const response = await this.httpClient.post(`${this.config.apiUrl}/api/v1/chorus/factures/consulter`, { identifiant_facture_cpp: identifiantFactureCpp }, {
|
|
709
|
+
headers: { Authorization: `Bearer ${this.accessToken}`, 'Content-Type': 'application/json' },
|
|
710
|
+
});
|
|
711
|
+
return response.data;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
// ==================== Validation ====================
|
|
715
|
+
|
|
716
|
+
/**
|
|
717
|
+
* Valide un PDF Factur-X.
|
|
718
|
+
* @param pdfBuffer - Contenu du PDF en Buffer
|
|
719
|
+
* @param options - Options de validation
|
|
720
|
+
* @param options.profil - Profil Factur-X (MINIMUM, BASIC, EN16931, EXTENDED). Si non spécifié, auto-détecté.
|
|
721
|
+
* @param options.useVerapdf - Active la validation stricte PDF/A avec VeraPDF (défaut: false)
|
|
722
|
+
*/
|
|
723
|
+
async validerPdfFacturx(
|
|
724
|
+
pdfBuffer: Buffer,
|
|
725
|
+
options: { profil?: string; useVerapdf?: boolean } = {}
|
|
726
|
+
): Promise<Record<string, unknown>> {
|
|
727
|
+
await this.ensureAuthenticated();
|
|
728
|
+
const form = new FormData();
|
|
729
|
+
form.append('fichier_pdf', pdfBuffer, { filename: 'facture.pdf', contentType: 'application/pdf' });
|
|
730
|
+
if (options.profil) {
|
|
731
|
+
form.append('profil', options.profil);
|
|
732
|
+
}
|
|
733
|
+
form.append('use_verapdf', String(options.useVerapdf ?? false));
|
|
734
|
+
const response = await this.httpClient.post(`${this.config.apiUrl}/api/v1/traitement/valider-pdf-facturx`, form, {
|
|
735
|
+
headers: { ...form.getHeaders(), Authorization: `Bearer ${this.accessToken}` },
|
|
736
|
+
});
|
|
737
|
+
return response.data;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
async validerXmlFacturx(xmlContent: string, profil = 'EN16931'): Promise<Record<string, unknown>> {
|
|
741
|
+
await this.ensureAuthenticated();
|
|
742
|
+
const form = new FormData();
|
|
743
|
+
form.append('fichier_xml', Buffer.from(xmlContent, 'utf-8'), { filename: 'facture.xml', contentType: 'application/xml' });
|
|
744
|
+
form.append('profil', profil);
|
|
745
|
+
const response = await this.httpClient.post(`${this.config.apiUrl}/api/v1/traitement/valider-xml`, form, {
|
|
746
|
+
headers: { ...form.getHeaders(), Authorization: `Bearer ${this.accessToken}` },
|
|
747
|
+
});
|
|
748
|
+
return response.data;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
async validerSignaturePdf(pdfBuffer: Buffer): Promise<Record<string, unknown>> {
|
|
752
|
+
await this.ensureAuthenticated();
|
|
753
|
+
const form = new FormData();
|
|
754
|
+
form.append('fichier_pdf', pdfBuffer, { filename: 'document.pdf', contentType: 'application/pdf' });
|
|
755
|
+
const response = await this.httpClient.post(`${this.config.apiUrl}/api/v1/traitement/valider-signature-pdf`, form, {
|
|
756
|
+
headers: { ...form.getHeaders(), Authorization: `Bearer ${this.accessToken}` },
|
|
757
|
+
});
|
|
758
|
+
return response.data;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
// ==================== Signature ====================
|
|
762
|
+
|
|
763
|
+
/**
|
|
764
|
+
* Signe un PDF avec le certificat configuré côté serveur (via client_uid du JWT).
|
|
765
|
+
* Le certificat doit être préalablement configuré dans Django Admin.
|
|
766
|
+
*/
|
|
767
|
+
async signerPdf(
|
|
768
|
+
pdfBuffer: Buffer,
|
|
769
|
+
options: { raison?: string; localisation?: string; contact?: string; usePadesLt?: boolean; useTimestamp?: boolean } = {}
|
|
770
|
+
): Promise<Buffer> {
|
|
771
|
+
await this.ensureAuthenticated();
|
|
772
|
+
const form = new FormData();
|
|
773
|
+
form.append('fichier_pdf', pdfBuffer, { filename: 'document.pdf', contentType: 'application/pdf' });
|
|
774
|
+
if (options.raison) form.append('raison', options.raison);
|
|
775
|
+
if (options.localisation) form.append('localisation', options.localisation);
|
|
776
|
+
if (options.contact) form.append('contact', options.contact);
|
|
777
|
+
if (options.usePadesLt !== undefined) form.append('use_pades_lt', String(options.usePadesLt));
|
|
778
|
+
if (options.useTimestamp !== undefined) form.append('use_timestamp', String(options.useTimestamp));
|
|
779
|
+
const response = await this.httpClient.post(`${this.config.apiUrl}/api/v1/traitement/signer-pdf`, form, {
|
|
780
|
+
headers: { ...form.getHeaders(), Authorization: `Bearer ${this.accessToken}` },
|
|
781
|
+
});
|
|
782
|
+
// L'API retourne du JSON avec pdf_signe_base64
|
|
783
|
+
const data = response.data as { pdf_signe_base64?: string };
|
|
784
|
+
if (data.pdf_signe_base64) {
|
|
785
|
+
return Buffer.from(data.pdf_signe_base64, 'base64');
|
|
786
|
+
}
|
|
787
|
+
throw new Error('Réponse de signature invalide');
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
/**
|
|
791
|
+
* Génère un certificat de test (NON PRODUCTION).
|
|
792
|
+
* Le certificat doit ensuite être configuré dans Django Admin.
|
|
793
|
+
*/
|
|
794
|
+
async genererCertificatTest(
|
|
795
|
+
options: { cn?: string; organisation?: string; email?: string; dureeJours?: number; tailleClé?: number } = {}
|
|
796
|
+
): Promise<Record<string, unknown>> {
|
|
797
|
+
await this.ensureAuthenticated();
|
|
798
|
+
const response = await this.httpClient.post(`${this.config.apiUrl}/api/v1/traitement/generer-certificat-test`, {
|
|
799
|
+
cn: options.cn || 'Test Organisation',
|
|
800
|
+
organisation: options.organisation || 'Test Organisation',
|
|
801
|
+
email: options.email || 'test@example.com',
|
|
802
|
+
duree_jours: options.dureeJours || 365,
|
|
803
|
+
taille_cle: options.tailleClé || 2048,
|
|
804
|
+
}, {
|
|
805
|
+
headers: { Authorization: `Bearer ${this.accessToken}`, 'Content-Type': 'application/json' },
|
|
806
|
+
});
|
|
807
|
+
return response.data;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
// ==================== Workflow complet ====================
|
|
811
|
+
|
|
812
|
+
/**
|
|
813
|
+
* Workflow complet : génération + validation + signature + soumission AFNOR.
|
|
814
|
+
* Note: La signature utilise le certificat configuré côté serveur (via client_uid du JWT).
|
|
815
|
+
* @param factureData Données de la facture
|
|
816
|
+
* @param pdfPath Chemin vers le PDF source
|
|
817
|
+
* @param options Options du workflow
|
|
818
|
+
* @returns Résultat avec pdfBuffer, validation, signature et afnor
|
|
819
|
+
*/
|
|
820
|
+
async genererFacturxComplet(
|
|
821
|
+
factureData: Record<string, unknown>,
|
|
822
|
+
pdfPath: string,
|
|
823
|
+
options: {
|
|
824
|
+
profil?: string;
|
|
825
|
+
valider?: boolean;
|
|
826
|
+
signer?: boolean;
|
|
827
|
+
soumettreAfnor?: boolean;
|
|
828
|
+
afnorFlowName?: string;
|
|
829
|
+
afnorTrackingId?: string;
|
|
830
|
+
timeout?: number;
|
|
831
|
+
} = {}
|
|
832
|
+
): Promise<{
|
|
833
|
+
pdfBuffer: Buffer;
|
|
834
|
+
validation?: Record<string, unknown>;
|
|
835
|
+
signature?: { signe: boolean };
|
|
836
|
+
afnor?: Record<string, unknown>;
|
|
837
|
+
}> {
|
|
838
|
+
const {
|
|
839
|
+
profil = 'EN16931',
|
|
840
|
+
valider = true,
|
|
841
|
+
signer = false,
|
|
842
|
+
soumettreAfnor = false,
|
|
843
|
+
afnorFlowName,
|
|
844
|
+
afnorTrackingId,
|
|
845
|
+
timeout,
|
|
846
|
+
} = options;
|
|
847
|
+
const result: {
|
|
848
|
+
pdfBuffer: Buffer;
|
|
849
|
+
validation?: Record<string, unknown>;
|
|
850
|
+
signature?: { signe: boolean };
|
|
851
|
+
afnor?: Record<string, unknown>;
|
|
852
|
+
} = { pdfBuffer: Buffer.alloc(0) };
|
|
853
|
+
|
|
854
|
+
// 1. Génération
|
|
855
|
+
const pdfBuffer = await this.genererFacturx(factureData, pdfPath, profil, 'pdf', true, timeout) as Buffer;
|
|
856
|
+
result.pdfBuffer = pdfBuffer;
|
|
857
|
+
|
|
858
|
+
// 2. Validation
|
|
859
|
+
if (valider) {
|
|
860
|
+
const validation = await this.validerPdfFacturx(pdfBuffer, { profil });
|
|
861
|
+
result.validation = validation;
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
// 3. Signature (utilise le certificat configuré côté serveur)
|
|
865
|
+
if (signer) {
|
|
866
|
+
const signedPdf = await this.signerPdf(result.pdfBuffer);
|
|
867
|
+
result.pdfBuffer = signedPdf;
|
|
868
|
+
result.signature = { signe: true };
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
// 4. Soumission AFNOR
|
|
872
|
+
if (soumettreAfnor) {
|
|
873
|
+
const numeroFacture = (factureData.numeroFacture || factureData.numero_facture || 'FACTURE') as string;
|
|
874
|
+
const flowName = afnorFlowName || `Facture ${numeroFacture}`;
|
|
875
|
+
const trackingId = afnorTrackingId || numeroFacture;
|
|
876
|
+
const afnorResult = await this.soumettreFactureAfnor(result.pdfBuffer, flowName, { trackingId });
|
|
877
|
+
result.afnor = afnorResult;
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
return result;
|
|
881
|
+
}
|
|
882
|
+
}
|