소스 검색

Refonte gf2analytique

olivier.massot 7 년 전
부모
커밋
7994140b98
3개의 변경된 파일161개의 추가작업 그리고 149개의 파일을 삭제
  1. 29 0
      core/pde.py
  2. 106 149
      gf2analytique.py
  3. 26 0
      resources/test_gf2analytique.py

+ 29 - 0
core/pde.py

@@ -196,6 +196,35 @@ class Tarification(Model):
         self.strStatut = None, str
         self.strNumFacture = None, str
 
+
+class FactureGf(Model):
+    """ Modèle de données d'une facture Astre-GF """
+    def __init__(self):
+        self.numExBudget = 0, int
+        self.codeColl = "", str
+        self.codeBudg = "", str
+        self.numEnv = 0, int
+        self.codeSection = "", str
+        self.typeMvt = "", str
+        self.numMandat = 0, int
+        self.numLiqMandat = 0, int
+        self.numLigneMandat = 0, int
+        self.codeAxe = "", str
+        self.libAxe = "", str
+        self.codeCout = "", str
+        self.libCout = "", str
+        self.dateMandat = None, datetime
+        self.numBj = 0, int
+        self.numTiers = 0, int
+        self.libRai = "", str
+        self.refIntMandat = "", str
+        self.codePeriode = "", str
+        self.dateDepDelai = None, datetime
+        self.typeNomencMarche = "", str
+        self.mntTtcMandat = 0, float
+        self.mntTvaMandat = 0, float
+        self.mntVent = 0, float
+
 class EnTete(Model):
     """ En-tête d'une facture dans FacturesDb """
     def __init__(self):

+ 106 - 149
gf2analytique.py

@@ -10,9 +10,10 @@
     3- Une fois les données valides, import dans Analytique
 
     **IMPORTANT**: pour lancer le script sans interaction avec l'utilisateur
-    (par ex, dans le cas d'une tâche planifiée), appeller le script avec l'option '-n'.
+    (par ex, dans le cas d'une tâche planifiée), appeller le script avec l'option '--auto'.
 
 '''
+from datetime import datetime
 import logging
 import re
 import sys
@@ -20,8 +21,8 @@ import sys
 from path import Path  # @UnusedImport
 
 from core import logconf
-from core.model import Model
-from core.pde import AnalytiqueDb, mk_workdir
+from core.model import Sql
+from core.pde import AnalytiqueDb, FactureGf
 from core.webservice import GfWebservice
 
 
@@ -31,10 +32,10 @@ logconf.start("gf2analytique", logging.DEBUG)
 # # POUR TESTER, décommenter les lignes suivantes
 ##-----------------------------------------------
 
-# GfWebservice._url = r"http://webservices-t.bas-rhin.fr/CG67.AstreGF.WebServices/public/WsPDE.asmx"
-# AnalytiqueDb._path = Path(r"\\h2o\local\4-transversal\BDD\mdb_test\Db_analytique.mdb")
-# logger.handlers = [h for h in logger.handlers if (type(h) == logging.StreamHandler)]
-# logger.warning("<<<<<<<<<<<<<<   Mode TEST   >>>>>>>>>>>>>>>>>")
+GfWebservice._url = r"http://webservices-t.bas-rhin.fr/CG67.AstreGF.WebServices/public/WsPDE.asmx"
+AnalytiqueDb._path = Path(r"\\h2o\local\4-transversal\BDD\mdb_test\Db_analytique.mdb")
+logger.handlers = [h for h in logger.handlers if (type(h) == logging.StreamHandler)]
+logger.warning("<<<<<<<<<<<<<<   Mode TEST   >>>>>>>>>>>>>>>>>")
 
 ##-----------------------------------------------
 
@@ -43,7 +44,7 @@ def main():
     # *** Initialisation
     logger.info("Initialisation...")
 
-    no_prompt = ("-n" in sys.argv)
+    no_prompt = ("--auto" in sys.argv)
     if no_prompt:
         logger.info("> Lancé en mode automatique (sans interruption)")
 
@@ -52,151 +53,112 @@ def main():
 
     # Connect to the astre gf webservice
     ws = GfWebservice("GetPDEFactures")
-
-    # Make the working directory
-    workdir = mk_workdir("gf2analytique")
-    importfile = workdir / "import.csv"
-
-    # > Supprime le fichier d'import s'il existe, et le recréé avec la ligne d'en-tête
-    if importfile.exists():
-        logger.debug("Supprime le fichier %s", importfile)
-        importfile.remove()
-
-    class CsvFormatError(Exception):
-        pass
-
-    class Facture(Model):
-        """ Modèle de données d'une facture """
-        _FIELDS = ["numExBudget", "codeColl", "codeBudg", "numEnv", "codeSection", "typeMvt", "numMandat", "numLiqMandat",
-                     "numLigneMandat", "codeAxe", "libAxe", "codeCout", "libCout", "dateMandat", "numBj", "numTiers",
-                     "libRai", "refIntMandat", "codePeriode", "dateDepDelai", "typeNomencMarche", "mntTtcMandat",
-                     "mntTvaMandat", "mntVent"]
-
-        def __init__(self):
-            super(Facture, self).__init__()
-
-        def est_importee(self):
-            """ Renvoie True si la facture a déjà été importée dans Analytique
-            ATTENTION: en l'absence d'identifiants uniques, il est difficile de contrôler de manière certaine si une ligne a déjà été importée.
-            C'est pour cette raison que les données sont importées 'par blocs' """
-
-            sql = """SELECT dblFactureId FROM tbl_Factures
-                    WHERE intExercice = {} AND strLiquidation = '{}' AND strEngagement = '{}' AND strService='7710'
-                  """.format(self.numExBudget, self.numLiqMandat, self.numMandat)
-            return analytique_db.exists(sql)
-
-        def autocorrection(self):
-            """ Procède à certaines corrections automatiques sur les données de la facture """
-            # correction auto des codes chantiers
-            if self.codeAxe == "AFFAI" and re.match(r"\d{2}5\d{3}", self.codeCout):
-                self.codeCout += "/1"
-
-            # echappe les apostrophes
-            self.libRai = self.libRai.replace("'", "''")
-
-            # renomme automatiquement les noms de materiels
-            if self.codeAxe == "ENGIN":
-                row = analytique_db.first("""SELECT txtMateriel FROM tbl_materiel
-                                                WHERE txtMateriel='{codeCout}' or txtMateriel='ZZ {codeCout}'
-                                                """.format(codeCout=self.codeCout))
-                if row:
-                    self.codeCout = row.txtMateriel
-
-        def is_valid(self):
-            """ controle la validité des données d'une facture """
-            for field in self._FIELDS:
-                if not hasattr(self, field):
-                    raise CsvFormatError("Un ou plusieurs champs sont manquants dans le fichier '{}'".format(importfile))
-
-            errors = False
-            if not int(self.numExBudget) > 2000:
-                logger.error("Exercice budgetaire invalide: %s", self.numExBudget)
-                errors = True
-            if self.codeColl != "CG67":
-                logger.error("Code collectivité invalide: %s", self.codeColl)
-                errors = True
-            if self.codeBudg != "02":
-                logger.error("Code budgetaire invalide: %s", self.codeBudg)
-                errors = True
-            if self.codeAxe == "ENGIN":
-                # Controle l'existence du materiel
-                if not analytique_db.first("SELECT intlMaterielID FROM tbl_materiel WHERE txtMateriel='{}'".format(self.codeCout)):
-                    logger.error("Le materiel n'existe pas: %s", self.codeCout)
-                    errors = True
-            elif self.codeAxe == "AFFAI":
-                # Controle l'existence de l'affaire
-                if not analytique_db.first("SELECT dblAffaireId FROM tbl_Affaires WHERE strLiaisonControle='{}'".format(self.codeCout)):
-                    logger.error("L'affaire n'existe pas: %s", self.codeCout)
-                    errors = True
-            else:
-                # CodeAxe invalide
-                logger.error("Code axe inconnu: %s", self.codeAxe)
-                errors = True
-            return (not errors)
-
+    ws.parse()
 
     # *** 1- Parcourt les factures renvoyées par le webservice, et stoque toutes les lignes non-importées dans Analytique dans un fichier import.csv
     logger.info("Parcourt les données fournies par le webservice")
-    logger.info("(les ligne à importer sont ajoutées au fichier %s)", importfile)
+
+    factures = []
 
     for data in ws:
         # Génère la facture à partir des données fournies par le web-service
-        facture = Facture.from_dict(data)
+        facture = FactureGf.from_dict(data)
 
         # Contrôle si la facture est déjà importée. Si c'est le cas, passe à la facture suivante.
-        if facture.est_importee():
+        #  ATTENTION: en l'absence d'identifiants uniques, il est difficile de contrôler de manière certaine si une ligne a déjà été importée.
+        #  C'est pour cette raison que les données sont importées 'par blocs'
+        if analytique_db.exists(Sql.format("""SELECT dblFactureId FROM tbl_Factures
+                    WHERE intExercice = {} AND strLiquidation = {:text} AND strEngagement = {:text} AND strService='7710'
+                  """, facture.numExBudget, facture.numLiqMandat, facture.numMandat)):
             continue
 
         logger.info("* Facture %s/%s/%s: import", facture.numExBudget, facture.numMandat, facture.numLiqMandat)
 
-        # procède à une auto-correction des données
-        facture.autocorrection()
+        # # Auto-correction des données
+        # correction auto des codes chantiers
+        if facture.codeAxe == "AFFAI" and re.match(r"\d{2}5\d{3}", facture.codeCout):
+            facture.codeCout += "/1"
+
+        # echappe les apostrophes
+        facture.libRai = facture.libRai.replace("'", "''")
+
+        # renomme automatiquement les noms de materiels
+        if facture.codeAxe == "ENGIN":
+            row = analytique_db.first(Sql.format("""SELECT txtMateriel FROM tbl_materiel
+                                            WHERE txtMateriel={codeCout:text} or txtMateriel='ZZ {codeCout}'
+                                            """, codeCout=facture.codeCout))
+            if row:
+                facture.codeCout = row.txtMateriel
 
-        # Ajoute les données au format CSV au fichier d'import
-        facture.dump_to_csv(importfile)
+        factures.append(facture)
 
-    if not importfile.exists():
+    if not factures:
         logger.info("** Aucune nouvelle facture à importer **")
-        sys.exit(0)
+        return
 
     # *** 2- Contrôle les données. En cas d'erreur, le script est interrompu et la position et la description des erreurs sont loggés.
     errors = -1
 
     while errors:
         errors = 0
-        logger.info("Contrôle des données")
+        logger.info("# Contrôle des données")
+
+        for facture in factures:
+            prefix = "* Facture {}/{}/{}: ".format(facture.numExBudget, facture.numMandat, facture.numLiqMandat)
 
-        # Parcourt les lignes du fichier d'import, et teste la validité de chacune.
-        for facture in Facture.load_csv(importfile):
-            if not facture.is_valid():
+            if not int(facture.numExBudget) > 2000:
+                logger.error(prefix + "Exercice budgetaire invalide: %s", facture.numExBudget)
                 errors += 1
+                if not no_prompt:
+                    facture.numExBudget = input("Saisir la nouvelle valeur:")
 
-        if errors:
-            logger.error("<!> Une ou plusieurs erreurs ont été détectées, voir le fichier de log pour plus d'information <!>")
-            if no_prompt:
-                logger.info("Veuillez executer le script manuellement: %s", Path(__file__).abspath())
-            else:
-                logger.info("Veuillez corriger les données du fichier %s", importfile)
+            if facture.codeColl != "CG67":
+                logger.error(prefix + "Code collectivité invalide: %s", facture.codeColl)
+                errors += 1
+                if not no_prompt:
+                    facture.codeColl = input("Saisir la nouvelle valeur:")
+
+            if facture.codeBudg != "02":
+                logger.error(prefix + "Code budgetaire invalide: %s", facture.codeBudg)
+                errors += 1
+                if not no_prompt:
+                    facture.codeBudg = input("Saisir la nouvelle valeur:")
+
+            if facture.codeAxe == "ENGIN":
+                # Controle l'existence du materiel
+                if not analytique_db.exists(Sql.format("SELECT intlMaterielID FROM tbl_materiel WHERE txtMateriel={:text}", facture.codeCout)):
+                    logger.error(prefix + "Le materiel n'existe pas: %s", facture.codeCout)
+                    errors += 1
+                    if not no_prompt:
+                        facture.codeCout = input("Saisir la nouvelle valeur:")
+
+            elif facture.codeAxe == "AFFAI":
+                # Controle l'existence de l'affaire
+                if not analytique_db.exists(Sql.format("SELECT dblAffaireId FROM tbl_Affaires WHERE strLiaisonControle={:text}", facture.codeCout)):
+                    logger.error(prefix + "L'affaire n'existe pas: %s", facture.codeCout)
+                    errors += 1
+                    if not no_prompt:
+                        facture.codeCout = input("Saisir la nouvelle valeur:")
 
-            # En cas d'erreur(s), deux possibilités:
-            # - Le script a été lancé en mode sans interruption avec l'option '-n', on interrompt le script.
-            # - Le script a été lancé normalement, sans option: on attend une correction manuelle de l'utilisateur.
-            if no_prompt:
-                sys.exit(errors)
             else:
-                try:
-                    from core import tsv_editor
-                    tsv_editor.exec_(importfile.abspath())
-                except:
-                    logger.error("Erreur à l'ouverture du fichier %s", importfile)
-                    input("Presser une touche pour continuer...")
+                # CodeAxe invalide
+                logger.error(prefix + "Code axe inconnu: %s", facture.codeAxe)
+                errors += 1
+                if not no_prompt:
+                    facture.codeAxe = input("Saisir la nouvelle valeur:")
+
+        if no_prompt:
+            if errors:
+                logger.error("<!> Une ou plusieurs erreurs ont été détectées, voir le fichier de log pour plus d'information <!>")
+                logger.info("* Opération annulée *")
+                return
 
     logger.info("Les données sont valides.")
 
     # 3- Si toutes les données sont valides, parcourt les lignes du fichier import.csv et les insère dans la table tbl_Facture.
-    logger.info("Mise à jour des tables de %s", AnalytiqueDb._path)
+    logger.info("Mise à jour des tables de la base Analytique")
 
-    for facture in Facture.load_csv(importfile):
+    for facture in factures:
 
         logger.info("* Facture %s/%s/%s: traitement", facture.numExBudget, facture.numMandat, facture.numLiqMandat)
         # NB: les données ne sont committées qu'aprés l'exécution de toutes les requêtes suivantes
@@ -204,17 +166,16 @@ def main():
         logger.info("> mise à jour de tbl_Factures")
 
         # Insère les données dans la table tbl_Factures
-        sql = """INSERT INTO tbl_Factures ( intExercice, strLiquidation, intLiquidationLigne, strEngagement,
+        sql = Sql.format("""INSERT INTO tbl_Factures ( intExercice, strLiquidation, intLiquidationLigne, strEngagement,
                                             strEnveloppe, strService, strTiers, strTiersLibelle, strMotsClefs,
                                             dtmDeb, intOperation, strNomenclature0, strAXE, strCentreCout,
                                             strObjet, dblMontantTotal, dblMontantTVA, strORIGINE_DONNEES
                                         )
-                  VALUES ({intExercice}, '{strLiquidation}', {intLiquidationLigne}, '{strEngagement}',
-                          '{strEnveloppe}', '{strService}', '{strTiers}', '{strTiersLibelle}', '{strMotsClefs}',
-                          #{dtmDeb}#, {intOperation}, '{strNomenclature0}', '{strAxe}', '{strCentreCout}',
-                          '{strObjet}', {dblMontantTotal}, {dblMontantTVA}, '{strORIGINE_DONNEES}')
-              """.format(
-                         intExercice=facture.numExBudget,
+                  VALUES ({intExercice}, {strLiquidation:text}, {intLiquidationLigne}, {strEngagement:text},
+                          {strEnveloppe:text}, {strService:text}, {strTiers:text}, {strTiersLibelle:text}, {strMotsClefs:text},
+                          {dtmDeb:date}, {intOperation}, {strNomenclature0:text}, {strAxe:text}, {strCentreCout:text},
+                          {strObjet:text}, {dblMontantTotal}, {dblMontantTVA}, {strORIGINE_DONNEES:text})
+              """, intExercice=facture.numExBudget,
                          strLiquidation=facture.numLiqMandat,
                          intLiquidationLigne=facture.numLigneMandat,
                          strEngagement=facture.numMandat,
@@ -222,13 +183,13 @@ def main():
                          strService='7710',
                          strTiers=facture.numTiers,
                          strTiersLibelle=facture.libRai,
-                         strMotsClefs=AnalytiqueDb.nz(facture.refIntMandat),
-                         dtmDeb=AnalytiqueDb.format_date(facture.dateDepDelai),
-                         intOperation=AnalytiqueDb.nz(facture.codePeriode, "Null"),
+                         strMotsClefs=facture.refIntMandat,
+                         dtmDeb=facture.dateDepDelai,
+                         intOperation=facture.codePeriode,
                          strNomenclature0=facture.typeNomencMarche,
                          strAxe=facture.codeAxe,
                          strCentreCout=facture.codeCout,
-                         strObjet=AnalytiqueDb.format_date(facture.dateMandat, out_format="%d/%m/%Y"),
+                         strObjet=datetime.strptime(str(facture.dateMandat), "%Y-%m-%dT%H:%M:%S").strftime("%d/%m/%Y"),
                          dblMontantTVA=facture.mntTvaMandat,
                          dblMontantTotal=facture.mntVent,
                          strORIGINE_DONNEES='ASTRE'
@@ -247,12 +208,12 @@ def main():
             materielId = materiel.intlMaterielID if materiel else '859'
             logger.debug("retrieve intlMaterielID: %s", materielId)
 
-            sql = """INSERT INTO tbl_Facture_Engin ( intlMaterielID, txtMateriel, dblFactureId, strLibelle, dblMontant, strType )
-                    VALUES ({}, '{}', {}, '{}', {}, '{}')
-            """.format(materielId,
+            sql = Sql.format("""INSERT INTO tbl_Facture_Engin ( intlMaterielID, txtMateriel, dblFactureId, strLibelle, dblMontant, strType )
+                    VALUES ({}, {:text}, {}, {:text}, {}, {:text})
+            """, materielId,
                        facture.codeCout,
                        facture.factureId,
-                       AnalytiqueDb.nz(facture.libCout),
+                       facture.libCout,
                        facture.mntVent,
                        facture.libRai
                        )
@@ -263,13 +224,13 @@ def main():
             # La ligne concerne une affaire: insère les données dans la table tbl_Facture_Affaire
             logger.info("> mise à jour de tbl_Facture_Affaire")
 
-            sql = """INSERT INTO tbl_Facture_Affaire ( strAffaireId, dblFactureId, strLibelle, dblMontant, strType )
-                  VALUES ('{}', {}, '{}', {}, '{}')
-                  """.format(facture.codeCout,
+            sql = Sql.format("""INSERT INTO tbl_Facture_Affaire ( strAffaireId, dblFactureId, strLibelle, dblMontant, strType )
+                  VALUES ({:text}, {}, {:text}, {}, {:text})
+                  """, facture.codeCout,
                              facture.factureId,
                              facture.libRai ,
                              facture.mntVent,
-                             AnalytiqueDb.nz(facture.libCout),
+                             facture.libCout,
                              )
             logger.debug("> %s", sql)
             analytique_db.execute(sql)
@@ -278,13 +239,9 @@ def main():
         logger.info("> mise à jour de tbl_Mandatement")
 
         # Insère les données dans la table tbl_Mandatement
-        sql = """INSERT INTO tbl_Mandatement ( dblFacture, strNumMandat, dtmMandat, strBordereau )
-              VALUES ({}, '{}', #{}#, '{}')
-              """.format(facture.factureId,
-                         facture.numMandat,
-                         AnalytiqueDb.format_date(facture.dateMandat),
-                         facture.numBj
-                       )
+        sql = Sql.format("""INSERT INTO tbl_Mandatement ( dblFacture, strNumMandat, dtmMandat, strBordereau )
+              VALUES ({}, {:text}, {:date}, {:text})
+              """, facture.factureId, facture.numMandat, facture.dateMandat, facture.numBj)
         logger.debug("> %s", sql)
         analytique_db.execute(sql)
 
@@ -296,4 +253,4 @@ def main():
 
 if __name__ == "__main__":
     main()
-    logger.info("-- Fin --")
+    logger.info("-- Fin --")

+ 26 - 0
resources/test_gf2analytique.py

@@ -0,0 +1,26 @@
+'''
+
+@author: olivier.massot
+'''
+from path import Path
+
+from core.pde import AnalytiqueDb
+
+if __name__ == "__main__":
+
+    # reinitialise les données pour les tests de ctrl2analytique.py
+
+    AnalytiqueDb._path = Path(r"\\h2o\local\4-transversal\BDD\mdb_test\Db_analytique.mdb")
+    analytique_db = AnalytiqueDb(autocommit=False)
+
+    for num_env in ("19264", "20297", "22339"):
+        for row in analytique_db.read("SELECT * FROM tbl_Factures WHERE [strEnveloppe]='{}' and intExercice=2018".format(num_env)):
+            factid = row.dblFactureId
+            print(factid)
+            analytique_db.execute("DELETE * FROM tbl_Facture_Affaire WHERE [dblFactureId]={}".format(factid))
+            analytique_db.execute("DELETE * FROM tbl_Facture_Engin WHERE [dblFactureId]={}".format(factid))
+            analytique_db.execute("DELETE * FROM tbl_Factures WHERE [dblFactureId]={}".format(factid))
+
+        analytique_db.commit()
+
+