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.

596 lines
21 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 'code_prefix' for groups
  69. # and code_prefix + 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. allowed_groups = self.env["account.group"].search([])
  360. if not allowed_groups:
  361. return
  362. query_inject_account_group = """
  363. INSERT INTO
  364. report_trial_balance_account
  365. (
  366. report_id,
  367. create_uid,
  368. create_date,
  369. account_group_id,
  370. parent_id,
  371. code,
  372. name,
  373. sequence,
  374. level
  375. )
  376. SELECT
  377. %s AS report_id,
  378. %s AS create_uid,
  379. NOW() AS create_date,
  380. accgroup.id,
  381. accgroup.parent_id,
  382. coalesce(accgroup.code_prefix, accgroup.name),
  383. accgroup.name,
  384. accgroup.code_prefix,
  385. accgroup.level
  386. FROM
  387. account_group accgroup
  388. WHERE
  389. accgroup.id in %s
  390. """
  391. query_inject_account_params = (
  392. self.id,
  393. self.env.uid,
  394. tuple(allowed_groups.ids),
  395. )
  396. self.env.cr.execute(query_inject_account_group,
  397. query_inject_account_params)
  398. def _update_account_group_child_values(self):
  399. """Compute values for report_trial_balance_account group in child."""
  400. query_update_account_group = """
  401. WITH computed AS (WITH RECURSIVE cte AS (
  402. SELECT account_group_id, code, account_group_id AS parent_id,
  403. initial_balance, initial_balance_foreign_currency, debit, credit,
  404. period_balance, final_balance, final_balance_foreign_currency
  405. FROM report_trial_balance_account
  406. WHERE report_id = %s
  407. GROUP BY report_trial_balance_account.id
  408. UNION ALL
  409. SELECT c.account_group_id, c.code, p.account_group_id,
  410. p.initial_balance, p.initial_balance_foreign_currency, p.debit, p.credit,
  411. p.period_balance, p.final_balance, p.final_balance_foreign_currency
  412. FROM cte c
  413. JOIN report_trial_balance_account p USING (parent_id)
  414. WHERE p.report_id = %s
  415. )
  416. SELECT account_group_id, code,
  417. sum(initial_balance) AS initial_balance,
  418. sum(initial_balance_foreign_currency) AS initial_balance_foreign_currency,
  419. sum(debit) AS debit,
  420. sum(credit) AS credit,
  421. sum(debit) - sum(credit) AS period_balance,
  422. sum(final_balance) AS final_balance,
  423. sum(final_balance_foreign_currency) AS final_balance_foreign_currency
  424. FROM cte
  425. GROUP BY cte.account_group_id, cte.code
  426. ORDER BY account_group_id
  427. )
  428. UPDATE report_trial_balance_account
  429. SET initial_balance = computed.initial_balance,
  430. initial_balance_foreign_currency =
  431. computed.initial_balance_foreign_currency,
  432. debit = computed.debit,
  433. credit = computed.credit,
  434. period_balance = computed.period_balance,
  435. final_balance = computed.final_balance,
  436. final_balance_foreign_currency =
  437. computed.final_balance_foreign_currency
  438. FROM computed
  439. WHERE report_trial_balance_account.account_group_id = computed.account_group_id
  440. AND report_trial_balance_account.report_id = %s
  441. """
  442. query_update_account_params = (self.id, self.id, self.id,)
  443. self.env.cr.execute(query_update_account_group,
  444. query_update_account_params)
  445. def _add_account_group_account_values(self):
  446. """Compute values for report_trial_balance_account group in child."""
  447. query_update_account_group = """
  448. DROP AGGREGATE IF EXISTS array_concat_agg(anyarray);
  449. CREATE AGGREGATE array_concat_agg(anyarray) (
  450. SFUNC = array_cat,
  451. STYPE = anyarray
  452. );
  453. WITH aggr AS(WITH computed AS (WITH RECURSIVE cte AS (
  454. SELECT account_group_id, account_group_id AS parent_id,
  455. ARRAY[account_id]::int[] as child_account_ids
  456. FROM report_trial_balance_account
  457. WHERE report_id = %s
  458. GROUP BY report_trial_balance_account.id
  459. UNION ALL
  460. SELECT c.account_group_id, p.account_group_id, ARRAY[p.account_id]::int[]
  461. FROM cte c
  462. JOIN report_trial_balance_account p USING (parent_id)
  463. WHERE p.report_id = %s
  464. )
  465. SELECT account_group_id,
  466. array_concat_agg(DISTINCT child_account_ids)::int[] as child_account_ids
  467. FROM cte
  468. GROUP BY cte.account_group_id, cte.child_account_ids
  469. ORDER BY account_group_id
  470. )
  471. SELECT account_group_id,
  472. array_concat_agg(DISTINCT child_account_ids)::int[]
  473. AS child_account_ids from computed
  474. GROUP BY account_group_id)
  475. UPDATE report_trial_balance_account
  476. SET child_account_ids = aggr.child_account_ids
  477. FROM aggr
  478. WHERE report_trial_balance_account.account_group_id = aggr.account_group_id
  479. AND report_trial_balance_account.report_id = %s
  480. """
  481. query_update_account_params = (self.id, self.id, self.id,)
  482. self.env.cr.execute(query_update_account_group,
  483. query_update_account_params)
  484. def _update_account_group_computed_values(self):
  485. """Compute values for report_trial_balance_account group in compute."""
  486. query_update_account_group = """
  487. WITH RECURSIVE accgroup AS
  488. (SELECT
  489. accgroup.id,
  490. sum(coalesce(ra.initial_balance, 0)) as initial_balance,
  491. sum(coalesce(ra.initial_balance_foreign_currency, 0))
  492. as initial_balance_foreign_currency,
  493. sum(coalesce(ra.debit, 0)) as debit,
  494. sum(coalesce(ra.credit, 0)) as credit,
  495. sum(coalesce(ra.debit, 0)) - sum(coalesce(ra.credit, 0)) as period_balance,
  496. sum(coalesce(ra.final_balance, 0)) as final_balance,
  497. sum(coalesce(ra.final_balance_foreign_currency, 0))
  498. as final_balance_foreign_currency
  499. FROM
  500. account_group accgroup
  501. LEFT OUTER JOIN account_account AS acc
  502. ON strpos(acc.code, accgroup.code_prefix) = 1
  503. LEFT OUTER JOIN report_trial_balance_account AS ra
  504. ON ra.account_id = acc.id
  505. WHERE ra.report_id = %s
  506. GROUP BY accgroup.id
  507. )
  508. UPDATE report_trial_balance_account
  509. SET initial_balance = accgroup.initial_balance,
  510. initial_balance_foreign_currency =
  511. accgroup.initial_balance_foreign_currency,
  512. debit = accgroup.debit,
  513. credit = accgroup.credit,
  514. period_balance = accgroup.period_balance,
  515. final_balance = accgroup.final_balance,
  516. final_balance_foreign_currency =
  517. accgroup.final_balance_foreign_currency
  518. FROM accgroup
  519. WHERE report_trial_balance_account.account_group_id = accgroup.id
  520. """
  521. query_update_account_params = (self.id,)
  522. self.env.cr.execute(query_update_account_group,
  523. query_update_account_params)
  524. def _update_account_sequence(self):
  525. """Compute sequence, level for report_trial_balance_account account."""
  526. query_update_account_group = """
  527. UPDATE report_trial_balance_account
  528. SET sequence = CONCAT(newline.sequence, newline.code),
  529. level = newline.level + 1
  530. FROM report_trial_balance_account as newline
  531. WHERE newline.account_group_id = report_trial_balance_account.parent_id
  532. AND report_trial_balance_account.report_id = newline.report_id
  533. AND report_trial_balance_account.account_id is not null
  534. AND report_trial_balance_account.report_id = %s"""
  535. query_update_account_params = (self.id,)
  536. self.env.cr.execute(query_update_account_group,
  537. query_update_account_params)
  538. def _compute_group_accounts(self):
  539. groups = self.account_ids.filtered(
  540. lambda a: a.account_group_id is not False)
  541. for group in groups:
  542. if self.hierarchy_on == 'computed':
  543. group.compute_account_ids = \
  544. group.account_group_id.compute_account_ids
  545. else:
  546. if group.child_account_ids:
  547. chacc = group.child_account_ids.replace(
  548. '}', '').replace('{', '').split(',')
  549. if 'NULL' in chacc:
  550. chacc.remove('NULL')
  551. if chacc:
  552. group.compute_account_ids = [
  553. (6, 0, [int(g) for g in chacc])]