analytique2facture.py 18 KB

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