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.

589 lines
20 KiB

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