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.

586 lines
20 KiB

  1. # -*- coding: utf-8 -*-
  2. # © 2016 Julien Coux (Camptocamp)
  3. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
  4. from odoo import models, fields, api
  5. class TrialBalanceReport(models.TransientModel):
  6. """ Here, we just define class fields.
  7. For methods, go more bottom at this file.
  8. The class hierarchy is :
  9. * TrialBalanceReport
  10. ** TrialBalanceReportAccount
  11. *** TrialBalanceReportPartner
  12. If "show_partner_details" is selected
  13. """
  14. _name = 'report_trial_balance_qweb'
  15. _inherit = 'report_qweb_abstract'
  16. # Filters fields, used for data computation
  17. date_from = fields.Date()
  18. date_to = fields.Date()
  19. fy_start_date = fields.Date()
  20. only_posted_moves = fields.Boolean()
  21. hide_account_at_0 = fields.Boolean()
  22. foreign_currency = fields.Boolean()
  23. company_id = fields.Many2one(comodel_name='res.company')
  24. filter_account_ids = fields.Many2many(comodel_name='account.account')
  25. filter_partner_ids = fields.Many2many(comodel_name='res.partner')
  26. filter_journal_ids = fields.Many2many(comodel_name='account.journal')
  27. show_partner_details = fields.Boolean()
  28. hierarchy_on = fields.Selection(
  29. [('computed', 'Computed Accounts'),
  30. ('relation', 'Child Accounts'),
  31. ('none', 'No hierarchy')],
  32. string='Hierarchy On',
  33. required=True,
  34. default='computed',
  35. help="""Computed Accounts: Use when the account group have codes
  36. that represent prefixes of the actual accounts.\n
  37. Child Accounts: Use when your account groups are hierarchical.\n
  38. No hierarchy: Use to display just the accounts, without any grouping.
  39. """, period_balance=fields.Float(digits=(16, 2))
  40. )
  41. limit_hierarchy_level = fields.Boolean('Limit hierarchy levels')
  42. show_hierarchy_level = fields.Integer('Hierarchy Levels to display',
  43. default=1)
  44. hide_parent_hierarchy_level = fields.Boolean(
  45. 'Do not display parent levels', default=False)
  46. # General Ledger Report Data fields,
  47. # used as base for compute the data reports
  48. general_ledger_id = fields.Many2one(
  49. comodel_name='report_general_ledger_qweb'
  50. )
  51. # Data fields, used to browse report data
  52. account_ids = fields.One2many(
  53. comodel_name='report_trial_balance_qweb_account',
  54. inverse_name='report_id'
  55. )
  56. class TrialBalanceReportAccount(models.TransientModel):
  57. _name = 'report_trial_balance_qweb_account'
  58. _inherit = 'report_qweb_abstract'
  59. _order = 'sequence, code ASC, name'
  60. report_id = fields.Many2one(
  61. comodel_name='report_trial_balance_qweb',
  62. ondelete='cascade',
  63. index=True
  64. )
  65. hide_line = fields.Boolean(compute='_compute_hide_line')
  66. # Data fields, used to keep link with real object
  67. sequence = fields.Integer(index=True, default=1)
  68. level = fields.Integer(index=True, default=1)
  69. # Data fields, used to keep link with real object
  70. account_id = fields.Many2one(
  71. 'account.account',
  72. index=True
  73. )
  74. account_group_id = fields.Many2one(
  75. 'account.group',
  76. index=True
  77. )
  78. parent_id = fields.Many2one(
  79. 'account.group',
  80. index=True
  81. )
  82. child_account_ids = fields.Char(
  83. string="Accounts")
  84. compute_account_ids = fields.Many2many(
  85. 'account.account',
  86. string="Accounts", store=True)
  87. # Data fields, used for report display
  88. code = fields.Char()
  89. name = fields.Char()
  90. initial_balance = fields.Float(digits=(16, 2))
  91. initial_balance_foreign_currency = fields.Float(digits=(16, 2))
  92. debit = fields.Float(digits=(16, 2))
  93. credit = fields.Float(digits=(16, 2))
  94. period_balance = fields.Float(digits=(16, 2))
  95. currency_id = fields.Many2one(comodel_name='res.currency')
  96. final_balance = fields.Float(digits=(16, 2))
  97. final_balance_foreign_currency = fields.Float(digits=(16, 2))
  98. # Data fields, used to browse report data
  99. partner_ids = fields.One2many(
  100. comodel_name='report_trial_balance_qweb_partner',
  101. inverse_name='report_account_id'
  102. )
  103. @api.multi
  104. def _compute_hide_line(self):
  105. for rec in self:
  106. report = rec.report_id
  107. rec.hide_line = False
  108. if report.hide_account_at_0 and (
  109. not rec.initial_balance and
  110. not rec.final_balance and
  111. not rec.debit and
  112. not rec.credit):
  113. rec.hide_line = True
  114. elif report.limit_hierarchy_level and report.show_hierarchy_level:
  115. if report.hide_parent_hierarchy_level:
  116. distinct_level = rec.level != report.show_hierarchy_level
  117. if rec.account_group_id and distinct_level:
  118. rec.hide_line = True
  119. elif rec.level and distinct_level:
  120. rec.hide_line = True
  121. elif not report.hide_parent_hierarchy_level and \
  122. rec.level > report.show_hierarchy_level:
  123. rec.hide_line = True
  124. class TrialBalanceReportPartner(models.TransientModel):
  125. _name = 'report_trial_balance_qweb_partner'
  126. _inherit = 'report_qweb_abstract'
  127. report_account_id = fields.Many2one(
  128. comodel_name='report_trial_balance_qweb_account',
  129. ondelete='cascade',
  130. index=True
  131. )
  132. # Data fields, used to keep link with real object
  133. partner_id = fields.Many2one(
  134. 'res.partner',
  135. index=True
  136. )
  137. # Data fields, used for report display
  138. name = fields.Char()
  139. initial_balance = fields.Float(digits=(16, 2))
  140. initial_balance_foreign_currency = fields.Float(digits=(16, 2))
  141. debit = fields.Float(digits=(16, 2))
  142. credit = fields.Float(digits=(16, 2))
  143. period_balance = fields.Float(digits=(16, 2))
  144. currency_id = fields.Many2one(comodel_name='res.currency')
  145. final_balance = fields.Float(digits=(16, 2))
  146. final_balance_foreign_currency = fields.Float(digits=(16, 2))
  147. @api.model
  148. def _generate_order_by(self, order_spec, query):
  149. """Custom order to display "No partner allocated" at last position."""
  150. return """
  151. ORDER BY
  152. CASE
  153. WHEN "report_trial_balance_qweb_partner"."partner_id" IS NOT NULL
  154. THEN 0
  155. ELSE 1
  156. END,
  157. "report_trial_balance_qweb_partner"."name"
  158. """
  159. class TrialBalanceReportCompute(models.TransientModel):
  160. """ Here, we just define methods.
  161. For class fields, go more top at this file.
  162. """
  163. _inherit = 'report_trial_balance_qweb'
  164. @api.multi
  165. def print_report(self, report_type):
  166. self.ensure_one()
  167. if report_type == 'xlsx':
  168. report_name = 'account_financial_report_qweb.' \
  169. 'report_trial_balance_xlsx'
  170. else:
  171. report_name = 'account_financial_report_qweb.' \
  172. 'report_trial_balance_qweb'
  173. return self.env['report'].get_action(docids=self.ids,
  174. report_name=report_name)
  175. def _get_html(self):
  176. result = {}
  177. rcontext = {}
  178. context = dict(self.env.context)
  179. report = self.browse(context.get('active_id'))
  180. if report:
  181. rcontext['o'] = report
  182. result['html'] = self.env.ref(
  183. 'account_financial_report_qweb.'
  184. 'report_trial_balance_html').render(rcontext)
  185. return result
  186. @api.model
  187. def get_html(self, given_context=None):
  188. return self._get_html()
  189. def _prepare_report_general_ledger(self, account_ids):
  190. self.ensure_one()
  191. return {
  192. 'date_from': self.date_from,
  193. 'date_to': self.date_to,
  194. 'only_posted_moves': self.only_posted_moves,
  195. 'foreign_currency': self.foreign_currency,
  196. 'company_id': self.company_id.id,
  197. 'filter_account_ids': [(6, 0, account_ids.ids)],
  198. 'filter_partner_ids': [(6, 0, self.filter_partner_ids.ids)],
  199. 'filter_journal_ids': [(6, 0, self.filter_journal_ids.ids)],
  200. 'fy_start_date': self.fy_start_date,
  201. }
  202. @api.multi
  203. def compute_data_for_report(self):
  204. self.ensure_one()
  205. # Compute General Ledger Report Data.
  206. # The data of Trial Balance Report
  207. # are based on General Ledger Report data.
  208. model = self.env['report_general_ledger_qweb']
  209. if self.filter_account_ids:
  210. account_ids = self.filter_account_ids
  211. else:
  212. account_ids = self.env['account.account'].search(
  213. [('company_id', '=', self.company_id.id)])
  214. self.general_ledger_id = model.create(
  215. self._prepare_report_general_ledger(account_ids)
  216. )
  217. self.general_ledger_id.compute_data_for_report(
  218. with_line_details=False, with_partners=self.show_partner_details
  219. )
  220. # Compute report data
  221. self._inject_account_values(account_ids)
  222. if self.show_partner_details:
  223. self._inject_partner_values()
  224. if not self.filter_account_ids:
  225. if self.hierarchy_on != 'none':
  226. self._inject_account_group_values()
  227. if self.hierarchy_on == 'computed':
  228. self._update_account_group_computed_values()
  229. else:
  230. self._update_account_group_child_values()
  231. self._update_account_sequence()
  232. self._add_account_group_account_values()
  233. self.refresh()
  234. if not self.filter_account_ids and self.hierarchy_on != 'none':
  235. self._compute_group_accounts()
  236. else:
  237. for line in self.account_ids:
  238. line.write({'level': 0})
  239. def _inject_account_values(self, account_ids):
  240. """Inject report values for report_trial_balance_qweb_account"""
  241. query_inject_account = """
  242. INSERT INTO
  243. report_trial_balance_qweb_account
  244. (
  245. report_id,
  246. create_uid,
  247. create_date,
  248. account_id,
  249. parent_id,
  250. code,
  251. name,
  252. initial_balance,
  253. debit,
  254. credit,
  255. period_balance,
  256. final_balance,
  257. currency_id,
  258. initial_balance_foreign_currency,
  259. final_balance_foreign_currency
  260. )
  261. SELECT
  262. %s AS report_id,
  263. %s AS create_uid,
  264. NOW() AS create_date,
  265. acc.id,
  266. acc.group_id,
  267. acc.code,
  268. acc.name,
  269. round(coalesce(rag.initial_balance, 0), 2) AS initial_balance,
  270. round(coalesce(rag.final_debit - rag.initial_debit, 0), 2) AS debit,
  271. round(coalesce(rag.final_credit - rag.initial_credit, 0), 2) AS credit,
  272. round(coalesce(rag.final_balance - rag.initial_balance, 0), 2) AS period_balance,
  273. round(coalesce(rag.final_balance, 0), 2) AS final_balance,
  274. rag.currency_id AS currency_id,
  275. round(coalesce(rag.initial_balance_foreign_currency, 0), 2)
  276. AS initial_balance_foreign_currency,
  277. round(coalesce(rag.final_balance_foreign_currency, 0), 2)
  278. AS final_balance_foreign_currency
  279. FROM
  280. account_account acc
  281. LEFT OUTER JOIN report_general_ledger_qweb_account AS rag
  282. ON rag.account_id = acc.id AND rag.report_id = %s
  283. WHERE
  284. acc.id in %s
  285. """
  286. query_inject_account_params = (
  287. self.id,
  288. self.env.uid,
  289. self.general_ledger_id.id,
  290. account_ids._ids,
  291. )
  292. self.env.cr.execute(query_inject_account, query_inject_account_params)
  293. def _inject_partner_values(self):
  294. """Inject report values for report_trial_balance_qweb_partner"""
  295. query_inject_partner = """
  296. INSERT INTO
  297. report_trial_balance_qweb_partner
  298. (
  299. report_account_id,
  300. create_uid,
  301. create_date,
  302. partner_id,
  303. name,
  304. initial_balance,
  305. initial_balance_foreign_currency,
  306. debit,
  307. credit,
  308. period_balance,
  309. final_balance,
  310. final_balance_foreign_currency
  311. )
  312. SELECT
  313. ra.id AS report_account_id,
  314. %s AS create_uid,
  315. NOW() AS create_date,
  316. rpg.partner_id,
  317. rpg.name,
  318. round(rpg.initial_balance, 2) AS initial_balance,
  319. round(rpg.initial_balance_foreign_currency, 2)
  320. AS initial_balance_foreign_currency,
  321. round(rpg.final_debit - rpg.initial_debit, 2) AS debit,
  322. round(rpg.final_credit - rpg.initial_credit, 2) AS credit,
  323. round(rpg.final_balance - rpg.initial_balance, 2) AS period_balance,
  324. round(rpg.final_balance, 2) AS final_balance,
  325. round(rpg.final_balance_foreign_currency, 2)
  326. AS final_balance_foreign_currency
  327. FROM
  328. report_general_ledger_qweb_partner rpg
  329. INNER JOIN
  330. report_general_ledger_qweb_account rag ON rpg.report_account_id = rag.id
  331. INNER JOIN
  332. report_trial_balance_qweb_account ra ON rag.code = ra.code
  333. WHERE
  334. rag.report_id = %s
  335. AND ra.report_id = %s
  336. """
  337. query_inject_partner_params = (
  338. self.env.uid,
  339. self.general_ledger_id.id,
  340. self.id,
  341. )
  342. self.env.cr.execute(query_inject_partner, query_inject_partner_params)
  343. def _inject_account_group_values(self):
  344. """Inject report values for report_trial_balance_qweb_account"""
  345. query_inject_account_group = """
  346. INSERT INTO
  347. report_trial_balance_qweb_account
  348. (
  349. report_id,
  350. create_uid,
  351. create_date,
  352. account_group_id,
  353. parent_id,
  354. code,
  355. name,
  356. sequence,
  357. level
  358. )
  359. SELECT
  360. %s AS report_id,
  361. %s AS create_uid,
  362. NOW() AS create_date,
  363. accgroup.id,
  364. accgroup.parent_id,
  365. coalesce(accgroup.code_prefix, accgroup.name),
  366. accgroup.name,
  367. accgroup.parent_left * 100000,
  368. accgroup.level
  369. FROM
  370. account_group accgroup"""
  371. query_inject_account_params = (
  372. self.id,
  373. self.env.uid,
  374. )
  375. self.env.cr.execute(query_inject_account_group,
  376. query_inject_account_params)
  377. def _update_account_group_child_values(self):
  378. """Compute values for report_trial_balance_qweb_account group
  379. in child."""
  380. query_update_account_group = """
  381. WITH computed AS (WITH RECURSIVE cte AS (
  382. SELECT account_group_id, code, account_group_id AS parent_id,
  383. initial_balance, initial_balance_foreign_currency, debit, credit,
  384. period_balance, final_balance, final_balance_foreign_currency
  385. FROM report_trial_balance_qweb_account
  386. WHERE report_id = %s
  387. GROUP BY report_trial_balance_qweb_account.id
  388. UNION ALL
  389. SELECT c.account_group_id, c.code, p.account_group_id,
  390. p.initial_balance, p.initial_balance_foreign_currency, p.debit, p.credit,
  391. p.period_balance, p.final_balance, p.final_balance_foreign_currency
  392. FROM cte c
  393. JOIN report_trial_balance_qweb_account p USING (parent_id)
  394. WHERE p.report_id = %s
  395. )
  396. SELECT account_group_id, code,
  397. sum(round(initial_balance, 2)) AS initial_balance,
  398. sum(round(initial_balance_foreign_currency, 2))
  399. AS initial_balance_foreign_currency,
  400. sum(round(debit, 2)) AS debit,
  401. sum(round(credit, 2)) AS credit,
  402. sum(round(debit, 2)) - sum(round(credit, 2)) AS period_balance,
  403. sum(round(final_balance, 2)) AS final_balance,
  404. sum(round(final_balance_foreign_currency, 2))
  405. AS final_balance_foreign_currency
  406. FROM cte
  407. GROUP BY cte.account_group_id, cte.code
  408. ORDER BY account_group_id
  409. )
  410. UPDATE report_trial_balance_qweb_account
  411. SET initial_balance = computed.initial_balance,
  412. initial_balance_foreign_currency =
  413. computed.initial_balance_foreign_currency,
  414. debit = computed.debit,
  415. credit = computed.credit,
  416. period_balance = computed.period_balance,
  417. final_balance = computed.final_balance,
  418. final_balance_foreign_currency =
  419. computed.final_balance_foreign_currency
  420. FROM computed
  421. WHERE report_trial_balance_qweb_account.account_group_id =
  422. computed.account_group_id
  423. AND report_trial_balance_qweb_account.report_id = %s
  424. """
  425. query_update_account_params = (self.id, self.id, self.id,)
  426. self.env.cr.execute(query_update_account_group,
  427. query_update_account_params)
  428. def _add_account_group_account_values(self):
  429. """Compute values for report_trial_balance_qweb_account group in
  430. child."""
  431. query_update_account_group = """
  432. DROP AGGREGATE IF EXISTS array_concat_agg(anyarray);
  433. CREATE AGGREGATE array_concat_agg(anyarray) (
  434. SFUNC = array_cat,
  435. STYPE = anyarray
  436. );
  437. WITH aggr AS(WITH computed AS (WITH RECURSIVE cte AS (
  438. SELECT account_group_id, account_group_id AS parent_id,
  439. ARRAY[account_id]::int[] as child_account_ids
  440. FROM report_trial_balance_qweb_account
  441. WHERE report_id = %s
  442. GROUP BY report_trial_balance_qweb_account.id
  443. UNION ALL
  444. SELECT c.account_group_id, p.account_group_id, ARRAY[p.account_id]::int[]
  445. FROM cte c
  446. JOIN report_trial_balance_qweb_account p USING (parent_id)
  447. WHERE p.report_id = %s
  448. )
  449. SELECT account_group_id,
  450. array_concat_agg(DISTINCT child_account_ids)::int[] as child_account_ids
  451. FROM cte
  452. GROUP BY cte.account_group_id, cte.child_account_ids
  453. ORDER BY account_group_id
  454. )
  455. SELECT account_group_id,
  456. array_concat_agg(DISTINCT child_account_ids)::int[]
  457. AS child_account_ids from computed
  458. GROUP BY account_group_id)
  459. UPDATE report_trial_balance_qweb_account
  460. SET child_account_ids = aggr.child_account_ids
  461. FROM aggr
  462. WHERE report_trial_balance_qweb_account.account_group_id =
  463. aggr.account_group_id
  464. AND report_trial_balance_qweb_account.report_id = %s
  465. """
  466. query_update_account_params = (self.id, self.id, self.id,)
  467. self.env.cr.execute(query_update_account_group,
  468. query_update_account_params)
  469. def _update_account_group_computed_values(self):
  470. """Compute values for report_trial_balance_qweb_account group
  471. in compute."""
  472. query_update_account_group = """
  473. WITH RECURSIVE accgroup AS
  474. (SELECT
  475. accgroup.id,
  476. sum(round(coalesce(ra.initial_balance, 0), 2)) as initial_balance,
  477. sum(round(coalesce(ra.initial_balance_foreign_currency, 0), 2))
  478. as initial_balance_foreign_currency,
  479. sum(round(coalesce(ra.debit, 0), 2)) as debit,
  480. sum(round(coalesce(ra.credit, 0), 2)) as credit,
  481. sum(round(coalesce(ra.debit, 0), 2)) -
  482. sum(round(coalesce(ra.credit, 0), 2))
  483. as period_balance,
  484. sum(round(coalesce(ra.final_balance, 0), 2)) as final_balance,
  485. sum(round(coalesce(ra.final_balance_foreign_currency, 0), 2))
  486. as final_balance_foreign_currency
  487. FROM
  488. account_group accgroup
  489. LEFT OUTER JOIN account_account AS acc
  490. ON strpos(acc.code, accgroup.code_prefix) = 1
  491. LEFT OUTER JOIN report_trial_balance_qweb_account AS ra
  492. ON ra.account_id = acc.id
  493. WHERE ra.report_id = %s
  494. GROUP BY accgroup.id
  495. )
  496. UPDATE report_trial_balance_qweb_account
  497. SET initial_balance = accgroup.initial_balance,
  498. initial_balance_foreign_currency =
  499. accgroup.initial_balance_foreign_currency,
  500. debit = accgroup.debit,
  501. credit = accgroup.credit,
  502. period_balance = accgroup.period_balance,
  503. final_balance = accgroup.final_balance,
  504. final_balance_foreign_currency =
  505. accgroup.final_balance_foreign_currency
  506. FROM accgroup
  507. WHERE report_trial_balance_qweb_account.account_group_id = accgroup.id
  508. """
  509. query_update_account_params = (self.id,)
  510. self.env.cr.execute(query_update_account_group,
  511. query_update_account_params)
  512. def _update_account_sequence(self):
  513. """Compute sequence, level for report_trial_balance_qweb_
  514. account account."""
  515. query_update_account_group = """
  516. UPDATE report_trial_balance_qweb_account
  517. SET sequence = newline.sequence + 1,
  518. level = newline.level + 1
  519. FROM report_trial_balance_qweb_account as newline
  520. WHERE newline.account_group_id = report_trial_balance_qweb_account.parent_id
  521. AND report_trial_balance_qweb_account.report_id = newline.report_id
  522. AND report_trial_balance_qweb_account.account_id is not null
  523. AND report_trial_balance_qweb_account.report_id = %s"""
  524. query_update_account_params = (self.id,)
  525. self.env.cr.execute(query_update_account_group,
  526. query_update_account_params)
  527. def _compute_group_accounts(self):
  528. groups = self.account_ids.filtered(
  529. lambda a: a.account_group_id is not False)
  530. for group in groups:
  531. if self.hierarchy_on == 'compute':
  532. group.compute_account_ids = \
  533. group.account_group_id.compute_account_ids
  534. else:
  535. if group.child_account_ids:
  536. chacc = group.child_account_ids.replace(
  537. '}', '').replace('{', '').split(',')
  538. if 'NULL' in chacc:
  539. chacc.remove('NULL')
  540. if chacc:
  541. group.compute_account_ids = [
  542. (6, 0, [int(g) for g in chacc])]