analytique2facture.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  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 {:%d/%m/%Y}{}".format(interv.dtmFin, "\r\nRapport n° : {}".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. # on met a jour tbl_Tarification.strNumFacture et strStatut pour marquer la ligne comme facturée.
  153. # !! Cette opération ne sera committée qu'à la fin du traitement de cette intervention
  154. sql = Sql.format(""" UPDATE tbl_Tarification SET strStatut = 'Facturée', strNumFacture = {:text}
  155. WHERE DblTarifId={}""", piece_id, interv.DblTarifId)
  156. analytique_db.execute(sql)
  157. lignes.append(ligne)
  158. # Toutes les lignes ont été ajoutées: on créé maintenant les lignes de totaux
  159. montant_ht = float(sum([ligne.dblPThtNetDev for ligne in lignes]))
  160. taux_tva = float(lignes[0].dblTVATaux)
  161. montant_tva = (0.01 * taux_tva) * montant_ht
  162. montant_ttc = montant_ht + montant_tva
  163. # Sous-total HT (ligne 32000)
  164. ligne_total_1 = Ligne()
  165. ligne_total_1.lngPieceId = piece_id
  166. ligne_total_1.intLigneId = 32000
  167. ligne_total_1.strArticleId = ""
  168. ligne_total_1.strArticle = "Total H.T."
  169. ligne_total_1.dblQte = 0
  170. ligne_total_1.dblPThtNetDev = montant_ht
  171. ligne_total_1.dblPTttcNetDev = montant_ttc
  172. ligne_total_1.bytLigneSousTotal = 10
  173. ligne_total_1.blnLigneVisible = False
  174. ligne_total_1.dblPThtNet = montant_ht
  175. ligne_total_1.dblPTttcNet = round(montant_ttc, 2)
  176. ligne_total_1.dblPUSaisie = round(montant_ht, 2)
  177. ligne_total_1.strPUAff = "{:03.02f} EUR".format(montant_ht)
  178. lignes.append(ligne_total_1)
  179. # Sous-total TVA (ligne 32001)
  180. ligne_total_2 = Ligne()
  181. ligne_total_2.lngPieceId = piece_id
  182. ligne_total_2.intLigneId = 32001
  183. ligne_total_2.strArticleId = ""
  184. ligne_total_2.strArticle = """TVA 1 : {taux_tva}% H.T. : {montant_ht:03.02f} T.V.A. :
  185. {montant_tva:03.02f} T.T.C. : {montant_ttc:03.02f}:""".format(taux_tva=taux_tva,
  186. montant_ht=montant_ht,
  187. montant_tva=montant_tva,
  188. montant_ttc=montant_ttc)
  189. ligne_total_2.dblQte = 0
  190. ligne_total_2.bytTVAId = lignes[0].bytTVAId
  191. ligne_total_2.dblTVATaux = taux_tva
  192. ligne_total_2.dblPThtNetDev = montant_ht
  193. ligne_total_2.dblPTttcNetDev = montant_ttc
  194. ligne_total_2.bytLigneSousTotal = 11
  195. ligne_total_2.blnLigneVisible = False
  196. ligne_total_2.dblPThtNet = montant_ht
  197. ligne_total_2.dblPTttcNet = round(ligne_total_1.dblPTttcNetDev, 2)
  198. ligne_total_2.dblPUSaisie = round(montant_tva, 2)
  199. ligne_total_2.strPUAff = "{:03.02f} EUR".format(montant_tva)
  200. lignes.append(ligne_total_2)
  201. # Sous-total TTC (ligne 32500)
  202. ligne_total_3 = Ligne()
  203. ligne_total_3.lngPieceId = piece_id
  204. ligne_total_3.intLigneId = 32500
  205. ligne_total_3.strArticleId = ""
  206. ligne_total_3.strArticle = "Total T.T.C."
  207. ligne_total_3.dblQte = 0
  208. ligne_total_3.dblPThtNetDev = montant_ht
  209. ligne_total_3.dblPTttcNetDev = montant_ttc
  210. ligne_total_3.bytLigneSousTotal = 12
  211. ligne_total_3.blnLigneVisible = False
  212. ligne_total_3.dblPThtNet = montant_ht
  213. ligne_total_3.dblPTttcNet = round(montant_ttc, 2)
  214. ligne_total_3.dblPUSaisie = round(montant_ttc, 2)
  215. ligne_total_3.strPUAff = "{:03.02f} EUR".format(montant_ttc)
  216. lignes.append(ligne_total_3)
  217. logger.info("* Nouvelle facture: {}".format(entete.lngPieceId))
  218. # Insertion de l'en-tête
  219. sql = Sql.format("""
  220. INSERT INTO tblPieceEntete ( lngPieceId, lngDocId, lngTiersId, lngASTRE, strCodeProduit, bytTitreId, strCodeAdresse, strAdresse1, strAdresse2,
  221. strAdresse3, strAdresse4, strAdresse5, [strPaysIdIso3166-A2], bytTVATiersId, strTelephone, strTelecopie, strPortable,
  222. strEMail, strWeb, strLangueIdIso639, strCompteComptable, strDeviseIdIso4217, bytReglementId, strReglement,
  223. bytClasseTarifId, bytClasseRemiseTiersId, bytNbExFacture, strStatTiers, bytSituationIdPrincipale, bytSituationIdSecondaire,
  224. memObsEntete, memObsInterne, bytTypeDocumentId, strStatDocEntete, dblCoursDevise, dtmCoursDevise,
  225. strUserIdCreation, strUserIdLastMod )
  226. VALUES ({entete.lngPieceId}, {entete.lngDocId}, {entete.lngTiersId}, {entete.lngASTRE}, {entete.strCodeProduit:text}, {entete.bytTitreId},
  227. {entete.strCodeAdresse:text}, {entete.strAdresse1:text}, {entete.strAdresse2:text}, {entete.strAdresse3:text}, {entete.strAdresse4:text}, {entete.strAdresse5:text},
  228. {entete.strPaysIdIso3166:text}, {entete.bytTVATiersId}, {entete.strTelephone:text}, {entete.strTelecopie:text}, {entete.strPortable:text}, {entete.strEMail:text},
  229. {entete.strWeb:text}, {entete.strLangueIdIso639:text}, {entete.strCompteComptable:text}, {entete.strDeviseIdIso4217:text}, {entete.bytReglementId},
  230. {entete.strReglement:text}, {entete.bytClasseTarifId}, {entete.bytClasseRemiseTiersId}, {entete.bytNbExFacture}, {entete.strStatTiers:text},
  231. {entete.bytSituationIdPrincipale}, {entete.bytSituationIdSecondaire}, {entete.memObsEntete:text}, {entete.memObsInterne:text},
  232. {entete.bytTypeDocumentId}, {entete.strStatDocEntete:text}, {entete.dblCoursDevise}, {entete.dtmCoursDevise:date},
  233. {entete.strUserIdCreation:text}, {entete.strUserIdLastMod:text})
  234. """, entete=entete)
  235. facture_db.execute(sql)
  236. for ligne in lignes:
  237. sql = Sql.format("""
  238. INSERT INTO tblPieceLigne ( lngPieceId, intLigneId, strArticleId, strArticle, bytTVAArticleId, bytTVAId, dblTVATaux, dblQte, bytUniteIdQuantite,
  239. bytNbDecQuantite, bytUniteIdPrix, dblCnvUniteCoef, bytNbDecQuantiteConvertie, dblPUhtBrutDev, dblPUttcBrutDev, dblPUhtNetDev,
  240. dblPUttcNetDev, bytNbDecPU, dblPThtBrutDev, dblPTttcBrutDev, dblPThtNetDev, dblPTttcNetDev, strStatArticle, strStatDocLigne,
  241. dblTauxRemise1, dblTauxRemise2, dblTauxRemise3, blnPrestation, strCompteComptable, memObs, memObsInterne, bytLigneSousTotal,
  242. intLigneIdRattachement, blnLigneVisible, blnLigneGeneree, bytGenerateurId, dblPUhtBrut, dblPUttcBrut, dblPUhtNet, dblPUttcNet,
  243. dblPThtBrut, dblPTttcBrut, dblPThtNet, dblPTttcNet, bytClasseRemiseArticleId, dblPUSaisie, strPUAff, strQteAff )
  244. VALUES ({ligne.lngPieceId}, {ligne.intLigneId}, {ligne.strArticleId:text}, {ligne.strArticle:text}, {ligne.bytTVAArticleId}, {ligne.bytTVAId}, {ligne.dblTVATaux},
  245. {ligne.dblQte}, {ligne.bytUniteIdQuantite}, {ligne.bytNbDecQuantite}, {ligne.bytUniteIdPrix}, {ligne.dblCnvUniteCoef}, {ligne.bytNbDecQuantiteConvertie},
  246. {ligne.dblPUhtBrutDev}, {ligne.dblPUttcBrutDev}, {ligne.dblPUhtNetDev}, {ligne.dblPUttcNetDev}, {ligne.bytNbDecPU}, {ligne.dblPThtBrutDev},
  247. {ligne.dblPTttcBrutDev}, {ligne.dblPThtNetDev}, {ligne.dblPTttcNetDev}, {ligne.strStatArticle:text}, {ligne.strStatDocLigne:text},
  248. {ligne.dblTauxRemise1}, {ligne.dblTauxRemise2}, {ligne.dblTauxRemise3}, {ligne.blnPrestation}, {ligne.strCompteComptable:text},
  249. {ligne.memObs:text}, {ligne.memObsInterne:text}, {ligne.bytLigneSousTotal}, {ligne.intLigneIdRattachement}, {ligne.blnLigneVisible},
  250. {ligne.blnLigneGeneree}, {ligne.bytGenerateurId}, {ligne.dblPUhtBrut}, {ligne.dblPUttcBrut}, {ligne.dblPUhtNet}, {ligne.dblPUttcNet},
  251. {ligne.dblPThtBrut}, {ligne.dblPTttcBrut}, {ligne.dblPThtNet}, {ligne.dblPTttcNet}, {ligne.bytClasseRemiseArticleId}, {ligne.dblPUSaisie},
  252. {ligne.strPUAff:text}, {ligne.strQteAff:text})
  253. """, ligne=ligne)
  254. facture_db.execute(sql)
  255. facture_db.commit()
  256. analytique_db.commit()