analytique2facture.py 18 KB

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