You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

374 lines
15 KiB

  1. # -*- coding: utf-8 -*-
  2. ##############################################################################
  3. #
  4. # Author Joel Grand-Guillaume and Vincent Renaville Copyright 2013 Camptocamp SA
  5. # CSV data formating inspired from http://docs.python.org/2.7/library/csv.html?highlight=csv#examples
  6. #
  7. # This program is free software: you can redistribute it and/or modify
  8. # it under the terms of the GNU Affero General Public License as
  9. # published by the Free Software Foundation, either version 3 of the
  10. # License, or (at your option) any later version.
  11. #
  12. # This program is distributed in the hope that it will be useful,
  13. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. # GNU Affero General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU Affero General Public License
  18. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  19. #
  20. ##############################################################################
  21. import time
  22. import StringIO
  23. import cStringIO
  24. import base64
  25. import csv
  26. import codecs
  27. from openerp.osv import orm, fields
  28. from openerp.tools.translate import _
  29. class AccountUnicodeWriter(object):
  30. """
  31. A CSV writer which will write rows to CSV file "f",
  32. which is encoded in the given encoding.
  33. """
  34. def __init__(self, f, dialect=csv.excel, encoding="utf-8", **kwds):
  35. # Redirect output to a queue
  36. self.queue = cStringIO.StringIO()
  37. # created a writer with Excel formating settings
  38. self.writer = csv.writer(self.queue, dialect=dialect, **kwds)
  39. self.stream = f
  40. self.encoder = codecs.getincrementalencoder(encoding)()
  41. def writerow(self, row):
  42. #we ensure that we do not try to encode none or bool
  43. row = [x or u'' for x in row]
  44. encoded_row = []
  45. for c in row:
  46. if type(c) == unicode:
  47. encoded_row.append(c.encode("utf-8"))
  48. else:
  49. encoded_row.append(c)
  50. self.writer.writerow(encoded_row)
  51. # Fetch UTF-8 output from the queue ...
  52. data = self.queue.getvalue()
  53. data = data.decode("utf-8")
  54. # ... and reencode it into the target encoding
  55. data = self.encoder.encode(data)
  56. # write to the target stream
  57. self.stream.write(data)
  58. # empty queue
  59. self.queue.truncate(0)
  60. def writerows(self, rows):
  61. for row in rows:
  62. self.writerow(row)
  63. class AccountCSVExport(orm.TransientModel):
  64. _name = 'account.csv.export'
  65. _description = 'Export Accounting'
  66. _columns = {
  67. 'data': fields.binary('CSV',readonly=True),
  68. 'company_id': fields.many2one('res.company', 'Company', invisible=True),
  69. 'fiscalyear_id': fields.many2one('account.fiscalyear', 'Fiscalyear', required=True),
  70. 'periods': fields.many2many('account.period','rel_wizard_period','wizard_id','period_id','Periods',help='All periods in the fiscal year if empty'),
  71. 'journal_ids': fields.many2many('account.journal','rel_wizard_journal','wizard_id','journal_id','Journals', help='If empty, use all journals, only used for journal entries'),
  72. 'export_filename': fields.char('Export CSV Filename', size=128),
  73. }
  74. def _get_company_default(self, cr, uid, context=None):
  75. comp_obj = self.pool['res.company']
  76. return comp_obj._company_default_get(cr, uid, 'account.fiscalyear', context=context)
  77. def _get_fiscalyear_default(self, cr, uid, context=None):
  78. fiscalyear_obj = self.pool['account.fiscalyear']
  79. context['company_id'] = self._get_company_default(cr, uid, context)
  80. return fiscalyear_obj.find(cr,uid,dt=None,exception=True, context=context)
  81. _defaults = {'company_id': _get_company_default,
  82. 'fiscalyear_id' : _get_fiscalyear_default,
  83. 'export_filename' : 'account_export.csv'}
  84. def action_manual_export_account(self, cr, uid, ids, context=None):
  85. this = self.browse(cr, uid, ids)[0]
  86. rows = self.get_data(cr, uid, ids,"account", context)
  87. file_data = StringIO.StringIO()
  88. try:
  89. writer = AccountUnicodeWriter(file_data)
  90. writer.writerows(rows)
  91. file_value = file_data.getvalue()
  92. self.write(cr, uid, ids,
  93. {'data': base64.encodestring(file_value)},
  94. context=context)
  95. finally:
  96. file_data.close()
  97. return {
  98. 'type': 'ir.actions.act_window',
  99. 'res_model': 'account.csv.export',
  100. 'view_mode': 'form',
  101. 'view_type': 'form',
  102. 'res_id': this.id,
  103. 'views': [(False, 'form')],
  104. 'target': 'new',
  105. }
  106. def _get_header_account(self, cr, uid, ids, context=None):
  107. return [_(u'CODE'),
  108. _(u'NAME'),
  109. _(u'DEBIT'),
  110. _(u'CREDIT'),
  111. _(u'BALANCE'),
  112. ]
  113. def _get_rows_account(self, cr, uid, ids, fiscalyear_id,period_range_ids,journal_ids,company_id,context=None):
  114. """
  115. Return list to generate rows of the CSV file
  116. """
  117. cr.execute("""
  118. select ac.code,ac.name,
  119. sum(debit) as sum_debit,sum(credit) as sum_credit,sum(debit) - sum(credit) as balance
  120. from account_move_line as aml,account_account as ac
  121. where aml.account_id = ac.id
  122. and period_id in %(period_ids)s
  123. and aml.company_id = %(company_id)s
  124. group by ac.id,ac.code,ac.name
  125. order by ac.code
  126. """,
  127. {'fiscalyear_id': fiscalyear_id,'company_id':company_id,'period_ids':tuple(period_range_ids)}
  128. )
  129. res = cr.fetchall()
  130. rows = []
  131. for line in res:
  132. rows.append(list(line))
  133. return rows
  134. def action_manual_export_analytic(self, cr, uid, ids, context=None):
  135. this = self.browse(cr, uid, ids)[0]
  136. rows = self.get_data(cr, uid, ids,"analytic", context)
  137. file_data = StringIO.StringIO()
  138. try:
  139. writer = AccountUnicodeWriter(file_data)
  140. writer.writerows(rows)
  141. file_value = file_data.getvalue()
  142. self.write(cr, uid, ids,
  143. {'data': base64.encodestring(file_value)},
  144. context=context)
  145. finally:
  146. file_data.close()
  147. return {
  148. 'type': 'ir.actions.act_window',
  149. 'res_model': 'account.csv.export',
  150. 'view_mode': 'form',
  151. 'view_type': 'form',
  152. 'res_id': this.id,
  153. 'views': [(False, 'form')],
  154. 'target': 'new',
  155. }
  156. def _get_header_analytic(self, cr, uid, ids, context=None):
  157. return [_(u'ANALYTIC CODE'),
  158. _(u'ANALYTIC NAME'),
  159. _(u'CODE'),
  160. _(u'ACCOUNT NAME'),
  161. _(u'DEBIT'),
  162. _(u'CREDIT'),
  163. _(u'BALANCE'),
  164. ]
  165. def _get_rows_analytic(self, cr, uid, ids, fiscalyear_id,period_range_ids,journal_ids,company_id,context=None):
  166. """
  167. Return list to generate rows of the CSV file
  168. """
  169. cr.execute(""" select aac.code as analytic_code,aac.name as analytic_name,ac.code,ac.name,
  170. sum(debit) as sum_debit,sum(credit) as sum_credit,sum(debit) - sum(credit) as balance
  171. from account_move_line
  172. left outer join account_analytic_account as aac
  173. on (account_move_line.analytic_account_id = aac.id)
  174. inner join account_account as ac
  175. on account_move_line.account_id = ac.id
  176. and account_move_line.period_id in %(period_ids)s
  177. and account_move_line.company_id = %(company_id)s
  178. group by aac.id,aac.code,aac.name,ac.id,ac.code,ac.name
  179. order by aac.code
  180. """,
  181. {'fiscalyear_id': fiscalyear_id,'company_id':company_id,'period_ids':tuple(period_range_ids)}
  182. )
  183. res = cr.fetchall()
  184. rows = []
  185. for line in res:
  186. rows.append(list(line))
  187. return rows
  188. def action_manual_export_journal_entries(self, cr, uid, ids, context=None):
  189. """
  190. Here we use TemporaryFile to avoid full filling the OpenERP worker Memory
  191. We also write the data to the wizard with SQL query as write seams to use
  192. too much memory as well.
  193. Those improvements permitted to improve the export from a 100k line to 200k lines
  194. with default `limit_memory_hard = 805306368` (768MB) with more lines,
  195. you might encounter a MemoryError when trying to download the file even
  196. if it has been generated.
  197. To be able to export bigger volume of data, it is advised to set
  198. limit_memory_hard to 2097152000 (2 GB) to generate the file and let
  199. OpenERP load it in the wizard when trying to download it.
  200. Tested with up to a generation of 700k entry lines
  201. """
  202. this = self.browse(cr, uid, ids)[0]
  203. rows = self.get_data(cr, uid, ids, "journal_entries", context)
  204. file_data = StringIO.StringIO()
  205. try:
  206. writer = AccountUnicodeWriter(file_data)
  207. writer.writerows(rows)
  208. file_value = file_data.getvalue()
  209. self.write(cr, uid, ids,
  210. {'data': base64.encodestring(file_value)},
  211. context=context)
  212. finally:
  213. file_data.close()
  214. return {
  215. 'type': 'ir.actions.act_window',
  216. 'res_model': 'account.csv.export',
  217. 'view_mode': 'form',
  218. 'view_type': 'form',
  219. 'res_id': this.id,
  220. 'views': [(False, 'form')],
  221. 'target': 'new',
  222. }
  223. def _get_header_journal_entries(self, cr, uid, ids, context=None):
  224. return [
  225. # Standard Sage export fields
  226. _(u'DATE'),
  227. _(u'JOURNAL CODE'),
  228. _(u'ACCOUNT CODE'),
  229. _(u'PARTNER NAME'),
  230. _(u'REF'),
  231. _(u'DESCRIPTION'),
  232. _(u'DEBIT'),
  233. _(u'CREDIT'),
  234. _(u'FULL RECONCILE'),
  235. _(u'PARTIAL RECONCILE'),
  236. _(u'ANALYTIC ACCOUNT CODE'),
  237. # Other fields
  238. _(u'ENTRY NUMBER'),
  239. _(u'ACCOUNT NAME'),
  240. _(u'BALANCE'),
  241. _(u'AMOUNT CURRENCY'),
  242. _(u'CURRENCY'),
  243. _(u'ANALYTIC ACCOUNT NAME'),
  244. _(u'JOURNAL'),
  245. _(u'MONTH'),
  246. _(u'FISCAL YEAR'),
  247. _(u'TAX CODE CODE'),
  248. _(u'TAX CODE NAME'),
  249. _(u'TAX AMOUNT'),
  250. ]
  251. def _get_rows_journal_entries(self, cr, uid, ids,
  252. fiscalyear_id,
  253. period_range_ids,
  254. journal_ids,
  255. company_id,
  256. context=None):
  257. print period_range_ids
  258. print journal_ids
  259. """
  260. Create a generator of rows of the CSV file
  261. """
  262. cr.execute("""
  263. SELECT
  264. account_move_line.date AS date,
  265. account_journal.name as journal,
  266. account_account.code AS account_code,
  267. res_partner.name AS partner_name,
  268. account_move_line.ref AS ref,
  269. account_move_line.name AS description,
  270. account_move_line.debit AS debit,
  271. account_move_line.credit AS credit,
  272. account_move_reconcile.name as full_reconcile,
  273. account_move_line.reconcile_partial_id AS partial_reconcile_id,
  274. account_analytic_account.code AS analytic_account_code,
  275. account_move.name AS entry_number,
  276. account_account.name AS account_name,
  277. account_move_line.debit - account_move_line.credit AS balance,
  278. account_move_line.amount_currency AS amount_currency,
  279. res_currency.name AS currency,
  280. account_analytic_account.name AS analytic_account_name,
  281. account_journal.name as journal,
  282. account_period.code AS month,
  283. account_fiscalyear.name as fiscal_year,
  284. account_tax_code.code AS aml_tax_code_code,
  285. account_tax_code.name AS aml_tax_code_name,
  286. account_move_line.tax_amount AS aml_tax_amount
  287. FROM
  288. public.account_move_line
  289. JOIN account_account on (account_account.id=account_move_line.account_id)
  290. JOIN account_period on (account_period.id=account_move_line.period_id)
  291. JOIN account_fiscalyear on (account_fiscalyear.id=account_period.fiscalyear_id)
  292. JOIN account_journal on (account_journal.id = account_move_line.journal_id)
  293. LEFT JOIN res_currency on (res_currency.id=account_move_line.currency_id)
  294. LEFT JOIN account_move_reconcile on (account_move_reconcile.id = account_move_line.reconcile_id)
  295. LEFT JOIN res_partner on (res_partner.id=account_move_line.partner_id)
  296. LEFT JOIN account_move on (account_move.id=account_move_line.move_id)
  297. LEFT JOIN account_tax on (account_tax.id=account_move_line.account_tax_id)
  298. LEFT JOIN account_tax_code on (account_tax_code.id=account_move_line.tax_code_id)
  299. LEFT JOIN account_analytic_account on (account_analytic_account.id=account_move_line.analytic_account_id)
  300. WHERE account_period.id IN %(period_ids)s
  301. AND account_journal.id IN %(journal_ids)s
  302. AND account_move_line.company_id = %(company_id)s
  303. ORDER BY account_move_line.date
  304. """,
  305. {'period_ids': tuple(period_range_ids), 'journal_ids': tuple(journal_ids), 'company_id': company_id}
  306. )
  307. while 1:
  308. # http://initd.org/psycopg/docs/cursor.html#cursor.fetchmany
  309. # Set cursor.arraysize to minimize network round trips
  310. cr.arraysize=100
  311. rows = cr.fetchmany()
  312. if not rows:
  313. break
  314. for row in rows:
  315. yield row
  316. def get_data(self, cr, uid, ids,result_type,context=None):
  317. get_header_func = getattr(self,("_get_header_%s"%(result_type)), None)
  318. get_rows_func = getattr(self,("_get_rows_%s"%(result_type)), None)
  319. form = self.browse(cr, uid, ids[0], context=context)
  320. fiscalyear_id = form.fiscalyear_id.id
  321. user_obj = self.pool.get('res.users')
  322. company_id = user_obj.browse(cr, uid, uid).company_id.id
  323. if form.periods:
  324. period_range_ids = [x.id for x in form.periods]
  325. else:
  326. # If not period selected , we take all periods
  327. p_obj = self.pool.get("account.period")
  328. period_range_ids = p_obj.search(cr,uid,[('fiscalyear_id','=',fiscalyear_id)],context=context)
  329. journal_ids = None
  330. if form.journal_ids:
  331. journal_ids = [x.id for x in form.journal_ids]
  332. else:
  333. j_obj = self.pool.get("account.journal")
  334. journal_ids = j_obj.search(cr, uid, [], context=context)
  335. rows = []
  336. rows.append(get_header_func(cr, uid, ids, context=context))
  337. rows.extend(get_rows_func(cr, uid, ids, fiscalyear_id,period_range_ids,journal_ids,company_id, context=context))
  338. return rows