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.

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