dolibarrAlchemyHledger.py 17 KB

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