analytique2facture.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. '''
  2. Génère des factures dans la base Factures à partir des données de Tarification de Analytique
  3. @author: olivier.massot, févr. 2018
  4. '''
  5. from datetime import datetime
  6. import logging
  7. import sys
  8. from path import Path
  9. from core import logconf
  10. from core.pde import FacturesDb, AnalytiqueDb, CommunDb, EnTete, Ligne
  11. from core.sqlformatter import SqlFormatter
  12. logger = logging.getLogger("analytique2facture")
  13. logconf.start("analytique2facture", logging.DEBUG)
  14. # # POUR TESTER, décommenter les lignes suivantes
  15. ##-----------------------------------------------
  16. logger.warning("<<<<<<<<<<<<<< Mode TEST >>>>>>>>>>>>>>>>>")
  17. AnalytiqueDb._path = Path(r"\\h2o\local\4-transversal\BDD\mdb_test\Db_analytique.mdb")
  18. FacturesDb._path = Path(r"\\h2o\local\4-transversal\BDD\mdb_test\Facture_data.mdb")
  19. CommunDb._path = Path(r"\\h2o\local\4-transversal\BDD\mdb_test\Commun_Data.mdb")
  20. ##-----------------------------------------------
  21. # Connexion à Analytique
  22. analytique_db = AnalytiqueDb(autocommit=False)
  23. # Connexion à Controles
  24. facture_db = FacturesDb(autocommit=False)
  25. # Connexion à CommunDb
  26. commun_db = CommunDb(autocommit=False)
  27. Sql = SqlFormatter()
  28. current = "{:%m/%Y}".format(datetime.now())
  29. # mois_facturation = input("Veuillez renseigner le mois de facturation [defaut: {}] ('q' pour quitter): ".format(current)) # Format: voir avec jacky
  30. # if mois_facturation == 'q':
  31. # sys.exit(1)
  32. mois_facturation = "01/2018"
  33. sql = """SELECT * FROM tbl_Tarification
  34. WHERE strStatut='A facturer'
  35. ORDER BY DblAffaireId, DblTarifId DESC;
  36. """
  37. # Liste les interventions à facturer par affaire
  38. a_facturer = {}
  39. for interv in analytique_db.read(sql):
  40. if not interv.DblAffaireId in a_facturer.keys():
  41. a_facturer[interv.DblAffaireId] = []
  42. a_facturer[interv.DblAffaireId].append(interv)
  43. if not a_facturer:
  44. logger.info("Aucune facture à créer - Opération annulée")
  45. sys.exit(1)
  46. # Pour chaque facture, on va ajouter une ligne dans tblPieceEntete, et une ligne par intervention dans tblPieceLigne
  47. # > NB: On ne touche pas aux interventions de cette affaire qui ont déja été facturées
  48. for affaireId, interventions in a_facturer.items():
  49. piece_id = facture_db.first("SELECT Max(lngpieceId) As Id FROM tblPieceEntete").Id + 1
  50. if str(piece_id)[:2] != mois_facturation[-2:]:
  51. # On démarre une nouvelle année
  52. piece_id = 10000 * int(mois_facturation[:2]) + 1
  53. affaire = analytique_db.first("SELECT * FROM tbl_Affaires WHERE dblAffaireId={}".format(affaireId))
  54. if not affaire:
  55. logger.error("Affaire {} - L'affaire n'existe pas dans tbl_Affaires".format(affaireId))
  56. continue
  57. if not affaire.strMOId:
  58. logger.error("Affaire {} - Destinataire manquant dans tbl_Affaires".format(affaireId))
  59. continue
  60. destinataire = commun_db.first("SELECT * FROM tblTiers WHERE lngTiersId={}".format(affaire.strMOId))
  61. if not destinataire:
  62. logger.warning("Affaire {} - Destinataire introuvable dans tblTiers ('{}'), remplacé par tiers n°190".format(affaireId, affaire.strMOId))
  63. destinataire = commun_db.first("SELECT * FROM tblTiers WHERE lngTiersId=190")
  64. entete = EnTete()
  65. entete.lngPieceId = piece_id
  66. # on recupere les champs de tblTiers depuis le champ lngASTRE (champ n°3) jusqu'au champ strCodeProduit (champ n°26)
  67. # ATTENTION: le champ 'strPaysIdIso3166-A2' n'etant pas un nom de propriété valide, il est renomme en strPaysIdIso3166 dans le modèle.
  68. entete.lngTiersId = destinataire.lngTiersId
  69. entete.lngASTRE = destinataire.lngASTRE
  70. entete.strCodeProduit = destinataire.strCodeProduit
  71. entete.bytTitreId = destinataire.bytTitreId
  72. entete.strCodeAdresse = destinataire.strCodeAdresse
  73. entete.strAdresse1 = destinataire.strAdresse1
  74. entete.strAdresse2 = destinataire.strAdresse2
  75. entete.strAdresse3 = destinataire.strAdresse3
  76. entete.strAdresse4 = destinataire.strAdresse4
  77. entete.strAdresse5 = destinataire.strAdresse5
  78. entete.strPaysIdIso3166 = destinataire.field_11
  79. entete.bytTVATiersId = destinataire.bytTVATiersId
  80. entete.strTelephone = destinataire.strTelephone
  81. entete.strTelecopie = destinataire.strTelecopie
  82. entete.strPortable = destinataire.strPortable
  83. entete.strEMail = destinataire.strEMail
  84. entete.strWeb = destinataire.strWeb
  85. entete.strLangueIdIso639 = destinataire.strLangueIdIso639
  86. entete.strCompteComptable = destinataire.strCompteComptable
  87. entete.strDeviseIdIso4217 = destinataire.strDeviseIdIso4217
  88. entete.bytReglementId = destinataire.bytReglementId
  89. entete.bytClasseTarifId = destinataire.bytClasseTarifId
  90. entete.bytNbExFacture = destinataire.bytNbExFacture
  91. entete.bytClasseRemiseTiersId = destinataire.bytClasseRemiseTiersId
  92. entete.bytNbExFacture = destinataire.bytNbExFacture
  93. entete.strStatTiers = destinataire.strStatTiers
  94. # Valeurs fixes
  95. entete.lngDocId = 0
  96. entete.strLangueIdIso639 = "fr"
  97. entete.strDeviseIdIso4217 = "EUR"
  98. entete.bytReglementId = 1
  99. entete.bytClasseTarifId = 1
  100. entete.bytClasseRemiseTiersId = 1
  101. entete.bytNbExFacture = 1
  102. entete.strStatTiers = ""
  103. entete.bytSituationIdPrincipale = 2
  104. entete.bytSituationIdSecondaire = 41
  105. entete.bytTypeDocumentId = 50
  106. entete.strStatDocEntete = "PI00"
  107. entete.dblCoursDevise = 1
  108. entete.dtmCoursDevise = datetime(2002, 1, 1)
  109. entete.bytTypeTarif = 1
  110. entete.strReglement = "POUR ACQUITTER CET ETAT DE REDEVANCE,\r\n ATTENDEZ IMPERATIVEMENT DE RECEVOIR LE TITRE DE PERCEPTION EMANANT DE LA PAIERIE DEPARTEMENTALE"
  111. # Commentaires
  112. entete.memObsEntete = "[Mois : {mois_facturation}]\r\n" \
  113. "[Lieu de travail : ]{affaire.strLieux}\r\n" \
  114. "[V/Cde : ] {affaire.Ref} du {affaire.dtmCommande:%d/%m/%Y}\r\n" \
  115. "".format(mois_facturation=mois_facturation,
  116. affaire=affaire)
  117. entete.memObsInterne = "N° Affaire analytique : {}".format(affaire.strLiaisonControle)
  118. entete.strStatDocEntete = "PC00" if ("/" in affaire.strLiaisonControle or affaire.strLiaisonControle == "RDBOEC") else "PT00"
  119. entete.strUserIdCreation = "script"
  120. entete.strUserIdLastMod = "script"
  121. i = 0
  122. lignes = []
  123. for interv in interventions:
  124. i += 1
  125. ligne = Ligne()
  126. ligne.lngPieceId = piece_id
  127. ligne.intLigneId = i
  128. ligne.strArticleId = interv.strArticleId
  129. article = commun_db.first("SELECT * FROM tblArticle WHERE strArticleId='{}'".format(interv.strArticleId))
  130. ligne.strArticle = article.strArticle if article else ""
  131. ligne.bytTVAArticleId = article.bytTVAArticleId if article else 0
  132. tva = commun_db.first("SELECT bytTVAId FROM tblTVA WHERE bytTVATiersId={} AND bytTVAArticleId={}".format(destinataire.bytTVATiersId, ligne.bytTVAArticleId))
  133. ligne.bytTVAId = tva.bytTVAId
  134. taux = commun_db.first("SELECT dblTVATaux FROM tblTVATaux WHERE bytTVAId={}".format(ligne.bytTVAId))
  135. ligne.dblTVATaux = taux.dblTVATaux
  136. ligne.dblQte = interv.dblQuantite
  137. unite = commun_db.first("SELECT * FROM tblUnite WHERE strUniteCourt='{}'".format(interv.strUnite))
  138. ligne.bytUniteIdQuantite = unite.bytUniteId
  139. ligne.bytUniteIdPrix = unite.bytUniteId
  140. ligne.dblPUhtBrutDev = interv.dblPrixUnitaire
  141. ligne.dblPUhtNetDev = interv.dblPrixUnitaire
  142. ligne.dblPThtNetDev = interv.dblPrixTotal
  143. ligne.blnPrestation = True
  144. ligne.memObs = "Intervention le {}{}".format(interv.dtmFin, "\nRapport no : {}".format(interv.strRapportId) if interv.strRapportId else "")
  145. ligne.blnLigneGeneree = True
  146. ligne.bytGenerateurId = 1
  147. ligne.dblPThtNet = interv.dblPrixTotal
  148. ligne.bytClasseRemiseArticleId = 1
  149. ligne.dblPUSaisie = interv.dblPrixUnitaire
  150. ligne.strPUAff = "{:03.02f} EUR/{}".format(interv.dblPrixUnitaire, interv.strUnite)
  151. ligne.strQteAff = "{:03.02f} {}".format(interv.dblQuantite, interv.strUnite)
  152. lignes.append(ligne)
  153. # Toutes les lignes ont été ajoutées: on créé maintenant les lignes de totaux
  154. montant_ht = float(sum([ligne.dblPThtNetDev for ligne in lignes]))
  155. taux_tva = float(lignes[0].dblTVATaux)
  156. montant_tva = (0.01 * taux_tva) * montant_ht
  157. montant_ttc = montant_ht + montant_tva
  158. # Sous-total HT (ligne 32000)
  159. ligne_total_1 = Ligne()
  160. ligne_total_1.lngPieceId = piece_id
  161. ligne_total_1.intLigneId = 32000
  162. ligne_total_1.strArticleId = ""
  163. ligne_total_1.strArticle = "Total H.T."
  164. ligne_total_1.dblQte = 0
  165. ligne_total_1.dblPThtNetDev = montant_ht
  166. ligne_total_1.dblPTttcNetDev = montant_ttc
  167. ligne_total_1.bytLigneSousTotal = 10
  168. ligne_total_1.blnLigneVisible = False
  169. ligne_total_1.dblPThtNet = montant_ht
  170. ligne_total_1.dblPTttcNet = round(montant_ttc, 2)
  171. ligne_total_1.dblPUSaisie = round(montant_ht, 2)
  172. ligne_total_1.strPUAff = "{:03.02f} EUR".format(montant_ht)
  173. lignes.append(ligne_total_1)
  174. # Sous-total TVA (ligne 32001)
  175. ligne_total_2 = Ligne()
  176. ligne_total_2.lngPieceId = piece_id
  177. ligne_total_2.intLigneId = 32001
  178. ligne_total_2.strArticleId = ""
  179. ligne_total_2.strArticle = """TVA 1 : {taux_tva}% H.T. : {montant_ht:03.02f} T.V.A. :
  180. {montant_tva:03.02f} T.T.C. : {montant_ttc:03.02f}:""".format(taux_tva=taux_tva,
  181. montant_ht=montant_ht,
  182. montant_tva=montant_tva,
  183. montant_ttc=montant_ttc)
  184. ligne_total_2.dblQte = 0
  185. ligne_total_2.bytTVAId = lignes[0].bytTVAId
  186. ligne_total_2.dblTVATaux = taux_tva
  187. ligne_total_2.dblPThtNetDev = montant_ht
  188. ligne_total_2.dblPTttcNetDev = montant_ttc
  189. ligne_total_2.bytLigneSousTotal = 11
  190. ligne_total_2.blnLigneVisible = False
  191. ligne_total_2.dblPThtNet = montant_ht
  192. ligne_total_2.dblPTttcNet = round(ligne_total_1.dblPTttcNetDev, 2)
  193. ligne_total_2.dblPUSaisie = round(montant_tva, 2)
  194. ligne_total_2.strPUAff = "{:03.02f} EUR".format(montant_tva)
  195. lignes.append(ligne_total_2)
  196. # Sous-total TTC (ligne 32500)
  197. ligne_total_3 = Ligne()
  198. ligne_total_3.lngPieceId = piece_id
  199. ligne_total_3.intLigneId = 32500
  200. ligne_total_3.strArticleId = ""
  201. ligne_total_3.strArticle = "Total T.T.C."
  202. ligne_total_3.dblQte = 0
  203. ligne_total_3.dblPThtNetDev = montant_ht
  204. ligne_total_3.dblPTttcNetDev = montant_ttc
  205. ligne_total_3.bytLigneSousTotal = 12
  206. ligne_total_3.blnLigneVisible = False
  207. ligne_total_3.dblPThtNet = montant_ht
  208. ligne_total_3.dblPTttcNet = round(montant_ttc, 2)
  209. ligne_total_3.dblPUSaisie = round(montant_ttc, 2)
  210. ligne_total_3.strPUAff = "{:03.02f} EUR".format(montant_ttc)
  211. lignes.append(ligne_total_3)
  212. logger.info("* Nouvelle facture: {}".format(entete.lngPieceId))
  213. # Insertion de l'en-tête
  214. sql = Sql.format("""
  215. INSERT INTO tblPieceEntete ( lngPieceId, lngDocId, lngTiersId, lngASTRE, strCodeProduit, bytTitreId, strCodeAdresse, strAdresse1, strAdresse2,
  216. strAdresse3, strAdresse4, strAdresse5, [strPaysIdIso3166-A2], bytTVATiersId, strTelephone, strTelecopie, strPortable,
  217. strEMail, strWeb, strLangueIdIso639, strCompteComptable, strDeviseIdIso4217, bytReglementId, strReglement,
  218. bytClasseTarifId, bytClasseRemiseTiersId, bytNbExFacture, strStatTiers, bytSituationIdPrincipale, bytSituationIdSecondaire,
  219. memObsEntete, memObsInterne, bytTypeDocumentId, strStatDocEntete, dblCoursDevise, dtmCoursDevise,
  220. strUserIdCreation, strUserIdLastMod )
  221. VALUES ({entete.lngPieceId}, {entete.lngDocId}, {entete.lngTiersId}, {entete.lngASTRE}, {entete.strCodeProduit:text}, {entete.bytTitreId},
  222. {entete.strCodeAdresse:text}, {entete.strAdresse1:text}, {entete.strAdresse2:text}, {entete.strAdresse3:text}, {entete.strAdresse4:text}, {entete.strAdresse5:text},
  223. {entete.strPaysIdIso3166:text}, {entete.bytTVATiersId}, {entete.strTelephone:text}, {entete.strTelecopie:text}, {entete.strPortable:text}, {entete.strEMail:text},
  224. {entete.strWeb:text}, {entete.strLangueIdIso639:text}, {entete.strCompteComptable:text}, {entete.strDeviseIdIso4217:text}, {entete.bytReglementId},
  225. {entete.strReglement:text}, {entete.bytClasseTarifId}, {entete.bytClasseRemiseTiersId}, {entete.bytNbExFacture}, {entete.strStatTiers:text},
  226. {entete.bytSituationIdPrincipale}, {entete.bytSituationIdSecondaire}, {entete.memObsEntete:text}, {entete.memObsInterne:text},
  227. {entete.bytTypeDocumentId}, {entete.strStatDocEntete:text}, {entete.dblCoursDevise}, {entete.dtmCoursDevise:date},
  228. {entete.strUserIdCreation:text}, {entete.strUserIdLastMod:text})
  229. """, entete=entete)
  230. logger.debug(sql)
  231. facture_db.execute(sql)
  232. for ligne in lignes:
  233. sql = Sql.format("""
  234. INSERT INTO tblPieceLigne ( lngPieceId, intLigneId, strArticleId, strArticle, bytTVAArticleId, bytTVAId, dblTVATaux, dblQte, bytUniteIdQuantite,
  235. bytNbDecQuantite, bytUniteIdPrix, dblCnvUniteCoef, bytNbDecQuantiteConvertie, dblPUhtBrutDev, dblPUttcBrutDev, dblPUhtNetDev,
  236. dblPUttcNetDev, bytNbDecPU, dblPThtBrutDev, dblPTttcBrutDev, dblPThtNetDev, dblPTttcNetDev, strStatArticle, strStatDocLigne,
  237. dblTauxRemise1, dblTauxRemise2, dblTauxRemise3, blnPrestation, strCompteComptable, memObs, memObsInterne, bytLigneSousTotal,
  238. intLigneIdRattachement, blnLigneVisible, blnLigneGeneree, bytGenerateurId, dblPUhtBrut, dblPUttcBrut, dblPUhtNet, dblPUttcNet,
  239. dblPThtBrut, dblPTttcBrut, dblPThtNet, dblPTttcNet, bytClasseRemiseArticleId, dblPUSaisie, strPUAff, strQteAff )
  240. VALUES ({ligne.lngPieceId}, {ligne.intLigneId}, {ligne.strArticleId:text}, {ligne.strArticle:text}, {ligne.bytTVAArticleId}, {ligne.bytTVAId}, {ligne.dblTVATaux},
  241. {ligne.dblQte}, {ligne.bytUniteIdQuantite}, {ligne.bytNbDecQuantite}, {ligne.bytUniteIdPrix}, {ligne.dblCnvUniteCoef}, {ligne.bytNbDecQuantiteConvertie},
  242. {ligne.dblPUhtBrutDev}, {ligne.dblPUttcBrutDev}, {ligne.dblPUhtNetDev}, {ligne.dblPUttcNetDev}, {ligne.bytNbDecPU}, {ligne.dblPThtBrutDev},
  243. {ligne.dblPTttcBrutDev}, {ligne.dblPThtNetDev}, {ligne.dblPTttcNetDev}, {ligne.strStatArticle:text}, {ligne.strStatDocLigne:text},
  244. {ligne.dblTauxRemise1}, {ligne.dblTauxRemise2}, {ligne.dblTauxRemise3}, {ligne.blnPrestation}, {ligne.strCompteComptable:text},
  245. {ligne.memObs:text}, {ligne.memObsInterne:text}, {ligne.bytLigneSousTotal}, {ligne.intLigneIdRattachement}, {ligne.blnLigneVisible},
  246. {ligne.blnLigneGeneree}, {ligne.bytGenerateurId}, {ligne.dblPUhtBrut}, {ligne.dblPUttcBrut}, {ligne.dblPUhtNet}, {ligne.dblPUttcNet},
  247. {ligne.dblPThtBrut}, {ligne.dblPTttcBrut}, {ligne.dblPThtNet}, {ligne.dblPTttcNet}, {ligne.bytClasseRemiseArticleId}, {ligne.dblPUSaisie},
  248. {ligne.strPUAff:text}, {ligne.strQteAff:text})
  249. """, ligne=ligne)
  250. logger.debug(sql)
  251. facture_db.execute(sql)
  252. # Maj tbl_Tarification.strNumFacture et strStatut pour marquer la ligne comme facturée.
  253. sql = Sql.format(""" UPDATE tbl_Tarification SET strStatut = 'Facturée', strNumFacture = '{}'
  254. WHERE DblTarifId={}; """, piece_id, interv.DblTarifId)
  255. logger.debug(sql)
  256. analytique_db.execute(sql)
  257. facture_db.commit()
  258. analytique_db.commit()