analytique2facture.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  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. # Connexion à Analytique
  23. analytique_db = AnalytiqueDb(autocommit=False)
  24. # Connexion à Controles
  25. facture_db = FacturesDb(autocommit=False)
  26. # Connexion à CommunDb
  27. commun_db = CommunDb(autocommit=False)
  28. Sql = SqlFormatter()
  29. current = "{:%m/%Y}".format(datetime.now())
  30. mois_facturation = input("Veuillez renseigner le mois de facturation [defaut: {}] ('q' pour quitter): ".format(current)) # Format: voir avec jacky
  31. if mois_facturation == 'q':
  32. sys.exit(1)
  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 : ]{strLieux}\r\n" \
  114. "[V/Cde : ] {Ref} du {dtmCommande}\r\n" \
  115. "".format(mois_facturation=mois_facturation,
  116. strLieux=affaire.strLieux,
  117. Ref=affaire.Ref,
  118. dtmCommande="{:%d/%m/%Y}".format(affaire.dtmCommande) if affaire.dtmCommande else "",
  119. )
  120. entete.memObsInterne = "N° Affaire analytique : {}".format(affaire.strLiaisonControle)
  121. entete.strStatDocEntete = "PC00" if ("/" in affaire.strLiaisonControle or affaire.strLiaisonControle == "RDBOEC") else "PT00"
  122. entete.strUserIdCreation = "script"
  123. entete.strUserIdLastMod = "script"
  124. i = 0
  125. lignes = []
  126. for interv in interventions:
  127. i += 1
  128. ligne = Ligne()
  129. ligne.lngPieceId = piece_id
  130. ligne.intLigneId = i
  131. ligne.strArticleId = interv.strArticleId
  132. article = commun_db.first("SELECT * FROM tblArticle WHERE strArticleId='{}'".format(interv.strArticleId))
  133. ligne.strArticle = article.strArticle if article else ""
  134. ligne.bytTVAArticleId = article.bytTVAArticleId if article else 0
  135. tva = commun_db.first("SELECT bytTVAId FROM tblTVA WHERE bytTVATiersId={} AND bytTVAArticleId={}".format(destinataire.bytTVATiersId, ligne.bytTVAArticleId))
  136. ligne.bytTVAId = tva.bytTVAId
  137. taux = commun_db.first("SELECT dblTVATaux FROM tblTVATaux WHERE bytTVAId={}".format(ligne.bytTVAId))
  138. ligne.dblTVATaux = taux.dblTVATaux
  139. ligne.dblQte = interv.dblQuantite
  140. if interv.strUnite:
  141. unite = commun_db.first("SELECT * FROM tblUnite WHERE strUniteCourt='{}'".format(interv.strUnite))
  142. ligne.bytUniteIdQuantite = unite.bytUniteId
  143. ligne.bytUniteIdPrix = unite.bytUniteId
  144. else:
  145. ligne.bytUniteIdQuantite = 0
  146. ligne.bytUniteIdPrix = 0
  147. ligne.dblPUhtBrutDev = interv.dblPrixUnitaire
  148. ligne.dblPUhtNetDev = interv.dblPrixUnitaire
  149. ligne.dblPThtNetDev = interv.dblPrixTotal
  150. ligne.blnPrestation = True
  151. ligne.memObs = "Intervention le {:%d/%m/%Y}{}".format(interv.dtmFin, "\r\nRapport n° : {}".format(interv.strRapportId) if interv.strRapportId else "")
  152. ligne.blnLigneGeneree = True
  153. ligne.bytGenerateurId = 1
  154. ligne.dblPThtNet = interv.dblPrixTotal
  155. ligne.bytClasseRemiseArticleId = 1
  156. ligne.dblPUSaisie = interv.dblPrixUnitaire
  157. ligne.strPUAff = "{:03.02f} EUR/{}".format(interv.dblPrixUnitaire, interv.strUnite)
  158. ligne.strQteAff = "{:03.02f} {}".format(interv.dblQuantite, interv.strUnite)
  159. # on met a jour tbl_Tarification.strNumFacture et strStatut pour marquer la ligne comme facturée.
  160. # !! Cette opération ne sera committée qu'à la fin du traitement de cette intervention
  161. sql = Sql.format(""" UPDATE tbl_Tarification SET strStatut = 'Facturée', strNumFacture = {:text}
  162. WHERE DblTarifId={}""", piece_id, interv.DblTarifId)
  163. analytique_db.execute(sql)
  164. lignes.append(ligne)
  165. # Toutes les lignes ont été ajoutées: on créé maintenant les lignes de totaux
  166. montant_ht = float(sum([ligne.dblPThtNetDev for ligne in lignes]))
  167. taux_tva = float(lignes[0].dblTVATaux)
  168. montant_tva = (0.01 * taux_tva) * montant_ht
  169. montant_ttc = montant_ht + montant_tva
  170. # Sous-total HT (ligne 32000)
  171. ligne_total_1 = Ligne()
  172. ligne_total_1.lngPieceId = piece_id
  173. ligne_total_1.intLigneId = 32000
  174. ligne_total_1.strArticleId = ""
  175. ligne_total_1.strArticle = "Total H.T."
  176. ligne_total_1.dblQte = 0
  177. ligne_total_1.dblPThtNetDev = montant_ht
  178. ligne_total_1.dblPTttcNetDev = montant_ttc
  179. ligne_total_1.bytLigneSousTotal = 10
  180. ligne_total_1.blnLigneVisible = False
  181. ligne_total_1.dblPThtNet = montant_ht
  182. ligne_total_1.dblPTttcNet = round(montant_ttc, 2)
  183. ligne_total_1.dblPUSaisie = round(montant_ht, 2)
  184. ligne_total_1.strPUAff = "{:03.02f} EUR".format(montant_ht)
  185. lignes.append(ligne_total_1)
  186. # Sous-total TVA (ligne 32001)
  187. ligne_total_2 = Ligne()
  188. ligne_total_2.lngPieceId = piece_id
  189. ligne_total_2.intLigneId = 32001
  190. ligne_total_2.strArticleId = ""
  191. ligne_total_2.strArticle = """TVA 1 : {taux_tva}% H.T. : {montant_ht:03.02f} T.V.A. :
  192. {montant_tva:03.02f} T.T.C. : {montant_ttc:03.02f}:""".format(taux_tva=taux_tva,
  193. montant_ht=montant_ht,
  194. montant_tva=montant_tva,
  195. montant_ttc=montant_ttc)
  196. ligne_total_2.dblQte = 0
  197. ligne_total_2.bytTVAId = lignes[0].bytTVAId
  198. ligne_total_2.dblTVATaux = taux_tva
  199. ligne_total_2.dblPThtNetDev = montant_ht
  200. ligne_total_2.dblPTttcNetDev = montant_ttc
  201. ligne_total_2.bytLigneSousTotal = 11
  202. ligne_total_2.blnLigneVisible = False
  203. ligne_total_2.dblPThtNet = montant_ht
  204. ligne_total_2.dblPTttcNet = round(ligne_total_1.dblPTttcNetDev, 2)
  205. ligne_total_2.dblPUSaisie = round(montant_tva, 2)
  206. ligne_total_2.strPUAff = "{:03.02f} EUR".format(montant_tva)
  207. lignes.append(ligne_total_2)
  208. # Sous-total TTC (ligne 32500)
  209. ligne_total_3 = Ligne()
  210. ligne_total_3.lngPieceId = piece_id
  211. ligne_total_3.intLigneId = 32500
  212. ligne_total_3.strArticleId = ""
  213. ligne_total_3.strArticle = "Total T.T.C."
  214. ligne_total_3.dblQte = 0
  215. ligne_total_3.dblPThtNetDev = montant_ht
  216. ligne_total_3.dblPTttcNetDev = montant_ttc
  217. ligne_total_3.bytLigneSousTotal = 12
  218. ligne_total_3.blnLigneVisible = False
  219. ligne_total_3.dblPThtNet = montant_ht
  220. ligne_total_3.dblPTttcNet = round(montant_ttc, 2)
  221. ligne_total_3.dblPUSaisie = round(montant_ttc, 2)
  222. ligne_total_3.strPUAff = "{:03.02f} EUR".format(montant_ttc)
  223. lignes.append(ligne_total_3)
  224. logger.info("* Nouvelle facture: {}".format(entete.lngPieceId))
  225. # Insertion de l'en-tête
  226. sql = Sql.format("""
  227. INSERT INTO tblPieceEntete ( lngPieceId, lngDocId, lngTiersId, lngASTRE, strCodeProduit, bytTitreId, strCodeAdresse, strAdresse1, strAdresse2,
  228. strAdresse3, strAdresse4, strAdresse5, [strPaysIdIso3166-A2], bytTVATiersId, strTelephone, strTelecopie, strPortable,
  229. strEMail, strWeb, strLangueIdIso639, strCompteComptable, strDeviseIdIso4217, bytReglementId, strReglement,
  230. bytClasseTarifId, bytClasseRemiseTiersId, bytNbExFacture, strStatTiers, bytSituationIdPrincipale, bytSituationIdSecondaire,
  231. memObsEntete, memObsInterne, bytTypeDocumentId, strStatDocEntete, dblCoursDevise, dtmCoursDevise,
  232. strUserIdCreation, strUserIdLastMod )
  233. VALUES ({entete.lngPieceId}, {entete.lngDocId}, {entete.lngTiersId}, {entete.lngASTRE}, {entete.strCodeProduit:text}, {entete.bytTitreId},
  234. {entete.strCodeAdresse:text}, {entete.strAdresse1:text}, {entete.strAdresse2:text}, {entete.strAdresse3:text}, {entete.strAdresse4:text}, {entete.strAdresse5:text},
  235. {entete.strPaysIdIso3166:text}, {entete.bytTVATiersId}, {entete.strTelephone:text}, {entete.strTelecopie:text}, {entete.strPortable:text}, {entete.strEMail:text},
  236. {entete.strWeb:text}, {entete.strLangueIdIso639:text}, {entete.strCompteComptable:text}, {entete.strDeviseIdIso4217:text}, {entete.bytReglementId},
  237. {entete.strReglement:text}, {entete.bytClasseTarifId}, {entete.bytClasseRemiseTiersId}, {entete.bytNbExFacture}, {entete.strStatTiers:text},
  238. {entete.bytSituationIdPrincipale}, {entete.bytSituationIdSecondaire}, {entete.memObsEntete:text}, {entete.memObsInterne:text},
  239. {entete.bytTypeDocumentId}, {entete.strStatDocEntete:text}, {entete.dblCoursDevise}, {entete.dtmCoursDevise:date},
  240. {entete.strUserIdCreation:text}, {entete.strUserIdLastMod:text})
  241. """, entete=entete)
  242. facture_db.execute(sql)
  243. for ligne in lignes:
  244. sql = Sql.format("""
  245. INSERT INTO tblPieceLigne ( lngPieceId, intLigneId, strArticleId, strArticle, bytTVAArticleId, bytTVAId, dblTVATaux, dblQte, bytUniteIdQuantite,
  246. bytNbDecQuantite, bytUniteIdPrix, dblCnvUniteCoef, bytNbDecQuantiteConvertie, dblPUhtBrutDev, dblPUttcBrutDev, dblPUhtNetDev,
  247. dblPUttcNetDev, bytNbDecPU, dblPThtBrutDev, dblPTttcBrutDev, dblPThtNetDev, dblPTttcNetDev, strStatArticle, strStatDocLigne,
  248. dblTauxRemise1, dblTauxRemise2, dblTauxRemise3, blnPrestation, strCompteComptable, memObs, memObsInterne, bytLigneSousTotal,
  249. intLigneIdRattachement, blnLigneVisible, blnLigneGeneree, bytGenerateurId, dblPUhtBrut, dblPUttcBrut, dblPUhtNet, dblPUttcNet,
  250. dblPThtBrut, dblPTttcBrut, dblPThtNet, dblPTttcNet, bytClasseRemiseArticleId, dblPUSaisie, strPUAff, strQteAff )
  251. VALUES ({ligne.lngPieceId}, {ligne.intLigneId}, {ligne.strArticleId:text}, {ligne.strArticle:text}, {ligne.bytTVAArticleId}, {ligne.bytTVAId}, {ligne.dblTVATaux},
  252. {ligne.dblQte}, {ligne.bytUniteIdQuantite}, {ligne.bytNbDecQuantite}, {ligne.bytUniteIdPrix}, {ligne.dblCnvUniteCoef}, {ligne.bytNbDecQuantiteConvertie},
  253. {ligne.dblPUhtBrutDev}, {ligne.dblPUttcBrutDev}, {ligne.dblPUhtNetDev}, {ligne.dblPUttcNetDev}, {ligne.bytNbDecPU}, {ligne.dblPThtBrutDev},
  254. {ligne.dblPTttcBrutDev}, {ligne.dblPThtNetDev}, {ligne.dblPTttcNetDev}, {ligne.strStatArticle:text}, {ligne.strStatDocLigne:text},
  255. {ligne.dblTauxRemise1}, {ligne.dblTauxRemise2}, {ligne.dblTauxRemise3}, {ligne.blnPrestation}, {ligne.strCompteComptable:text},
  256. {ligne.memObs:text}, {ligne.memObsInterne:text}, {ligne.bytLigneSousTotal}, {ligne.intLigneIdRattachement}, {ligne.blnLigneVisible},
  257. {ligne.blnLigneGeneree}, {ligne.bytGenerateurId}, {ligne.dblPUhtBrut}, {ligne.dblPUttcBrut}, {ligne.dblPUhtNet}, {ligne.dblPUttcNet},
  258. {ligne.dblPThtBrut}, {ligne.dblPTttcBrut}, {ligne.dblPThtNet}, {ligne.dblPTttcNet}, {ligne.bytClasseRemiseArticleId}, {ligne.dblPUSaisie},
  259. {ligne.strPUAff:text}, {ligne.strQteAff:text})
  260. """, ligne=ligne)
  261. facture_db.execute(sql)
  262. facture_db.commit()
  263. analytique_db.commit()