Dépôt / Vente

Installation et configuration de la file d'impression

Le principe
Mise en oeuvre
API
Le client
Le script python
Retour

Le principe de fonctionnement de la file d'attente

Lorsque l'application génère un document PDF, ce dernier est "envoyé" dans une file d'attente.

Un poste de travail dédié doit être configuré pour traiter cette file d'attente (située sur le serveur depot-vente.fvdw.fr).
Ce poste (Poste 3 sur le schéma) utilise les API (service impression) pour extraire les documents PDF à imprimer sur son imprimante.
impression avec file

Pour imprimer physiquement les documents, il faut utiliser un programme qui va consulter la file d'attente à interval réguliers. Si la file contient au moins un document, le programme "télécharge" le document et l'envoie à imprimante. Quand l'impression est terminée, le programme "retire" le document de la fille d'attente. Le programme se met "en attente du prochain document".

Aucune intervention humaine n'est nécessaire.

Mise en oeuvre sur un poste "Windows"

Nous vous proposons un programme de traitement de la file d'attente écrit en Python fonctionnant sous Windows.
Le script (qui tient sur un seul fichier) interroge le serveur via les API, et utilise le programme Sumatra PDF pour imprimer le fichier PDF vers l'imprimante par défaut.

Installation du programme de traitement de la file d'attente :

  1. Créez un dossier de travail par exemple C:\file_depot_vente.
  2. Téléchargez et copiez le script spool_client.py dans le dossier (C:\file_depot_vente).
  3. Le script étant écrit en Python, l'installation de l'interpréteur Python est nécessaire pour l'exécuter.
    Si python n'est pas présent sur le PC, téléchargez Python et installez-le sur votre poste.
  4. Installez le lecteur de documents PDF :
    Pour permettre l'envoi des documents PDF vers une imprimante, vous avez besoin d'un lecteur "PDF" : Sumatra PDF . Ce programme est gratuit, léger et peut même être installé en version portable (dézippez l'archive là où vous le désirez).
  5. Créez un fichier de type .cmd (par exemple file_attente.cmd) qui lancera python.exe avec le script spool_client.py en paramètre ainsi que les paramètres de votre configuration et application.
    Exemple :
    @echo off
    echo traitement de la file d'attente...
    python spool_client.py 6B024676
    pause
    Dans cet exemple, ce fichier lance le traitement de la file d'attente pour l'application "Demo" (code application 6B024676) et considère que vous avez installé Sumatra PDF dans le dossier courant.
    NB : Si vous avez installé Sumatra PDF en mode standard, spécifiez le chemin complet du fichier SumatraPDF.exe, par exemple :
    python spool_client.py 6B024676 "C:\Program Files\SumatraPDF\SumatraPDF.exe"
  6. Pour obtenir votre code application, depuis l'application, à la page "Administration", cliquez sur "Paramètres de l'application". La page "Paramétrage de l'application" affiche votre code application.
    Code de l'application

Votre dossier C:\file_depot_vente devrait ressembler à ceci :
contenu du dossier file_depot_vente

Pour lancer le traitement de la file d'attente, double cliquez sur file_attente.cmd, une fenêtre de type "cmd" s'ouvre et affiche le traitement en cours.
file attente

Les API

L'application depot-vente.fvdw.fr présente une API pour consommer les données de la file d'impression.
Les requêtes s'effectuent en GET et sont disponibles aux adresses suivantes
  • https://depot-vente.fvdw.fr/SPL-PING-code organisation
  • https://depot-vente.fvdw.fr/SPL-NEXT-code organisation
  • https://depot-vente.fvdw.fr/SPL-GET-numéro de document
  • https://depot-vente.fvdw.fr/SPL-DEL-numéro de document
Toutes ces méthodes retournent un flux JSON. Les URL de ces API sont accessibles depuis la page administration -> Paramétrage de l'évènement.

PING

Permet de tester la connexion au serveur et valider le code organisation.
Cet appel retourne un flux JSON.
> curl https:/depot-vente.fvdw.fr/SPL-PING-6B024676
{'error':false,'message':'OK Démo'}
        
Valeurs de retour
CléTypeValeur
error boolean true|false
message string OK ou la cause de l'erreur

NEXT

Interroge le serveur : si il existe : donne le prochain document à imprimer.
Si il existe au moins un document est dans la file
> curl https:/depot-vente.fvdw.fr/SPL-NEXT-6B024676
{'pdf_b64':'JVBERi … U9GCg==','spool_slug':'285481266561','crc':2025417623,'error':false}
        
Si la file d'attente est vide
> curl https:/depot-vente.fvdw.fr/SPL-NEXT-6B024676
{'pdf_b64':'','spool_slug':'','crc':false,'error':false,'message':"Aucun document PDF dans la file d'attente"}
        
Valeurs de retour
CléTypeValeur
pdf_b64 string s'il existe : une chaine encodée en base 64 ou vide s'il n'existe pas
spool_slug string s'il existe : une série de chiffres, identifiant unique du document ou vide s'il n'existe pas
crc string s'il existe : une série de chiffres, résultat du CRC32 du pdf (non encodé) ou False s'il n'existe pas
error boolean true|false
message string Cette clé existe uniquement si une erreur se produit la cause de l'erreur

GET

Demande un document de la file d'attente.
On spécifie l'identifiant du document dans l'url SPL-GET-xxx où xxx est le numéro retourné lors de l'appel de SPL-NEXT, sous la clé "spool_slug". Sur base de l'exemple ci-dessus : l'url devient SPL-GET-285481266561
Le document est dans la file d'attente.
> curl https:/depot-vente.fvdw.fr/SPL-GET-285481266561
{'pdf_b64':"JVBERi0 … RU9GCg==','spool_slug':'285481266561','crc':2025417623,'error":false}
        
Le document n'est pas dans la file d'attente.
> curl https:/depot-vente.fvdw.fr/SPL-GET-285481266000
{'error':true,'message':'Aucun document PDF avec le code #285481266560'}
        
Valeurs de retour
CléTypeValeur
pdf_b64 string s'il existe : une chaine encodée en base 64
spool_slug string s'il existe : une série de chiffres, identifiant unique du document ou vide s'il n'existe pas
crc string s'il existe : une série de chiffres, résultat du CRC32 du pdf (non encodé) ou False s'il n'existe pas
error boolean true|false
message string Cette clé existe uniquement si une erreur se produit la cause de l'erreur

DEL

Demande l'effacement d'un document de la file d'attente.
On spécifie l'identifiant du document dans l'url SPL-GET-xxx où xxx est le numéro retourné lors de l'appel de SPL-NEXT, sous la clé 'spool_slug'. Sur base de l'exemple ci-dessus : l'url devient SPL-DEL-285481266561
Le document est dans la file d'attente.
> curl https:/depot-vente.fvdw.fr/SPL-DEL-285481266561
{'error':false,'message':"Document #285481266561 effac\u00e9 de la file d'attente"}
        
Le document n'est pas dans la file d'attente.
> curl https:/depot-vente.fvdw.fr/SPL-DEL-285481266000
{'error':false,'message':"Aucun document PDF \u00e0 effacer de la file d'attente"}
        
Valeurs de retour
Clé Type Valeur
error boolean true|false
message string message de confirmation ou la cause de l'erreur

Le client


Le fonctionnement préconisé est une boucle sur la méthode SPL-NEXT-… qui attend les documents de la file d'impression. Si des documents sont disponibles, on transforme les données reçues encodées en base 64 pour obtenir un fichier PDF que l'on passe à un programme chargé d'imprimer ce document.
Quand le document est traité, il faut demander au serveur d'effacer ce document de la file d'attente
Boucle sur file des document a imprimer

Script python

 # (c) depot-vente.fvdw.fr - octobre 2026
#
# Script pour consommer la file d'attente des documents PDF pour impression
# Version pour Windows, utilisant SumatraPDF qui imprime sur l'imprimante par défaut
#
import sys
import base64
import time
import tempfile
import os
import subprocess
import json
import binascii
from urllib.request import urlopen
from urllib.error import URLError, HTTPError

# -------------------------------------------------------------------------------------
def get_remote(url):
    try:
        with urlopen(url, timeout=10) as response:
            resp_data = json.loads(response.read())
    except HTTPError as e:
        return {'success' : False, 'message': f"Erreur HTTP : {e.code} - {e.reason}" }
    except URLError as e:
        return {'success' : False, 'message': f"Erreur URL : {e.reason}" }
    except TimeoutError:
        return {'success' : False, 'message': "Erreur Timeout 10s" }
    except Exception as e:
        return {'success' : False, 'message': f"Une erreur inattendue est survenue : {e}"}
    finally:
        # print(f"Requête reussie. Code HTTP : {response.getcode()}")
        # print(content[:100])  # Affiche les 100 premiers caracteres
        resp_data["success"] = True
        return resp_data

# -------------------------------------------------------------------------------------
def print_pdf(pdf_data, sumatra_path):
    # Enregistre le PDF dans un fichier temporaire
    with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as f:
        f.write(pdf_data)
        pdf_path = f.name

    # Imprime le PDF via SumatraPDF
    try:
        subprocess.run([sumatra_path, "-print-to-default", pdf_path], check=True)
    except Exception as e:
        print( f"Impression via Sumatra : Une erreur inattendue est survenue : {e}")
        return False
    finally:
        # Supprime le fichier local temporaire apres impression
        os.unlink(pdf_path)
        return True

# -------------------------------------------------------------------------------------
def ping(url):
    ret = get_remote(url)

    ret["action"] = "ping"
    if not ret["success"]:
        ret["error"]  = True
    else :
        if ret["error"]:
            ret["success"] = False
    return ret

# -------------------------------------------------------------------------------------
def del_document(url):
    """
    Demande de supression du document cote serveur (file d'attente)
    """
    ret = get_remote(url)
    ret["action"] = "del"
    if not ret["success"]:
        print("Erreur " + ret["message"])
    return ret

# -------------------------------------------------------------------------------------
def next_document(url):
    """
    Interroge la file d'attente : si au moins un document en attente retourne
    le binaire du 1er document de la file
    """
    content = get_remote(url)
    content["action"] = "next"
    if not content['success']:
        # Erreur
        content["pdf_data"] = False
        return content

    if not content['pdf_b64']:
        # Pas de fichier en attente
        content["pdf_data"] = False
        return content

    # ctrl CRC
    pdf_data = base64.b64decode(content['pdf_b64'])
    crc = binascii.crc32(pdf_data)
    if crc != content["crc"] :
        content["success"] = False
        content["message"] = "Le flux reçu est corrompu !"
        content["pdf_b64"] = "/"
        content["pdf_data"] = False
        return content
    content["pdf_b64"] = "/"
    content["pdf_data"] = pdf_data # data binaires

    ## print("Ctrl : local crc = %d - remote crc = %d "  % (crc, content["crc"]) )
    return content

# -------------------------------------------------------------------------------------
def main():
    """
    Boucle sur les douments de la file d'attente
    """
    ret = ping(g_ping_url)
    if not ret["success"]:
        print("Echec de connexion au serveur : " + ret["message"])
        return

    print("Appuyez sur 'Ctrl'+'C' pour arrêter")

    while True:
        ret = next_document(g_next_url)
        if ret["success"]:
            if ret["pdf_data"]:
                # document a traiter
                print("Impression du document #%s..." % (ret["spool_slug"]))
                if not print_pdf(ret["pdf_data"], g_sumatra_path):
                    print("Echec impression")
                else:
                    print("Document imprime")
                    # supression cote serveur
                    r = del_document(g_del_url + ret["spool_slug"])
                    if not r["success"]:
                        print("Echec mise a jour du serveur")
                    else:
                        print("Serveur mis a jour")
            else:
                try:
                    print("Pas de document en attente ... pause (30 sec)")
                    # ATTENTION : ne pas utiliser une valeur inferieure a 30 (secondes) sans quoi les
                    # performances globales seront diminuees
                    time.sleep(30)
                except KeyboardInterrupt:
                    print("Interruption utilisateur, fin du traitement de la file d'attente.")
                    return
        else:
            print("Erreur : %s" % (ret["message"]))
            break


# -------------------------------------------------------------------------------------
#  Point d'entree
# -------------------------------------------------------------------------------------
if __name__ == "__main__":

    arg_info = """
    1 : chaine de caractères sur 8 positions = Identifant application. Vous trouverez ce code dans la partie "Administration" - "Paramètres de l'application"
    2 : path pour le programme SumatraPDF. Facultatif, par défaut "SumatraPDF.exe"
    3 : url de dépôt/vente. Facultatif, par défaut : "https://depot-vente.fvdw.fr"
"""

    if len(sys.argv) < 2:
        print("Spécifiez dans la ligne de commande les informations suivantes :"+arg_info+"\nPar exemple " + sys.argv[0] + " 22AB22CD22 SumatraPDF.exe")
        sys.exit(1)

    id_app = sys.argv[1]
    url = "https://depot-vente.fvdw.fr"
    g_sumatra_path = r"SumatraPDF.exe"
    if len(sys.argv) > 2 :
        g_sumatra_path = sys.argv[2]
    # ctrl path Sumatra
    if not os.path.isfile(g_sumatra_path) or not os.access(g_sumatra_path, os.X_OK):
        print("Le paramètre 2 : path pour l'executable SumatraPDF est invalide ('"+g_sumatra_path+"').\nLes paramètres attendus sont :"+arg_info)
        sys.exit(1)
    if len(sys.argv) > 3:
        url = sys.argv[3]
    g_ping_url = url+"/SPL-PING-"+id_app
    g_next_url = url+"/SPL-NEXT-"+id_app
    g_del_url  = url+"/SPL-DEL-"

    main()

# EoF

Téléchargez le script spool_client.py


Schémas : https://excalidraw.com/ | Captures d'écrans : https://getgreenshot.org/