Przeglądaj źródła

CHG Met a jour la structure des scripts

olivier.massot 7 lat temu
rodzic
commit
faa752e999
12 zmienionych plików z 1850 dodań i 1805 usunięć
  1. 289 284
      analytique2facture.py
  2. 580 573
      ctrl2analytique.py
  3. 236 232
      gf2analytique.py
  4. 34 29
      gf2factures.py
  5. 76 69
      mails_rappel_ctrl.py
  6. 98 107
      pda2suiviactivite.py
  7. 164 159
      qgis_sync_compactage.py
  8. 71 67
      qgis_sync_etancheite.py
  9. 2 0
      qgis_sync_video.py
  10. 29 22
      qgis_sync_videores.py
  11. 1 2
      qgis_sync_wincan.py
  12. 270 261
      wincan2ctrl.py

+ 289 - 284
analytique2facture.py

@@ -30,291 +30,296 @@ logconf.start("analytique2facture", logging.DEBUG)
 
 ##-----------------------------------------------
 
-# Connexion à Analytique
-analytique_db = AnalytiqueDb(autocommit=False)
-
-# Connexion à Controles
-facture_db = FacturesDb(autocommit=False)
-
-# Connexion à CommunDb
-commun_db = CommunDb(autocommit=False)
-
-Sql = SqlFormatter()
-
-current = "{:%m/%Y}".format(datetime.now())
-
-mois_facturation = input("Veuillez renseigner le mois de facturation [defaut: {}] ('q' pour quitter): ".format(current))  # Format: voir avec jacky
-if mois_facturation == 'q':
-    sys.exit(1)
-
-sql = """SELECT * FROM tbl_Tarification
-        WHERE strStatut='A facturer'
-        ORDER BY DblAffaireId, DblTarifId DESC;
-        """
-
-# Liste les interventions à facturer par affaire
-a_facturer = {}
-for interv in analytique_db.read(sql):
-    if not interv.DblAffaireId in a_facturer.keys():
-        a_facturer[interv.DblAffaireId] = []
-    a_facturer[interv.DblAffaireId].append(interv)
-
-if not a_facturer:
-    logger.info("Aucune facture à créer - Opération annulée")
-    sys.exit(1)
-
-# Pour chaque facture, on va ajouter une ligne dans tblPieceEntete, et une ligne par intervention dans tblPieceLigne
-# > NB: On ne touche pas aux interventions de cette affaire qui ont déja été facturées
-
-for affaireId, interventions in a_facturer.items():
-
-    piece_id = facture_db.first("SELECT Max(lngpieceId) As Id FROM tblPieceEntete").Id + 1
-    if str(piece_id)[:2] != mois_facturation[-2:]:
-        # On démarre une nouvelle année
-        piece_id = 10000 * int(mois_facturation[:2]) + 1
-
-    affaire = analytique_db.first("SELECT * FROM tbl_Affaires WHERE dblAffaireId={}".format(affaireId))
-    if not affaire:
-        logger.error("Affaire {} - L'affaire n'existe pas dans tbl_Affaires".format(affaireId))
-        continue
-
-    if not affaire.strMOId:
-        logger.error("Affaire {} - Destinataire manquant dans tbl_Affaires".format(affaireId))
-        continue
-
-    destinataire = commun_db.first("SELECT * FROM tblTiers WHERE lngTiersId={}".format(affaire.strMOId))
-    if not destinataire:
-        logger.warning("Affaire {} - Destinataire introuvable dans tblTiers ('{}'), remplacé par tiers n°190".format(affaireId, affaire.strMOId))
-        destinataire = commun_db.first("SELECT * FROM tblTiers WHERE lngTiersId=190")
-
-    entete = EnTete()
-    entete.lngPieceId = piece_id
-
-    # on recupere les champs de tblTiers depuis le champ lngASTRE (champ n°3) jusqu'au champ strCodeProduit (champ n°26)
-    # ATTENTION: le champ 'strPaysIdIso3166-A2' n'etant pas un nom de propriété valide, il est renomme en strPaysIdIso3166 dans le modèle.
-    entete.lngTiersId = destinataire.lngTiersId
-    entete.lngASTRE = destinataire.lngASTRE
-    entete.strCodeProduit = destinataire.strCodeProduit
-    entete.bytTitreId = destinataire.bytTitreId
-    entete.strCodeAdresse = destinataire.strCodeAdresse
-    entete.strAdresse1 = destinataire.strAdresse1
-    entete.strAdresse2 = destinataire.strAdresse2
-    entete.strAdresse3 = destinataire.strAdresse3
-    entete.strAdresse4 = destinataire.strAdresse4
-    entete.strAdresse5 = destinataire.strAdresse5
-    entete.strPaysIdIso3166 = destinataire.field_11
-    entete.bytTVATiersId = destinataire.bytTVATiersId
-    entete.strTelephone = destinataire.strTelephone
-    entete.strTelecopie = destinataire.strTelecopie
-    entete.strPortable = destinataire.strPortable
-    entete.strEMail = destinataire.strEMail
-    entete.strWeb = destinataire.strWeb
-    entete.strLangueIdIso639 = destinataire.strLangueIdIso639
-    entete.strCompteComptable = destinataire.strCompteComptable
-    entete.strDeviseIdIso4217 = destinataire.strDeviseIdIso4217
-    entete.bytReglementId = destinataire.bytReglementId
-    entete.bytClasseTarifId = destinataire.bytClasseTarifId
-    entete.bytNbExFacture = destinataire.bytNbExFacture
-    entete.bytClasseRemiseTiersId = destinataire.bytClasseRemiseTiersId
-    entete.bytNbExFacture = destinataire.bytNbExFacture
-    entete.strStatTiers = destinataire.strStatTiers
-
-    # Valeurs fixes
-    entete.lngDocId = 0
-    entete.strLangueIdIso639 = "fr"
-    entete.strDeviseIdIso4217 = "EUR"
-    entete.bytReglementId = 1
-    entete.bytClasseTarifId = 1
-    entete.bytClasseRemiseTiersId = 1
-    entete.bytNbExFacture = 1
-    entete.strStatTiers = ""
-    entete.bytSituationIdPrincipale = 2
-    entete.bytSituationIdSecondaire = 41
-    entete.bytTypeDocumentId = 50
-    entete.strStatDocEntete = "PI00"
-    entete.dblCoursDevise = 1
-    entete.dtmCoursDevise = datetime(2002, 1, 1)
-    entete.bytTypeTarif = 1
-    entete.strReglement = "POUR ACQUITTER CET ETAT DE REDEVANCE,\r\n ATTENDEZ IMPERATIVEMENT DE RECEVOIR LE TITRE DE PERCEPTION EMANANT DE LA PAIERIE DEPARTEMENTALE"
-
-    # Commentaires
-    entete.memObsEntete = "[Mois : {mois_facturation}]\r\n" \
-                          "[Lieu de travail : ]{strLieux}\r\n" \
-                          "[V/Cde : ] {Ref} du {dtmCommande}\r\n" \
-                          "".format(mois_facturation=mois_facturation,
-                                    strLieux=affaire.strLieux,
-                                    Ref=affaire.Ref,
-                                    dtmCommande="{:%d/%m/%Y}".format(affaire.dtmCommande) if affaire.dtmCommande else "",
-                                    )
-
-    entete.memObsInterne = "N° Affaire analytique : {}".format(affaire.strLiaisonControle)
-
-    entete.strStatDocEntete = "PC00" if ("/" in affaire.strLiaisonControle or affaire.strLiaisonControle == "RDBOEC") else "PT00"
-
-    entete.strUserIdCreation = "script"
-    entete.strUserIdLastMod = "script"
-
-    i = 0
-    lignes = []
-    for interv in interventions:
-        i += 1
-
-        ligne = Ligne()
-        ligne.lngPieceId = piece_id
-        ligne.intLigneId = i
-        ligne.strArticleId = interv.strArticleId
-
-        article = commun_db.first("SELECT * FROM tblArticle WHERE strArticleId='{}'".format(interv.strArticleId))
-        ligne.strArticle = article.strArticle if article else ""
-        ligne.bytTVAArticleId = article.bytTVAArticleId if article else 0
-
-        tva = commun_db.first("SELECT bytTVAId FROM tblTVA WHERE bytTVATiersId={} AND bytTVAArticleId={}".format(destinataire.bytTVATiersId, ligne.bytTVAArticleId))
-        ligne.bytTVAId = tva.bytTVAId
-
-        taux = commun_db.first("SELECT dblTVATaux FROM tblTVATaux WHERE bytTVAId={}".format(ligne.bytTVAId))
-        ligne.dblTVATaux = taux.dblTVATaux
-
-        ligne.dblQte = interv.dblQuantite
-
-        if interv.strUnite:
-            unite = commun_db.first("SELECT * FROM tblUnite WHERE strUniteCourt='{}'".format(interv.strUnite))
-            ligne.bytUniteIdQuantite = unite.bytUniteId
-            ligne.bytUniteIdPrix = unite.bytUniteId
-        else:
-            ligne.bytUniteIdQuantite = 0
-            ligne.bytUniteIdPrix = 0
-        ligne.dblPUhtBrutDev = interv.dblPrixUnitaire
-        ligne.dblPUhtNetDev = interv.dblPrixUnitaire
-        ligne.dblPThtNetDev = interv.dblPrixTotal
-        ligne.blnPrestation = True
-        ligne.memObs = "Intervention le {:%d/%m/%Y}{}".format(interv.dtmFin, "\r\nRapport n° : {}".format(interv.strRapportId) if interv.strRapportId else "")
-        ligne.blnLigneGeneree = True
-        ligne.bytGenerateurId = 1
-        ligne.dblPThtNet = interv.dblPrixTotal
-        ligne.bytClasseRemiseArticleId = 1
-        ligne.dblPUSaisie = interv.dblPrixUnitaire
-        ligne.strPUAff = "{:03.02f} EUR/{}".format(interv.dblPrixUnitaire, interv.strUnite)
-        ligne.strQteAff = "{:03.02f} {}".format(interv.dblQuantite, interv.strUnite)
-
-        # on met a jour tbl_Tarification.strNumFacture et strStatut pour marquer la ligne comme facturée.
-        # !! Cette opération ne sera committée qu'à la fin du traitement de cette intervention
-        sql = Sql.format(""" UPDATE tbl_Tarification SET strStatut = 'Facturée', strNumFacture = {:text}
-                              WHERE DblTarifId={}""", piece_id, interv.DblTarifId)
-        analytique_db.execute(sql)
-
-        lignes.append(ligne)
-
-    # Toutes les lignes ont été ajoutées: on créé maintenant les lignes de totaux
-    montant_ht = float(sum([ligne.dblPThtNetDev for ligne in lignes]))
-    taux_tva = float(lignes[0].dblTVATaux)
-    montant_tva = (0.01 * taux_tva) * montant_ht
-    montant_ttc = montant_ht + montant_tva
-
-    # Sous-total HT (ligne 32000)
-    ligne_total_1 = Ligne()
-    ligne_total_1.lngPieceId = piece_id
-    ligne_total_1.intLigneId = 32000
-    ligne_total_1.strArticleId = ""
-    ligne_total_1.strArticle = "Total H.T."
-    ligne_total_1.dblQte = 0
-    ligne_total_1.dblPThtNetDev = montant_ht
-    ligne_total_1.dblPTttcNetDev = montant_ttc
-    ligne_total_1.bytLigneSousTotal = 10
-    ligne_total_1.blnLigneVisible = False
-    ligne_total_1.dblPThtNet = montant_ht
-    ligne_total_1.dblPTttcNet = round(montant_ttc, 2)
-    ligne_total_1.dblPUSaisie = round(montant_ht, 2)
-    ligne_total_1.strPUAff = "{:03.02f} EUR".format(montant_ht)
-
-    lignes.append(ligne_total_1)
-
-    # Sous-total TVA (ligne 32001)
-    ligne_total_2 = Ligne()
-    ligne_total_2.lngPieceId = piece_id
-    ligne_total_2.intLigneId = 32001
-    ligne_total_2.strArticleId = ""
-    ligne_total_2.strArticle = """TVA 1 : {taux_tva}% H.T. : {montant_ht:03.02f} T.V.A. :
-                                 {montant_tva:03.02f} T.T.C. : {montant_ttc:03.02f}:""".format(taux_tva=taux_tva,
-                                                                                               montant_ht=montant_ht,
-                                                                                               montant_tva=montant_tva,
-                                                                                               montant_ttc=montant_ttc)
-    ligne_total_2.dblQte = 0
-    ligne_total_2.bytTVAId = lignes[0].bytTVAId
-    ligne_total_2.dblTVATaux = taux_tva
-    ligne_total_2.dblPThtNetDev = montant_ht
-    ligne_total_2.dblPTttcNetDev = montant_ttc
-    ligne_total_2.bytLigneSousTotal = 11
-    ligne_total_2.blnLigneVisible = False
-    ligne_total_2.dblPThtNet = montant_ht
-    ligne_total_2.dblPTttcNet = round(ligne_total_1.dblPTttcNetDev, 2)
-    ligne_total_2.dblPUSaisie = round(montant_tva, 2)
-    ligne_total_2.strPUAff = "{:03.02f} EUR".format(montant_tva)
-
-    lignes.append(ligne_total_2)
-
-    # Sous-total TTC (ligne 32500)
-    ligne_total_3 = Ligne()
-    ligne_total_3.lngPieceId = piece_id
-    ligne_total_3.intLigneId = 32500
-    ligne_total_3.strArticleId = ""
-    ligne_total_3.strArticle = "Total T.T.C."
-    ligne_total_3.dblQte = 0
-    ligne_total_3.dblPThtNetDev = montant_ht
-    ligne_total_3.dblPTttcNetDev = montant_ttc
-    ligne_total_3.bytLigneSousTotal = 12
-    ligne_total_3.blnLigneVisible = False
-    ligne_total_3.dblPThtNet = montant_ht
-    ligne_total_3.dblPTttcNet = round(montant_ttc, 2)
-    ligne_total_3.dblPUSaisie = round(montant_ttc, 2)
-    ligne_total_3.strPUAff = "{:03.02f} EUR".format(montant_ttc)
-
-    lignes.append(ligne_total_3)
-
-
-
-    logger.info("* Nouvelle facture: {}".format(entete.lngPieceId))
-
-    # Insertion de l'en-tête
-    sql = Sql.format("""
-            INSERT INTO tblPieceEntete ( lngPieceId, lngDocId, lngTiersId, lngASTRE, strCodeProduit, bytTitreId, strCodeAdresse, strAdresse1, strAdresse2,
-                                        strAdresse3, strAdresse4, strAdresse5, [strPaysIdIso3166-A2], bytTVATiersId, strTelephone, strTelecopie, strPortable,
-                                        strEMail, strWeb, strLangueIdIso639, strCompteComptable, strDeviseIdIso4217, bytReglementId, strReglement,
-                                        bytClasseTarifId, bytClasseRemiseTiersId, bytNbExFacture, strStatTiers, bytSituationIdPrincipale, bytSituationIdSecondaire,
-                                        memObsEntete, memObsInterne, bytTypeDocumentId, strStatDocEntete, dblCoursDevise, dtmCoursDevise,
-                                        strUserIdCreation, strUserIdLastMod )
-            VALUES ({entete.lngPieceId}, {entete.lngDocId}, {entete.lngTiersId}, {entete.lngASTRE}, {entete.strCodeProduit:text}, {entete.bytTitreId},
-                    {entete.strCodeAdresse:text}, {entete.strAdresse1:text}, {entete.strAdresse2:text}, {entete.strAdresse3:text}, {entete.strAdresse4:text}, {entete.strAdresse5:text},
-                    {entete.strPaysIdIso3166:text}, {entete.bytTVATiersId}, {entete.strTelephone:text}, {entete.strTelecopie:text}, {entete.strPortable:text}, {entete.strEMail:text},
-                    {entete.strWeb:text}, {entete.strLangueIdIso639:text}, {entete.strCompteComptable:text}, {entete.strDeviseIdIso4217:text}, {entete.bytReglementId},
-                    {entete.strReglement:text}, {entete.bytClasseTarifId}, {entete.bytClasseRemiseTiersId}, {entete.bytNbExFacture}, {entete.strStatTiers:text},
-                    {entete.bytSituationIdPrincipale}, {entete.bytSituationIdSecondaire}, {entete.memObsEntete:text}, {entete.memObsInterne:text},
-                    {entete.bytTypeDocumentId}, {entete.strStatDocEntete:text}, {entete.dblCoursDevise}, {entete.dtmCoursDevise:date},
-                    {entete.strUserIdCreation:text}, {entete.strUserIdLastMod:text})
-            """, entete=entete)
-    facture_db.execute(sql)
-
-    for ligne in lignes:
+def main():
+
+    # Connexion à Analytique
+    analytique_db = AnalytiqueDb(autocommit=False)
+
+    # Connexion à Controles
+    facture_db = FacturesDb(autocommit=False)
+
+    # Connexion à CommunDb
+    commun_db = CommunDb(autocommit=False)
+
+    Sql = SqlFormatter()
+
+    current = "{:%m/%Y}".format(datetime.now())
+
+    mois_facturation = input("Veuillez renseigner le mois de facturation [defaut: {}] ('q' pour quitter): ".format(current))  # Format: voir avec jacky
+    if mois_facturation == 'q':
+        sys.exit(1)
+
+    sql = """SELECT * FROM tbl_Tarification
+            WHERE strStatut='A facturer'
+            ORDER BY DblAffaireId, DblTarifId DESC;
+            """
+
+    # Liste les interventions à facturer par affaire
+    a_facturer = {}
+    for interv in analytique_db.read(sql):
+        if not interv.DblAffaireId in a_facturer.keys():
+            a_facturer[interv.DblAffaireId] = []
+        a_facturer[interv.DblAffaireId].append(interv)
+
+    if not a_facturer:
+        logger.info("Aucune facture à créer - Opération annulée")
+        sys.exit(1)
+
+    # Pour chaque facture, on va ajouter une ligne dans tblPieceEntete, et une ligne par intervention dans tblPieceLigne
+    # > NB: On ne touche pas aux interventions de cette affaire qui ont déja été facturées
+
+    for affaireId, interventions in a_facturer.items():
+
+        piece_id = facture_db.first("SELECT Max(lngpieceId) As Id FROM tblPieceEntete").Id + 1
+        if str(piece_id)[:2] != mois_facturation[-2:]:
+            # On démarre une nouvelle année
+            piece_id = 10000 * int(mois_facturation[:2]) + 1
+
+        affaire = analytique_db.first("SELECT * FROM tbl_Affaires WHERE dblAffaireId={}".format(affaireId))
+        if not affaire:
+            logger.error("Affaire {} - L'affaire n'existe pas dans tbl_Affaires".format(affaireId))
+            continue
+
+        if not affaire.strMOId:
+            logger.error("Affaire {} - Destinataire manquant dans tbl_Affaires".format(affaireId))
+            continue
+
+        destinataire = commun_db.first("SELECT * FROM tblTiers WHERE lngTiersId={}".format(affaire.strMOId))
+        if not destinataire:
+            logger.warning("Affaire {} - Destinataire introuvable dans tblTiers ('{}'), remplacé par tiers n°190".format(affaireId, affaire.strMOId))
+            destinataire = commun_db.first("SELECT * FROM tblTiers WHERE lngTiersId=190")
+
+        entete = EnTete()
+        entete.lngPieceId = piece_id
+
+        # on recupere les champs de tblTiers depuis le champ lngASTRE (champ n°3) jusqu'au champ strCodeProduit (champ n°26)
+        # ATTENTION: le champ 'strPaysIdIso3166-A2' n'etant pas un nom de propriété valide, il est renomme en strPaysIdIso3166 dans le modèle.
+        entete.lngTiersId = destinataire.lngTiersId
+        entete.lngASTRE = destinataire.lngASTRE
+        entete.strCodeProduit = destinataire.strCodeProduit
+        entete.bytTitreId = destinataire.bytTitreId
+        entete.strCodeAdresse = destinataire.strCodeAdresse
+        entete.strAdresse1 = destinataire.strAdresse1
+        entete.strAdresse2 = destinataire.strAdresse2
+        entete.strAdresse3 = destinataire.strAdresse3
+        entete.strAdresse4 = destinataire.strAdresse4
+        entete.strAdresse5 = destinataire.strAdresse5
+        entete.strPaysIdIso3166 = destinataire.field_11
+        entete.bytTVATiersId = destinataire.bytTVATiersId
+        entete.strTelephone = destinataire.strTelephone
+        entete.strTelecopie = destinataire.strTelecopie
+        entete.strPortable = destinataire.strPortable
+        entete.strEMail = destinataire.strEMail
+        entete.strWeb = destinataire.strWeb
+        entete.strLangueIdIso639 = destinataire.strLangueIdIso639
+        entete.strCompteComptable = destinataire.strCompteComptable
+        entete.strDeviseIdIso4217 = destinataire.strDeviseIdIso4217
+        entete.bytReglementId = destinataire.bytReglementId
+        entete.bytClasseTarifId = destinataire.bytClasseTarifId
+        entete.bytNbExFacture = destinataire.bytNbExFacture
+        entete.bytClasseRemiseTiersId = destinataire.bytClasseRemiseTiersId
+        entete.bytNbExFacture = destinataire.bytNbExFacture
+        entete.strStatTiers = destinataire.strStatTiers
+
+        # Valeurs fixes
+        entete.lngDocId = 0
+        entete.strLangueIdIso639 = "fr"
+        entete.strDeviseIdIso4217 = "EUR"
+        entete.bytReglementId = 1
+        entete.bytClasseTarifId = 1
+        entete.bytClasseRemiseTiersId = 1
+        entete.bytNbExFacture = 1
+        entete.strStatTiers = ""
+        entete.bytSituationIdPrincipale = 2
+        entete.bytSituationIdSecondaire = 41
+        entete.bytTypeDocumentId = 50
+        entete.strStatDocEntete = "PI00"
+        entete.dblCoursDevise = 1
+        entete.dtmCoursDevise = datetime(2002, 1, 1)
+        entete.bytTypeTarif = 1
+        entete.strReglement = "POUR ACQUITTER CET ETAT DE REDEVANCE,\r\n ATTENDEZ IMPERATIVEMENT DE RECEVOIR LE TITRE DE PERCEPTION EMANANT DE LA PAIERIE DEPARTEMENTALE"
+
+        # Commentaires
+        entete.memObsEntete = "[Mois : {mois_facturation}]\r\n" \
+                              "[Lieu de travail : ]{strLieux}\r\n" \
+                              "[V/Cde : ] {Ref} du {dtmCommande}\r\n" \
+                              "".format(mois_facturation=mois_facturation,
+                                        strLieux=affaire.strLieux,
+                                        Ref=affaire.Ref,
+                                        dtmCommande="{:%d/%m/%Y}".format(affaire.dtmCommande) if affaire.dtmCommande else "",
+                                        )
+
+        entete.memObsInterne = "N° Affaire analytique : {}".format(affaire.strLiaisonControle)
+
+        entete.strStatDocEntete = "PC00" if ("/" in affaire.strLiaisonControle or affaire.strLiaisonControle == "RDBOEC") else "PT00"
+
+        entete.strUserIdCreation = "script"
+        entete.strUserIdLastMod = "script"
+
+        i = 0
+        lignes = []
+        for interv in interventions:
+            i += 1
+
+            ligne = Ligne()
+            ligne.lngPieceId = piece_id
+            ligne.intLigneId = i
+            ligne.strArticleId = interv.strArticleId
+
+            article = commun_db.first("SELECT * FROM tblArticle WHERE strArticleId='{}'".format(interv.strArticleId))
+            ligne.strArticle = article.strArticle if article else ""
+            ligne.bytTVAArticleId = article.bytTVAArticleId if article else 0
+
+            tva = commun_db.first("SELECT bytTVAId FROM tblTVA WHERE bytTVATiersId={} AND bytTVAArticleId={}".format(destinataire.bytTVATiersId, ligne.bytTVAArticleId))
+            ligne.bytTVAId = tva.bytTVAId
+
+            taux = commun_db.first("SELECT dblTVATaux FROM tblTVATaux WHERE bytTVAId={}".format(ligne.bytTVAId))
+            ligne.dblTVATaux = taux.dblTVATaux
+
+            ligne.dblQte = interv.dblQuantite
+
+            if interv.strUnite:
+                unite = commun_db.first("SELECT * FROM tblUnite WHERE strUniteCourt='{}'".format(interv.strUnite))
+                ligne.bytUniteIdQuantite = unite.bytUniteId
+                ligne.bytUniteIdPrix = unite.bytUniteId
+            else:
+                ligne.bytUniteIdQuantite = 0
+                ligne.bytUniteIdPrix = 0
+            ligne.dblPUhtBrutDev = interv.dblPrixUnitaire
+            ligne.dblPUhtNetDev = interv.dblPrixUnitaire
+            ligne.dblPThtNetDev = interv.dblPrixTotal
+            ligne.blnPrestation = True
+            ligne.memObs = "Intervention le {:%d/%m/%Y}{}".format(interv.dtmFin, "\r\nRapport n° : {}".format(interv.strRapportId) if interv.strRapportId else "")
+            ligne.blnLigneGeneree = True
+            ligne.bytGenerateurId = 1
+            ligne.dblPThtNet = interv.dblPrixTotal
+            ligne.bytClasseRemiseArticleId = 1
+            ligne.dblPUSaisie = interv.dblPrixUnitaire
+            ligne.strPUAff = "{:03.02f} EUR/{}".format(interv.dblPrixUnitaire, interv.strUnite)
+            ligne.strQteAff = "{:03.02f} {}".format(interv.dblQuantite, interv.strUnite)
+
+            # on met a jour tbl_Tarification.strNumFacture et strStatut pour marquer la ligne comme facturée.
+            # !! Cette opération ne sera committée qu'à la fin du traitement de cette intervention
+            sql = Sql.format(""" UPDATE tbl_Tarification SET strStatut = 'Facturée', strNumFacture = {:text}
+                                  WHERE DblTarifId={}""", piece_id, interv.DblTarifId)
+            analytique_db.execute(sql)
+
+            lignes.append(ligne)
+
+        # Toutes les lignes ont été ajoutées: on créé maintenant les lignes de totaux
+        montant_ht = float(sum([ligne.dblPThtNetDev for ligne in lignes]))
+        taux_tva = float(lignes[0].dblTVATaux)
+        montant_tva = (0.01 * taux_tva) * montant_ht
+        montant_ttc = montant_ht + montant_tva
+
+        # Sous-total HT (ligne 32000)
+        ligne_total_1 = Ligne()
+        ligne_total_1.lngPieceId = piece_id
+        ligne_total_1.intLigneId = 32000
+        ligne_total_1.strArticleId = ""
+        ligne_total_1.strArticle = "Total H.T."
+        ligne_total_1.dblQte = 0
+        ligne_total_1.dblPThtNetDev = montant_ht
+        ligne_total_1.dblPTttcNetDev = montant_ttc
+        ligne_total_1.bytLigneSousTotal = 10
+        ligne_total_1.blnLigneVisible = False
+        ligne_total_1.dblPThtNet = montant_ht
+        ligne_total_1.dblPTttcNet = round(montant_ttc, 2)
+        ligne_total_1.dblPUSaisie = round(montant_ht, 2)
+        ligne_total_1.strPUAff = "{:03.02f} EUR".format(montant_ht)
+
+        lignes.append(ligne_total_1)
+
+        # Sous-total TVA (ligne 32001)
+        ligne_total_2 = Ligne()
+        ligne_total_2.lngPieceId = piece_id
+        ligne_total_2.intLigneId = 32001
+        ligne_total_2.strArticleId = ""
+        ligne_total_2.strArticle = """TVA 1 : {taux_tva}% H.T. : {montant_ht:03.02f} T.V.A. :
+                                     {montant_tva:03.02f} T.T.C. : {montant_ttc:03.02f}:""".format(taux_tva=taux_tva,
+                                                                                                   montant_ht=montant_ht,
+                                                                                                   montant_tva=montant_tva,
+                                                                                                   montant_ttc=montant_ttc)
+        ligne_total_2.dblQte = 0
+        ligne_total_2.bytTVAId = lignes[0].bytTVAId
+        ligne_total_2.dblTVATaux = taux_tva
+        ligne_total_2.dblPThtNetDev = montant_ht
+        ligne_total_2.dblPTttcNetDev = montant_ttc
+        ligne_total_2.bytLigneSousTotal = 11
+        ligne_total_2.blnLigneVisible = False
+        ligne_total_2.dblPThtNet = montant_ht
+        ligne_total_2.dblPTttcNet = round(ligne_total_1.dblPTttcNetDev, 2)
+        ligne_total_2.dblPUSaisie = round(montant_tva, 2)
+        ligne_total_2.strPUAff = "{:03.02f} EUR".format(montant_tva)
+
+        lignes.append(ligne_total_2)
+
+        # Sous-total TTC (ligne 32500)
+        ligne_total_3 = Ligne()
+        ligne_total_3.lngPieceId = piece_id
+        ligne_total_3.intLigneId = 32500
+        ligne_total_3.strArticleId = ""
+        ligne_total_3.strArticle = "Total T.T.C."
+        ligne_total_3.dblQte = 0
+        ligne_total_3.dblPThtNetDev = montant_ht
+        ligne_total_3.dblPTttcNetDev = montant_ttc
+        ligne_total_3.bytLigneSousTotal = 12
+        ligne_total_3.blnLigneVisible = False
+        ligne_total_3.dblPThtNet = montant_ht
+        ligne_total_3.dblPTttcNet = round(montant_ttc, 2)
+        ligne_total_3.dblPUSaisie = round(montant_ttc, 2)
+        ligne_total_3.strPUAff = "{:03.02f} EUR".format(montant_ttc)
+
+        lignes.append(ligne_total_3)
+
+
+
+        logger.info("* Nouvelle facture: {}".format(entete.lngPieceId))
+
+        # Insertion de l'en-tête
         sql = Sql.format("""
-                INSERT INTO tblPieceLigne ( lngPieceId, intLigneId, strArticleId, strArticle, bytTVAArticleId, bytTVAId, dblTVATaux, dblQte, bytUniteIdQuantite,
-                                             bytNbDecQuantite, bytUniteIdPrix, dblCnvUniteCoef, bytNbDecQuantiteConvertie, dblPUhtBrutDev, dblPUttcBrutDev, dblPUhtNetDev,
-                                             dblPUttcNetDev, bytNbDecPU, dblPThtBrutDev, dblPTttcBrutDev, dblPThtNetDev, dblPTttcNetDev, strStatArticle, strStatDocLigne,
-                                             dblTauxRemise1, dblTauxRemise2, dblTauxRemise3, blnPrestation, strCompteComptable, memObs, memObsInterne, bytLigneSousTotal,
-                                             intLigneIdRattachement, blnLigneVisible, blnLigneGeneree, bytGenerateurId, dblPUhtBrut, dblPUttcBrut, dblPUhtNet, dblPUttcNet,
-                                             dblPThtBrut, dblPTttcBrut, dblPThtNet, dblPTttcNet, bytClasseRemiseArticleId, dblPUSaisie, strPUAff, strQteAff )
-                VALUES ({ligne.lngPieceId}, {ligne.intLigneId}, {ligne.strArticleId:text}, {ligne.strArticle:text}, {ligne.bytTVAArticleId}, {ligne.bytTVAId}, {ligne.dblTVATaux},
-                        {ligne.dblQte}, {ligne.bytUniteIdQuantite}, {ligne.bytNbDecQuantite}, {ligne.bytUniteIdPrix}, {ligne.dblCnvUniteCoef}, {ligne.bytNbDecQuantiteConvertie},
-                        {ligne.dblPUhtBrutDev}, {ligne.dblPUttcBrutDev}, {ligne.dblPUhtNetDev}, {ligne.dblPUttcNetDev}, {ligne.bytNbDecPU}, {ligne.dblPThtBrutDev},
-                        {ligne.dblPTttcBrutDev}, {ligne.dblPThtNetDev}, {ligne.dblPTttcNetDev}, {ligne.strStatArticle:text}, {ligne.strStatDocLigne:text},
-                        {ligne.dblTauxRemise1}, {ligne.dblTauxRemise2}, {ligne.dblTauxRemise3}, {ligne.blnPrestation}, {ligne.strCompteComptable:text},
-                        {ligne.memObs:text}, {ligne.memObsInterne:text}, {ligne.bytLigneSousTotal}, {ligne.intLigneIdRattachement}, {ligne.blnLigneVisible},
-                        {ligne.blnLigneGeneree}, {ligne.bytGenerateurId}, {ligne.dblPUhtBrut}, {ligne.dblPUttcBrut}, {ligne.dblPUhtNet}, {ligne.dblPUttcNet},
-                        {ligne.dblPThtBrut}, {ligne.dblPTttcBrut}, {ligne.dblPThtNet}, {ligne.dblPTttcNet}, {ligne.bytClasseRemiseArticleId}, {ligne.dblPUSaisie},
-                        {ligne.strPUAff:text}, {ligne.strQteAff:text})
-                """, ligne=ligne)
+                INSERT INTO tblPieceEntete ( lngPieceId, lngDocId, lngTiersId, lngASTRE, strCodeProduit, bytTitreId, strCodeAdresse, strAdresse1, strAdresse2,
+                                            strAdresse3, strAdresse4, strAdresse5, [strPaysIdIso3166-A2], bytTVATiersId, strTelephone, strTelecopie, strPortable,
+                                            strEMail, strWeb, strLangueIdIso639, strCompteComptable, strDeviseIdIso4217, bytReglementId, strReglement,
+                                            bytClasseTarifId, bytClasseRemiseTiersId, bytNbExFacture, strStatTiers, bytSituationIdPrincipale, bytSituationIdSecondaire,
+                                            memObsEntete, memObsInterne, bytTypeDocumentId, strStatDocEntete, dblCoursDevise, dtmCoursDevise,
+                                            strUserIdCreation, strUserIdLastMod )
+                VALUES ({entete.lngPieceId}, {entete.lngDocId}, {entete.lngTiersId}, {entete.lngASTRE}, {entete.strCodeProduit:text}, {entete.bytTitreId},
+                        {entete.strCodeAdresse:text}, {entete.strAdresse1:text}, {entete.strAdresse2:text}, {entete.strAdresse3:text}, {entete.strAdresse4:text}, {entete.strAdresse5:text},
+                        {entete.strPaysIdIso3166:text}, {entete.bytTVATiersId}, {entete.strTelephone:text}, {entete.strTelecopie:text}, {entete.strPortable:text}, {entete.strEMail:text},
+                        {entete.strWeb:text}, {entete.strLangueIdIso639:text}, {entete.strCompteComptable:text}, {entete.strDeviseIdIso4217:text}, {entete.bytReglementId},
+                        {entete.strReglement:text}, {entete.bytClasseTarifId}, {entete.bytClasseRemiseTiersId}, {entete.bytNbExFacture}, {entete.strStatTiers:text},
+                        {entete.bytSituationIdPrincipale}, {entete.bytSituationIdSecondaire}, {entete.memObsEntete:text}, {entete.memObsInterne:text},
+                        {entete.bytTypeDocumentId}, {entete.strStatDocEntete:text}, {entete.dblCoursDevise}, {entete.dtmCoursDevise:date},
+                        {entete.strUserIdCreation:text}, {entete.strUserIdLastMod:text})
+                """, entete=entete)
         facture_db.execute(sql)
 
-    facture_db.commit()
-    analytique_db.commit()
-
-
+        for ligne in lignes:
+            sql = Sql.format("""
+                    INSERT INTO tblPieceLigne ( lngPieceId, intLigneId, strArticleId, strArticle, bytTVAArticleId, bytTVAId, dblTVATaux, dblQte, bytUniteIdQuantite,
+                                                 bytNbDecQuantite, bytUniteIdPrix, dblCnvUniteCoef, bytNbDecQuantiteConvertie, dblPUhtBrutDev, dblPUttcBrutDev, dblPUhtNetDev,
+                                                 dblPUttcNetDev, bytNbDecPU, dblPThtBrutDev, dblPTttcBrutDev, dblPThtNetDev, dblPTttcNetDev, strStatArticle, strStatDocLigne,
+                                                 dblTauxRemise1, dblTauxRemise2, dblTauxRemise3, blnPrestation, strCompteComptable, memObs, memObsInterne, bytLigneSousTotal,
+                                                 intLigneIdRattachement, blnLigneVisible, blnLigneGeneree, bytGenerateurId, dblPUhtBrut, dblPUttcBrut, dblPUhtNet, dblPUttcNet,
+                                                 dblPThtBrut, dblPTttcBrut, dblPThtNet, dblPTttcNet, bytClasseRemiseArticleId, dblPUSaisie, strPUAff, strQteAff )
+                    VALUES ({ligne.lngPieceId}, {ligne.intLigneId}, {ligne.strArticleId:text}, {ligne.strArticle:text}, {ligne.bytTVAArticleId}, {ligne.bytTVAId}, {ligne.dblTVATaux},
+                            {ligne.dblQte}, {ligne.bytUniteIdQuantite}, {ligne.bytNbDecQuantite}, {ligne.bytUniteIdPrix}, {ligne.dblCnvUniteCoef}, {ligne.bytNbDecQuantiteConvertie},
+                            {ligne.dblPUhtBrutDev}, {ligne.dblPUttcBrutDev}, {ligne.dblPUhtNetDev}, {ligne.dblPUttcNetDev}, {ligne.bytNbDecPU}, {ligne.dblPThtBrutDev},
+                            {ligne.dblPTttcBrutDev}, {ligne.dblPThtNetDev}, {ligne.dblPTttcNetDev}, {ligne.strStatArticle:text}, {ligne.strStatDocLigne:text},
+                            {ligne.dblTauxRemise1}, {ligne.dblTauxRemise2}, {ligne.dblTauxRemise3}, {ligne.blnPrestation}, {ligne.strCompteComptable:text},
+                            {ligne.memObs:text}, {ligne.memObsInterne:text}, {ligne.bytLigneSousTotal}, {ligne.intLigneIdRattachement}, {ligne.blnLigneVisible},
+                            {ligne.blnLigneGeneree}, {ligne.bytGenerateurId}, {ligne.dblPUhtBrut}, {ligne.dblPUttcBrut}, {ligne.dblPUhtNet}, {ligne.dblPUttcNet},
+                            {ligne.dblPThtBrut}, {ligne.dblPTttcBrut}, {ligne.dblPThtNet}, {ligne.dblPTttcNet}, {ligne.bytClasseRemiseArticleId}, {ligne.dblPUSaisie},
+                            {ligne.strPUAff:text}, {ligne.strQteAff:text})
+                    """, ligne=ligne)
+            facture_db.execute(sql)
+
+        facture_db.commit()
+        analytique_db.commit()
+
+if __name__ == "__main__":
+    main()
+
+    logger.info("-- Fin --")
 

+ 580 - 573
ctrl2analytique.py

@@ -36,627 +36,634 @@ logconf.start("ctrl2analytique", logging.DEBUG)
 
 ##-----------------------------------------------
 
+def main():
+    # #########    INITIALISATION    ##########
+    logger.info("Initialisation...")
 
-# #########    INITIALISATION    ##########
-logger.info("Initialisation...")
+    Sql = SqlFormatter()
 
-Sql = SqlFormatter()
+    no_prompt = ("-n" in sys.argv)
+    if no_prompt:
+        logger.info("> Lancé en mode automatique (sans interruption)")
 
-no_prompt = ("-n" in sys.argv)
-if no_prompt:
-    logger.info("> Lancé en mode automatique (sans interruption)")
+    # Connexion à Analytique
+    analytique_db = AnalytiqueDb(autocommit=False)
 
-# Connexion à Analytique
-analytique_db = AnalytiqueDb(autocommit=False)
+    # Connexion à Controles
+    controles_db = ControlesDb(autocommit=False)
 
-# Connexion à Controles
-controles_db = ControlesDb(autocommit=False)
+    # Connexion à CommunDb
+    commun_db = CommunDb(autocommit=False)
 
-# Connexion à CommunDb
-commun_db = CommunDb(autocommit=False)
+    # Créé le répertoire de travail
+    workdir = mk_workdir("ctrl2analytique")
+    affaires_file = workdir / "affaires.csv"
+    intervs_file = workdir / "intervs.csv"
 
-# Créé le répertoire de travail
-workdir = mk_workdir("ctrl2analytique")
-affaires_file = workdir / "affaires.csv"
-intervs_file = workdir / "intervs.csv"
+    # > Supprime les fichiers d'import s'il existent
+    for file in (affaires_file, intervs_file):
+        if file.exists():
+            logger.debug("Supprime le fichier %s", file)
+            file.remove()
 
-# > Supprime les fichiers d'import s'il existent
-for file in (affaires_file, intervs_file):
-    if file.exists():
-        logger.debug("Supprime le fichier %s", file)
-        file.remove()
+    sqlHelper = AccessSqlHelper
 
-sqlHelper = AccessSqlHelper
+    # date zéro pour Access
+    date_zero = datetime(1899, 12, 30, 0, 0, 0)
 
-# date zéro pour Access
-date_zero = datetime(1899, 12, 30, 0, 0, 0)
+    def get_type_id(lngChantierId, bytCommandeId):
+        """ Recupère le type de chantier.
+        'ZP': Chantier de contrôle d'étanchéité
+        'ZC': Chantier de contrôle du compactage
+        'ZI': Chantier d'inspection vidéo
+        'ZZ': Chantier mixte.
+        '': Inconnu
+        """
+        sql = """SELECT lngChantierId, 'ZP' as type FROM tblEtancheiteBases WHERE [lngChantierId] = {chantier} AND [bytCommandeId] = {commande}
+                UNION
+                SELECT lngChantierId, 'ZC' as type FROM tblCompactageBases WHERE [lngChantierId] = {chantier}
+                UNION
+                SELECT lngChantierId, 'ZI' as type FROM tblVideoBases WHERE [lngChantierId] = {chantier};
+                """.format(chantier=lngChantierId,
+                           commande=bytCommandeId)
+        res = controles_db.read_all(sql)
+
+        if len(res) == 0:
+            return ""
+        elif len(res) == 1:
+            return res[0].type
+        else:
+            return "ZZ"
 
-def get_type_id(lngChantierId, bytCommandeId):
-    """ Recupère le type de chantier.
-    'ZP': Chantier de contrôle d'étanchéité
-    'ZC': Chantier de contrôle du compactage
-    'ZI': Chantier d'inspection vidéo
-    'ZZ': Chantier mixte.
-    '': Inconnu
-    """
-    sql = """SELECT lngChantierId, 'ZP' as type FROM tblEtancheiteBases WHERE [lngChantierId] = {chantier} AND [bytCommandeId] = {commande}
-            UNION
-            SELECT lngChantierId, 'ZC' as type FROM tblCompactageBases WHERE [lngChantierId] = {chantier}
-            UNION
-            SELECT lngChantierId, 'ZI' as type FROM tblVideoBases WHERE [lngChantierId] = {chantier};
-            """.format(chantier=lngChantierId,
-                       commande=bytCommandeId)
-    res = controles_db.read_all(sql)
+    def get_coeff_k(lngChantierId):
+        """ Récupère le coefficient de calcul des frais généraux (batiments, frais administratifs...Etc.) """
+        # On déduit l'année du chantier à partir du code chantier
+        annee = "20" + str(lngChantierId)[:2] if len(str(lngChantierId)) == 6 else "200" + str(lngChantierId)[:1]
+        return analytique_db.first(Sql.format("SELECT [COEFFG] FROM tbl_COEFFG WHERE [ANNEE] = {}", annee)).COEFFG / 100
 
-    if len(res) == 0:
-        return ""
-    elif len(res) == 1:
-        return res[0].type
-    else:
-        return "ZZ"
 
-def get_coeff_k(lngChantierId):
-    """ Récupère le coefficient de calcul des frais généraux (batiments, frais administratifs...Etc.) """
-    # On déduit l'année du chantier à partir du code chantier
-    annee = "20" + str(lngChantierId)[:2] if len(str(lngChantierId)) == 6 else "200" + str(lngChantierId)[:1]
-    return analytique_db.first(Sql.format("SELECT [COEFFG] FROM tbl_COEFFG WHERE [ANNEE] = {}", annee)).COEFFG / 100
 
 
+    # ##########   IMPORT DES AFFAIRES    ##########
 
+    # Parcourt les chantiers de contrôle pour lesquels aucune affaire n'a été créée, et les ajoute au fichier affaire.csv
 
-# ##########   IMPORT DES AFFAIRES    ##########
+    compteur = 0
+    sql = """ SELECT tblCommandes.lngChantierId, tblCommandes.bytCommandeId, tblChantiers.strSubdivisionId, tblChantiers.strCollectiviteId as ChantierCollectiviteId, tblChantiers.strLocChantier,
+              tblChantiers.strEntrepriseId, tblCommandes.strCollectiviteId as CommandeCollectiviteId, tblCommandes.dtmCommande, tblCommandes.strRefCommande, tblCommandes.blnMarche, tblCommandes.dblMtMarche, tblCommandes.strdevis
+              FROM tblChantiers INNER JOIN tblCommandes ON tblChantiers.lngChantierId = tblCommandes.lngChantierId
+              WHERE (((tblCommandes.sngAffaireIdMos) Is Null Or (tblCommandes.sngAffaireIdMos)=0))
+              """
 
-# Parcourt les chantiers de contrôle pour lesquels aucune affaire n'a été créée, et les ajoute au fichier affaire.csv
+    for data in controles_db.read(sql):
 
-compteur = 0
-sql = """ SELECT tblCommandes.lngChantierId, tblCommandes.bytCommandeId, tblChantiers.strSubdivisionId, tblChantiers.strCollectiviteId as ChantierCollectiviteId, tblChantiers.strLocChantier,
-          tblChantiers.strEntrepriseId, tblCommandes.strCollectiviteId as CommandeCollectiviteId, tblCommandes.dtmCommande, tblCommandes.strRefCommande, tblCommandes.blnMarche, tblCommandes.dblMtMarche, tblCommandes.strdevis
-          FROM tblChantiers INNER JOIN tblCommandes ON tblChantiers.lngChantierId = tblCommandes.lngChantierId
-          WHERE (((tblCommandes.sngAffaireIdMos) Is Null Or (tblCommandes.sngAffaireIdMos)=0))
-          """
+        # Création de l'affaire
+        affaire = Affaire()
+        affaire.strLiaisonControle = "{}/{}".format(data.lngChantierId, data.bytCommandeId)
+        affaire.strMOeId = data.strSubdivisionId
+        affaire.strCommneId = data.ChantierCollectiviteId
+        affaire.strLieux = data.strLocChantier
+        affaire.strEntrepriseId = data.strEntrepriseId
+        affaire.strMOId = data.CommandeCollectiviteId
+        affaire.dtmCommande = data.dtmCommande
+        affaire.Ref = data.strRefCommande
+        affaire.blnMarche = data.blnMarche
+        affaire.dblMarche = data.dblMtMarche
+        affaire.intDevisId = data.strdevis if data.strdevis else 0
+        affaire.intTypeContrat = 1
+        affaire.strCT = '1'
+        affaire.strTypeId = get_type_id(data.lngChantierId, data.bytCommandeId)
+        affaire.intCoefFG = get_coeff_k(data.lngChantierId)
+        affaire.strSituation = "En cours"
 
-for data in controles_db.read(sql):
-
-    # Création de l'affaire
-    affaire = Affaire()
-    affaire.strLiaisonControle = "{}/{}".format(data.lngChantierId, data.bytCommandeId)
-    affaire.strMOeId = data.strSubdivisionId
-    affaire.strCommneId = data.ChantierCollectiviteId
-    affaire.strLieux = data.strLocChantier
-    affaire.strEntrepriseId = data.strEntrepriseId
-    affaire.strMOId = data.CommandeCollectiviteId
-    affaire.dtmCommande = data.dtmCommande
-    affaire.Ref = data.strRefCommande
-    affaire.blnMarche = data.blnMarche
-    affaire.dblMarche = data.dblMtMarche
-    affaire.intDevisId = data.strdevis if data.strdevis else 0
-    affaire.intTypeContrat = 1
-    affaire.strCT = '1'
-    affaire.strTypeId = get_type_id(data.lngChantierId, data.bytCommandeId)
-    affaire.intCoefFG = get_coeff_k(data.lngChantierId)
-    affaire.strSituation = "En cours"
-
-    # pour garder le lien avec la donnée d'origine:
-    affaire.lngChantierId = data.lngChantierId
-    affaire.bytCommandeId = data.bytCommandeId
-
-    # Créé la ligne dans le fichier affaires.csv
-    affaire.dump_to_csv(affaires_file)
-
-    compteur += 1
-
-logger.info("> {} affaires ajoutées au fichier".format(compteur))
-
-
-
-
-# ########## IMPORT DES INTERVENTIONS DE COMPACTAGE ##########
-
-# Importe les interventions de contrôle du compactage dans le fichier intervs.csv
-
-def engin_existe(strEnginId):
-    """ retourne True si le code de l'engin existe dans la table tbl_Engin """
-    return analytique_db.exists(Sql.format("SELECT strEnginId FROM tbl_Engin WHERE strEnginId={:text}", strEnginId))
-
-def get_periode_validite(date_interv):
-    """ retourne la préiode comptable correspondant à la date de l'intervention """
-    if not date_interv:
-        return None
-    sql = Sql.format("""SELECT intPeriodeValiditeId FROM tblTarifValidite
-                        WHERE [dtmValiditeDebut] <= {date_interv:date} AND [dtmValiditeFin] > {date_interv:date} AND [bytClasseTarifId]=1
-                    """, date_interv=date_interv)
-    return commun_db.first(sql).intPeriodeValiditeId
-
-compteur = 0
-
-sql = """SELECT tblCompactageIntervs.lngChantierId, tblCompactageIntervs.bytCommandeId, tblCompactageIntervs.bytIntervId, tblCompactageIntervs.strEquipeId,
-            tblCompactageEngins.strEnginId, tblCompactageIntervs.lngRapportId, tblCompactageBases.memTravaux, tblCompactageResultats.dtmEssai, tblCompactageResultats.dtmDuree,
-            tblCompactagePartChantiers.strTrcRegard, tblMateriaux.strMatériau AS str_materiau_remblai, tblMateriaux_1.strMatériau AS str_materiau_enrobage,
-            tblMateriaux_2.strMatériau AS str_materiau_lit, tblCompactageResultats.bytPartChantierId, tblCompactageIntervs.sngIntervIdMos
-        FROM ((tblMateriaux RIGHT JOIN ((((tblCompactageIntervs LEFT JOIN tblCompactageEngins ON tblCompactageIntervs.strEquipeId = tblCompactageEngins.strEquipeId)
-            INNER JOIN tblCompactageResultats ON (tblCompactageIntervs.lngChantierId = tblCompactageResultats.lngChantierId) AND
-            (tblCompactageIntervs.bytIntervId = tblCompactageResultats.bytIntervId)) INNER JOIN tblCompactagePartChantiers ON
-            (tblCompactageResultats.lngChantierId = tblCompactagePartChantiers.lngChantierId) AND
-            (tblCompactageResultats.bytPartChantierId = tblCompactagePartChantiers.bytPartChantierId))
-            INNER JOIN tblCompactageBases ON tblCompactageIntervs.lngChantierId = tblCompactageBases.lngChantierId)
-            ON tblMateriaux.strMateriauId = tblCompactagePartChantiers.strMateriauRemblaiId) LEFT JOIN tblMateriaux AS tblMateriaux_1
-            ON tblCompactagePartChantiers.strMateriauEnrobageId = tblMateriaux_1.strMateriauId) LEFT JOIN tblMateriaux AS tblMateriaux_2
-            ON tblCompactagePartChantiers.strMateriauLitId = tblMateriaux_2.strMateriauId
-        WHERE (((tblCompactageIntervs.sngIntervIdMos)=0 Or (tblCompactageIntervs.sngIntervIdMos) Is Null))
-      """
-
-def get_type_compactage_interv(observation):
-    """ retourne le sous-type d'intervention à partir du commentaire associé """
-    if "ASSAINISEMENT" or "ASSAINISEMENT" in observation:
-        return "CC3"
-    elif "CABLE" in observation:
-        return "CC1"
-    elif "A.E.P" in observation:
-        return "CC2"
-    elif "GAZ" in observation:
-        return "CC4"
-    else:
-        return "CC3"
-
-for data in controles_db.read(sql):
-    interv = Interv()
-
-    interv.strEquipeId = "C{}".format(data.strEquipeId)
-    interv.strEnginId = data.strEnginId
-    interv.strRapportId = data.lngRapportId
-    interv.strTypeInterventionId = get_type_compactage_interv(data.memTravaux)
-    interv.strCatégorieInterventionId = "CC"
-    interv.dblquantite = 1.0
-    interv.strunite = "u"
-    interv.dtmIntervention = data.dtmEssai
-    interv.dtmDureeIntervention = data.dtmDuree
-    interv.dtmDureeInstallation = date_zero  # Les temps d'installation seront calculés en fin de traitement
-    interv.strLiaisonControle = "{}/{}/{}".format(data.lngChantierId, data.bytCommandeId, data.bytIntervId)
-    interv.strArticleId = data.strEnginId
-    interv.intPeriode = get_periode_validite(data.dtmEssai)
-    interv.remarques = data.strTrcRegard if data.strTrcRegard else "-"
-    interv.strgrandeur1 = data.str_materiau_remblai
-    interv.strgrandeur2 = data.str_materiau_lit
-    interv.strgrandeur3 = data.str_materiau_enrobage
-    interv.strcaracteristique1 = "Matériau remblai"
-    interv.strcaracteristique2 = "Matériau lit de pose"
-    interv.strcaracteristique3 = "Matériau enrobage"
-    interv.strunite1 = ""
-    interv.strunite2 = ""
-    interv.strunite3 = ""
-    interv.dtmImportation = "{}".format(datetime.now().strftime("%Y-%m-%d"))
-    interv.strTest = "{}/{}/{}/{}".format(data.lngChantierId, data.bytCommandeId, data.bytIntervId, data.bytPartChantierId)
-    interv.LienAff = "{}/{}".format(data.lngChantierId, data.bytCommandeId)
-
-    # pour garder le lien avec la donnée d'origine:
-    interv.lngChantierId = data.lngChantierId
-    interv.bytCommandeId = data.bytCommandeId
-    interv.bytIntervId = data.bytIntervId
-
-    # Créé la ligne dans le fichier intervs.csv
-    interv.dump_to_csv(intervs_file)
-
-    compteur += 1
-
-logger.info("> {} interventions Compactage ajoutées au fichier".format(compteur))
-
-
-
-# ########## IMPORT DES INTERVENTIONS D'ETANCHEITE ##########
-
-# Importe les interventions de contrôle d'étanchéité dans le fichier intervs.csv
-
-compteur = 0
-
-sql = """SELECT tblEtancheiteIntervs.lngChantierId, tblEtancheiteIntervs.bytCommandeId, tblEtancheiteIntervs.bytIntervId, tblEtancheiteIntervs.strEquipeId,
-            tblEtancheiteIntervs.lngRapportId, tblEtancheitePartChantiers.bytTypeEssai, tblMateriaux.strMateriauId, tblMateriaux.strMatériau,
-            tblEtancheitePartChantiers.intDiametre, tblEtancheitePartChantiers.sngLgHt, tblEtancheitePartChantiers.intNbJoint, tblEtancheiteResultats.dtmDuree,
-            tblEtancheiteResultats.dtmEssai, tblEtancheitePartChantiers.strTrcRegard, tblEtancheiteResultats.bytPartChantierId
-        FROM ((tblEtancheiteIntervs INNER JOIN tblEtancheiteResultats ON (tblEtancheiteIntervs.lngChantierId = tblEtancheiteResultats.lngChantierId)
-            AND (tblEtancheiteIntervs.bytIntervId = tblEtancheiteResultats.bytIntervId)) INNER JOIN tblEtancheitePartChantiers
-            ON (tblEtancheiteResultats.lngChantierId = tblEtancheitePartChantiers.lngChantierId)
-            AND (tblEtancheiteResultats.bytPartChantierId = tblEtancheitePartChantiers.bytPartChantierId)) INNER JOIN tblMateriaux
-            ON tblEtancheitePartChantiers.strMateriauId = tblMateriaux.strMateriauId
-        WHERE (((tblEtancheiteIntervs.sngIntervIdMos)=0 Or (tblEtancheiteIntervs.sngIntervIdMos) Is Null));
-      """
-
-def get_engin_etancheite(equipe, diametre, materiau, type_essai):
-    """ retourne l'engin correspondant à l'essai en fonction eds caractéristiques de l'essai """
-    sql = """SELECT strEnginId FROM tblEtancheiteEngins
-            WHERE ([strEquipeId] = '{}') AND ([intDiametre] = {}) AND ([strMateriauId] = '{}') AND ([bytTypeEssaiId] ={})
-            """.format(equipe, diametre, materiau, type_essai)
-    row = controles_db.first(sql)
-    return row.strEnginId if row else ""
-
-for data in controles_db.read(sql):
-    interv = Interv()
-
-    interv.strEquipeId = "C{}".format(data.strEquipeId)
-    interv.strEnginId = get_engin_etancheite(data.strEquipeId, data.intDiametre, data.strMateriauId, data.bytTypeEssai)
-    interv.strRapportId = data.lngRapportId
-    interv.strTypeInterventionId = "CE{}".format(data.bytTypeEssai)
-    interv.strCatégorieInterventionId = "CE"
-    interv.dblquantite = float(data.intNbJoint)
-    interv.strunite = "u"
-    interv.dtmIntervention = data.dtmEssai
-    interv.dtmDureeIntervention = data.dtmDuree
-    interv.dtmDureeInstallation = date_zero  # Les temps d'installation seront recalculés en fin de traitement
-    interv.strLiaisonControle = "{}/{}/{}".format(data.lngChantierId, data.bytCommandeId, data.bytIntervId)
-    interv.strArticleId = interv.strEnginId
-    interv.intPeriode = get_periode_validite(data.dtmEssai)
-    interv.remarques = data.strTrcRegard if data.strTrcRegard else "-"
-    interv.strgrandeur1 = data.strMatériau
-    interv.strgrandeur2 = data.intDiametre
-    interv.strgrandeur3 = data.sngLgHt
-    interv.strcaracteristique1 = "Matériau"
-    interv.strcaracteristique2 = "Diamètre"
-    interv.strcaracteristique3 = "Longueur"
-    interv.strunite1 = ""
-    interv.strunite2 = "mm"
-    interv.strunite3 = "m"
-    interv.dtmImportation = "{}".format(datetime.now().strftime("%Y-%m-%d"))
-    interv.strTest = "{}/{}/{}/{}".format(data.lngChantierId, data.bytCommandeId, data.bytIntervId, data.bytPartChantierId)
-    interv.LienAff = "{}/{}".format(data.lngChantierId, data.bytCommandeId)
-
-    # pour garder le lien avec la donnée d'origine:
-    interv.lngChantierId = data.lngChantierId
-    interv.bytCommandeId = data.bytCommandeId
-    interv.bytIntervId = data.bytIntervId
-
-    # Créé la ligne dans le fichier intervs.csv
-    interv.dump_to_csv(intervs_file)
-
-    compteur += 1
-
-logger.info("> {} interventions Etanchéité ajoutées au fichier".format(compteur))
-
-
-
-# ########## IMPORT DES INTERVENTIONS D'INSPECTION VIDEO ##########
-
-
-# Importe les interventions d'inspection vidéo dans le fichier intervs.csv
-
-compteur = 0
-
-sql = """SELECT tblVideoIntervs.lngChantierId, tblVideoIntervs.bytCommandeId, tblVideoIntervs.bytIntervId, tblVideoIntervs.strEquipeId,
-        tblVideoEngins.strEnginId, tblVideoIntervs.lngRapportId, First(tblso_rate_Analyse.MateriauCourt) AS strmateriau, tblVideoIntervs.lngTroncon,
-        tblVideoIntervs.sngNbJourFact, First(tblso_rate_Analyse.MaxDeDiametre) AS diam, tblVideoIntervs.dtmDuree, tblVideoIntervs.dtmIntervDu,
-        First(tblVideoIntervs.memObservation) AS memObservation, tblChantiers.strEntrepriseId
-        FROM ((tblVideoEngins RIGHT JOIN tblVideoIntervs ON tblVideoEngins.strEquipeId = tblVideoIntervs.strEquipeId) INNER JOIN tblso_rate_Analyse ON
-        (tblVideoIntervs.lngChantierId = tblso_rate_Analyse.lngChantierId) AND (tblVideoIntervs.bytIntervId = tblso_rate_Analyse.bytIntervId)) INNER JOIN
-        tblChantiers ON tblVideoIntervs.lngChantierId = tblChantiers.lngChantierId
-        WHERE (((tblVideoIntervs.sngIntervIdMos) Is Null Or (tblVideoIntervs.sngIntervIdMos)=0))
-        GROUP BY tblVideoIntervs.lngChantierId, tblVideoIntervs.bytCommandeId, tblVideoIntervs.bytIntervId, tblVideoIntervs.strEquipeId,
-        tblVideoIntervs.lngRapportId, tblVideoIntervs.lngTroncon, tblVideoIntervs.sngNbJourFact, tblVideoIntervs.dtmDuree,
-        tblVideoIntervs.dtmIntervDu, tblVideoEngins.strEnginId, tblChantiers.strEntrepriseId
-        """
+        # pour garder le lien avec la donnée d'origine:
+        affaire.lngChantierId = data.lngChantierId
+        affaire.bytCommandeId = data.bytCommandeId
 
-for data in controles_db.read(sql):
-    interv = Interv()
+        # Créé la ligne dans le fichier affaires.csv
+        affaire.dump_to_csv(affaires_file)
 
-    interv.strEquipeId = "C{}".format(data.strEquipeId)
-    interv.strEnginId = data.strEnginId
-    interv.strRapportId = data.lngRapportId
-    interv.strTypeInterventionId = "CI1" if data.strEntrepriseId != 195 else "CI2"
-    interv.strCatégorieInterventionId = "CI"
-    interv.dblquantite = float(data.sngNbJourFact)
-    interv.strunite = "j"
-    interv.dtmIntervention = data.dtmIntervDu
-    interv.dtmDureeIntervention = data.dtmDuree
-    interv.dtmDureeInstallation = date_zero  # Les temps d'installation seront recalculés en fin de traitement
-    interv.strLiaisonControle = "{}/{}/{}".format(data.lngChantierId, data.bytCommandeId, data.bytIntervId)
-    interv.strArticleId = data.strEnginId
-    interv.intPeriode = get_periode_validite(data.dtmIntervDu)
-    interv.remarques = data.memObservation if data.memObservation else "-"
-    interv.strgrandeur1 = data.strmateriau
-    interv.strgrandeur2 = data.diam
-    interv.strgrandeur3 = data.lngTroncon
-    interv.strcaracteristique1 = "Matériau"
-    interv.strcaracteristique2 = "Diamètre"
-    interv.strcaracteristique3 = "Longueur inspectée"
-    interv.strunite1 = ""
-    interv.strunite2 = "mm"
-    interv.strunite3 = "m"
-    interv.dtmImportation = "{}".format(datetime.now().strftime("%Y-%m-%d"))
-    interv.strTest = "{}/{}/{}/1".format(data.lngChantierId, data.bytCommandeId, data.bytIntervId)
-    interv.LienAff = "{}/{}".format(data.lngChantierId, data.bytCommandeId)
+        compteur += 1
 
-    # pour garder le lien avec la donnée d'origine:
-    interv.lngChantierId = data.lngChantierId
-    interv.bytCommandeId = data.bytCommandeId
-    interv.bytIntervId = data.bytIntervId
+    logger.info("> {} affaires ajoutées au fichier".format(compteur))
 
-    # Créé la ligne dans le fichier intervs.csv
-    interv.dump_to_csv(intervs_file)
 
-    compteur += 1
 
-logger.info("> {} interventions ITV ajoutées au fichier".format(compteur))
 
+    # ########## IMPORT DES INTERVENTIONS DE COMPACTAGE ##########
 
-logging.info("Les données à importer ont été ajoutées aux fichiers '{}' et '{}'".format(affaires_file, intervs_file))
-logging.info("Ces fichiers sont au format CSV (séparateur: tabulation)")
+    # Importe les interventions de contrôle du compactage dans le fichier intervs.csv
 
+    def engin_existe(strEnginId):
+        """ retourne True si le code de l'engin existe dans la table tbl_Engin """
+        return analytique_db.exists(Sql.format("SELECT strEnginId FROM tbl_Engin WHERE strEnginId={:text}", strEnginId))
 
+    def get_periode_validite(date_interv):
+        """ retourne la préiode comptable correspondant à la date de l'intervention """
+        if not date_interv:
+            return None
+        sql = Sql.format("""SELECT intPeriodeValiditeId FROM tblTarifValidite
+                            WHERE [dtmValiditeDebut] <= {date_interv:date} AND [dtmValiditeFin] > {date_interv:date} AND [bytClasseTarifId]=1
+                        """, date_interv=date_interv)
+        return commun_db.first(sql).intPeriodeValiditeId
 
+    compteur = 0
 
+    sql = """SELECT tblCompactageIntervs.lngChantierId, tblCompactageIntervs.bytCommandeId, tblCompactageIntervs.bytIntervId, tblCompactageIntervs.strEquipeId,
+                tblCompactageEngins.strEnginId, tblCompactageIntervs.lngRapportId, tblCompactageBases.memTravaux, tblCompactageResultats.dtmEssai, tblCompactageResultats.dtmDuree,
+                tblCompactagePartChantiers.strTrcRegard, tblMateriaux.strMatériau AS str_materiau_remblai, tblMateriaux_1.strMatériau AS str_materiau_enrobage,
+                tblMateriaux_2.strMatériau AS str_materiau_lit, tblCompactageResultats.bytPartChantierId, tblCompactageIntervs.sngIntervIdMos
+            FROM ((tblMateriaux RIGHT JOIN ((((tblCompactageIntervs LEFT JOIN tblCompactageEngins ON tblCompactageIntervs.strEquipeId = tblCompactageEngins.strEquipeId)
+                INNER JOIN tblCompactageResultats ON (tblCompactageIntervs.lngChantierId = tblCompactageResultats.lngChantierId) AND
+                (tblCompactageIntervs.bytIntervId = tblCompactageResultats.bytIntervId)) INNER JOIN tblCompactagePartChantiers ON
+                (tblCompactageResultats.lngChantierId = tblCompactagePartChantiers.lngChantierId) AND
+                (tblCompactageResultats.bytPartChantierId = tblCompactagePartChantiers.bytPartChantierId))
+                INNER JOIN tblCompactageBases ON tblCompactageIntervs.lngChantierId = tblCompactageBases.lngChantierId)
+                ON tblMateriaux.strMateriauId = tblCompactagePartChantiers.strMateriauRemblaiId) LEFT JOIN tblMateriaux AS tblMateriaux_1
+                ON tblCompactagePartChantiers.strMateriauEnrobageId = tblMateriaux_1.strMateriauId) LEFT JOIN tblMateriaux AS tblMateriaux_2
+                ON tblCompactagePartChantiers.strMateriauLitId = tblMateriaux_2.strMateriauId
+            WHERE (((tblCompactageIntervs.sngIntervIdMos)=0 Or (tblCompactageIntervs.sngIntervIdMos) Is Null))
+          """
 
-# ########## CONTROLE ET CORRECTION DES DONNEES ##########
+    def get_type_compactage_interv(observation):
+        """ retourne le sous-type d'intervention à partir du commentaire associé """
+        if "ASSAINISEMENT" or "ASSAINISEMENT" in observation:
+            return "CC3"
+        elif "CABLE" in observation:
+            return "CC1"
+        elif "A.E.P" in observation:
+            return "CC2"
+        elif "GAZ" in observation:
+            return "CC4"
+        else:
+            return "CC3"
+
+    for data in controles_db.read(sql):
+        interv = Interv()
+
+        interv.strEquipeId = "C{}".format(data.strEquipeId)
+        interv.strEnginId = data.strEnginId
+        interv.strRapportId = data.lngRapportId
+        interv.strTypeInterventionId = get_type_compactage_interv(data.memTravaux)
+        interv.strCatégorieInterventionId = "CC"
+        interv.dblquantite = 1.0
+        interv.strunite = "u"
+        interv.dtmIntervention = data.dtmEssai
+        interv.dtmDureeIntervention = data.dtmDuree
+        interv.dtmDureeInstallation = date_zero  # Les temps d'installation seront calculés en fin de traitement
+        interv.strLiaisonControle = "{}/{}/{}".format(data.lngChantierId, data.bytCommandeId, data.bytIntervId)
+        interv.strArticleId = data.strEnginId
+        interv.intPeriode = get_periode_validite(data.dtmEssai)
+        interv.remarques = data.strTrcRegard if data.strTrcRegard else "-"
+        interv.strgrandeur1 = data.str_materiau_remblai
+        interv.strgrandeur2 = data.str_materiau_lit
+        interv.strgrandeur3 = data.str_materiau_enrobage
+        interv.strcaracteristique1 = "Matériau remblai"
+        interv.strcaracteristique2 = "Matériau lit de pose"
+        interv.strcaracteristique3 = "Matériau enrobage"
+        interv.strunite1 = ""
+        interv.strunite2 = ""
+        interv.strunite3 = ""
+        interv.dtmImportation = "{}".format(datetime.now().strftime("%Y-%m-%d"))
+        interv.strTest = "{}/{}/{}/{}".format(data.lngChantierId, data.bytCommandeId, data.bytIntervId, data.bytPartChantierId)
+        interv.LienAff = "{}/{}".format(data.lngChantierId, data.bytCommandeId)
+
+        # pour garder le lien avec la donnée d'origine:
+        interv.lngChantierId = data.lngChantierId
+        interv.bytCommandeId = data.bytCommandeId
+        interv.bytIntervId = data.bytIntervId
+
+        # Créé la ligne dans le fichier intervs.csv
+        interv.dump_to_csv(intervs_file)
+
+        compteur += 1
+
+    logger.info("> {} interventions Compactage ajoutées au fichier".format(compteur))
+
+
+
+    # ########## IMPORT DES INTERVENTIONS D'ETANCHEITE ##########
+
+    # Importe les interventions de contrôle d'étanchéité dans le fichier intervs.csv
+
+    compteur = 0
+
+    sql = """SELECT tblEtancheiteIntervs.lngChantierId, tblEtancheiteIntervs.bytCommandeId, tblEtancheiteIntervs.bytIntervId, tblEtancheiteIntervs.strEquipeId,
+                tblEtancheiteIntervs.lngRapportId, tblEtancheitePartChantiers.bytTypeEssai, tblMateriaux.strMateriauId, tblMateriaux.strMatériau,
+                tblEtancheitePartChantiers.intDiametre, tblEtancheitePartChantiers.sngLgHt, tblEtancheitePartChantiers.intNbJoint, tblEtancheiteResultats.dtmDuree,
+                tblEtancheiteResultats.dtmEssai, tblEtancheitePartChantiers.strTrcRegard, tblEtancheiteResultats.bytPartChantierId
+            FROM ((tblEtancheiteIntervs INNER JOIN tblEtancheiteResultats ON (tblEtancheiteIntervs.lngChantierId = tblEtancheiteResultats.lngChantierId)
+                AND (tblEtancheiteIntervs.bytIntervId = tblEtancheiteResultats.bytIntervId)) INNER JOIN tblEtancheitePartChantiers
+                ON (tblEtancheiteResultats.lngChantierId = tblEtancheitePartChantiers.lngChantierId)
+                AND (tblEtancheiteResultats.bytPartChantierId = tblEtancheitePartChantiers.bytPartChantierId)) INNER JOIN tblMateriaux
+                ON tblEtancheitePartChantiers.strMateriauId = tblMateriaux.strMateriauId
+            WHERE (((tblEtancheiteIntervs.sngIntervIdMos)=0 Or (tblEtancheiteIntervs.sngIntervIdMos) Is Null));
+          """
 
-errors = -1
+    def get_engin_etancheite(equipe, diametre, materiau, type_essai):
+        """ retourne l'engin correspondant à l'essai en fonction eds caractéristiques de l'essai """
+        sql = """SELECT strEnginId FROM tblEtancheiteEngins
+                WHERE ([strEquipeId] = '{}') AND ([intDiametre] = {}) AND ([strMateriauId] = '{}') AND ([bytTypeEssaiId] ={})
+                """.format(equipe, diametre, materiau, type_essai)
+        row = controles_db.first(sql)
+        return row.strEnginId if row else ""
+
+    for data in controles_db.read(sql):
+        interv = Interv()
+
+        interv.strEquipeId = "C{}".format(data.strEquipeId)
+        interv.strEnginId = get_engin_etancheite(data.strEquipeId, data.intDiametre, data.strMateriauId, data.bytTypeEssai)
+        interv.strRapportId = data.lngRapportId
+        interv.strTypeInterventionId = "CE{}".format(data.bytTypeEssai)
+        interv.strCatégorieInterventionId = "CE"
+        interv.dblquantite = float(data.intNbJoint)
+        interv.strunite = "u"
+        interv.dtmIntervention = data.dtmEssai
+        interv.dtmDureeIntervention = data.dtmDuree
+        interv.dtmDureeInstallation = date_zero  # Les temps d'installation seront recalculés en fin de traitement
+        interv.strLiaisonControle = "{}/{}/{}".format(data.lngChantierId, data.bytCommandeId, data.bytIntervId)
+        interv.strArticleId = interv.strEnginId
+        interv.intPeriode = get_periode_validite(data.dtmEssai)
+        interv.remarques = data.strTrcRegard if data.strTrcRegard else "-"
+        interv.strgrandeur1 = data.strMatériau
+        interv.strgrandeur2 = data.intDiametre
+        interv.strgrandeur3 = data.sngLgHt
+        interv.strcaracteristique1 = "Matériau"
+        interv.strcaracteristique2 = "Diamètre"
+        interv.strcaracteristique3 = "Longueur"
+        interv.strunite1 = ""
+        interv.strunite2 = "mm"
+        interv.strunite3 = "m"
+        interv.dtmImportation = "{}".format(datetime.now().strftime("%Y-%m-%d"))
+        interv.strTest = "{}/{}/{}/{}".format(data.lngChantierId, data.bytCommandeId, data.bytIntervId, data.bytPartChantierId)
+        interv.LienAff = "{}/{}".format(data.lngChantierId, data.bytCommandeId)
+
+        # pour garder le lien avec la donnée d'origine:
+        interv.lngChantierId = data.lngChantierId
+        interv.bytCommandeId = data.bytCommandeId
+        interv.bytIntervId = data.bytIntervId
+
+        # Créé la ligne dans le fichier intervs.csv
+        interv.dump_to_csv(intervs_file)
+
+        compteur += 1
+
+    logger.info("> {} interventions Etanchéité ajoutées au fichier".format(compteur))
+
+
+
+    # ########## IMPORT DES INTERVENTIONS D'INSPECTION VIDEO ##########
+
+
+    # Importe les interventions d'inspection vidéo dans le fichier intervs.csv
+
+    compteur = 0
+
+    sql = """SELECT tblVideoIntervs.lngChantierId, tblVideoIntervs.bytCommandeId, tblVideoIntervs.bytIntervId, tblVideoIntervs.strEquipeId,
+            tblVideoEngins.strEnginId, tblVideoIntervs.lngRapportId, First(tblso_rate_Analyse.MateriauCourt) AS strmateriau, tblVideoIntervs.lngTroncon,
+            tblVideoIntervs.sngNbJourFact, First(tblso_rate_Analyse.MaxDeDiametre) AS diam, tblVideoIntervs.dtmDuree, tblVideoIntervs.dtmIntervDu,
+            First(tblVideoIntervs.memObservation) AS memObservation, tblChantiers.strEntrepriseId
+            FROM ((tblVideoEngins RIGHT JOIN tblVideoIntervs ON tblVideoEngins.strEquipeId = tblVideoIntervs.strEquipeId) INNER JOIN tblso_rate_Analyse ON
+            (tblVideoIntervs.lngChantierId = tblso_rate_Analyse.lngChantierId) AND (tblVideoIntervs.bytIntervId = tblso_rate_Analyse.bytIntervId)) INNER JOIN
+            tblChantiers ON tblVideoIntervs.lngChantierId = tblChantiers.lngChantierId
+            WHERE (((tblVideoIntervs.sngIntervIdMos) Is Null Or (tblVideoIntervs.sngIntervIdMos)=0))
+            GROUP BY tblVideoIntervs.lngChantierId, tblVideoIntervs.bytCommandeId, tblVideoIntervs.bytIntervId, tblVideoIntervs.strEquipeId,
+            tblVideoIntervs.lngRapportId, tblVideoIntervs.lngTroncon, tblVideoIntervs.sngNbJourFact, tblVideoIntervs.dtmDuree,
+            tblVideoIntervs.dtmIntervDu, tblVideoEngins.strEnginId, tblChantiers.strEntrepriseId
+            """
+
+    for data in controles_db.read(sql):
+        interv = Interv()
+
+        interv.strEquipeId = "C{}".format(data.strEquipeId)
+        interv.strEnginId = data.strEnginId
+        interv.strRapportId = data.lngRapportId
+        interv.strTypeInterventionId = "CI1" if data.strEntrepriseId != 195 else "CI2"
+        interv.strCatégorieInterventionId = "CI"
+        interv.dblquantite = float(data.sngNbJourFact)
+        interv.strunite = "j"
+        interv.dtmIntervention = data.dtmIntervDu
+        interv.dtmDureeIntervention = data.dtmDuree
+        interv.dtmDureeInstallation = date_zero  # Les temps d'installation seront recalculés en fin de traitement
+        interv.strLiaisonControle = "{}/{}/{}".format(data.lngChantierId, data.bytCommandeId, data.bytIntervId)
+        interv.strArticleId = data.strEnginId
+        interv.intPeriode = get_periode_validite(data.dtmIntervDu)
+        interv.remarques = data.memObservation if data.memObservation else "-"
+        interv.strgrandeur1 = data.strmateriau
+        interv.strgrandeur2 = data.diam
+        interv.strgrandeur3 = data.lngTroncon
+        interv.strcaracteristique1 = "Matériau"
+        interv.strcaracteristique2 = "Diamètre"
+        interv.strcaracteristique3 = "Longueur inspectée"
+        interv.strunite1 = ""
+        interv.strunite2 = "mm"
+        interv.strunite3 = "m"
+        interv.dtmImportation = "{}".format(datetime.now().strftime("%Y-%m-%d"))
+        interv.strTest = "{}/{}/{}/1".format(data.lngChantierId, data.bytCommandeId, data.bytIntervId)
+        interv.LienAff = "{}/{}".format(data.lngChantierId, data.bytCommandeId)
+
+        # pour garder le lien avec la donnée d'origine:
+        interv.lngChantierId = data.lngChantierId
+        interv.bytCommandeId = data.bytCommandeId
+        interv.bytIntervId = data.bytIntervId
+
+        # Créé la ligne dans le fichier intervs.csv
+        interv.dump_to_csv(intervs_file)
+
+        compteur += 1
+
+    logger.info("> {} interventions ITV ajoutées au fichier".format(compteur))
+
+
+    logging.info("Les données à importer ont été ajoutées aux fichiers '{}' et '{}'".format(affaires_file, intervs_file))
+    logging.info("Ces fichiers sont au format CSV (séparateur: tabulation)")
+
+
+
+
+
+    # ########## CONTROLE ET CORRECTION DES DONNEES ##########
+
+    errors = -1
+
+    while errors:
+        errors = []
+
+        for affaire in Affaire.load_csv(affaires_file):
+
+            prefix = "Affaire {}: ".format(affaire.strLiaisonControle)
+            if not affaire.strMOId:
+                errors.append(prefix + "MO manquant")
+            else:
+                if not commun_db.exists(Sql.format("SELECT [lngTiersId] FROM tblTiers WHERE [lngTiersId]={}", affaire.strMOId)):
+                    errors.append(prefix + "Le MO {} n'existe pas dans tblTiers".format(affaire.strMOId))
+            if not affaire.strMOeId:
+                errors.append(prefix + "MOe manquant")
+            else:
+                if not commun_db.exists(Sql.format("SELECT [lngTiersId] FROM tblTiers WHERE [lngTiersId]={}", affaire.strMOeId)):
+                    errors.append(prefix + "Le MOe {} n'existe pas dans tblTiers".format(affaire.strMOeId))
+            if not affaire.strEntrepriseId:
+                errors.append(prefix + "Entreprise manquante")
+            else:
+                if not commun_db.exists(Sql.format("SELECT [lngTiersId] FROM tblTiers WHERE [lngTiersId]={}", affaire.strEntrepriseId)):
+                    errors.append(prefix + "L'entreprise {} n'existe pas dans tblTiers".format(affaire.strEntrepriseId))
+            if not affaire.strCommneId:
+                errors.append(prefix + "Commune manquante")
+            else:
+                if not commun_db.exists(Sql.format("SELECT [lngTiersId] FROM tblTiers WHERE [lngTiersId]={}", affaire.strCommneId)):
+                    errors.append(prefix + "La commune {} n'existe pas dans tblTiers".format(affaire.strCommneId))
+            if not affaire.strTypeId:
+                errors.append(prefix + "Type d'affaire manquant")
+            if not affaire.dtmCommande:
+                errors.append(prefix + "Date de commande manquante")
+            if affaire.blnMarche == True:
+                if not affaire.intDevisId:
+                    errors.append(prefix + "Numéro de devis manquant")
+
+            if analytique_db.exists(Sql.format("SELECT dblAffaireId FROM tbl_Affaires WHERE [strLiaisonControle]='{}'", affaire.strLiaisonControle)):
+                errors.append(prefix + "Une affaire portant ce code existe déjà: {}".format(affaire.strLiaisonControle))
+
+        for interv in Interv.load_csv(intervs_file):
+
+            prefix = "Intervention {}: ".format(interv.strTest)
+            if not interv.strEquipeId:
+                errors.append(prefix + "Equipe manquante")
+            if not interv.strEnginId:
+                errors.append(prefix + "Engin manquant")
+            if not interv.strRapportId:
+                errors.append(prefix + "Rapport manquant")
+            if not interv.strCatégorieInterventionId:
+                errors.append(prefix + "Catégorie de l'intervention manquante")
+            if not interv.strTypeInterventionId:
+                errors.append(prefix + "Type d'intervention manquant")
+            if not interv.dblquantite:
+                errors.append(prefix + "Quantité nulle")
+            if not interv.strunite:
+                errors.append(prefix + "Unité non renseignée")
+            if not interv.dtmIntervention:
+                errors.append(prefix + "Erreur : date d'intervention")
+            if not interv.dtmDureeIntervention or interv.dtmDureeIntervention == date_zero:
+                errors.append(prefix + "Durée d'intervention nulle")
+            if not interv.strunite:
+                errors.append(prefix + "Unité non renseignée")
+
+            if not engin_existe(interv.strEnginId):
+                errors.append(prefix + "l'engin {} n'existe pas".format(interv.strEnginId))
+
+        # *** 6- Interruption pour corection manuelle des données (si nécessaire)
+        if errors:
+            logging.error("<!> Des erreurs ont été détectées dans les données à importer. <!>")
+            for msg in errors:
+                logging.error(msg)
+            if no_prompt:
+                logger.info("# Annulation de l'import")
+                sys.exit(1)
+        else:
+            logging.info("Aucune erreur n'a été détectée dans les données.")
+            if no_prompt:
+                break
 
-while errors:
-    errors = []
+        # Même si aucune erreur n'a été détectée, on demande un controle visuel.
+        prompt = ""
+        while prompt != "v":
+            logger.info(">> Veuillez contrôler les données, puis taper: \n\t'v' pour continuer\n\t'f' pour forcer le traitement à se poursuivre\n\t'q' pour annuler")
+            try:
+                from core import tsv_editor
+                tsv_editor.exec_(affaires_file.abspath())
+                tsv_editor.exec_(intervs_file.abspath())
+            except:
+                logger.error("Erreur à l'ouverture du fichier %s", affaires_file)
+                logger.error("Erreur à l'ouverture du fichier %s", intervs_file)
+
+            prompt = input("")
+            if prompt == "f":
+                break
+            if prompt == "q":
+                logger.info("# Annulation de l'import")
+                sys.exit(1)
+
+    # ########## MISE A JOUR DE LA BASE DE DONNEES ANALYTIQUE ##########
+
+    # On charge en mémoire les affaires et les interventions
+    logger.info("# Mise à jour de la base Analytique")
+    logger.info("> NB: Les modifications ne seront appliquées à la base que si toutes les opérations se déroulent normalement.")
+
+    affaires = list(Affaire.load_csv(affaires_file))
+    intervs = list(Interv.load_csv(intervs_file))
+
+    # On insère les affaires, interventions dans Analytique, et on génère la ou les lignes de tarification associées
+
+    for affaire in affaires:
+
+        # insertion dans tbl_Affaires
+
+        sql = Sql.format(""" INSERT INTO tbl_Affaires ( strMOId, strMOeId, strEntrepriseId, strCommneId, strLieux, strTypeId, dtmCommande, Ref,
+                                            blnMarche, dblMarche, intTypeContrat, strCT, strLiaisonControle, blnTarification,
+                                            blnAnalyse, strSituation, intCoefFG )
+           VALUES ({affaire.strMOId:text}, {affaire.strMOeId:text}, {affaire.strEntrepriseId:text}, {affaire.strCommneId:text}, {affaire.strLieux:text}, {affaire.strTypeId:text},
+           {affaire.dtmCommande:date}, {affaire.Ref:text}, {affaire.blnMarche}, {affaire.dblMarche}, {affaire.intTypeContrat}, {affaire.strCT:text},
+           {affaire.strLiaisonControle:text}, True, False, {affaire.strSituation:text}, {affaire.intCoefFG})
+           """, affaire=affaire)
+
+        analytique_db.execute(sql)
+
+        logger.info("> Ajout de l'affaire: {}".format(affaire.strLiaisonControle))
+
+
+    # On insère les interventions dans tbl_Intervention
+    for interv in intervs:
+
+        affaire = analytique_db.first(Sql.format("SELECT TOP 1 DblAffaireId FROM tbl_Affaires WHERE [strLiaisonControle]='{}'", interv.LienAff))
+        if not affaire:
+            logger.error("Intervention {} : Impossible de trouver l'affaire {}".format(interv.strTest, interv.LienAff))
+            continue
+
+        interv.dblAffaireId = affaire.DblAffaireId
+        if not interv.intPeriode:
+            interv.intPeriode = get_periode_validite(data.dtmIntervDu)  # Si la date d'interv manquait avant la validation, la periode n'a pa été mise à jour
+
+        sql = Sql.format("""INSERT INTO tbl_Intervention ( DblAffaireId, strEquipeId, strEnginId, strRapportId, strCatégorieInterventionId, strTypeInterventionId,
+                  dblquantite, strunite, dtmIntervention, dtmDureeIntervention, dtmDureeInstallation, strcaracteristique1, strgrandeur1, strunite1,
+                  strcaracteristique2, strgrandeur2, strunite2, strcaracteristique3, strgrandeur3, strunite3, strLiaisonControle, strarticleId,
+                  intPeriode, blnTarification, blnAnalyse, blnFacturer, remarques, blnPeriode, dtmImportation, strTest )
+          VALUES ({interv.dblAffaireId}, {interv.strEquipeId:text}, {interv.strEnginId:text}, {interv.strRapportId:text}, {interv.strCatégorieInterventionId:text},
+              {interv.strTypeInterventionId:text}, {interv.dblquantite}, {interv.strunite:text}, {interv.dtmIntervention:date}, {interv.dtmDureeIntervention:date}, {date_zero:date},
+              {interv.strcaracteristique1:text}, {interv.strgrandeur1:text}, {interv.strunite1:text}, {interv.strcaracteristique2:text},
+              {interv.strgrandeur2:text}, {interv.strunite2:text}, {interv.strcaracteristique3:text}, {interv.strgrandeur3:text}, {interv.strunite3:text},
+              {interv.strLiaisonControle:text}, {interv.strArticleId:text}, {interv.intPeriode}, True, False, False, {interv.remarques:text},
+              False, {interv.dtmImportation:date}, {interv.strTest:text})
+          """, interv=interv, date_zero=date_zero)
+
+        analytique_db.execute(sql)
+
+        logger.info("> Ajout de l'intervention: {}".format(interv.strTest))
+
+
+
+    # Calcul de la tarification et ajout à tbl_Tarification
+
+    # > On va créer une ligne de tarification pour chaque groupe d'interventions
+    # > partageant le même lngRapportid et strArticleId (cad le même engin)
+    for strRapportId, strArticleId in set([(interv.strRapportId, interv.strArticleId) for interv in intervs]):
+
+        tarif = Tarification()
+        tarif.intervs = [interv for interv in intervs if interv.strRapportId == strRapportId and interv.strArticleId == strArticleId]
+
+        # recupere le prix unitaire de l'engin
+        tarif_engin = commun_db.first(Sql.format("""SELECT dblPU FROM tblTarif WHERE [strArticleId]={:text} AND [intPeriodeValiditeId]={}
+                                                 """, strArticleId, get_periode_validite(intervs[0].dtmIntervention)))
+        if not tarif_engin:
+            logger.error("Aucun tarif trouvé dans tblTarif pour l'article {}, periode {}".format(strArticleId, tarif.intervs[0].intPeriode))
+        prix_unitaire = tarif_engin.dblPU
+
+        # recupere le taux de tva applicable à l'engin
+        taux_tva = commun_db.first(Sql.format("""SELECT tblTVATaux.dblTVATaux FROM tblArticle INNER JOIN tblTVATaux ON tblArticle.bytTVAArticleId = tblTVATaux.bytTVAId
+                                                 WHERE tblArticle.strArticleId={:text};""", strArticleId)).dblTVATaux
+
+        tarif.DblAffaireId = tarif.intervs[0].dblAffaireId
+        tarif.strRapportId = strRapportId
+        tarif.strArticleId = strArticleId
+        tarif.dblQuantite = sum([float(interv.dblquantite) for interv in tarif.intervs])
+        tarif.strUnite = tarif.intervs[0].strunite
+        tarif.dtmDebut = min([interv.dtmIntervention for interv in tarif.intervs])
+        tarif.dtmFin = max([interv.dtmIntervention for interv in tarif.intervs])
+        tarif.bytPeriode = tarif.intervs[0].intPeriode
+        tarif.dblPrixUnitaire = prix_unitaire
+        tarif.dblPrixTotal = tarif.dblQuantite * tarif.dblPrixUnitaire
+        tarif.dblTauxTVA = taux_tva
+        tarif.dblPrixTVA = tarif.dblPrixTotal * (0.01 * tarif.dblTauxTVA)
+        tarif.strStatut = 'A facturer'
+
+        sql = Sql.format(""" INSERT INTO tbl_Tarification ( DblAffaireId, strRapportId, strArticleId, dblQuantite, strUnite, dtmDebut, dtmFin, bytPeriode,
+                                                 dblPrixUnitaire, dblPrixTotal, dblTauxTVA, dblPrixTVA, strStatut )
+                  VALUES ({tarif.DblAffaireId}, {tarif.strRapportId:text}, {tarif.strArticleId:text}, {tarif.dblQuantite}, {tarif.strUnite:text}, {tarif.dtmDebut:date},
+                           {tarif.dtmFin:date}, {tarif.bytPeriode}, {tarif.dblPrixUnitaire}, {tarif.dblPrixTotal},
+                           {tarif.dblTauxTVA}, {tarif.dblPrixTVA}, {tarif.strStatut:text})
+                """, tarif=tarif)
+        analytique_db.execute(sql)
 
-    for affaire in Affaire.load_csv(affaires_file):
+        logger.info("> Génération d'une ligne de tarification pour l'affaire {} (rapport {}, article: {})".format(tarif.intervs[0].LienAff, strRapportId, strArticleId))
 
-        prefix = "Affaire {}: ".format(affaire.strLiaisonControle)
-        if not affaire.strMOId:
-            errors.append(prefix + "MO manquant")
-        else:
-            if not commun_db.exists(Sql.format("SELECT [lngTiersId] FROM tblTiers WHERE [lngTiersId]={}", affaire.strMOId)):
-                errors.append(prefix + "Le MO {} n'existe pas dans tblTiers".format(affaire.strMOId))
-        if not affaire.strMOeId:
-            errors.append(prefix + "MOe manquant")
-        else:
-            if not commun_db.exists(Sql.format("SELECT [lngTiersId] FROM tblTiers WHERE [lngTiersId]={}", affaire.strMOeId)):
-                errors.append(prefix + "Le MOe {} n'existe pas dans tblTiers".format(affaire.strMOeId))
-        if not affaire.strEntrepriseId:
-            errors.append(prefix + "Entreprise manquante")
+
+    # Maj champs MOS
+    # Ces champs sont utilisés dans les tables Controles pour savoir si une ligne a déjà été importée
+    for affaire in affaires:
+        dblAffaireId = analytique_db.first(Sql.format("SELECT TOP 1 DblAffaireId FROM tbl_Affaires WHERE [strLiaisonControle]={:text}", affaire.strLiaisonControle)).DblAffaireId
+
+        sql = Sql.format("""UPDATE tblCommandes SET tblCommandes.sngAffaireIdMos = {DblAffaireId}
+                WHERE [lngChantierId]={lngChantierId} AND [bytCommandeId]={bytCommandeId}
+                """, DblAffaireId=dblAffaireId, lngChantierId=affaire.lngChantierId, bytCommandeId=affaire.bytCommandeId)
+        controles_db.execute(sql)
+
+    for interv in intervs:
+        if interv.strCatégorieInterventionId == "CC":
+            tbl = "tblCompactageIntervs"
+        elif interv.strCatégorieInterventionId == "CE":
+            tbl = "tblEtancheiteIntervs"
+        elif interv.strCatégorieInterventionId == "CI":
+            tbl = "tblVideoIntervs"
         else:
-            if not commun_db.exists(Sql.format("SELECT [lngTiersId] FROM tblTiers WHERE [lngTiersId]={}", affaire.strEntrepriseId)):
-                errors.append(prefix + "L'entreprise {} n'existe pas dans tblTiers".format(affaire.strEntrepriseId))
-        if not affaire.strCommneId:
-            errors.append(prefix + "Commune manquante")
-        else:
-            if not commun_db.exists(Sql.format("SELECT [lngTiersId] FROM tblTiers WHERE [lngTiersId]={}", affaire.strCommneId)):
-                errors.append(prefix + "La commune {} n'existe pas dans tblTiers".format(affaire.strCommneId))
-        if not affaire.strTypeId:
-            errors.append(prefix + "Type d'affaire manquant")
-        if not affaire.dtmCommande:
-            errors.append(prefix + "Date de commande manquante")
-        if affaire.blnMarche == True:
-            if not affaire.intDevisId:
-                errors.append(prefix + "Numéro de devis manquant")
-
-        if analytique_db.exists(Sql.format("SELECT dblAffaireId FROM tbl_Affaires WHERE [strLiaisonControle]='{}'", affaire.strLiaisonControle)):
-            errors.append(prefix + "Une affaire portant ce code existe déjà: {}".format(affaire.strLiaisonControle))
-
-    for interv in Interv.load_csv(intervs_file):
-
-        prefix = "Intervention {}: ".format(interv.strTest)
-        if not interv.strEquipeId:
-            errors.append(prefix + "Equipe manquante")
-        if not interv.strEnginId:
-            errors.append(prefix + "Engin manquant")
-        if not interv.strRapportId:
-            errors.append(prefix + "Rapport manquant")
-        if not interv.strCatégorieInterventionId:
-            errors.append(prefix + "Catégorie de l'intervention manquante")
-        if not interv.strTypeInterventionId:
-            errors.append(prefix + "Type d'intervention manquant")
-        if not interv.dblquantite:
-            errors.append(prefix + "Quantité nulle")
-        if not interv.strunite:
-            errors.append(prefix + "Unité non renseignée")
-        if not interv.dtmIntervention:
-            errors.append(prefix + "Erreur : date d'intervention")
-        if not interv.dtmDureeIntervention or interv.dtmDureeIntervention == date_zero:
-            errors.append(prefix + "Durée d'intervention nulle")
-        if not interv.strunite:
-            errors.append(prefix + "Unité non renseignée")
-
-        if not engin_existe(interv.strEnginId):
-            errors.append(prefix + "l'engin {} n'existe pas".format(interv.strEnginId))
-
-    # *** 6- Interruption pour corection manuelle des données (si nécessaire)
-    if errors:
-        logging.error("<!> Des erreurs ont été détectées dans les données à importer. <!>")
-        for msg in errors:
-            logging.error(msg)
-        if no_prompt:
-            logger.info("# Annulation de l'import")
-            sys.exit(1)
-    else:
-        logging.info("Aucune erreur n'a été détectée dans les données.")
-        if no_prompt:
-            break
-
-    # Même si aucune erreur n'a été détectée, on demande un controle visuel.
-    prompt = ""
-    while prompt != "v":
-        logger.info(">> Veuillez contrôler les données, puis taper: \n\t'v' pour continuer\n\t'f' pour forcer le traitement à se poursuivre\n\t'q' pour annuler")
-        try:
-            from core import tsv_editor
-            tsv_editor.exec_(affaires_file.abspath())
-            tsv_editor.exec_(intervs_file.abspath())
-        except:
-            logger.error("Erreur à l'ouverture du fichier %s", affaires_file)
-            logger.error("Erreur à l'ouverture du fichier %s", intervs_file)
-
-        prompt = input("")
-        if prompt == "f":
-            break
-        if prompt == "q":
-            logger.info("# Annulation de l'import")
-            sys.exit(1)
-
-# ########## MISE A JOUR DE LA BASE DE DONNEES ANALYTIQUE ##########
-
-# On charge en mémoire les affaires et les interventions
-logger.info("# Mise à jour de la base Analytique")
-logger.info("> NB: Les modifications ne seront appliquées à la base que si toutes les opérations se déroulent normalement.")
-
-affaires = list(Affaire.load_csv(affaires_file))
-intervs = list(Interv.load_csv(intervs_file))
-
-# On insère les affaires, interventions dans Analytique, et on génère la ou les lignes de tarification associées
-
-for affaire in affaires:
-
-    # insertion dans tbl_Affaires
-
-    sql = Sql.format(""" INSERT INTO tbl_Affaires ( strMOId, strMOeId, strEntrepriseId, strCommneId, strLieux, strTypeId, dtmCommande, Ref,
-                                        blnMarche, dblMarche, intTypeContrat, strCT, strLiaisonControle, blnTarification,
-                                        blnAnalyse, strSituation, intCoefFG )
-       VALUES ({affaire.strMOId:text}, {affaire.strMOeId:text}, {affaire.strEntrepriseId:text}, {affaire.strCommneId:text}, {affaire.strLieux:text}, {affaire.strTypeId:text},
-       {affaire.dtmCommande:date}, {affaire.Ref:text}, {affaire.blnMarche}, {affaire.dblMarche}, {affaire.intTypeContrat}, {affaire.strCT:text},
-       {affaire.strLiaisonControle:text}, True, False, {affaire.strSituation:text}, {affaire.intCoefFG})
-       """, affaire=affaire)
-
-    analytique_db.execute(sql)
-
-    logger.info("> Ajout de l'affaire: {}".format(affaire.strLiaisonControle))
-
-
-# On insère les interventions dans tbl_Intervention
-for interv in intervs:
-
-    affaire = analytique_db.first(Sql.format("SELECT TOP 1 DblAffaireId FROM tbl_Affaires WHERE [strLiaisonControle]='{}'", interv.LienAff))
-    if not affaire:
-        logger.error("Intervention {} : Impossible de trouver l'affaire {}".format(interv.strTest, interv.LienAff))
-        continue
-
-    interv.dblAffaireId = affaire.DblAffaireId
-    if not interv.intPeriode:
-        interv.intPeriode = get_periode_validite(data.dtmIntervDu)  # Si la date d'interv manquait avant la validation, la periode n'a pa été mise à jour
-
-    sql = Sql.format("""INSERT INTO tbl_Intervention ( DblAffaireId, strEquipeId, strEnginId, strRapportId, strCatégorieInterventionId, strTypeInterventionId,
-              dblquantite, strunite, dtmIntervention, dtmDureeIntervention, dtmDureeInstallation, strcaracteristique1, strgrandeur1, strunite1,
-              strcaracteristique2, strgrandeur2, strunite2, strcaracteristique3, strgrandeur3, strunite3, strLiaisonControle, strarticleId,
-              intPeriode, blnTarification, blnAnalyse, blnFacturer, remarques, blnPeriode, dtmImportation, strTest )
-      VALUES ({interv.dblAffaireId}, {interv.strEquipeId:text}, {interv.strEnginId:text}, {interv.strRapportId:text}, {interv.strCatégorieInterventionId:text},
-          {interv.strTypeInterventionId:text}, {interv.dblquantite}, {interv.strunite:text}, {interv.dtmIntervention:date}, {interv.dtmDureeIntervention:date}, {date_zero:date},
-          {interv.strcaracteristique1:text}, {interv.strgrandeur1:text}, {interv.strunite1:text}, {interv.strcaracteristique2:text},
-          {interv.strgrandeur2:text}, {interv.strunite2:text}, {interv.strcaracteristique3:text}, {interv.strgrandeur3:text}, {interv.strunite3:text},
-          {interv.strLiaisonControle:text}, {interv.strArticleId:text}, {interv.intPeriode}, True, False, False, {interv.remarques:text},
-          False, {interv.dtmImportation:date}, {interv.strTest:text})
-      """, interv=interv, date_zero=date_zero)
-
-    analytique_db.execute(sql)
-
-    logger.info("> Ajout de l'intervention: {}".format(interv.strTest))
-
-
-
-# Calcul de la tarification et ajout à tbl_Tarification
-
-# > On va créer une ligne de tarification pour chaque groupe d'interventions
-# > partageant le même lngRapportid et strArticleId (cad le même engin)
-for strRapportId, strArticleId in set([(interv.strRapportId, interv.strArticleId) for interv in intervs]):
-
-    tarif = Tarification()
-    tarif.intervs = [interv for interv in intervs if interv.strRapportId == strRapportId and interv.strArticleId == strArticleId]
-
-    # recupere le prix unitaire de l'engin
-    tarif_engin = commun_db.first(Sql.format("""SELECT dblPU FROM tblTarif WHERE [strArticleId]={:text} AND [intPeriodeValiditeId]={}
-                                             """, strArticleId, get_periode_validite(intervs[0].dtmIntervention)))
-    if not tarif_engin:
-        logger.error("Aucun tarif trouvé dans tblTarif pour l'article {}, periode {}".format(strArticleId, tarif.intervs[0].intPeriode))
-    prix_unitaire = tarif_engin.dblPU
-
-    # recupere le taux de tva applicable à l'engin
-    taux_tva = commun_db.first(Sql.format("""SELECT tblTVATaux.dblTVATaux FROM tblArticle INNER JOIN tblTVATaux ON tblArticle.bytTVAArticleId = tblTVATaux.bytTVAId
-                                             WHERE tblArticle.strArticleId={:text};""", strArticleId)).dblTVATaux
-
-    tarif.DblAffaireId = tarif.intervs[0].dblAffaireId
-    tarif.strRapportId = strRapportId
-    tarif.strArticleId = strArticleId
-    tarif.dblQuantite = sum([float(interv.dblquantite) for interv in tarif.intervs])
-    tarif.strUnite = tarif.intervs[0].strunite
-    tarif.dtmDebut = min([interv.dtmIntervention for interv in tarif.intervs])
-    tarif.dtmFin = max([interv.dtmIntervention for interv in tarif.intervs])
-    tarif.bytPeriode = tarif.intervs[0].intPeriode
-    tarif.dblPrixUnitaire = prix_unitaire
-    tarif.dblPrixTotal = tarif.dblQuantite * tarif.dblPrixUnitaire
-    tarif.dblTauxTVA = taux_tva
-    tarif.dblPrixTVA = tarif.dblPrixTotal * (0.01 * tarif.dblTauxTVA)
-    tarif.strStatut = 'A facturer'
+            continue
+
+        sql = Sql.format("""UPDATE {tbl} SET {tbl}.sngIntervIdMos = {DblAffaireId}
+                WHERE [lngChantierId]={lngChantierId} AND [bytCommandeId]={bytCommandeId} AND [bytIntervId]={bytIntervId}
+                """, tbl=tbl,
+                       DblAffaireId=interv.dblAffaireId,
+                       lngChantierId=interv.lngChantierId,
+                       bytCommandeId=interv.bytCommandeId,
+                       bytIntervId=interv.bytIntervId)
+        controles_db.execute(sql)
+
+    logger.info("> Mise à jour des champs MOS")
+
+    # On commit les modifications
+    logger.info("Commit des modifications...")
+    analytique_db.commit()
+
+
+
+    # ########## MISE A JOUR DES TEMPS D'INSTALLATION ##########
+
+    # > Le temps d'installation est le temps passé par chaque agent en transport, préparation, reporting...etc.
+    # > C'est donc le temps de travail théorique, moins le temps d'intervention.
+    # > pour des raisons de performances, on ne commence le traitement qu'à partir de l'année N-1
+
+    logger.info("Mise à jour des temps d'installation...")
 
-    sql = Sql.format(""" INSERT INTO tbl_Tarification ( DblAffaireId, strRapportId, strArticleId, dblQuantite, strUnite, dtmDebut, dtmFin, bytPeriode,
-                                             dblPrixUnitaire, dblPrixTotal, dblTauxTVA, dblPrixTVA, strStatut )
-              VALUES ({tarif.DblAffaireId}, {tarif.strRapportId:text}, {tarif.strArticleId:text}, {tarif.dblQuantite}, {tarif.strUnite:text}, {tarif.dtmDebut:date},
-                       {tarif.dtmFin:date}, {tarif.bytPeriode}, {tarif.dblPrixUnitaire}, {tarif.dblPrixTotal},
-                       {tarif.dblTauxTVA}, {tarif.dblPrixTVA}, {tarif.strStatut:text})
-            """, tarif=tarif)
-    analytique_db.execute(sql)
 
-    logger.info("> Génération d'une ligne de tarification pour l'affaire {} (rapport {}, article: {})".format(tarif.intervs[0].LienAff, strRapportId, strArticleId))
 
+    # On parcourt les interventions.
+    # Lorsque le temps d'intervention total d'une même équipe un même jour est inférieur à 8h,
+    # On affecte la différence de temps à la première intervention en tant que 'temps d'installation'
 
-# Maj champs MOS
-# Ces champs sont utilisés dans les tables Controles pour savoir si une ligne a déjà été importée
-for affaire in affaires:
-    dblAffaireId = analytique_db.first(Sql.format("SELECT TOP 1 DblAffaireId FROM tbl_Affaires WHERE [strLiaisonControle]={:text}", affaire.strLiaisonControle)).DblAffaireId
+    sql = Sql.format("""SELECT First(tbl_Intervention.dblInterventionId) AS dblInterventionId, tbl_Intervention.strEquipeId,
+                tbl_Intervention.dtmIntervention, CDate(Sum(tbl_Intervention.dtmDureeIntervention)) AS SD
+            FROM tbl_Intervention
+            WHERE tbl_Intervention.strLiaisonControle Like '%/%'
+                AND Year([dtmIntervention])>={}
+                AND tbl_Intervention.dtmDureeIntervention > 0
+                AND tbl_Intervention.strEquipeId Is Not Null
+            GROUP BY tbl_Intervention.strEquipeId, tbl_Intervention.dtmIntervention
+            HAVING (((CDate(Sum(tbl_Intervention.dtmDureeIntervention)))<#1899/12/30 8:0:0#))
+            """, datetime.now().year - 1)
 
-    sql = Sql.format("""UPDATE tblCommandes SET tblCommandes.sngAffaireIdMos = {DblAffaireId}
-            WHERE [lngChantierId]={lngChantierId} AND [bytCommandeId]={bytCommandeId}
-            """, DblAffaireId=dblAffaireId, lngChantierId=affaire.lngChantierId, bytCommandeId=affaire.bytCommandeId)
-    controles_db.execute(sql)
-
-for interv in intervs:
-    if interv.strCatégorieInterventionId == "CC":
-        tbl = "tblCompactageIntervs"
-    elif interv.strCatégorieInterventionId == "CE":
-        tbl = "tblEtancheiteIntervs"
-    elif interv.strCatégorieInterventionId == "CI":
-        tbl = "tblVideoIntervs"
-    else:
-        continue
-
-    sql = Sql.format("""UPDATE {tbl} SET {tbl}.sngIntervIdMos = {DblAffaireId}
-            WHERE [lngChantierId]={lngChantierId} AND [bytCommandeId]={bytCommandeId} AND [bytIntervId]={bytIntervId}
-            """, tbl=tbl,
-                   DblAffaireId=interv.dblAffaireId,
-                   lngChantierId=interv.lngChantierId,
-                   bytCommandeId=interv.bytCommandeId,
-                   bytIntervId=interv.bytIntervId)
-    controles_db.execute(sql)
-
-logger.info("> Mise à jour des champs MOS")
-
-# On commit les modifications
-logger.info("Commit des modifications...")
-analytique_db.commit()
+    for interv in analytique_db.read_all(sql):
+        tps_install = (date_zero + timedelta(hours=8) - interv.SD)
+        sql = Sql.format("""UPDATE tbl_Intervention SET dtmDureeInstallation = #{}#
+                             WHERE dblInterventionId={}""", date_zero + tps_install, interv.dblInterventionId)
+        analytique_db.execute(sql)
 
+        logger.debug("* Mise à jour du temps d'installation de l'intervention {}".format(interv.dblInterventionId))
+    logger.info("Commit des modifications...")
+    analytique_db.commit()
 
 
-# ########## MISE A JOUR DES TEMPS D'INSTALLATION ##########
-
-# > Le temps d'installation est le temps passé par chaque agent en transport, préparation, reporting...etc.
-# > C'est donc le temps de travail théorique, moins le temps d'intervention.
-# > pour des raisons de performances, on ne commence le traitement qu'à partir de l'année N-1
 
-logger.info("Mise à jour des temps d'installation...")
+    logger.info("# Import terminé")
 
 
+if __name__ == "__main__":
 
-# On parcourt les interventions.
-# Lorsque le temps d'intervention total d'une même équipe un même jour est inférieur à 8h,
-# On affecte la différence de temps à la première intervention en tant que 'temps d'installation'
+    main()
 
-sql = Sql.format("""SELECT First(tbl_Intervention.dblInterventionId) AS dblInterventionId, tbl_Intervention.strEquipeId,
-            tbl_Intervention.dtmIntervention, CDate(Sum(tbl_Intervention.dtmDureeIntervention)) AS SD
-        FROM tbl_Intervention
-        WHERE tbl_Intervention.strLiaisonControle Like '%/%'
-            AND Year([dtmIntervention])>={}
-            AND tbl_Intervention.dtmDureeIntervention > 0
-            AND tbl_Intervention.strEquipeId Is Not Null
-        GROUP BY tbl_Intervention.strEquipeId, tbl_Intervention.dtmIntervention
-        HAVING (((CDate(Sum(tbl_Intervention.dtmDureeIntervention)))<#1899/12/30 8:0:0#))
-        """, datetime.now().year - 1)
-
-for interv in analytique_db.read_all(sql):
-    tps_install = (date_zero + timedelta(hours=8) - interv.SD)
-    sql = Sql.format("""UPDATE tbl_Intervention SET dtmDureeInstallation = #{}#
-                         WHERE dblInterventionId={}""", date_zero + tps_install, interv.dblInterventionId)
-    analytique_db.execute(sql)
-
-    logger.debug("* Mise à jour du temps d'installation de l'intervention {}".format(interv.dblInterventionId))
-logger.info("Commit des modifications...")
-analytique_db.commit()
-
-
-
-logger.info("# Import terminé")
+    logger.info("-- Fin --")

+ 236 - 232
gf2analytique.py

@@ -38,258 +38,262 @@ logconf.start("gf2analytique", logging.DEBUG)
 
 ##-----------------------------------------------
 
-# *** Initialisation
-logger.info("Initialisation...")
-
-no_prompt = ("-n" in sys.argv)
-if no_prompt:
-    logger.info("> Lancé en mode automatique (sans interruption)")
-
-# Connect to factures.mdb
-analytique_db = AnalytiqueDb(autocommit=False)
-
-# 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)
+
+def main():
+    # *** Initialisation
+    logger.info("Initialisation...")
+
+    no_prompt = ("-n" in sys.argv)
+    if no_prompt:
+        logger.info("> Lancé en mode automatique (sans interruption)")
+
+    # Connect to factures.mdb
+    analytique_db = AnalytiqueDb(autocommit=False)
+
+    # 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
-        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)
+            if self.codeBudg != "02":
+                logger.error("Code budgetaire invalide: %s", self.codeBudg)
                 errors = True
-        else:
-            # CodeAxe invalide
-            logger.error("Code axe inconnu: %s", self.codeAxe)
-            errors = True
-        return (not errors)
+            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)
+
+
+    # *** 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)
+
+    for data in ws:
+        # Génère la facture à partir des données fournies par le web-service
+        facture = Facture.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():
+            continue
 
+        logger.info("* Facture %s/%s/%s: import", facture.numExBudget, facture.numMandat, facture.numLiqMandat)
 
-# *** 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)
+        # procède à une auto-correction des données
+        facture.autocorrection()
 
-for data in ws:
-    # Génère la facture à partir des données fournies par le web-service
-    facture = Facture.from_dict(data)
+        # Ajoute les données au format CSV au fichier d'import
+        facture.dump_to_csv(importfile)
 
-    # Contrôle si la facture est déjà importée. Si c'est le cas, passe à la facture suivante.
-    if facture.est_importee():
-        continue
+    if not importfile.exists():
+        logger.info("** Aucune nouvelle facture à importer **")
+        sys.exit(0)
 
-    logger.info("* Facture %s/%s/%s: import", facture.numExBudget, facture.numMandat, facture.numLiqMandat)
+    # *** 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
 
-    # procède à une auto-correction des données
-    facture.autocorrection()
+    while errors:
+        errors = 0
+        logger.info("Contrôle des données")
 
-    # Ajoute les données au format CSV au fichier d'import
-    facture.dump_to_csv(importfile)
+        # 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():
+                errors += 1
 
-if not importfile.exists():
-    logger.info("** Aucune nouvelle facture à importer **")
-    sys.exit(0)
+        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)
 
-# *** 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
+            # 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...")
 
-while errors:
-    errors = 0
-    logger.info("Contrôle des données")
+    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)
 
-    # 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():
-            errors += 1
-
-    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)
-
-        # 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...")
-
-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)
-
-for facture in Facture.load_csv(importfile):
-
-    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
-
-    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,
-                                        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,
-                     strLiquidation=facture.numLiqMandat,
-                     intLiquidationLigne=facture.numLigneMandat,
-                     strEngagement=facture.numMandat,
-                     strEnveloppe=facture.numEnv,
-                     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"),
-                     strNomenclature0=facture.typeNomencMarche,
-                     strAxe=facture.codeAxe,
-                     strCentreCout=facture.codeCout,
-                     strObjet=AnalytiqueDb.format_date(facture.dateMandat, out_format="%d/%m/%Y"),
-                     dblMontantTVA=facture.mntTvaMandat,
-                     dblMontantTotal=facture.mntVent,
-                     strORIGINE_DONNEES='ASTRE'
-                     )
-    logger.debug("> %s", sql)
-    analytique_db.execute(sql)
-
-    facture.factureId = analytique_db.first("SELECT TOP 1 dblFactureId FROM tbl_Factures ORDER BY dblFactureId DESC").dblFactureId
-
-
-    if facture.codeAxe == "ENGIN":
-        # La ligne concerne un engin: insère les données dans la table tbl_Facture_Engin
-        logger.info("> mise à jour de tbl_Facture_Engin")
-
-        materiel = analytique_db.first("SELECT intlMaterielID FROM tbl_Materiel WHERE [txtMateriel]='{}'".format(facture.codeCout))
-        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,
-                   facture.codeCout,
-                   facture.factureId,
-                   AnalytiqueDb.nz(facture.libCout),
-                   facture.mntVent,
-                   facture.libRai
-                   )
-        logger.debug("> %s", sql)
-        analytique_db.execute(sql)
 
-    elif facture.codeAxe == "AFFAI":
-        # 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,
-                         facture.factureId,
-                         facture.libRai ,
-                         facture.mntVent,
-                         AnalytiqueDb.nz(facture.libCout),
+        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
+
+        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,
+                                            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,
+                         strLiquidation=facture.numLiqMandat,
+                         intLiquidationLigne=facture.numLigneMandat,
+                         strEngagement=facture.numMandat,
+                         strEnveloppe=facture.numEnv,
+                         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"),
+                         strNomenclature0=facture.typeNomencMarche,
+                         strAxe=facture.codeAxe,
+                         strCentreCout=facture.codeCout,
+                         strObjet=AnalytiqueDb.format_date(facture.dateMandat, out_format="%d/%m/%Y"),
+                         dblMontantTVA=facture.mntTvaMandat,
+                         dblMontantTotal=facture.mntVent,
+                         strORIGINE_DONNEES='ASTRE'
                          )
         logger.debug("> %s", sql)
         analytique_db.execute(sql)
 
+        facture.factureId = analytique_db.first("SELECT TOP 1 dblFactureId FROM tbl_Factures ORDER BY dblFactureId DESC").dblFactureId
+
+
+        if facture.codeAxe == "ENGIN":
+            # La ligne concerne un engin: insère les données dans la table tbl_Facture_Engin
+            logger.info("> mise à jour de tbl_Facture_Engin")
+
+            materiel = analytique_db.first("SELECT intlMaterielID FROM tbl_Materiel WHERE [txtMateriel]='{}'".format(facture.codeCout))
+            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,
+                       facture.codeCout,
+                       facture.factureId,
+                       AnalytiqueDb.nz(facture.libCout),
+                       facture.mntVent,
+                       facture.libRai
+                       )
+            logger.debug("> %s", sql)
+            analytique_db.execute(sql)
+
+        elif facture.codeAxe == "AFFAI":
+            # 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,
+                             facture.factureId,
+                             facture.libRai ,
+                             facture.mntVent,
+                             AnalytiqueDb.nz(facture.libCout),
+                             )
+            logger.debug("> %s", sql)
+            analytique_db.execute(sql)
+
+
+        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
+                       )
+        logger.debug("> %s", sql)
+        analytique_db.execute(sql)
 
-    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
-                   )
-    logger.debug("> %s", sql)
-    analytique_db.execute(sql)
-
-    # Commit les insertions dans la base
-    analytique_db.commit()
+        # Commit les insertions dans la base
+        analytique_db.commit()
 
-    logger.info("Facture %s : ok", facture.factureId)
+        logger.info("Facture %s : ok", facture.factureId)
 
 
-logging.shutdown()
+if __name__ == "__main__":
+    main()
+    logger.info("-- Fin --")

+ 34 - 29
gf2factures.py

@@ -27,48 +27,53 @@ logconf.start("gf2factures", logging.INFO)
 
 ##-----------------------------------------------
 
-logger.info("Initialization")
+def main():
+    logger.info("Initialization")
 
-# Connect to factures.mdb
-factures_db = FacturesDb(autocommit=True)
+    # Connect to factures.mdb
+    factures_db = FacturesDb(autocommit=True)
 
-# Connect to the astre gf webservice
-ws = GfWebservice("GetPDETitres")
+    # Connect to the astre gf webservice
+    ws = GfWebservice("GetPDETitres")
 
-analysed = 0
-updated = 0
+    analysed = 0
+    updated = 0
 
-logger.info("Mise à jour de FacturesDb")
-for titre in ws:
-    if not titre:
-        continue
+    logger.info("Mise à jour de FacturesDb")
+    for titre in ws:
+        if not titre:
+            continue
 
-    analysed += 1
+        analysed += 1
 
-    strfilter = "[lngDocId]={} AND [bytTypeDocumentId]=50".format(titre["docId"])
+        strfilter = "[lngDocId]={} AND [bytTypeDocumentId]=50".format(titre["docId"])
 
-    record = factures_db.first("SELECT memobsinterne FROM tblPieceEntete WHERE {}".format(strfilter))
-    if not record:
-        logger.warning("(!) no record found where '{}'".format(strfilter))
-        continue
+        record = factures_db.first("SELECT memobsinterne FROM tblPieceEntete WHERE {}".format(strfilter))
+        if not record:
+            logger.warning("(!) no record found where '{}'".format(strfilter))
+            continue
 
-    memobs = record.memobsinterne if record.memobsinterne else ""
+        memobs = record.memobsinterne if record.memobsinterne else ""
 
-    if "Titre n° : {}".format(titre["titreId"]) in memobs:
-        # already imported
-        continue
+        if "Titre n° : {}".format(titre["titreId"]) in memobs:
+            # already imported
+            continue
 
-    logger.info("Mise à jour de : {}".format(titre["docId"]))
+        logger.info("Mise à jour de : {}".format(titre["docId"]))
 
-    if memobs:
-        memobs += "\r\n"
+        if memobs:
+            memobs += "\r\n"
 
-    dt = datetime.strptime(titre["dateTitre"][:10], "%Y-%m-%d").strftime("%d/%m/%Y")
+        dt = datetime.strptime(titre["dateTitre"][:10], "%Y-%m-%d").strftime("%d/%m/%Y")
 
-    memobs += r"Titre n° : {} le {}".format(titre["titreId"], dt)
+        memobs += r"Titre n° : {} le {}".format(titre["titreId"], dt)
 
-    factures_db.execute("UPDATE tblPieceEntete SET [memObsInterne]='{}' WHERE {}".format(memobs, strfilter))
+        factures_db.execute("UPDATE tblPieceEntete SET [memObsInterne]='{}' WHERE {}".format(memobs, strfilter))
 
-    updated += 1
+        updated += 1
 
-logger.info("Operation ended: {} lines analysed, {} updated".format(analysed, updated))
+    logger.info("{} lines analysées, {} mises à jour".format(analysed, updated))
+
+if __name__ == "__main__":
+    main()
+    logger.info("-- Fin --")

+ 76 - 69
mails_rappel_ctrl.py

@@ -66,72 +66,79 @@ CONTENT = """<p>Bonjour,</p>
 
 ##-----------------------------------------------
 
-# #     INITIALISATION
-
-db = ControlesDb()
-
-# Sous requête: liste les chantiers compactage/étanchéite/video.
-# si le chantier est en état A1, a1_status est Vrai
-subsql = """SELECT tblCompactageBases.lngChantierId,
-                (tblCompactageBases.bytStatus=45 Or tblCompactageBases.bytStatus=46 Or tblCompactageBases.bytStatus=47) AS a1_status,
-                tblCompactageBases.dtmStatus AS since
-            FROM tblCompactageBases
-            UNION
-            SELECT tblEtancheiteBases.lngChantierId,
-                (tblEtancheiteBases.bytStatus=45 Or tblEtancheiteBases.bytStatus=46 Or tblEtancheiteBases.bytStatus=47) AS a1_status,
-                tblEtancheiteBases.dtmStatus AS since
-            FROM tblEtancheiteBases
-            UNION
-            SELECT tblVideoBases.lngChantierId,
-                (tblVideoBases.bytStatus=45 Or tblVideoBases.bytStatus=46 Or tblVideoBases.bytStatus=47) AS a1_status,
-                tblVideoBases.dtmStatus AS since
-            FROM tblVideoBases
-            """
-
-# Selectionne les chantiers pour lesquels un mail doit être envoyé
-sql = """SELECT tblChantiers.lngChantierId, tblChantiers.mailContact, tblChantiers.strInterlEntreprise,
-            tblChantiers.stopMails, statuts.a1_status, statuts.since
-        FROM tblChantiers INNER JOIN ({subsql}) as statuts ON tblChantiers.lngChantierId = statuts.lngChantierId
-        WHERE statuts.a1_status=True
-            AND tblChantiers.lngChantierId>={depart}
-            AND statuts.since<=(Date()-{seuil})
-            ;
-        """.format(subsql=subsql,
-                   seuil=SEUIL_DUREE,
-                   depart=CHANTIER_DEPART)
-
-# #    PROCESS
-qry = db.execute(sql)
-
-for row in qry:
-    chantier_id, mail_to, nom_dest, stop_mails, a1, since = row
-    if DEBUG:
-        mail_to = "olivier.massot@bas-rhin.fr"
-
-    if stop_mails:
-        logger.info("X Chantier %s: l'envoi de mail est bloqué", chantier_id)
-        continue
-
-    if not mail_to:
-        logger.warning("X Chantier %s: pas d'adresse de contact", chantier_id)
-        continue
-
-    logger.info("> Chantier %s: envoi d'un mail à %s", chantier_id, mail_to)
-
-    data = {
-            "Application": "scripts-pde",
-            "Sujet": "Rappel Chantier {chantier_id}".format(chantier_id=chantier_id),
-            "MessageURL": TEMPLATE_URL,
-            "Message": html.escape(CONTENT.format(chantier_id=chantier_id, date_status=since, contact=CONTACT), quote=False),
-            "Email": mail_to,
-            "NomExpediteur": "Script - Parc Départemental d'Erstein",
-            "NomDestinataire": nom_dest
-           }
-
-    r = requests.post("http://t-referentiel.bas-rhin.fr/Facteur/Poste/Contenu", auth=AUTH, data=data)
-
-    logger.info("Transmission du mail au serveur: %s %s", r.status_code, r.text)
-    r.raise_for_status()
-
-    if DEBUG:
-        break
+
+def main():
+
+    # #     INITIALISATION
+    db = ControlesDb()
+
+    # Sous requête: liste les chantiers compactage/étanchéite/video.
+    # si le chantier est en état A1, a1_status est Vrai
+    subsql = """SELECT tblCompactageBases.lngChantierId,
+                    (tblCompactageBases.bytStatus=45 Or tblCompactageBases.bytStatus=46 Or tblCompactageBases.bytStatus=47) AS a1_status,
+                    tblCompactageBases.dtmStatus AS since
+                FROM tblCompactageBases
+                UNION
+                SELECT tblEtancheiteBases.lngChantierId,
+                    (tblEtancheiteBases.bytStatus=45 Or tblEtancheiteBases.bytStatus=46 Or tblEtancheiteBases.bytStatus=47) AS a1_status,
+                    tblEtancheiteBases.dtmStatus AS since
+                FROM tblEtancheiteBases
+                UNION
+                SELECT tblVideoBases.lngChantierId,
+                    (tblVideoBases.bytStatus=45 Or tblVideoBases.bytStatus=46 Or tblVideoBases.bytStatus=47) AS a1_status,
+                    tblVideoBases.dtmStatus AS since
+                FROM tblVideoBases
+                """
+
+    # Selectionne les chantiers pour lesquels un mail doit être envoyé
+    sql = """SELECT tblChantiers.lngChantierId, tblChantiers.mailContact, tblChantiers.strInterlEntreprise,
+                tblChantiers.stopMails, statuts.a1_status, statuts.since
+            FROM tblChantiers INNER JOIN ({subsql}) as statuts ON tblChantiers.lngChantierId = statuts.lngChantierId
+            WHERE statuts.a1_status=True
+                AND tblChantiers.lngChantierId>={depart}
+                AND statuts.since<=(Date()-{seuil})
+                ;
+            """.format(subsql=subsql,
+                       seuil=SEUIL_DUREE,
+                       depart=CHANTIER_DEPART)
+
+    # #    PROCESS
+    qry = db.execute(sql)
+
+    for row in qry:
+        chantier_id, mail_to, nom_dest, stop_mails, _, since = row
+        if DEBUG:
+            mail_to = "olivier.massot@bas-rhin.fr"
+
+        if stop_mails:
+            logger.info("X Chantier %s: l'envoi de mail est bloqué", chantier_id)
+            continue
+
+        if not mail_to:
+            logger.warning("X Chantier %s: pas d'adresse de contact", chantier_id)
+            continue
+
+        logger.info("> Chantier %s: envoi d'un mail à %s", chantier_id, mail_to)
+
+        data = {
+                "Application": "scripts-pde",
+                "Sujet": "Rappel Chantier {chantier_id}".format(chantier_id=chantier_id),
+                "MessageURL": TEMPLATE_URL,
+                "Message": html.escape(CONTENT.format(chantier_id=chantier_id, date_status=since, contact=CONTACT), quote=False),
+                "Email": mail_to,
+                "NomExpediteur": "Script - Parc Départemental d'Erstein",
+                "NomDestinataire": nom_dest
+               }
+
+        r = requests.post("http://t-referentiel.bas-rhin.fr/Facteur/Poste/Contenu", auth=AUTH, data=data)
+
+        logger.info("Transmission du mail au serveur: %s %s", r.status_code, r.text)
+        r.raise_for_status()
+
+        if DEBUG:
+            break
+
+
+if __name__ == "__main__":
+    main()
+    logger.info("-- Fin --")

+ 98 - 107
pda2suiviactivite.py

@@ -20,8 +20,6 @@ from core.pde import PdaDb, PDA_FILES_DEST
 logger = logging.getLogger("pda2suiviactivite")
 logconf.start("pda2suiviactivite", logging.DEBUG)
 
-logger.info("Initialization")
-
 
 # # POUR TESTER, décommenter les lignes suivantes
 ##-----------------------------------------------
@@ -33,108 +31,101 @@ logger.info("Initialization")
 
 ##-----------------------------------------------
 
-
-# Connect to db_PDA.mdb
-pda_db = PdaDb(autocommit=True)
-
-
-# Explication du mapping ci dessous:
-    # (
-    #  nom de la table,
-    #  {champ: noeud),
-    #  nom des nodes contenant chaque enregistrement,
-    #  nom du fichier cible
-    # Order by
-    # )
-
-class PdaXmlFile():
-    def __init__(self):
-        self.source_table = ""
-        self.fields_mapping = {}
-        self.root_node = ""
-        self.item_node = ""
-        self.file_name = ""
-        self.order_by = []
-        self.nsmap = {'xsd': "http://www.w3.org/2001/XMLSchema",
-                      'xsi': "http://www.w3.org/2001/XMLSchema-instance"}
-
-    def create(self):
-        maker = builder.ElementMaker(nsmap=self.nsmap)
-
-        nodes = []
-
-        sql = "SELECT {mapping} FROM {tbl} ORDER BY {order}".format(
-                                                                mapping=",".join(["[{}].[{}] as {}".format(self.source_table, key, val) for key, val in self.fields_mapping.items()]),
-                                                                tbl=self.source_table,
-                                                                order=",".join(self.order_by)
-                                                                )
-        data = [record._asdict() for record in pda_db.read_all(sql)]
-
-        for record in data:
-            node = maker.__call__(self.item_node, *[maker.__call__(field, str(value)) for field, value in record.items()])
-            nodes.append(node)
-
-        root = maker.__call__(self.root_node, *nodes)
-
-        with open(PDA_FILES_DEST / self.file_name, "wb") as f:
-            f.write(etree.tostring(root, xml_declaration=True, encoding='utf-8', pretty_print=True))
-        logger.info("> Créé: {}".format(self.file_name))
-
-
-xml = PdaXmlFile()
-xml.source_table = "pdaEngin"
-xml.fields_mapping = {"strEnginId": "Id", "strEnginLibelleLong": "Nom"}
-xml.root_node = "ArrayOfAttelage"
-xml.item_node = "Attelage"
-xml.file_name = "attelages.xml"
-xml.order_by = ["strEnginLibelleLong"]
-xml.create()
-
-xml = PdaXmlFile()
-xml.source_table = "pdaorigine"
-xml.fields_mapping = {"lngorigine": "Id", "strorigine": "Nom"}
-xml.root_node = "ArrayOfDepart"
-xml.item_node = "Depart"
-xml.file_name = "depart.xml"
-xml.order_by = ["strorigine"]
-xml.create()
-
-xml = PdaXmlFile()
-xml.source_table = "pdaHeures"
-xml.fields_mapping = {"annee": "annee", "mois": "mois", "heures": "heures"}
-xml.root_node = "dataroot"
-xml.item_node = "pdaHeures"
-xml.file_name = "heures.xml"
-xml.order_by = ["annee", "mois"]
-xml.create()
-
-xml = PdaXmlFile()
-xml.source_table = "pdaEquip"
-xml.fields_mapping = {"strEquipesId": "Id", "strEquipesLibelle": "Nom"}
-xml.root_node = "ArrayOfEquipe"
-xml.item_node = "Equipe"
-xml.file_name = "equipes.xml"
-xml.order_by = ["strEquipesLibelle"]
-xml.create()
-
-xml = PdaXmlFile()
-xml.source_table = "pdalocalisation"
-xml.fields_mapping = {"lngTiersId": "Id", "strTiersMnemo": "Nom"}
-xml.root_node = "ArrayOfLocalisation"
-xml.item_node = "Localisation"
-xml.file_name = "localisation.xml"
-xml.order_by = ["strTiersMnemo"]
-xml.create()
-
-xml = PdaXmlFile()
-xml.source_table = "pdaNatInterv"
-xml.fields_mapping = {"strCategorieInterventioinId": "Id", "strCategorieInterventioinLibelle": "Nom"}
-xml.root_node = "ArrayOfNatureRealisation"
-xml.item_node = "NatureRealisation"
-xml.file_name = "naturesinterventions.xml"
-xml.order_by = ["strCategorieInterventioinLibelle"]
-xml.create()
-
-logger.info("Export termine")
-
-
+def main():
+    logger.info("Initialization")
+
+    # Connect to db_PDA.mdb
+    pda_db = PdaDb(autocommit=True)
+
+    class PdaXmlFile():
+        def __init__(self):
+            self.source_table = ""
+            self.fields_mapping = {}
+            self.root_node = ""
+            self.item_node = ""
+            self.file_name = ""
+            self.order_by = []
+            self.nsmap = {'xsd': "http://www.w3.org/2001/XMLSchema",
+                          'xsi': "http://www.w3.org/2001/XMLSchema-instance"}
+
+        def create(self):
+            maker = builder.ElementMaker(nsmap=self.nsmap)
+
+            nodes = []
+
+            sql = "SELECT {mapping} FROM {tbl} ORDER BY {order}".format(
+                                                                    mapping=",".join(["[{}].[{}] as {}".format(self.source_table, key, val) for key, val in self.fields_mapping.items()]),
+                                                                    tbl=self.source_table,
+                                                                    order=",".join(self.order_by)
+                                                                    )
+            data = [record._asdict() for record in pda_db.read_all(sql)]
+
+            for record in data:
+                node = maker.__call__(self.item_node, *[maker.__call__(field, str(value)) for field, value in record.items()])
+                nodes.append(node)
+
+            root = maker.__call__(self.root_node, *nodes)
+
+            with open(PDA_FILES_DEST / self.file_name, "wb") as f:
+                f.write(etree.tostring(root, xml_declaration=True, encoding='utf-8', pretty_print=True))
+            logger.info("> Créé: {}".format(self.file_name))
+
+
+    xml = PdaXmlFile()
+    xml.source_table = "pdaEngin"
+    xml.fields_mapping = {"strEnginId": "Id", "strEnginLibelleLong": "Nom"}
+    xml.root_node = "ArrayOfAttelage"
+    xml.item_node = "Attelage"
+    xml.file_name = "attelages.xml"
+    xml.order_by = ["strEnginLibelleLong"]
+    xml.create()
+
+    xml = PdaXmlFile()
+    xml.source_table = "pdaorigine"
+    xml.fields_mapping = {"lngorigine": "Id", "strorigine": "Nom"}
+    xml.root_node = "ArrayOfDepart"
+    xml.item_node = "Depart"
+    xml.file_name = "depart.xml"
+    xml.order_by = ["strorigine"]
+    xml.create()
+
+    xml = PdaXmlFile()
+    xml.source_table = "pdaHeures"
+    xml.fields_mapping = {"annee": "annee", "mois": "mois", "heures": "heures"}
+    xml.root_node = "dataroot"
+    xml.item_node = "pdaHeures"
+    xml.file_name = "heures.xml"
+    xml.order_by = ["annee", "mois"]
+    xml.create()
+
+    xml = PdaXmlFile()
+    xml.source_table = "pdaEquip"
+    xml.fields_mapping = {"strEquipesId": "Id", "strEquipesLibelle": "Nom"}
+    xml.root_node = "ArrayOfEquipe"
+    xml.item_node = "Equipe"
+    xml.file_name = "equipes.xml"
+    xml.order_by = ["strEquipesLibelle"]
+    xml.create()
+
+    xml = PdaXmlFile()
+    xml.source_table = "pdalocalisation"
+    xml.fields_mapping = {"lngTiersId": "Id", "strTiersMnemo": "Nom"}
+    xml.root_node = "ArrayOfLocalisation"
+    xml.item_node = "Localisation"
+    xml.file_name = "localisation.xml"
+    xml.order_by = ["strTiersMnemo"]
+    xml.create()
+
+    xml = PdaXmlFile()
+    xml.source_table = "pdaNatInterv"
+    xml.fields_mapping = {"strCategorieInterventioinId": "Id", "strCategorieInterventioinLibelle": "Nom"}
+    xml.root_node = "ArrayOfNatureRealisation"
+    xml.item_node = "NatureRealisation"
+    xml.file_name = "naturesinterventions.xml"
+    xml.order_by = ["strCategorieInterventioinLibelle"]
+    xml.create()
+
+
+if __name__ == "__main__":
+    main()
+    logger.info("-- Fin --")

+ 164 - 159
qgis_sync_compactage.py

@@ -38,167 +38,172 @@ logconf.start("qgis_sync_compactage", logging.DEBUG)
 
 ##-----------------------------------------------
 
-Sql = SqlFormatter()
+def main():
 
-# Connexion à ControlesSig (postgres)
-csig_db = CSigDb(autocommit=False)
-
-# Connexion à Controles
-controles_db = ControlesDb(autocommit=False)
-
-# Regex pour parser les noms de repertoires
-rxi = re.compile(r"^(\d{5,6})([-_]S?\d*)?[\s_]?(.*)$")  # non importés
-
-a_importer = [subdir for subdir in COMPACTAGE_DIR.dirs() if rxi.search(subdir.name)]
-
-if a_importer:
-    a_importer = select_list_dialog.exec_(a_importer, lambda x: x.name, "Sélectionnez les chantiers à importer")
-
-if not a_importer:
-    logger.info("Aucun nouveau dossier à importer")
-    sys.exit()
-
-chantiers = []
-
-# ** Read the data in the shapefiles and store it in memory **
-for chantier_dir_path in a_importer:
-    logger.info("** %s", chantier_dir_path)
-
-    # instanciate a new Chantier
-    chantier = QGisChantier()
-    chantier.dir_path = chantier_dir_path
-
-    logger.debug("> parse path")
-    parsed = rxi.search(chantier_dir_path.name)
-    chantier.number = parsed.group(1)
-    chantier.complement = parsed.group(2).replace("_", "-") if parsed.group(2) else ""
-    logger.debug("> number: %s, compl: %s", chantier.number, chantier.complement)
-
-    # query for name in ControlesDb
-    logger.debug("> Query ControlesDb for chantier's name")
-    row = controles_db.first("""SELECT tblChantiers.lngChantierId, tblCollectivites.strNom
-                                      FROM tblChantiers INNER JOIN tblCollectivites
-                                      ON tblChantiers.strCollectiviteId = tblCollectivites.strCollectiviteId
-                                      WHERE lngChantierId = {lngChantierId};""".format(lngChantierId=chantier.number))
-
-    chantier.name = "{}{} {}".format(chantier.number, chantier.complement, row.strNom)
-    logger.debug("> {}".format(chantier.name))
-
-    # importe le fichier shape dans une couche temp
-    shp_path = chantier_dir_path / "{}_p_PointCompactage.shp".format(chantier_dir_path.name)
-
-    logger.debug("Read the shapefile: %s", shp_path)
-
-    sf = shapefile.Reader(shp_path)
-
-    # should we check? :
-    if sf.shapeType != 1:
-        logger.error("Le fichier shapefile n'est pas de type POINT")
-        sys.exit(1)
-
-    sh_points = sf.shapeRecords()
-    if not sh_points:
-        logger.error("Le fichier shapefile ne contient aucune donnees")
-        sys.exit(1)
-
-    chantier.points = []
-    for sh_point in sh_points:
-
-        # create the Point instance
-        point = QGisPoint()
-        point.number = sh_point.record[0]
-        point.name = sh_point.record[1]
-        point.x, point.y = sh_point.shape.points[0]
-        logger.debug("> {}".format(point))
-        chantier.points.append(point)
-
-    del sf, sh_points
-
-    # compute the chantier's rect coordinates
-    logger.debug("Compute the chantier's rect coordinates")
-    chantier.x0 = min([point.x for point in chantier.points]) - 5
-    chantier.x1 = max([point.x for point in chantier.points]) + 5
-    chantier.y0 = min([point.y for point in chantier.points]) - 5
-    chantier.y1 = max([point.y for point in chantier.points]) + 5
-    logger.debug("> ({}, {}, {}, {})".format(chantier.x0, chantier.x1, chantier.y0, chantier.y1))
-
-    chantiers.append(chantier)
-
-# ** Insère les chantiers dans la base **
-
-for chantier in chantiers:
-    logger.debug("** Chantier {}: {} points".format(chantier.name, len(chantier.points)))
-
-    # Contrôle si un chantier de compactage portant ce nom n'existe pas déjà dans la base
-    qry = csig_db.read(u"SELECT id FROM t_chantiers WHERE nom = '{}' AND id_type_chantier=2;".format(chantier.name))
-    for row in qry:
-        logger.warning("Un chantier de compactage portant ce nom existe déjà '{}' (id {})".format(chantier.name, row.id))
-        if input("Voulez-vous le remplacer? (o/n)") == "o":
-            # delete the old chantier
-            csig_db.execute("""DELETE FROM t_points_compactage
-                              WHERE id_chantier = {};""".format(row.id))
-            csig_db.execute("""DELETE FROM t_chantiers
-                              WHERE id = {};""".format(row.id))
-            logger.info("> L'ancien chantier a été supprimé".format(chantier.name, row.id))
-        else:
-            logger.warning("Import du chantier annulé")
-            continue
-
-    # Créé le chantier
-    q = csig_db.first("""INSERT INTO t_chantiers(id_type_chantier, numero, nom, geom, archive)
-                           VALUES ({chantier_type}, {number}, '{name}', {geom}, {archive})
-                           RETURNING id;
-                        """.format(
-                            chantier_type=2,
-                            number=chantier.number,
-                            name=chantier.name,
-                            geom="ST_GeomFromText('POLYGON(({x0} {y0}, \
-                            {x0} {y1}, {x1} {y1}, {x1} {y0}, {x0} {y0}))', {srid})".format(x0=chantier.x0,
-                                                                                           x1=chantier.x1,
-                                                                                           y0=chantier.y0,
-                                                                                           y1=chantier.y1,
-                                                                                           srid=SRID),
-                            archive="FALSE"
-                            )
-                       )
-
-    # get its postgis ID
-    logger.debug("Getting newly created ID")
-    chantier.pgid = q.id
-    logger.debug("> {}".format(chantier.pgid))
-    q = None
-
-    # create the points
-    for point in chantier.points:
-        csig_db.execute("""INSERT INTO t_points_compactage(numero, nom, id_chantier, geom, archive)
-                           VALUES ({number}, '{name}', {chantier_id}, ST_GeomFromText('POINT({x} {y})', {srid}), False);
-                        """.format(
-                            number=point.number,
-                            name=point.name,
-                            chantier_id=chantier.pgid,
-                            x=point.x,
-                            y=point.y,
-                            srid=SRID
-                            )
-                       )
-
-    csig_db.commit()
-
-    # rename the directory to mark it as imported ('I_')
-    new_path = r"{}\I_{}".format(chantier.dir_path.parent, chantier.dir_path.name)
-    logger.debug("Rename {} to {}".format(chantier.dir_path, new_path))
-
-    try:
-        chantier.dir_path.rename(new_path)
-    except:
-        logger.error("Impossible de renommer le dossier")
-
-    logger.info("Le chantier %s a été importé", chantier.name)
-
-
-csig_db.close()
-controles_db.close()
+    Sql = SqlFormatter()
 
+    # Connexion à ControlesSig (postgres)
+    csig_db = CSigDb(autocommit=False)
 
+    # Connexion à Controles
+    controles_db = ControlesDb(autocommit=False)
+
+    # Regex pour parser les noms de repertoires
+    rxi = re.compile(r"^(\d{5,6})([-_]S?\d*)?[\s_]?(.*)$")  # non importés
+
+    a_importer = [subdir for subdir in COMPACTAGE_DIR.dirs() if rxi.search(subdir.name)]
+
+    if a_importer:
+        a_importer = select_list_dialog.exec_(a_importer, lambda x: x.name, "Sélectionnez les chantiers à importer")
+
+    if not a_importer:
+        logger.info("Aucun nouveau dossier à importer")
+        sys.exit()
+
+    chantiers = []
+
+    # ** Read the data in the shapefiles and store it in memory **
+    for chantier_dir_path in a_importer:
+        logger.info("** %s", chantier_dir_path)
+
+        # instanciate a new Chantier
+        chantier = QGisChantier()
+        chantier.dir_path = chantier_dir_path
+
+        logger.debug("> parse path")
+        parsed = rxi.search(chantier_dir_path.name)
+        chantier.number = parsed.group(1)
+        chantier.complement = parsed.group(2).replace("_", "-") if parsed.group(2) else ""
+        logger.debug("> number: %s, compl: %s", chantier.number, chantier.complement)
+
+        # query for name in ControlesDb
+        logger.debug("> Query ControlesDb for chantier's name")
+        row = controles_db.first("""SELECT tblChantiers.lngChantierId, tblCollectivites.strNom
+                                          FROM tblChantiers INNER JOIN tblCollectivites
+                                          ON tblChantiers.strCollectiviteId = tblCollectivites.strCollectiviteId
+                                          WHERE lngChantierId = {lngChantierId};""".format(lngChantierId=chantier.number))
+
+        chantier.name = "{}{} {}".format(chantier.number, chantier.complement, row.strNom)
+        logger.debug("> {}".format(chantier.name))
+
+        # importe le fichier shape dans une couche temp
+        shp_path = chantier_dir_path / "{}_p_PointCompactage.shp".format(chantier_dir_path.name)
+
+        logger.debug("Read the shapefile: %s", shp_path)
+
+        sf = shapefile.Reader(shp_path)
+
+        # should we check? :
+        if sf.shapeType != 1:
+            logger.error("Le fichier shapefile n'est pas de type POINT")
+            sys.exit(1)
+
+        sh_points = sf.shapeRecords()
+        if not sh_points:
+            logger.error("Le fichier shapefile ne contient aucune donnees")
+            sys.exit(1)
+
+        chantier.points = []
+        for sh_point in sh_points:
+
+            # create the Point instance
+            point = QGisPoint()
+            point.number = sh_point.record[0]
+            point.name = sh_point.record[1]
+            point.x, point.y = sh_point.shape.points[0]
+            logger.debug("> {}".format(point))
+            chantier.points.append(point)
+
+        del sf, sh_points
+
+        # compute the chantier's rect coordinates
+        logger.debug("Compute the chantier's rect coordinates")
+        chantier.x0 = min([point.x for point in chantier.points]) - 5
+        chantier.x1 = max([point.x for point in chantier.points]) + 5
+        chantier.y0 = min([point.y for point in chantier.points]) - 5
+        chantier.y1 = max([point.y for point in chantier.points]) + 5
+        logger.debug("> ({}, {}, {}, {})".format(chantier.x0, chantier.x1, chantier.y0, chantier.y1))
+
+        chantiers.append(chantier)
+
+    # ** Insère les chantiers dans la base **
+
+    for chantier in chantiers:
+        logger.debug("** Chantier {}: {} points".format(chantier.name, len(chantier.points)))
+
+        # Contrôle si un chantier de compactage portant ce nom n'existe pas déjà dans la base
+        qry = csig_db.read(u"SELECT id FROM t_chantiers WHERE nom = '{}' AND id_type_chantier=2;".format(chantier.name))
+        for row in qry:
+            logger.warning("Un chantier de compactage portant ce nom existe déjà '{}' (id {})".format(chantier.name, row.id))
+            if input("Voulez-vous le remplacer? (o/n)") == "o":
+                # delete the old chantier
+                csig_db.execute("""DELETE FROM t_points_compactage
+                                  WHERE id_chantier = {};""".format(row.id))
+                csig_db.execute("""DELETE FROM t_chantiers
+                                  WHERE id = {};""".format(row.id))
+                logger.info("> L'ancien chantier a été supprimé".format(chantier.name, row.id))
+            else:
+                logger.warning("Import du chantier annulé")
+                continue
+
+        # Créé le chantier
+        q = csig_db.first("""INSERT INTO t_chantiers(id_type_chantier, numero, nom, geom, archive)
+                               VALUES ({chantier_type}, {number}, '{name}', {geom}, {archive})
+                               RETURNING id;
+                            """.format(
+                                chantier_type=2,
+                                number=chantier.number,
+                                name=chantier.name,
+                                geom="ST_GeomFromText('POLYGON(({x0} {y0}, \
+                                {x0} {y1}, {x1} {y1}, {x1} {y0}, {x0} {y0}))', {srid})".format(x0=chantier.x0,
+                                                                                               x1=chantier.x1,
+                                                                                               y0=chantier.y0,
+                                                                                               y1=chantier.y1,
+                                                                                               srid=SRID),
+                                archive="FALSE"
+                                )
+                           )
+
+        # get its postgis ID
+        logger.debug("Getting newly created ID")
+        chantier.pgid = q.id
+        logger.debug("> {}".format(chantier.pgid))
+        q = None
+
+        # create the points
+        for point in chantier.points:
+            csig_db.execute("""INSERT INTO t_points_compactage(numero, nom, id_chantier, geom, archive)
+                               VALUES ({number}, '{name}', {chantier_id}, ST_GeomFromText('POINT({x} {y})', {srid}), False);
+                            """.format(
+                                number=point.number,
+                                name=point.name,
+                                chantier_id=chantier.pgid,
+                                x=point.x,
+                                y=point.y,
+                                srid=SRID
+                                )
+                           )
+
+        csig_db.commit()
+
+        # rename the directory to mark it as imported ('I_')
+        new_path = r"{}\I_{}".format(chantier.dir_path.parent, chantier.dir_path.name)
+        logger.debug("Rename {} to {}".format(chantier.dir_path, new_path))
+
+        try:
+            chantier.dir_path.rename(new_path)
+        except:
+            logger.error("Impossible de renommer le dossier")
+
+        logger.info("Le chantier %s a été importé", chantier.name)
+
+
+    csig_db.close()
+    controles_db.close()
+
+
+if __name__ == "__main__":
+    main()
+    logger.info("-- Fin --")
 
 

+ 71 - 67
qgis_sync_etancheite.py

@@ -39,94 +39,98 @@ IMPORT_DEPUIS = 24  # Ne cherche des données à importer que sur les X derniers
 
 ##-----------------------------------------------
 
-# Connexion à ControlesSig (postgres)
-csig_db = CSigDb(autocommit=False)
+def main():
 
-# Connexion à Controles
-controles_db = ControlesDb(autocommit=False)
+    # Connexion à ControlesSig (postgres)
+    csig_db = CSigDb(autocommit=False)
 
-# Connexion à Wincan
-wincan_db = WincanDb(autocommit=False)
+    # Connexion à Controles
+    controles_db = ControlesDb(autocommit=False)
 
+    # Connexion à Wincan
+    wincan_db = WincanDb(autocommit=False)
 
-# filter results on the last X months, depending on corresponding parameter
-date_min = datetime.today() - relativedelta(months=IMPORT_DEPUIS) if IMPORT_DEPUIS >= 0 else datetime(1899, 12, 30, 0, 0, 0)
 
+    # filter results on the last X months, depending on corresponding parameter
+    date_min = datetime.today() - relativedelta(months=IMPORT_DEPUIS) if IMPORT_DEPUIS >= 0 else datetime(1899, 12, 30, 0, 0, 0)
 
-logger.info("Loading data")
 
-# NB: utiliser une erquête access pré-enregistrée permet d'améliorer les perfs (la requete est pré-compilée)
-qessais = controles_db.read(Sql.format("""SELECT tblEtancheiteResultats.lngChantierId, tblEtancheitePartChantiers.strTrcRegard, tblTypeEssais.strObjetEssai, tblEtancheiteResultats.strResSigne, tblEtancheiteResultats.dtmEssai
-                                            FROM ((tblTypeEssais INNER JOIN tblEtancheitePartChantiers ON tblTypeEssais.bytTypeEssaiId = tblEtancheitePartChantiers.bytTypeEssai) INNER JOIN
-                                                   tblEtancheiteResultats ON (tblEtancheitePartChantiers.bytPartChantierId = tblEtancheiteResultats.bytPartChantierId) AND
-                                                   (tblEtancheitePartChantiers.lngChantierId = tblEtancheiteResultats.lngChantierId)) INNER JOIN (SELECT tblEtancheiteResultats.lngChantierId, Max(tblEtancheiteResultats.bytIntervId) AS MaxDebytIntervId, tblEtancheiteResultats.bytPartChantierId
-                                            FROM tblEtancheiteResultats
-                                            GROUP BY tblEtancheiteResultats.lngChantierId, tblEtancheiteResultats.bytPartChantierId) as filter_intervs ON
-                                                   (tblEtancheiteResultats.bytPartChantierId = filter_intervs.bytPartChantierId) AND
-                                                   (tblEtancheiteResultats.bytIntervId = filter_intervs.MaxDebytIntervId) AND
-                                                   (tblEtancheiteResultats.lngChantierId = filter_intervs.lngChantierId)
-                                            WHERE tblEtancheiteResultats.dtmEssai>{:date};
-                                            """, date_min))
+    logger.info("Loading data")
 
-nb_r, nb_t = 0, 0
+    # NB: utiliser une erquête access pré-enregistrée permet d'améliorer les perfs (la requete est pré-compilée)
+    qessais = controles_db.read(Sql.format("""SELECT tblEtancheiteResultats.lngChantierId, tblEtancheitePartChantiers.strTrcRegard, tblTypeEssais.strObjetEssai, tblEtancheiteResultats.strResSigne, tblEtancheiteResultats.dtmEssai
+                                                FROM ((tblTypeEssais INNER JOIN tblEtancheitePartChantiers ON tblTypeEssais.bytTypeEssaiId = tblEtancheitePartChantiers.bytTypeEssai) INNER JOIN
+                                                       tblEtancheiteResultats ON (tblEtancheitePartChantiers.bytPartChantierId = tblEtancheiteResultats.bytPartChantierId) AND
+                                                       (tblEtancheitePartChantiers.lngChantierId = tblEtancheiteResultats.lngChantierId)) INNER JOIN (SELECT tblEtancheiteResultats.lngChantierId, Max(tblEtancheiteResultats.bytIntervId) AS MaxDebytIntervId, tblEtancheiteResultats.bytPartChantierId
+                                                FROM tblEtancheiteResultats
+                                                GROUP BY tblEtancheiteResultats.lngChantierId, tblEtancheiteResultats.bytPartChantierId) as filter_intervs ON
+                                                       (tblEtancheiteResultats.bytPartChantierId = filter_intervs.bytPartChantierId) AND
+                                                       (tblEtancheiteResultats.bytIntervId = filter_intervs.MaxDebytIntervId) AND
+                                                       (tblEtancheiteResultats.lngChantierId = filter_intervs.lngChantierId)
+                                                WHERE tblEtancheiteResultats.dtmEssai>{:date};
+                                                """, date_min))
 
-for essai in qessais:
+    nb_r, nb_t = 0, 0
 
-    # L'essai concerne un regard
-    if essai.strObjetEssai == "R":
-        # # Cherche le regard correspondant dans qregards
+    for essai in qessais:
 
-        regard = csig_db.first(Sql.format("""SELECT t_regards.id, t_regards.res_ce
-                                    FROM t_regards INNER JOIN t_chantiers ON t_regards.id_chantier = t_chantiers.id
-                                    WHERE t_regards.nom={:text} AND t_chantiers.numero={} AND t_regards.archive=False
-                                    """, essai.strTrcRegard, essai.lngChantierId))
+        # L'essai concerne un regard
+        if essai.strObjetEssai == "R":
+            # # Cherche le regard correspondant dans qregards
 
-        if not regard:
-            continue
+            regard = csig_db.first(Sql.format("""SELECT t_regards.id, t_regards.res_ce
+                                        FROM t_regards INNER JOIN t_chantiers ON t_regards.id_chantier = t_chantiers.id
+                                        WHERE t_regards.nom={:text} AND t_chantiers.numero={} AND t_regards.archive=False
+                                        """, essai.strTrcRegard, essai.lngChantierId))
 
-        if regard.res_ce != essai.strResSigne:
-            q = csig_db.execute(Sql.format("""UPDATE t_regards
-                                              SET res_ce = {res_ce:text}
-                                              WHERE id = {pgid}
-                                              """, res_ce=essai.strResSigne, pgid=regard.id))
-            nb_r += 1
-            logger.info("Résultat mis à jour: %s, %s > %s", essai.lngChantierId, essai.strTrcRegard, essai.strResSigne)
+            if not regard:
+                continue
 
+            if regard.res_ce != essai.strResSigne:
+                q = csig_db.execute(Sql.format("""UPDATE t_regards
+                                                  SET res_ce = {res_ce:text}
+                                                  WHERE id = {pgid}
+                                                  """, res_ce=essai.strResSigne, pgid=regard.id))
+                nb_r += 1
+                logger.info("Résultat mis à jour: %s, %s > %s", essai.lngChantierId, essai.strTrcRegard, essai.strResSigne)
 
-    # L'essai concerne un tronçon
-    elif essai.strObjetEssai == "T":
 
-        # Parse le nom de tronçon
-        regex = re.search(r"^(\S*)\s?-\s?(\S*)$", essai.strTrcRegard)
-        if not regex:
-            logger.error(u"Nom de tronçon invalide: '{}' (chantier: {})".format(essai.strTrcRegard, essai.lngChantierId))
-            continue
-        r1, r2 = regex.group(1), regex.group(2)
+        # L'essai concerne un tronçon
+        elif essai.strObjetEssai == "T":
 
-        troncon = csig_db.first(Sql.format("""SELECT t_troncons.id, t_chantiers.numero, t_regards_1.nom AS r1,
-                                    t_regards.nom AS r2, t_troncons.res_ce, t_troncons.nom as nom
-                                    FROM ((t_troncons INNER JOIN t_chantiers ON t_troncons.id_chantier = t_chantiers.id)
-                                    INNER JOIN t_regards AS t_regards_1 ON t_troncons.id_regard_depart = t_regards_1.id)
-                                    INNER JOIN t_regards ON t_troncons.id_regard_fin = t_regards.id
-                                    WHERE ((t_regards_1.nom={r1:text} AND t_regards.nom={r2:text})
-                                        OR (t_regards_1.nom={r2:text} AND t_regards.nom={r1:text}))
-                                        AND t_chantiers.numero={num_chantier} AND t_troncons.archive=False
-                                    """, r1=r1, r2=r2, num_chantier=essai.lngChantierId))
+            # Parse le nom de tronçon
+            regex = re.search(r"^(\S*)\s?-\s?(\S*)$", essai.strTrcRegard)
+            if not regex:
+                logger.error(u"Nom de tronçon invalide: '{}' (chantier: {})".format(essai.strTrcRegard, essai.lngChantierId))
+                continue
+            r1, r2 = regex.group(1), regex.group(2)
 
-        if not troncon:
-            continue
+            troncon = csig_db.first(Sql.format("""SELECT t_troncons.id, t_chantiers.numero, t_regards_1.nom AS r1,
+                                        t_regards.nom AS r2, t_troncons.res_ce, t_troncons.nom as nom
+                                        FROM ((t_troncons INNER JOIN t_chantiers ON t_troncons.id_chantier = t_chantiers.id)
+                                        INNER JOIN t_regards AS t_regards_1 ON t_troncons.id_regard_depart = t_regards_1.id)
+                                        INNER JOIN t_regards ON t_troncons.id_regard_fin = t_regards.id
+                                        WHERE ((t_regards_1.nom={r1:text} AND t_regards.nom={r2:text})
+                                            OR (t_regards_1.nom={r2:text} AND t_regards.nom={r1:text}))
+                                            AND t_chantiers.numero={num_chantier} AND t_troncons.archive=False
+                                        """, r1=r1, r2=r2, num_chantier=essai.lngChantierId))
 
-        if troncon.res_ce != essai.strResSigne:
-            q = csig_db.execute(Sql.format("""UPDATE t_troncons
-                                              SET res_ce = {res_ce:text}
-                                              WHERE id = {pgid};""", res_ce=essai.strResSigne, pgid=troncon.id))
-            nb_t += 1
-            logger.info("Résultat mis à jour: %s, %s > %s", essai.lngChantierId, essai.strTrcRegard, essai.strResSigne)
+            if not troncon:
+                continue
 
-    csig_db.commit()
+            if troncon.res_ce != essai.strResSigne:
+                csig_db.execute(Sql.format("""UPDATE t_troncons
+                                                  SET res_ce = {res_ce:text}
+                                                  WHERE id = {pgid};""", res_ce=essai.strResSigne, pgid=troncon.id))
+                nb_t += 1
+                logger.info("Résultat mis à jour: %s, %s > %s", essai.lngChantierId, essai.strTrcRegard, essai.strResSigne)
 
-logger.info("- Opération terminée -")
-logger.info("%s regards et %s tronçons mis à jour", nb_r, nb_t)
+        csig_db.commit()
 
+    logger.info("%s regards et %s tronçons mis à jour", nb_r, nb_t)
 
 
+
+if __name__ == "__main__":
+    main()
+    logger.info("-- Fin --")

+ 2 - 0
qgis_sync_video.py

@@ -263,3 +263,5 @@ if __name__ == "__main__":
     qgis_sync_wincan.main()
 
     main()
+
+    logger.info("-- Fin --")

+ 29 - 22
qgis_sync_videores.py

@@ -35,32 +35,39 @@ IMPORT_DEPUIS = 24  # Ne cherche des données à importer que sur les X derniers
 
 ##-----------------------------------------------
 
-# Connexion à ControlesSig (postgres)
-csig_db = CSigDb(autocommit=False)
+def main():
 
-# Connexion à Controles
-controles_db = ControlesDb(autocommit=False)
+    # Connexion à ControlesSig (postgres)
+    csig_db = CSigDb(autocommit=False)
 
-# filter results on the last X months, depending on corresponding parameter
-date_min = datetime.today() - relativedelta(months=IMPORT_DEPUIS) if IMPORT_DEPUIS >= 0 else datetime(1899, 12, 30, 0, 0, 0)
+    # Connexion à Controles
+    controles_db = ControlesDb(autocommit=False)
 
-logger.info("Chargement des données")
-qessais = controles_db.read(Sql.format("SELECT lngChantierId, SI_AutoNumber, DG FROM csig_itv_results WHERE SI_Date > {:date}", date_min))
+    # filter results on the last X months, depending on corresponding parameter
+    date_min = datetime.today() - relativedelta(months=IMPORT_DEPUIS) if IMPORT_DEPUIS >= 0 else datetime(1899, 12, 30, 0, 0, 0)
 
-for essai in qessais:
+    logger.info("Chargement des données")
+    qessais = controles_db.read(Sql.format("SELECT lngChantierId, SI_AutoNumber, DG FROM csig_itv_results WHERE SI_Date > {:date}", date_min))
 
-    troncon = csig_db.first(Sql.format("""SELECT t_chantiers.numero, t_troncons.si_autonumber, t_troncons.res_itv
-                                          FROM (t_troncons INNER JOIN t_chantiers ON t_troncons.id_chantier = t_chantiers.id)
-                                          WHERE t_troncons.si_autonumber={}
-                                          """, essai.SI_AutoNumber))
-    if not troncon:
-        logger.warning("Le tronçon n'existe pas dans ControlesSIG (si_autonumber=%s)", essai.SI_AutoNumber)
-        continue
+    for essai in qessais:
 
-    if troncon.res_itv != essai.DG:
-        logger.info("Mise à jour du tronçon si_autonumber=%s", essai.SI_AutoNumber)
-        csig_db.execute(Sql.format("""UPDATE t_troncons
-                                      SET res_itv = {dg:text}
-                                      WHERE si_autonumber = {si_autonumber}""", dg=essai.DG, si_autonumber=essai.SI_AutoNumber))
+        troncon = csig_db.first(Sql.format("""SELECT t_chantiers.numero, t_troncons.si_autonumber, t_troncons.res_itv
+                                              FROM (t_troncons INNER JOIN t_chantiers ON t_troncons.id_chantier = t_chantiers.id)
+                                              WHERE t_troncons.si_autonumber={}
+                                              """, essai.SI_AutoNumber))
+        if not troncon:
+            logger.warning("Le tronçon n'existe pas dans ControlesSIG (si_autonumber=%s)", essai.SI_AutoNumber)
+            continue
 
-csig_db.commit()
+        if troncon.res_itv != essai.DG:
+            logger.info("Mise à jour du tronçon si_autonumber=%s", essai.SI_AutoNumber)
+            csig_db.execute(Sql.format("""UPDATE t_troncons
+                                          SET res_itv = {dg:text}
+                                          WHERE si_autonumber = {si_autonumber}""", dg=essai.DG, si_autonumber=essai.SI_AutoNumber))
+
+    csig_db.commit()
+
+
+if __name__ == "__main__":
+    main()
+    logger.info("-- Fin --")

+ 1 - 2
qgis_sync_wincan.py

@@ -156,8 +156,7 @@ def main():
         except:
             logger.error("Impossible de renommer le dossier")
 
-    logger.info("-- Fin --")
-
 if __name__ == "__main__":
     main()
 
+    logger.info("-- Fin --")

+ 270 - 261
wincan2ctrl.py

@@ -41,281 +41,290 @@ logger.warning("<<<<<<<<<<<<<<   Mode TEST   >>>>>>>>>>>>>>>>>")
 
 ##-----------------------------------------------
 
-logger.info("Initialisation...")
 
-Sql = SqlFormatter()
+def main():
 
-# Connexion à Controles
-controles_db = ControlesDb(autocommit=False)
+    logger.info("Initialisation...")
 
-# Connexion à Wincan
-wincan_db = WincanDb(autocommit=False)
+    Sql = SqlFormatter()
 
-# Connexion à CommunDb
-piraca_db = PiracaDb(autocommit=False)
+    # Connexion à Controles
+    controles_db = ControlesDb(autocommit=False)
 
+    # Connexion à Wincan
+    wincan_db = WincanDb(autocommit=False)
 
-def get_lib(lib_id, tbl_id):
-    row = piraca_db.first(Sql.format("""SELECT LIB_ID, LIB_LIB, TLB_ID
-                                            FROM LISTE_LIBELLE_WINCAN
-                                            WHERE TLB_ID='{}' AND LIB_ID='{}'""", tbl_id, lib_id))
-    if not row:
-        logger.error("Aucun libelle trouvé dans la table LISTE_LIBELLE_WINCAN pour %s, id=%s", tbl_id, lib_id)
-        raise KeyError()
-    return row.LIB_LIB
+    # Connexion à CommunDb
+    piraca_db = PiracaDb(autocommit=False)
 
 
-def get_materiau(wincan_id):
-    return controles_db.first(Sql.format("""SELECT strMateriauCourt
-                                            FROM tblMateriaux
-                                            WHERE strWincanId='{}'""", wincan_id)).strMateriauCourt
+    def get_lib(lib_id, tbl_id):
+        row = piraca_db.first(Sql.format("""SELECT LIB_ID, LIB_LIB, TLB_ID
+                                                FROM LISTE_LIBELLE_WINCAN
+                                                WHERE TLB_ID='{}' AND LIB_ID='{}'""", tbl_id, lib_id))
+        if not row:
+            logger.error("Aucun libelle trouvé dans la table LISTE_LIBELLE_WINCAN pour %s, id=%s", tbl_id, lib_id)
+            raise KeyError()
+        return row.LIB_LIB
 
-# Parse les arguments
-args = docopt(__doc__, help=False)
-chantier_id = args["-c"] if args["-c"] else 0
-inspname = args["-i"] if args["-i"] else ""
 
-# Demande à l'utilisateur de saisir les informations qui n'ont pas été passées en argument
-if not inspname:
-    # Propose une liste d'inspections possibles
-    sql = """SELECT SI_InspName
-                FROM SI_T
-                WHERE SI_Spare1 Is Null OR Len([SI_Spare1])=0
-                GROUP BY SI_InspName
-            """
-    candidats = wincan_db.read_all(sql)
+    def get_materiau(wincan_id):
+        return controles_db.first(Sql.format("""SELECT strMateriauCourt
+                                                FROM tblMateriaux
+                                                WHERE strWincanId='{}'""", wincan_id)).strMateriauCourt
 
-    print("Veuillez choisir une inspection Wincan à traiter en saisissant son numéro:")
-    for i, candidat in enumerate(candidats):
-        print("[{}] - {}".format(i, candidat.SI_InspName))
+    # Parse les arguments
+    args = docopt(__doc__, help=False)
+    chantier_id = args["-c"] if args["-c"] else 0
+    inspname = args["-i"] if args["-i"] else ""
 
-    while not inspname:
-        reponse = input("> ")
-        try:
-            inspname = candidats[int(reponse)].SI_InspName
-        except ValueError:
-            print("Valeur invalide!")
-
-if not chantier_id:
-    try:
-        default = int(inspname[:6])
-        chantier_id = input("Veuillez saisir le code chantier [{}]: ".format(default))
-        if not chantier_id:
-            chantier_id = default
-    except ValueError:
-        chantier_id = input("Veuillez saisir le code chantier: ")
-        if not chantier_id:
-            logger.error("Code chantier invalide")
-            sys.exit(1)
-
-# Calcul du numéro d'intervention
-last_interv_id = controles_db.first("SELECT max(bytIntervId) as last_interv_id FROM tblVideoIntervs WHERE lngChantierId={}".format(chantier_id)).last_interv_id
-interv_id = last_interv_id + 1 if last_interv_id else 1
-
-# Récupération du numéro de commande en cours
-commande_id = controles_db.first("SELECT bytCommandeId FROM tblVideoBases WHERE lngChantierId={}".format(chantier_id)).bytCommandeId
-
-
-# Affiche les infos et demande confirmation avant ed lancer le traitement
-logger.info("## Traitement de l'inspection Wincan")
-logger.info("Nom chantier: %s", inspname)
-logger.info("Code chantier: %s", chantier_id)
-logger.info("Numero d'intervention: %s", interv_id)
-logger.info("Numero de commande: %s", commande_id)
-if not args["-o"]:
-    if not input("Voulez-vous continuer? (o/n)") == 'o':
-        if input("Etes-vous sûr de vouloir annuler l'opération? (o/n)") == 'o':
-            logger.info("Opération annulée par l'utilisateur")
-            sys.exit(1)
-
-
-# Recuperation des données de l'intervention
-sql = """SELECT SI_T.SI_InspName, Sum(S_T.S_Sectionlength) AS Long_insp, Min(SI_T.SI_Date) AS mindate, Max(SI_T.SI_Date) AS maxdate,
-        First(SI_T.SI_Operator) AS Equipe, First(SI_T.SI_Vehicle) AS Mat, First(SI_T.SI_InspMethod) AS InspMethod,
-        First(SI_T.SI_ReasonOfInspection) AS SI_ReasonOfInspection, First(S_T.S_SectionPurpose) AS nature,
-        First(S_T.S_SectionUse) AS Fonction, First(S_T.S_SectionType) AS Type
-        FROM S_T INNER JOIN SI_T ON S_T.S_ID = SI_T.SI_Section_ID
-        WHERE (((SI_T.[SI_InspName])='{}'))
-        GROUP BY SI_T.SI_InspName
-        HAVING Sum(S_T.S_Sectionlength) Is Not Null AND First(SI_T.SI_Operator) Is Not Null AND First(SI_T.SI_Vehicle) Is Not Null AND First(SI_T.SI_InspMethod) Is Not Null
-        """.format(inspname)
-try:
-    inspection = next(wincan_db.read(sql))
-except StopIteration:
-    logger.error("Aucune inspection trouvée pour le nom '%s'", inspname)
-    raise
-
-# Insère l'inspection dans la table tblVideoIntervs de Controles
-logger.info("Création de l'intervention dans tblVideoIntervs")
-
-interv = InterventionITV()
-
-interv.lngChantierId = chantier_id
-interv.bytIntervId = interv_id
-interv.dtmIntervDu = inspection.mindate
-interv.dtmIntervAu = inspection.maxdate
-interv.strEquipeId = inspection.Equipe
-interv.intlMaterielID = inspection.Mat
-interv.bytCommandeId = commande_id
-interv.lngTroncon = inspection.Long_insp
-interv.SI_InspMethod = inspection.InspMethod
-interv.SI_ReasonOfInspection = get_lib(inspection.SI_ReasonOfInspection, "SI_REASONOfINSPECTION")
-
-sql = Sql.format("""INSERT INTO tblVideoIntervs ( lngChantierId, bytIntervId, dtmIntervDu, dtmIntervAu, strEquipeId,
-                                       intlMaterielID, bytCommandeId, lngTroncon, SI_InspMethod, SI_ReasonOfInspection )
-                    VALUES ({interv.lngChantierId}, {interv.bytIntervId}, {interv.dtmIntervDu:date}, {interv.dtmIntervAu:date}, {interv.strEquipeId:text},
-                            {interv.intlMaterielID}, {interv.bytCommandeId}, {interv.lngTroncon}, {interv.SI_InspMethod:text}, {interv.SI_ReasonOfInspection:text})
-                    """, interv=interv)
-controles_db.execute(sql)
-
-# Met a jour les champs SI_Spare1 et SI_Spare2 de la table Wincan en retour
-logger.info("Mise à jour en retour de SI_T")
-sql = """UPDATE SI_T
-         SET SI_Spare1='{}', SI_Spare2='{}'
-         WHERE SI_InspName='{}'
-         """.format(chantier_id, interv_id, inspection.SI_InspName)
-wincan_db.execute(sql)
-
-# Met a jour la table tbl_so_rate de Controles
-logger.info("Traitement des inspections")
-
-# Extrait les données des tronçons
-
-sql = """SELECT SI_T.SI_ID, S_T.S_ID, SI_T.SI_InspName, SI_T.SI_Spare1, SI_T.SI_Spare2, SI_T.SI_AutoNumber, SI_T.SI_MediaNumber1, S_T.S_SectionFlow,
-            S_T.S_StartNode, S_T.S_EndNode, S_T.S_StartNodeType, S_T.S_EndNodeType, S_T.S_StartNodeCoord_Z, S_T.S_EndNodeCoord_Z,
-            S_T.S_Sectionlength, S_T.S_SectionPurpose, S_T.S_SectionUse, S_T.S_SectionType, S_T.S_PipeMaterial, S_T.S_YearLayed,
-            S_T.S_PipeDia, S_T.S_Situation, S_T.S_Spare1, S_T.S_PipeShape, S_T.S_Pipelength, S_T.S_Spare3,
-            SI_T.SI_Spare0, SI_T.SI_Date
-        FROM S_T INNER JOIN SI_T ON S_T.S_ID = SI_T.SI_Section_ID
-        WHERE SI_T.SI_Spare1='{}' AND SI_T.SI_Spare2='{}'
-        """.format(chantier_id, interv_id)
-
-for data in wincan_db.read(sql):
-
-    logger.info("* Traitement de %s (S_ID: %s)", data.SI_InspName, data.S_ID)
-
-    inspection = InspectionTronconWincan()
-
-    inspection.s_guid = data.S_ID[2:-1] if (data.S_ID[:2] == "b'" and data.S_ID[-1:] == "'") else data.S_ID
-    inspection.si_guid = data.SI_ID[2:-1] if (data.SI_ID[:2] == "b'" and data.SI_ID[-1:] == "'") else data.SI_ID
-    inspection.nom_chantier = data.SI_InspName
-    inspection.lng_chantier_id = data.SI_Spare1
-    inspection.byt_interv_id = data.SI_Spare2
-    inspection.si_autonumber = data.SI_AutoNumber
-    inspection.classement_troncons = data.SI_MediaNumber1
-    inspection.nom_troncon = "{r1}-{r2}".format(r1=data.S_StartNode, r2=data.S_EndNode) if data.S_SectionFlow == '2' else "{r2}-{r1}".format(r1=data.S_StartNode, r2=data.S_EndNode)
-    inspection.startnode_type = get_lib(data.S_StartNodeType, "S_StartNodeType")
-    inspection.endnode_type = get_lib(data.S_StartNodeType, "S_EndNodeType")
-    inspection.sens_ecoul = ">>" if data.S_SectionFlow == '1' else ('<<' if data.S_SectionFlow == '2' else '')
-    inspection.startnode_z = data.S_StartNodeCoord_Z
-    inspection.endnode_z = data.S_EndNodeCoord_Z
-    inspection.section_length = data.S_Sectionlength
-    inspection.section_purpose = data.S_SectionPurpose
-    inspection.section_use = data.S_SectionUse
-    inspection.section_type = data.S_SectionType
-    inspection.materiau = get_materiau(data.S_PipeMaterial)
-    inspection.annee_pose = data.S_YearLayed
-    inspection.diametre = get_lib(int(data.S_PipeDia), "S_PipeDia")
-    inspection.route = get_lib(data.S_Situation, "S_Situation")
-    inspection.n_route = data.S_Spare1
-    inspection.pipe_shape = data.S_PipeShape
-    inspection.pipe_length = data.S_Pipelength
-    if data.S_Spare3:
-        inspection.arbres = get_lib(data.S_Spare3, "S_Spare3")
-    inspection.test_ecoulement = data.SI_Spare0
-    inspection.si_date = data.SI_Date
-
-    sql = """ SELECT SO_T.SO_ID, SO_T.SO_Rate, SO_T.SO_Photonumber1, SO_T.SO_Photonumber2
-                FROM SO_T
-                WHERE SO_T.SO_Inspecs_ID='{}'
-            """.format(inspection.si_guid)
-
-    # Parcours les opérations réalisées au cours de l'inspection du tronçon
-    for opdata in wincan_db.read(sql):
-        inspection.nb_ops += 1
-
-        if opdata.SO_Rate == 1:
-            inspection.rate_1 = inspection.rate_1 + 1 if inspection.rate_1 else 1
-        elif opdata.SO_Rate == 2:
-            inspection.rate_2 = inspection.rate_2 + 1 if inspection.rate_2 else 1
-        elif opdata.SO_Rate == 3:
-            inspection.rate_3 = inspection.rate_3 + 1 if inspection.rate_3 else 1
-        elif opdata.SO_Rate == 4:
-            inspection.rate_4 = inspection.rate_4 + 1 if inspection.rate_4 else 1
-        elif opdata.SO_Rate == 5:
-            inspection.rate_5 = inspection.rate_5 + 1 if inspection.rate_5 else 1
-        elif not opdata.SO_Rate:
-            pass
-        elif opdata.SO_Rate >= 6:
-            logger.error("Attention: une valeur de [SO_Rate] supérieure à 5 a été enregistrée (SO_ID: %s)", opdata.SO_ID)
-
-        if opdata.SO_Photonumber1:
-            inspection.nb_photos += 1
-        if opdata.SO_Photonumber2:
-            inspection.nb_photos += 1
-
-    if not any([inspection.rate_1, inspection.rate_2, inspection.rate_3, inspection.rate_4, inspection.rate_5]):
-        inspection.DG = 'ABS'  # Absence de défauts
-    elif not any([inspection.rate_1, inspection.rate_2, inspection.rate_3]):
-        inspection.DG = 'ACC'  # Défauts acceptables
-    else:
-        inspection.DG = 'INT'  # Défauts non-acceptables
-
-    logger.info("\t- Mise à jour de tblso_Rate_Analyse")
-    sql = Sql.format("""INSERT INTO tblso_Rate_Analyse (
-                    lngChantierId, bytIntervId, SI_InspName, SI_AutoNumber,
-                    [Classement tronons], Nom_troncon, S_StartNodeType,
-                    Sens_ecoul, S_EndNodeType, S_PipeShape, MateriauCourt,
-                    SI_Date, nb_Arbres, ANNEE_POSE,
-                    Route, NRoute, Test_ecoulement, MaxDeS_StartNodeCoord_Z, MaxDeS_EndNodeCoord_Z,
-                    MaxDeS_Sectionlength, MaxDeS_Pipelength, MaxDeDiametre, cpt_Photos, [Total de SO_ID],
-                    1, 2, 3, 4, 5, DG
-                    )
-            VALUES ({inspection.lng_chantier_id}, {inspection.byt_interv_id}, {inspection.nom_chantier:text}, {inspection.si_autonumber},
-                    {inspection.classement_troncons:text}, {inspection.nom_troncon:text}, {inspection.startnode_type:text},
-                    {inspection.sens_ecoul:text}, {inspection.endnode_type:text}, {inspection.pipe_shape:text}, {inspection.materiau:text},
-                    {inspection.si_date:date}, {inspection.arbres:text}, {inspection.annee_pose},
-                    {inspection.route:text}, {inspection.n_route:text}, {inspection.test_ecoulement:text}, {inspection.startnode_z}, {inspection.endnode_z},
-                    {inspection.section_length}, {inspection.pipe_length}, {inspection.diametre:text}, {inspection.nb_photos}, {inspection.nb_ops},
-                    {inspection.rate_1}, {inspection.rate_2}, {inspection.rate_3}, {inspection.rate_4}, {inspection.rate_5}, {inspection.DG:text}
-                    )
-            """, inspection=inspection)
-    controles_db.execute(sql)
+    # Demande à l'utilisateur de saisir les informations qui n'ont pas été passées en argument
+    if not inspname:
+        # Propose une liste d'inspections possibles
+        sql = """SELECT SI_InspName
+                    FROM SI_T
+                    WHERE SI_Spare1 Is Null OR Len([SI_Spare1])=0
+                    GROUP BY SI_InspName
+                """
+        candidats = wincan_db.read_all(sql)
 
-    # Met à jour tblvideointervs.strResGlobal avec le resultat global
-    #  le resultat global vaut '-' si un de ces trois champs n'est pas nul: tbl_so_rate.1, tbl_so_rate.2, tbl_so_rate.3
-    # >> On peut peut-être rassembler cette partie et l'insertion dans cette table au debut?
-    logger.info("\t- Mise à jour du resultat global dans tblVideoIntervs")
-    sql = """UPDATE tblVideoIntervs
-             SET strResGlobal='{}'
-             WHERE lngChantierId= {} AND bytIntervId= {}
-            """.format('-' if inspection.DG == 'INT' else '+',
-                       chantier_id,
-                       interv_id)
-    controles_db.execute(sql)
+        print("Veuillez choisir une inspection Wincan à traiter en saisissant son numéro:")
+        for i, candidat in enumerate(candidats):
+            print("[{}] - {}".format(i, candidat.SI_InspName))
 
-    # Met à jour la table tblVideoBases pour marquer le chantier comme traité
-    logger.info("\t- Mise à jour de tblVideoBases")
-    sql = """UPDATE tblVideoBases
-             SET blnWincan=True,bytNbInterv={}
-             WHERE lngChantierId={}
-             """.format(inspection.byt_interv_id,
-                        inspection.lng_chantier_id)
-    controles_db.execute(sql)
+        while not inspname:
+            reponse = input("> ")
+            try:
+                inspname = candidats[int(reponse)].SI_InspName
+            except ValueError:
+                print("Valeur invalide!")
 
-    # Met à jour les données du réseau dans tblChantiers
-    logger.info("\t- Mise à jour des données du réseau dans tblChantiers")
-    sql = """UPDATE tblChantiers
-             SET  bytFoncReseauId ={} , bytNatureReseauId={}, bytTypeReseauId={}
-             WHERE lngChantierId={}
-             """.format(inspection.section_use,
-                        inspection.section_purpose,
-                        inspection.section_type,
-                        inspection.lng_chantier_id)
+    if not chantier_id:
+        try:
+            default = int(inspname[:6])
+            chantier_id = input("Veuillez saisir le code chantier [{}]: ".format(default))
+            if not chantier_id:
+                chantier_id = default
+        except ValueError:
+            chantier_id = input("Veuillez saisir le code chantier: ")
+            if not chantier_id:
+                logger.error("Code chantier invalide")
+                sys.exit(1)
+
+    # Calcul du numéro d'intervention
+    last_interv_id = controles_db.first("SELECT max(bytIntervId) as last_interv_id FROM tblVideoIntervs WHERE lngChantierId={}".format(chantier_id)).last_interv_id
+    interv_id = last_interv_id + 1 if last_interv_id else 1
+
+    # Récupération du numéro de commande en cours
+    commande_id = controles_db.first("SELECT bytCommandeId FROM tblVideoBases WHERE lngChantierId={}".format(chantier_id)).bytCommandeId
+
+
+    # Affiche les infos et demande confirmation avant ed lancer le traitement
+    logger.info("## Traitement de l'inspection Wincan")
+    logger.info("Nom chantier: %s", inspname)
+    logger.info("Code chantier: %s", chantier_id)
+    logger.info("Numero d'intervention: %s", interv_id)
+    logger.info("Numero de commande: %s", commande_id)
+    if not args["-o"]:
+        if not input("Voulez-vous continuer? (o/n)") == 'o':
+            if input("Etes-vous sûr de vouloir annuler l'opération? (o/n)") == 'o':
+                logger.info("Opération annulée par l'utilisateur")
+                sys.exit(1)
+
+
+    # Recuperation des données de l'intervention
+    sql = """SELECT SI_T.SI_InspName, Sum(S_T.S_Sectionlength) AS Long_insp, Min(SI_T.SI_Date) AS mindate, Max(SI_T.SI_Date) AS maxdate,
+            First(SI_T.SI_Operator) AS Equipe, First(SI_T.SI_Vehicle) AS Mat, First(SI_T.SI_InspMethod) AS InspMethod,
+            First(SI_T.SI_ReasonOfInspection) AS SI_ReasonOfInspection, First(S_T.S_SectionPurpose) AS nature,
+            First(S_T.S_SectionUse) AS Fonction, First(S_T.S_SectionType) AS Type
+            FROM S_T INNER JOIN SI_T ON S_T.S_ID = SI_T.SI_Section_ID
+            WHERE (((SI_T.[SI_InspName])='{}'))
+            GROUP BY SI_T.SI_InspName
+            HAVING Sum(S_T.S_Sectionlength) Is Not Null AND First(SI_T.SI_Operator) Is Not Null AND First(SI_T.SI_Vehicle) Is Not Null AND First(SI_T.SI_InspMethod) Is Not Null
+            """.format(inspname)
+    try:
+        inspection = next(wincan_db.read(sql))
+    except StopIteration:
+        logger.error("Aucune inspection trouvée pour le nom '%s'", inspname)
+        raise
+
+    # Insère l'inspection dans la table tblVideoIntervs de Controles
+    logger.info("Création de l'intervention dans tblVideoIntervs")
+
+    interv = InterventionITV()
+
+    interv.lngChantierId = chantier_id
+    interv.bytIntervId = interv_id
+    interv.dtmIntervDu = inspection.mindate
+    interv.dtmIntervAu = inspection.maxdate
+    interv.strEquipeId = inspection.Equipe
+    interv.intlMaterielID = inspection.Mat
+    interv.bytCommandeId = commande_id
+    interv.lngTroncon = inspection.Long_insp
+    interv.SI_InspMethod = inspection.InspMethod
+    interv.SI_ReasonOfInspection = get_lib(inspection.SI_ReasonOfInspection, "SI_REASONOfINSPECTION")
+
+    sql = Sql.format("""INSERT INTO tblVideoIntervs ( lngChantierId, bytIntervId, dtmIntervDu, dtmIntervAu, strEquipeId,
+                                           intlMaterielID, bytCommandeId, lngTroncon, SI_InspMethod, SI_ReasonOfInspection )
+                        VALUES ({interv.lngChantierId}, {interv.bytIntervId}, {interv.dtmIntervDu:date}, {interv.dtmIntervAu:date}, {interv.strEquipeId:text},
+                                {interv.intlMaterielID}, {interv.bytCommandeId}, {interv.lngTroncon}, {interv.SI_InspMethod:text}, {interv.SI_ReasonOfInspection:text})
+                        """, interv=interv)
     controles_db.execute(sql)
 
-
-
-logger.info("Commit des modifications")
-controles_db.commit()
-wincan_db.commit()
+    # Met a jour les champs SI_Spare1 et SI_Spare2 de la table Wincan en retour
+    logger.info("Mise à jour en retour de SI_T")
+    sql = """UPDATE SI_T
+             SET SI_Spare1='{}', SI_Spare2='{}'
+             WHERE SI_InspName='{}'
+             """.format(chantier_id, interv_id, inspection.SI_InspName)
+    wincan_db.execute(sql)
+
+    # Met a jour la table tbl_so_rate de Controles
+    logger.info("Traitement des inspections")
+
+    # Extrait les données des tronçons
+
+    sql = """SELECT SI_T.SI_ID, S_T.S_ID, SI_T.SI_InspName, SI_T.SI_Spare1, SI_T.SI_Spare2, SI_T.SI_AutoNumber, SI_T.SI_MediaNumber1, S_T.S_SectionFlow,
+                S_T.S_StartNode, S_T.S_EndNode, S_T.S_StartNodeType, S_T.S_EndNodeType, S_T.S_StartNodeCoord_Z, S_T.S_EndNodeCoord_Z,
+                S_T.S_Sectionlength, S_T.S_SectionPurpose, S_T.S_SectionUse, S_T.S_SectionType, S_T.S_PipeMaterial, S_T.S_YearLayed,
+                S_T.S_PipeDia, S_T.S_Situation, S_T.S_Spare1, S_T.S_PipeShape, S_T.S_Pipelength, S_T.S_Spare3,
+                SI_T.SI_Spare0, SI_T.SI_Date
+            FROM S_T INNER JOIN SI_T ON S_T.S_ID = SI_T.SI_Section_ID
+            WHERE SI_T.SI_Spare1='{}' AND SI_T.SI_Spare2='{}'
+            """.format(chantier_id, interv_id)
+
+    for data in wincan_db.read(sql):
+
+        logger.info("* Traitement de %s (S_ID: %s)", data.SI_InspName, data.S_ID)
+
+        inspection = InspectionTronconWincan()
+
+        inspection.s_guid = data.S_ID[2:-1] if (data.S_ID[:2] == "b'" and data.S_ID[-1:] == "'") else data.S_ID
+        inspection.si_guid = data.SI_ID[2:-1] if (data.SI_ID[:2] == "b'" and data.SI_ID[-1:] == "'") else data.SI_ID
+        inspection.nom_chantier = data.SI_InspName
+        inspection.lng_chantier_id = data.SI_Spare1
+        inspection.byt_interv_id = data.SI_Spare2
+        inspection.si_autonumber = data.SI_AutoNumber
+        inspection.classement_troncons = data.SI_MediaNumber1
+        inspection.nom_troncon = "{r1}-{r2}".format(r1=data.S_StartNode, r2=data.S_EndNode) if data.S_SectionFlow == '2' else "{r2}-{r1}".format(r1=data.S_StartNode, r2=data.S_EndNode)
+        inspection.startnode_type = get_lib(data.S_StartNodeType, "S_StartNodeType")
+        inspection.endnode_type = get_lib(data.S_StartNodeType, "S_EndNodeType")
+        inspection.sens_ecoul = ">>" if data.S_SectionFlow == '1' else ('<<' if data.S_SectionFlow == '2' else '')
+        inspection.startnode_z = data.S_StartNodeCoord_Z
+        inspection.endnode_z = data.S_EndNodeCoord_Z
+        inspection.section_length = data.S_Sectionlength
+        inspection.section_purpose = data.S_SectionPurpose
+        inspection.section_use = data.S_SectionUse
+        inspection.section_type = data.S_SectionType
+        inspection.materiau = get_materiau(data.S_PipeMaterial)
+        inspection.annee_pose = data.S_YearLayed
+        inspection.diametre = get_lib(int(data.S_PipeDia), "S_PipeDia")
+        inspection.route = get_lib(data.S_Situation, "S_Situation")
+        inspection.n_route = data.S_Spare1
+        inspection.pipe_shape = data.S_PipeShape
+        inspection.pipe_length = data.S_Pipelength
+        if data.S_Spare3:
+            inspection.arbres = get_lib(data.S_Spare3, "S_Spare3")
+        inspection.test_ecoulement = data.SI_Spare0
+        inspection.si_date = data.SI_Date
+
+        sql = """ SELECT SO_T.SO_ID, SO_T.SO_Rate, SO_T.SO_Photonumber1, SO_T.SO_Photonumber2
+                    FROM SO_T
+                    WHERE SO_T.SO_Inspecs_ID='{}'
+                """.format(inspection.si_guid)
+
+        # Parcours les opérations réalisées au cours de l'inspection du tronçon
+        for opdata in wincan_db.read(sql):
+            inspection.nb_ops += 1
+
+            if opdata.SO_Rate == 1:
+                inspection.rate_1 = inspection.rate_1 + 1 if inspection.rate_1 else 1
+            elif opdata.SO_Rate == 2:
+                inspection.rate_2 = inspection.rate_2 + 1 if inspection.rate_2 else 1
+            elif opdata.SO_Rate == 3:
+                inspection.rate_3 = inspection.rate_3 + 1 if inspection.rate_3 else 1
+            elif opdata.SO_Rate == 4:
+                inspection.rate_4 = inspection.rate_4 + 1 if inspection.rate_4 else 1
+            elif opdata.SO_Rate == 5:
+                inspection.rate_5 = inspection.rate_5 + 1 if inspection.rate_5 else 1
+            elif not opdata.SO_Rate:
+                pass
+            elif opdata.SO_Rate >= 6:
+                logger.error("Attention: une valeur de [SO_Rate] supérieure à 5 a été enregistrée (SO_ID: %s)", opdata.SO_ID)
+
+            if opdata.SO_Photonumber1:
+                inspection.nb_photos += 1
+            if opdata.SO_Photonumber2:
+                inspection.nb_photos += 1
+
+        if not any([inspection.rate_1, inspection.rate_2, inspection.rate_3, inspection.rate_4, inspection.rate_5]):
+            inspection.DG = 'ABS'  # Absence de défauts
+        elif not any([inspection.rate_1, inspection.rate_2, inspection.rate_3]):
+            inspection.DG = 'ACC'  # Défauts acceptables
+        else:
+            inspection.DG = 'INT'  # Défauts non-acceptables
+
+        logger.info("\t- Mise à jour de tblso_Rate_Analyse")
+        sql = Sql.format("""INSERT INTO tblso_Rate_Analyse (
+                        lngChantierId, bytIntervId, SI_InspName, SI_AutoNumber,
+                        [Classement tronons], Nom_troncon, S_StartNodeType,
+                        Sens_ecoul, S_EndNodeType, S_PipeShape, MateriauCourt,
+                        SI_Date, nb_Arbres, ANNEE_POSE,
+                        Route, NRoute, Test_ecoulement, MaxDeS_StartNodeCoord_Z, MaxDeS_EndNodeCoord_Z,
+                        MaxDeS_Sectionlength, MaxDeS_Pipelength, MaxDeDiametre, cpt_Photos, [Total de SO_ID],
+                        1, 2, 3, 4, 5, DG
+                        )
+                VALUES ({inspection.lng_chantier_id}, {inspection.byt_interv_id}, {inspection.nom_chantier:text}, {inspection.si_autonumber},
+                        {inspection.classement_troncons:text}, {inspection.nom_troncon:text}, {inspection.startnode_type:text},
+                        {inspection.sens_ecoul:text}, {inspection.endnode_type:text}, {inspection.pipe_shape:text}, {inspection.materiau:text},
+                        {inspection.si_date:date}, {inspection.arbres:text}, {inspection.annee_pose},
+                        {inspection.route:text}, {inspection.n_route:text}, {inspection.test_ecoulement:text}, {inspection.startnode_z}, {inspection.endnode_z},
+                        {inspection.section_length}, {inspection.pipe_length}, {inspection.diametre:text}, {inspection.nb_photos}, {inspection.nb_ops},
+                        {inspection.rate_1}, {inspection.rate_2}, {inspection.rate_3}, {inspection.rate_4}, {inspection.rate_5}, {inspection.DG:text}
+                        )
+                """, inspection=inspection)
+        controles_db.execute(sql)
+
+        # Met à jour tblvideointervs.strResGlobal avec le resultat global
+        #  le resultat global vaut '-' si un de ces trois champs n'est pas nul: tbl_so_rate.1, tbl_so_rate.2, tbl_so_rate.3
+        # >> On peut peut-être rassembler cette partie et l'insertion dans cette table au debut?
+        logger.info("\t- Mise à jour du resultat global dans tblVideoIntervs")
+        sql = """UPDATE tblVideoIntervs
+                 SET strResGlobal='{}'
+                 WHERE lngChantierId= {} AND bytIntervId= {}
+                """.format('-' if inspection.DG == 'INT' else '+',
+                           chantier_id,
+                           interv_id)
+        controles_db.execute(sql)
+
+        # Met à jour la table tblVideoBases pour marquer le chantier comme traité
+        logger.info("\t- Mise à jour de tblVideoBases")
+        sql = """UPDATE tblVideoBases
+                 SET blnWincan=True,bytNbInterv={}
+                 WHERE lngChantierId={}
+                 """.format(inspection.byt_interv_id,
+                            inspection.lng_chantier_id)
+        controles_db.execute(sql)
+
+        # Met à jour les données du réseau dans tblChantiers
+        logger.info("\t- Mise à jour des données du réseau dans tblChantiers")
+        sql = """UPDATE tblChantiers
+                 SET  bytFoncReseauId ={} , bytNatureReseauId={}, bytTypeReseauId={}
+                 WHERE lngChantierId={}
+                 """.format(inspection.section_use,
+                            inspection.section_purpose,
+                            inspection.section_type,
+                            inspection.lng_chantier_id)
+        controles_db.execute(sql)
+
+
+
+    logger.info("Commit des modifications")
+    controles_db.commit()
+    wincan_db.commit()
+
+
+
+if __name__ == "__main__":
+    main()
+    logger.info("-- Fin --")