hledger.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525
  1. # -*- coding: utf-8 -*-
  2. from __future__ import unicode_literals
  3. import datetime
  4. import settings
  5. import MySQLdb as mdb
  6. import sys
  7. import os
  8. import codecs
  9. MYSQL_QUERIES = {
  10. "bank": "SELECT DISTINCT b.rowid as b_rowid, ba.ref as ba_ref, ba.label as ba_label, ba.account_number as ba_account_number, b.datev as b_datev, b.dateo as b_dateo, b.label as b_label, b.num_chq as b_num_chq, -b.amount as _b_amount, b.amount as b_amount, b.num_releve as b_num_releve, b.datec as b_datec, bu.url_id as bu_url_id, s.nom as s_nom, s.code_compta as s_code_compta, s.code_compta_fournisseur as s_code_compta_fournisseur, bca.label as bca_label, bca.rowid as bca_rowid, bcl.lineid as bcl_lineid FROM (llx_bank_account as ba, llx_bank as b) LEFT JOIN llx_bank_url as bu ON (bu.fk_bank = b.rowid AND bu.type = 'company') LEFT JOIN llx_societe as s ON bu.url_id = s.rowid LEFT JOIN llx_bank_class as bcl ON bcl.lineid = b.rowid LEFT JOIN llx_bank_categ as bca ON bca.rowid = bcl.fk_categ WHERE ba.rowid = b.fk_account AND ba.entity = 1 and b.num_releve <> '' ORDER BY b.datev, b.num_releve",
  11. "sells": "SELECT DISTINCT s.rowid as s_rowid, s.nom as s_nom, s.address as s_address, s.zip as s_zip, s.town as s_town, c.code as c_code, s.phone as s_phone, s.siren as s_siren, s.siret as s_siret, s.ape as s_ape, s.idprof4 as s_idprof4, s.code_compta as s_code_compta, s.code_compta_fournisseur as s_code_compta_fournisseur, s.tva_intra as s_tva_intra, f.rowid as f_rowid, f.facnumber as f_facnumber, f.datec as f_datec, f.datef as f_datef, f.date_lim_reglement as f_date_lim_reglement, f.total as f_total, f.total_ttc as f_total_ttc, f.tva as f_tva, f.paye as f_paye, f.fk_statut as f_fk_statut, f.note_private as f_note_private, f.note_public as f_note_public, fd.rowid as fd_rowid, fd.label as fd_label, fd.description as fd_description, fd.subprice as fd_subprice, fd.tva_tx as fd_tva_tx, fd.qty as fd_qty, fd.total_ht as fd_total_ht, fd.total_tva as fd_total_tva, fd.total_ttc as fd_total_ttc, fd.date_start as fd_date_start, fd.date_end as fd_date_end, fd.special_code as fd_special_code, fd.product_type as fd_product_type, fd.fk_product as fd_fk_product, p.ref as p_ref, p.label as p_label, p.accountancy_code_sell as p_accountancy_code_sell, a.account_number as a_account_number FROM llx_societe as s LEFT JOIN llx_c_pays as c on s.fk_pays = c.rowid, llx_facture as f LEFT JOIN llx_facture_extrafields as extra ON f.rowid = extra.fk_object , llx_facturedet as fd LEFT JOIN llx_product as p on (fd.fk_product = p.rowid) LEFT JOIN llx_accountingaccount as a ON fd.fk_code_ventilation = a.rowid WHERE f.fk_soc = s.rowid AND f.rowid = fd.fk_facture AND f.entity = 1",
  12. "suppliers": "SELECT DISTINCT s.rowid as s_rowid, s.nom as s_nom, s.address as s_address, s.zip as s_zip, s.town as s_town, s.code_compta_fournisseur as s_code_supplier, c.code as c_code, s.phone as s_phone, s.siren as s_siren, s.siret as s_siret, s.ape as s_ape, s.idprof4 as s_idprof4, s.idprof5 as s_idprof5, s.idprof6 as s_idprof6, s.tva_intra as s_tva_intra, f.rowid as f_rowid, f.ref as f_ref, f.ref_supplier as f_ref_supplier, f.datec as f_datec, f.datef as f_datef, f.total_ht as f_total_ht, f.total_ttc as f_total_ttc, f.total_tva as f_total_tva, f.paye as f_paye, f.fk_statut as f_fk_statut, f.note_public as f_note_public, fd.rowid as fd_rowid, fd.description as fd_description, fd.tva_tx as fd_tva_tx, fd.qty as fd_qty, fd.remise_percent as fd_remise_percent, fd.total_ht as fd_total_ht, fd.total_ttc as fd_total_ttc, fd.tva as fd_tva, fd.product_type as fd_product_type, fd.fk_product as fd_fk_product, p.ref as p_ref, p.label as p_label, p.accountancy_code_buy as p_accountancy_code_buy, a.account_number as a_account_number FROM llx_societe as s LEFT JOIN llx_c_pays as c ON s.fk_pays = c.rowid, llx_facture_fourn as f LEFT JOIN llx_facture_fourn_extrafields as extra ON f.rowid = extra.fk_object , llx_facture_fourn_det as fd LEFT JOIN llx_product as p on (fd.fk_product = p.rowid) LEFT JOIN llx_accountingaccount as a ON fd.fk_code_ventilation = a.rowid WHERE f.fk_soc = s.rowid AND f.rowid = fd.fk_facture_fourn AND f.entity = 1",
  13. }
  14. class Entry(object):
  15. accounting_years = settings.get('ACCOUNTING_YEARS')
  16. pc_default_client = settings.get('PC_REFS')['default_client']
  17. pc_default_supplier = settings.get('PC_REFS')['default_supplier']
  18. pc_default_produit = settings.get('PC_REFS')['default_produit']
  19. pc_default_charge = settings.get('PC_REFS')['default_charge']
  20. pc_default_bank = settings.get('PC_REFS')['default_bank']
  21. def __init__(self, cells):
  22. self.cells = cells
  23. for key in cells:
  24. value = self.cells[key]
  25. if value is None:
  26. self.cells[key] = ""
  27. elif isinstance(value, str):
  28. self.cells[key] = value.decode('iso8859-1')
  29. def get_accounting_year(self, field):
  30. value = self.cells[field]
  31. for (year, dbegin, dend) in Entry.accounting_years:
  32. if value >= dbegin and value <= dend:
  33. return year
  34. return str(value.year)
  35. class BankEntry(Entry):
  36. # ['ba_ref',
  37. # 's_nom',
  38. # 'b_rowid',
  39. # 'b_datev',
  40. # 'b_num_releve',
  41. # 'bu_url_id',
  42. # 'ba_label',
  43. # 's_code_compta',
  44. # 'b_label',
  45. # 'b_dateo',
  46. # '_b_amount',
  47. # 'b_num_chq',
  48. # 'b_datec',
  49. # 'b_amount',
  50. # 's_code_compta_fournisseur']
  51. @staticmethod
  52. def clean(row):
  53. return True
  54. #return row['b_num_releve'] != ""
  55. def addEntry(self, entry):
  56. pass
  57. def get_year(self):
  58. return self.get_accounting_year('b_datev')
  59. def getMissingPC(self):
  60. pc_missing = []
  61. if self.get_s_code() == "":
  62. pc_missing.append("tiers: %s" % (self.cells['s_nom']))
  63. if self.cells['ba_account_number'] == "":
  64. pc_missing.append("banque: %s" % (self.cells['ba_ref']))
  65. return pc_missing
  66. def get_s_code(self):
  67. supplier_or_client_by_bca_rowid = {
  68. 1: 'client',
  69. 2: 'client',
  70. 3: 'supplier',
  71. 4: 'supplier',
  72. 5: 'client',
  73. '': 'client',
  74. }
  75. supplier_or_client = supplier_or_client_by_bca_rowid[self.cells['bca_rowid']]
  76. print (self.cells['b_label'],self.cells['b_num_releve'],self.cells['b_datev'],self.cells['bca_label'],self.cells['s_nom'],self.cells['b_amount'])
  77. if supplier_or_client == "client":
  78. third_code = self.cells['s_code_compta']
  79. if third_code == "":
  80. third_code = self.pc_default_client
  81. else:
  82. third_code = self.cells['s_code_compta_fournisseur']
  83. if third_code == "":
  84. third_code = self.pc_default_supplier
  85. return third_code
  86. def get_ledger(self):
  87. s = ""
  88. s += "%(date)s %(description)s\n" % {
  89. 'date': self.cells['b_datev'].strftime("%Y/%m/%d"),
  90. 'description': self.cells['s_nom'] + " - " + self.cells['b_label']
  91. }
  92. amount = self.cells['b_amount']
  93. third_code = self.get_s_code()
  94. s += " %(account)s \t %(amount)s\n" % {
  95. 'account': settings.get_ledger_account(third_code),
  96. 'amount': amount
  97. }
  98. ba_code = self.cells['ba_account_number']
  99. if ba_code == "":
  100. ba_code = self.pc_default_bank
  101. s += " %(account)s \t %(amount)s\n" % {
  102. 'account': settings.get_ledger_account(ba_code),
  103. 'amount': -amount
  104. }
  105. return s
  106. class EntryCollection(object):
  107. def __init__(self, index, entry_class):
  108. self.collection = {}
  109. self.index = index
  110. self.entry_class = entry_class
  111. def addCollectionEntry(self, row):
  112. id = row[self.index]
  113. if id not in self.collection:
  114. self.collection[id] = self.entry_class(row)
  115. self.collection[id].addEntry(row)
  116. def get_collection(self):
  117. return self.collection.values()
  118. def get_by_year(self):
  119. by_year = {}
  120. for item in self.get_collection():
  121. item_year = item.get_year()
  122. if item_year not in by_year:
  123. by_year[item_year] = []
  124. by_year[item_year].append(item)
  125. return by_year
  126. def check_pc(self):
  127. pc_missing = set()
  128. for item in self.get_collection():
  129. pc_missing.update(item.getMissingPC())
  130. return pc_missing
  131. class Sell(Entry):
  132. @staticmethod
  133. def clean(row):
  134. return True
  135. def __init__(self, row):
  136. super(Sell, self).__init__(row)
  137. self.entries = {}
  138. def addEntry(self, row):
  139. id = row['fd_rowid']
  140. self.entries[id] = SellEntry(row)
  141. def get_year(self):
  142. return self.get_accounting_year('f_datef')
  143. def getMissingPC(self):
  144. pc_missing = []
  145. if self.cells['s_code_compta'] == "":
  146. pc_missing.append("tiers: %s %s" % (self.cells['s_nom'], self.cells['s_ape']))
  147. for entry in self.entries.values():
  148. if entry.get_product_account_code() == self.pc_default_produit:
  149. if self.cells['fd_description'] != "":
  150. description = self.cells['fd_description'].splitlines()[0]
  151. else:
  152. description = self.cells['fd_description']
  153. pc_missing.append("produit: %s %s" % (self.cells['s_nom'], description))
  154. return pc_missing
  155. def check(self):
  156. total_ttc = self.cells['f_total_ttc']
  157. total_tva = self.cells['f_tva']
  158. total_ht = self.cells['f_total']
  159. for entry in self.entries.values():
  160. total_ttc -= entry.cells['fd_total_ttc']
  161. total_tva -= entry.cells['fd_total_tva']
  162. total_ht -= entry.cells['fd_total_ht']
  163. if total_ttc > 1e-10:
  164. print "Erreur dans l'écriture %s: total ttc = %s" % (self.cells['f_facnumber'],total_ttc)
  165. if total_ht > 1e-10:
  166. print "Erreur dans l'écriture %s: total ht = %s" % (self.cells['f_facnumber'],total_ht)
  167. if total_tva > 1e-10:
  168. print "Erreur dans l'écriture %s: total tva = %s" % (self.cells['f_facnumber'],total_tva)
  169. def get_ledger(self):
  170. self.check()
  171. s = ""
  172. s += "%(date)s %(description)s\n" % {
  173. 'date': self.cells['f_datef'].strftime("%Y/%m/%d"),
  174. 'description': self.cells['f_facnumber'] + " - " + self.cells['s_nom'],
  175. }
  176. s_code = self.cells['s_code_compta']
  177. if s_code == "":
  178. s_code = self.pc_default_client
  179. s += " %(compte_tiers)s %(amount_ttc)s\n" % {
  180. 'compte_tiers': settings.get_ledger_account(s_code),
  181. 'amount_ttc': -self.cells['f_total_ttc'],
  182. }
  183. if float(self.cells['f_tva']) != 0:
  184. tva_deductible_account = settings.get('PC_REFS')['tva_deductible']
  185. s += " %(compte_tva_deductible)s %(amount_tva)s\n" % {
  186. 'compte_tva_deductible': settings.get_ledger_account(tva_deductible_account),
  187. 'amount_tva': self.cells['f_tva'],
  188. }
  189. for entry in self.entries.values():
  190. p_code = entry.get_product_account_code()
  191. s += " %(compte_produit)s %(amount_ht)s\n" % {
  192. 'compte_produit': settings.get_ledger_account(p_code),
  193. 'amount_ht': entry.cells['fd_total_ht']
  194. }
  195. return s
  196. class SellEntry(Entry):
  197. # ['f_date_lim_reglement',
  198. # 's_phone',
  199. # 'f_total_ttc',
  200. # 'c_code',
  201. # 's_zip',
  202. # 's_siren',
  203. # 's_tva_intra',
  204. # 'f_total',
  205. # 'f_note_public',
  206. # 's_siret',
  207. # 'fd_date_start',
  208. # 'fd_label',
  209. # 'f_facnumber',
  210. # 'p_accountancy_code_sell',
  211. # 'fd_date_end',
  212. # 'f_datec',
  213. # 's_idprof4',
  214. # 'fd_rowid',
  215. # 'fd_total_tva',
  216. # 'fd_total_ttc',
  217. # 's_nom',
  218. # 'f_paye',
  219. # 'fd_special_code',
  220. # 'f_datef',
  221. # 'p_ref',
  222. # 's_address',
  223. # 's_town',
  224. # 'fd_description',
  225. # 'fd_product_type',
  226. # 's_rowid',
  227. # 'fd_subprice',
  228. # 'fd_fk_product',
  229. # 's_ape',
  230. # 's_code_compta_fournisseur',
  231. # 'p_label',
  232. # 'fd_total_ht',
  233. # 'f_tva',
  234. # 'fd_qty',
  235. # 'fd_tva_tx',
  236. # 's_code_compta',
  237. # 'f_rowid',
  238. # 'f_fk_statut',
  239. # 'f_note_private']
  240. def get_product_account_code(self):
  241. p_code = self.cells['p_accountancy_code_sell']
  242. if p_code == "":
  243. p_code = self.cells['a_account_number']
  244. if p_code == "":
  245. p_code = self.pc_default_produit
  246. return p_code
  247. class Supplier(Entry):
  248. @staticmethod
  249. def clean(row):
  250. return True
  251. def __init__(self, row):
  252. super(Supplier, self).__init__(row)
  253. self.entries = {}
  254. def addEntry(self, row):
  255. id = row['fd_rowid']
  256. self.entries[id] = SupplierEntry(row)
  257. def check(self):
  258. total_ttc = self.cells['f_total_ttc']
  259. total_tva = self.cells['f_total_tva']
  260. total_ht = self.cells['f_total_ht']
  261. for entry in self.entries.values():
  262. total_ttc -= entry.cells['fd_total_ttc']
  263. total_tva -= entry.cells['fd_tva']
  264. total_ht -= entry.cells['fd_total_ht']
  265. if total_ttc > 1e-10:
  266. print "Erreur dans l'écriture %s: total ttc = %s" % (self.cells['f_ref_supplier'],total_ttc)
  267. if total_ht > 1e-10:
  268. print "Erreur dans l'écriture %s: total ht = %s" % (self.cells['f_ref_supplier'],total_ht)
  269. if total_tva > 1e-10:
  270. print "Erreur dans l'écriture %s: total tva = %s" % (self.cells['f_ref_supplier'],total_tva)
  271. def get_year(self):
  272. return self.get_accounting_year('f_datef')
  273. def getMissingPC(self):
  274. pc_missing = []
  275. if self.cells['s_code_supplier'] == "":
  276. pc_missing.append("tiers: %s %s" % (self.cells['s_nom'], self.cells['s_ape']))
  277. for entry in self.entries.values():
  278. if entry.get_product_account_code() == self.pc_default_charge:
  279. pc_missing.append("achat: %s - %s" % (self.cells['f_ref_supplier'], self.cells['fd_description'].splitlines()[0]))
  280. return pc_missing
  281. def get_ledger(self):
  282. self.check()
  283. s = ""
  284. s += "%(date)s %(description)s\n" % {
  285. 'date': self.cells['f_datef'].strftime("%Y/%m/%d"),
  286. 'description': self.cells['f_ref_supplier'] + " - " + self.cells['s_nom'],
  287. }
  288. s_code = self.cells['s_code_supplier']
  289. if s_code == "":
  290. s_code = self.pc_default_supplier
  291. s += " %(compte_tiers)s %(amount_ttc)s\n" % {
  292. 'compte_tiers': settings.get_ledger_account(s_code),
  293. 'amount_ttc': self.cells['f_total_ttc'],
  294. }
  295. if self.cells['f_total_tva'] != 0:
  296. tva_deductible_account = settings.get('PC_REFS')['tva_collecte']
  297. s += " %(compte_tva_deductible)s %(amount_tva)s\n" % {
  298. 'compte_tva_deductible': settings.get_ledger_account(tva_deductible_account),
  299. 'amount_tva': -self.cells['f_total_tva'],
  300. }
  301. for entry in self.entries.values():
  302. p_code = entry.get_product_account_code()
  303. s += " %(compte_produit)s %(amount_ht)s\n" % {
  304. 'compte_produit': settings.get_ledger_account(p_code),
  305. 'amount_ht': -entry.cells['fd_total_ht']
  306. }
  307. return s
  308. class SupplierEntry(Entry):
  309. # ['f_total_ttc',
  310. # 's_phone',
  311. # 'c_code',
  312. # 'f_total_tva',
  313. # 's_zip',
  314. # 's_siren',
  315. # 's_tva_intra',
  316. # 'f_ref',
  317. # 'f_note_public',
  318. # 's_siret',
  319. # 'f_ref_supplier',
  320. # 'f_datec',
  321. # 's_idprof5',
  322. # 's_idprof4',
  323. # 's_idprof6',
  324. # 'p_accountancy_code_buy',
  325. # 'fd_rowid',
  326. # 'f_total_ht',
  327. # 'fd_total_ttc',
  328. # 's_nom',
  329. # 'fd_tva',
  330. # 'f_paye',
  331. # 'f_datef',
  332. # 'p_ref',
  333. # 's_address',
  334. # 's_town',
  335. # 'fd_description',
  336. # 'fd_product_type',
  337. # 's_rowid',
  338. # 'fd_fk_product',
  339. # 's_ape',
  340. # 'p_label',
  341. # 'fd_total_ht',
  342. # 'f_fk_statut',
  343. # 'fd_qty',
  344. # 'fd_tva_tx',
  345. # 'f_rowid',
  346. # 'fd_remise_percent']
  347. def get_product_account_code(self):
  348. p_code = self.cells['p_accountancy_code_buy']
  349. if p_code == "":
  350. p_code = self.cells['a_account_number']
  351. if p_code == "":
  352. p_code = self.pc_default_charge
  353. return p_code
  354. class DolibarrSQL(object):
  355. def __init__(self, mysql_host, mysql_port, mysql_database, mysql_user, mysql_password):
  356. self.mysql_database = mysql_database
  357. self.mysql_host = mysql_host
  358. self.mysql_password = mysql_password
  359. self.mysql_user = mysql_user
  360. self.mysql_port = mysql_port
  361. self.queries = MYSQL_QUERIES
  362. def connect(self):
  363. try:
  364. self.cnx = mdb.connect(self.mysql_host, self.mysql_user, self.mysql_password, self.mysql_database, port = self.mysql_port)
  365. self.cursor = self.cnx.cursor()
  366. except mdb.Error, e:
  367. print "Error %d: %s" % (e.args[0], e.args[1])
  368. sys.exit(1)
  369. def disconnect(self):
  370. try:
  371. self.cnx.close()
  372. except mdb.Error, e:
  373. print "Error %d: %s" % (e.args[0], e.args[1])
  374. sys.exit(1)
  375. def get_bank_entries(self):
  376. return self.get_entries(self.queries['bank'], 'b_rowid', BankEntry)
  377. def get_sell_entries(self):
  378. return self.get_entries(self.queries['sells'], 'f_facnumber', Sell)
  379. def get_supplier_entries(self):
  380. return self.get_entries(self.queries['suppliers'], 'f_rowid', Supplier)
  381. def get_entries(self, query, index, entry_class):
  382. cur = self.cnx.cursor(cursorclass=mdb.cursors.DictCursor)
  383. cur.execute(query)
  384. rows = cur.fetchall()
  385. collection = EntryCollection(index, entry_class)
  386. for row in rows:
  387. if entry_class.clean(row):
  388. collection.addCollectionEntry(row)
  389. return collection
  390. class Writer(object):
  391. output_files = settings.get('OUTPUT_FILES')
  392. output_dir = settings.get('OUTPUT_DIR')
  393. @staticmethod
  394. def write(journal, entries):
  395. filename = Writer.output_files[journal]
  396. output = os.path.join(Writer.output_dir, filename)
  397. entries_by_year = entries.get_by_year()
  398. for year in entries_by_year:
  399. output_file = output.replace("%year%", year)
  400. output_dir = os.path.dirname(output_file)
  401. if not os.path.exists(output_dir):
  402. os.makedirs(os.path.dirname(output_file))
  403. elif not os.path.isdir(output_dir):
  404. print "Error: %s is not a dir\n" % (output_dir)
  405. raise os.error()
  406. f = codecs.open(output_file, 'w', 'utf-8')
  407. for entry in entries_by_year[year]:
  408. f.write(entry.get_ledger())
  409. f.write("\n")
  410. f.close()
  411. @staticmethod
  412. def write_hreport_plan():
  413. pc_names = settings.get('PC_NAMES')
  414. pc_descriptions = settings.get('PC_DESCRIPTIONS')
  415. filename = Writer.output_files['pc']
  416. output_file = os.path.join(Writer.output_dir, filename)
  417. f = codecs.open(output_file, 'w', 'utf-8')
  418. for pc in sorted(pc_names.keys()):
  419. name = pc_names[pc]
  420. if pc in pc_descriptions:
  421. desc = pc_descriptions[pc]
  422. else:
  423. desc = name
  424. s = "%s %s %s\n" %(name.ljust(80), pc.ljust(12), desc)
  425. f.write(s)
  426. f.close()