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.

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