analytique2facture.py 13 KB

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