dolibarrAlchemyHledger.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545
  1. # -*- coding: utf-8 -*-
  2. from __future__ import unicode_literals
  3. import settings
  4. import datetime
  5. import decimal
  6. from himports.dolibarrAlchemy import *
  7. class HledgerEntry(object):
  8. accounting_years = settings.get('ACCOUNTING_YEARS')
  9. pc_default_tiers = settings.get('PC_REFS')['default_tiers']
  10. pc_default_client = settings.get('PC_REFS')['default_client']
  11. pc_default_supplier = settings.get('PC_REFS')['default_supplier']
  12. pc_default_produit = settings.get('PC_REFS')['default_produit']
  13. pc_default_charge = settings.get('PC_REFS')['default_charge']
  14. pc_default_bank = settings.get('PC_REFS')['default_bank']
  15. tva_type = settings.get('TVA_TYPE')
  16. sql_class = None
  17. # Date permettant de déterminer dans quel exercice comptable
  18. # l'écriture doit se trouver
  19. k_accounting_date = None
  20. def __init__(self, e):
  21. super(HledgerEntry, self).__init__()
  22. self.e = e
  23. self.accounting_date = e
  24. for attr in self.k_accounting_date.split('.'):
  25. self.accounting_date = getattr(self.accounting_date, attr)
  26. @classmethod
  27. def get_entries(cls, session):
  28. return [cls(i) for i in session.query(cls.sql_class).all()]
  29. def get_ledger(self):
  30. print "WARNING: get_ledger not done"
  31. return u""
  32. def check_pc(self):
  33. return ()
  34. def get_year(self):
  35. raise Exception("TODO: get_year not implemented for class %s" % (self.__class__))
  36. def get_accounting_year(self):
  37. date = self.accounting_date
  38. if isinstance(date, datetime.datetime):
  39. date = date.date()
  40. for (year, dbegin, dend) in HledgerEntry.accounting_years:
  41. if date >= dbegin and date <= dend:
  42. return year
  43. return str(date.year)
  44. @staticmethod
  45. def _value(value):
  46. return '{number:.{digits}f}'.format(number=value, digits=4)
  47. class HledgerJournal(object):
  48. def __init__(self, session, cls_entry):
  49. self.entries = cls_entry.get_entries(session)
  50. def get_entries(self):
  51. return self.entries
  52. def get_by_year(self):
  53. by_year = {}
  54. for entry in self.get_entries():
  55. entry_year = entry.get_accounting_year()
  56. if entry_year not in by_year:
  57. by_year[entry_year] = []
  58. by_year[entry_year].append(entry)
  59. return by_year
  60. def check_pc(self):
  61. pc_missing = set()
  62. for entry in self.get_entries():
  63. pc_missing.update(entry.check_pc())
  64. return pc_missing
  65. class HledgerBankEntry(HledgerEntry):
  66. sql_class = Bank
  67. k_accounting_date = 'datev'
  68. @classmethod
  69. def get_third_code(cls, e):
  70. third_code = ""
  71. if e.url_payment_supplier:
  72. if e.url_company:
  73. third_code = e.url_company.societe.code_compta_fournisseur
  74. if e.url_payment_sc:
  75. code = e.url_payment_sc.payment_sc.cotisation_sociale.type.code
  76. if code in settings.get('SOCIAL_REFS'):
  77. third_code = settings.get('SOCIAL_REFS')[code]
  78. if e.url_payment:
  79. if e.url_company:
  80. third_code = e.url_company.societe.code_compta
  81. if third_code == "":
  82. fn = settings.get('PC_REFS')['fn_custom_code']
  83. third_code = fn(e)
  84. if third_code == "":
  85. third_code = cls.pc_default_tiers
  86. return third_code
  87. @classmethod
  88. def get_description(self, e):
  89. s_nom = ""
  90. s_description = ""
  91. if e.url_company:
  92. s_nom = e.url_company.societe.nom
  93. if e.url_payment_supplier:
  94. f_ids = [f.facture.ref_supplier for f in e.url_payment_supplier.payment_supplier.factures]
  95. s_description = "Règlement facture fournisseur - %s - %s" % (
  96. s_nom,
  97. "|".join(f_ids),
  98. )
  99. if e.url_payment:
  100. f_ids = [f.facture.facnumber for f in e.url_payment.payment.factures]
  101. s_description = "Règlement facture client - %s - %s" % (
  102. s_nom,
  103. "|".join(f_ids),
  104. )
  105. if s_description == "":
  106. s_description = s_nom + " - " + e.label
  107. return s_description
  108. def get_ledger(self):
  109. e = self.e
  110. s = ""
  111. s_description = self.get_description(self.e)
  112. s += "%(date)s %(description)s\n" % {
  113. 'date': e.datev.strftime("%Y/%m/%d"),
  114. 'description': s_description
  115. }
  116. third_code = self.get_third_code(self.e)
  117. ba_code = e.account.account_number
  118. if ba_code == "":
  119. ba_code = self.pc_default_bank
  120. s += " %(account)s \t %(amount)s\n" % {
  121. 'account': settings.get_ledger_account(ba_code),
  122. 'amount': self._value(-e.amount)
  123. }
  124. s += " %(account)s \t %(amount)s\n" % {
  125. 'account': settings.get_ledger_account(third_code),
  126. 'amount': self._value(e.amount)
  127. }
  128. if e.url_payment_supplier:
  129. for f in e.url_payment_supplier.payment_supplier.factures:
  130. tvas = HledgerSupplierEntry.get_tva_paiement_amounts(f.facture, journal="bank")
  131. for k in tvas:
  132. s += " %(account_tva)s \t %(amount_tva)s\n" % {
  133. 'account_tva': settings.get_ledger_account(k),
  134. 'amount_tva': self._value(tvas[k] * (f.amount / f.facture.total_ttc))
  135. }
  136. elif e.url_payment:
  137. for f in e.url_payment.payment.factures:
  138. tvas = HledgerSellEntry.get_tva_paiement_amounts(f.facture, journal="bank")
  139. for k in tvas:
  140. s += " %(account_tva)s \t %(amount_tva)s\n" % {
  141. 'account_tva': settings.get_ledger_account(k),
  142. 'amount_tva': self._value(tvas[k] * (f.amount / f.facture.total_ttc))
  143. }
  144. else:
  145. pass
  146. return s
  147. @classmethod
  148. def get_entries(cls, session):
  149. return [cls(e) for e in session.query(cls.sql_class).order_by(Bank.datev, Bank.num_releve).all()]
  150. class HledgerFactureEntry(HledgerEntry):
  151. @classmethod
  152. def get_entries(cls, session):
  153. return [cls(e) for e in session.query(cls.sql_class).order_by(cls.sql_class.datef).all()]
  154. @classmethod
  155. def is_tva_facture(cls, ed):
  156. return ed.productcls.tva_type == 'service_sur_debit' and ed.product_type == 1
  157. @classmethod
  158. def is_tva_paiement(cls, ed):
  159. return cls.tva_type != 'service_sur_debit' or ed.product_type != 1
  160. @classmethod
  161. def get_tva_amounts(cls, e, journal):
  162. tvas = dict()
  163. for ed in e.details:
  164. if isinstance(e, Facture):
  165. total_tva = -ed.total_tva
  166. elif isinstance(e, FactureFourn):
  167. total_tva = ed.tva
  168. else:
  169. raise Exception("Should be either Facture or FactureFourn")
  170. if total_tva == 0:
  171. continue
  172. tva_account = cls.get_tva_account(ed)
  173. tva_regul = cls.get_tva_regul_account(ed)
  174. if journal == "bank":
  175. if ed.product_type == 1 and cls.tva_type == 'standard':
  176. if tva_regul not in tvas:
  177. tvas[tva_regul] = 0
  178. if tva_account not in tvas:
  179. tvas[tva_account] = 0
  180. tvas[tva_account] += -total_tva
  181. tvas[tva_regul] += total_tva
  182. elif journal == "sell" or journal == "supplier":
  183. if ed.product_type == 1 and cls.tva_type == 'standard':
  184. if tva_regul not in tvas:
  185. tvas[tva_regul] = 0
  186. tvas[tva_regul] += -total_tva
  187. else:
  188. if tva_account not in tvas:
  189. tvas[tva_account] = 0
  190. tvas[tva_account] += -total_tva
  191. return tvas
  192. @classmethod
  193. def get_tva_regul_account(cls, ed):
  194. tx = int(float(ed.tva_tx) * 100)
  195. key = "tva_regul_%s" % (tx,)
  196. return settings.get('PC_REFS')[key]
  197. # Calcul de la tva à décaisser à paiement de la facture
  198. #
  199. # Cela du type de produit (bien ou service) et du type de tva
  200. @classmethod
  201. def get_tva_facture_amounts(cls, e, journal):
  202. return cls.get_tva_amounts(e, journal)
  203. @classmethod
  204. def get_tva_paiement_amounts(cls, e, journal):
  205. return cls.get_tva_amounts(e, journal)
  206. class HledgerSupplierEntry(HledgerFactureEntry):
  207. sql_class = FactureFourn
  208. k_accounting_date = 'datef'
  209. def check(self):
  210. e = self.e
  211. total_ttc = e.total_ttc
  212. total_tva = e.total_tva
  213. total_ht = e.total_ht
  214. for ed in e.details:
  215. total_ttc -= ed.total_ttc
  216. total_tva -= ed.tva
  217. total_ht -= ed.total_ht
  218. if total_ttc > 1e-10:
  219. print "Erreur dans l'écriture %s: total ttc = %s" % (e.ref_supplier, total_ttc)
  220. if total_ht > 1e-10:
  221. print "Erreur dans l'écriture %s: total ht = %s" % (e.ref_supplier, total_ht)
  222. if total_tva > 1e-10:
  223. print "Erreur dans l'écriture %s: total tva = %s" % (e.ref_supplier, total_tva)
  224. def getMissingPC(self):
  225. pc_missing = []
  226. if e.societe.code_compta_fournisseur == "":
  227. pc_missing.append("tiers:fournisseur: %s %s" % (e.societe.nom, s.societe.ape))
  228. for ed in e.details:
  229. if self.get_product_account_code(ed) == self.pc_default_charge:
  230. pc_missing.append("achat: %s - %s" % (e.ref_supplier, ed.description.splitlines()[0]))
  231. return pc_missing
  232. def get_ledger(self):
  233. e = self.e
  234. self.check()
  235. s = ""
  236. s += "%(date)s %(description)s\n" % {
  237. 'date': e.datef.strftime("%Y/%m/%d"),
  238. 'description': e.ref_supplier + " - " + e.societe.nom,
  239. }
  240. s_code = self.get_supplier_code(self.e)
  241. s += " %(compte_tiers)s \t %(amount_ttc)s\n" % {
  242. 'compte_tiers': settings.get_ledger_account(s_code),
  243. 'amount_ttc': self._value(e.total_ttc),
  244. }
  245. for ed in e.details:
  246. p_code = self.get_product_account_code(ed)
  247. s += " %(compte_produit)s \t %(amount_ht)s\n" % {
  248. 'compte_produit': settings.get_ledger_account(p_code),
  249. 'amount_ht': self._value(-ed.total_ht)
  250. }
  251. tvas = self.get_tva_facture_amounts(self.e, journal="supplier")
  252. for k in tvas:
  253. s += " %(compte_tva)s \t %(amount_tva)s\n" % {
  254. 'compte_tva': settings.get_ledger_account(k),
  255. 'amount_tva': self._value(tvas[k]),
  256. }
  257. return s
  258. @classmethod
  259. def get_tva_account(cls, ed):
  260. p_code = cls.get_product_account_code(ed)
  261. tx = int(float(ed.tva_tx) * 100)
  262. if p_code.startswith("2"):
  263. prefix = 'tva_deductible'
  264. else:
  265. prefix = 'tva_deductible_immo'
  266. key = "%s_%s" % (prefix, tx)
  267. tva_account = settings.get('PC_REFS')[key]
  268. return tva_account
  269. @classmethod
  270. def get_product_account_code(cls, ed):
  271. p_code = ""
  272. if ed.accounting_account:
  273. p_code = ed.accounting_account.account_number
  274. elif ed.product:
  275. p_code = ed.product.accountancy_code_buy
  276. else:
  277. p_code = cls.pc_default_charge
  278. return p_code
  279. @classmethod
  280. def get_supplier_code(cls, e):
  281. s_code = e.societe.code_compta_fournisseur
  282. if s_code == "":
  283. s_code = cls.pc_default_supplier
  284. return s_code
  285. class HledgerSellEntry(HledgerFactureEntry):
  286. sql_class = Facture
  287. k_accounting_date = 'datef'
  288. def get_ledger(self):
  289. e = self.e
  290. self.check()
  291. s = ""
  292. # Date et description
  293. s += "%(date)s %(description)s\n" % {
  294. 'date': e.datef.strftime("%Y/%m/%d"),
  295. 'description': e.facnumber + " - " + e.societe.nom,
  296. }
  297. # ligne pour compte client
  298. s_code = self.get_client_code(self.e)
  299. s += " %(compte_tiers)s %(amount_ttc)s\n" % {
  300. 'compte_tiers': settings.get_ledger_account(s_code),
  301. 'amount_ttc': self._value(-e.total_ttc),
  302. }
  303. # lignes pour compte de produits
  304. for ed in e.details:
  305. p_code = self.get_product_account_code(ed)
  306. s += " %(compte_produit)s %(amount_ht)s\n" % {
  307. 'compte_produit': settings.get_ledger_account(p_code),
  308. 'amount_ht': self._value(ed.total_ht)
  309. }
  310. # lignes pour la tva
  311. tvas = self.get_tva_facture_amounts(self.e, journal="sell")
  312. for k in tvas:
  313. s += " %(compte_tva)s %(amount_tva)s\n" % {
  314. 'compte_tva': settings.get_ledger_account(k),
  315. 'amount_tva': self._value(tvas[k]),
  316. }
  317. return s
  318. @classmethod
  319. def get_tva_account(cls, ed):
  320. tx = int(float(ed.tva_tx) * 100)
  321. key = "tva_collecte_%s" % (tx,)
  322. return settings.get('PC_REFS')[key]
  323. def getMissingPC(self):
  324. e = self.e
  325. pc_missing = []
  326. if e.societe.code_compta == "":
  327. pc_missing.append("tiers: %s %s" % (e.societe.nom, s.societe.ape))
  328. for ed in e.details:
  329. if self.get_product_account_code(ed) == self.pc_default_produit:
  330. if ed.description != "":
  331. description = ed.description.splitlines()[0]
  332. else:
  333. description = ed.description
  334. pc_missing.append("produit: %s - %s - %s" % (e.societe.nom, e.facnumber, description))
  335. return pc_missing
  336. @classmethod
  337. def get_product_account_code(cls, ed):
  338. p_code = ""
  339. if ed.accounting_account:
  340. p_code = ed.accounting_account.account_number
  341. elif ed.product:
  342. p_code = ed.product.accountancy_code_sell
  343. else:
  344. p_code = cls.pc_default_produit
  345. return p_code
  346. @classmethod
  347. def get_client_code(cls, e):
  348. s_code = e.societe.code_compta
  349. if s_code == "":
  350. s_code = cls.pc_default_client
  351. return s_code
  352. def check(self):
  353. e = self.e
  354. total_ttc = e.total_ttc
  355. total_tva = e.tva
  356. total_ht = e.total
  357. for ed in e.details:
  358. total_ttc -= ed.total_ttc
  359. total_tva -= ed.total_tva
  360. total_ht -= ed.total_ht
  361. if total_ttc > 1e-10:
  362. print "Erreur dans l'écriture %s: total ttc = %s" % (e.facnumber, total_ttc)
  363. if total_ht > 1e-10:
  364. print "Erreur dans l'écriture %s: total ht = %s" % (e.facnumber, total_ht)
  365. if total_tva > 1e-10:
  366. print "Erreur dans l'écriture %s: total tva = %s" % (e.facnumber, total_tva)
  367. class HledgerSocialEntry(HledgerEntry):
  368. sql_class = CotisationsSociales
  369. k_accounting_date = 'date_ech'
  370. @classmethod
  371. def get_entries(cls, session):
  372. return [cls(e) for e in session.query(cls.sql_class).order_by(CotisationsSociales.date_ech).all()]
  373. @classmethod
  374. def get_third_code(cls, e):
  375. third_code = ""
  376. if e.type.code in settings.get('SOCIAL_REFS'):
  377. third_code = settings.get('SOCIAL_REFS')[e.type.code]
  378. if third_code == "":
  379. third_code = cls.pc_default_supplier
  380. return third_code
  381. @classmethod
  382. def get_social_code(cls, e):
  383. s_code = ""
  384. if e.type:
  385. s_code = e.type.accountancy_code
  386. if s_code == "":
  387. s_code = cls.pc_default_charge
  388. return s_code
  389. def getMissingPC(self):
  390. pc_missing = []
  391. if self.get_social_code(self.e) == self.pc_default_charge:
  392. pc_missing.append("charges: %s" % (e.libelle))
  393. if self.get_third_code(self.e) == self.pc_default_supplier:
  394. pc_missing.append("tiers: %s (%s)" % (e.libelle, e.type.code))
  395. return pc_missing
  396. def get_ledger(self):
  397. e = self.e
  398. s = ""
  399. s += "%(date)s %(description)s\n" % {
  400. 'date': e.date_ech.strftime("%Y/%m/%d"),
  401. 'description': e.libelle + " - " + e.type.libelle
  402. }
  403. third_code = self.get_third_code(self.e)
  404. s_code = self.get_social_code(self.e)
  405. s += " %(account)s \t %(amount)s\n" % {
  406. 'account': settings.get_ledger_account(third_code),
  407. 'amount': self._value(e.amount)
  408. }
  409. s += " %(account)s \t %(amount)s\n" % {
  410. 'account': settings.get_ledger_account(s_code),
  411. 'amount': self._value(-e.amount)
  412. }
  413. return s
  414. def check(self):
  415. e = self.e
  416. class HledgerDolibarrSQLAlchemy(DolibarrSQLAlchemy):
  417. def get_bank_journal(self):
  418. return HledgerJournal(self.session, HledgerBankEntry)
  419. def get_supplier_journal(self):
  420. return HledgerJournal(self.session, HledgerSupplierEntry)
  421. def get_sell_journal(self):
  422. return HledgerJournal(self.session, HledgerSellEntry)
  423. def get_social_journal(self):
  424. return HledgerJournal(self.session, HledgerSocialEntry)