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.

580 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. rec.hide_line = False
  107. report = rec.report_id
  108. r = (rec.currency_id or report.company_id.currency_id).rounding
  109. if report.hide_account_at_0 and (
  110. float_is_zero(rec.initial_balance, precision_rounding=r)
  111. and float_is_zero(rec.final_balance, precision_rounding=r)
  112. and float_is_zero(rec.debit, precision_rounding=r)
  113. and float_is_zero(rec.credit, precision_rounding=r)
  114. ):
  115. rec.hide_line = True
  116. elif report.limit_hierarchy_level and report.show_hierarchy_level:
  117. if report.hide_parent_hierarchy_level:
  118. distinct_level = rec.level != report.show_hierarchy_level
  119. if rec.account_group_id and distinct_level:
  120. rec.hide_line = True
  121. elif rec.level and distinct_level:
  122. rec.hide_line = True
  123. elif (
  124. not report.hide_parent_hierarchy_level
  125. and rec.level > report.show_hierarchy_level
  126. ):
  127. rec.hide_line = True
  128. class TrialBalanceReportPartner(models.TransientModel):
  129. _name = "report_trial_balance_partner"
  130. _inherit = "account_financial_report_abstract"
  131. report_account_id = fields.Many2one(
  132. comodel_name="report_trial_balance_account", ondelete="cascade", index=True
  133. )
  134. # Data fields, used to keep link with real object
  135. partner_id = fields.Many2one("res.partner", index=True)
  136. # Data fields, used for report display
  137. name = fields.Char()
  138. currency_id = fields.Many2one("res.currency")
  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. final_balance = fields.Float(digits=(16, 2))
  145. final_balance_foreign_currency = fields.Float(digits=(16, 2))
  146. @api.model
  147. def _generate_order_by(self, order_spec, query):
  148. """Custom order to display "No partner allocated" at last position."""
  149. return """
  150. ORDER BY
  151. CASE
  152. WHEN "report_trial_balance_partner"."partner_id" IS NOT NULL
  153. THEN 0
  154. ELSE 1
  155. END,
  156. "report_trial_balance_partner"."name"
  157. """
  158. class TrialBalanceReportCompute(models.TransientModel):
  159. """ Here, we just define methods.
  160. For class fields, go more top at this file.
  161. """
  162. _inherit = "report_trial_balance"
  163. def print_report(self, report_type):
  164. self.ensure_one()
  165. if report_type == "xlsx":
  166. report_name = "a_f_r.report_trial_balance_xlsx"
  167. else:
  168. report_name = "account_financial_report." "report_trial_balance_qweb"
  169. return (
  170. self.env["ir.actions.report"]
  171. .search(
  172. [("report_name", "=", report_name), ("report_type", "=", report_type)],
  173. limit=1,
  174. )
  175. .report_action(self, config=False)
  176. )
  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"
  186. ).render(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. def compute_data_for_report(self):
  207. self.ensure_one()
  208. # Compute General Ledger Report Data.
  209. # The data of Trial Balance Report
  210. # are based on General Ledger Report data.
  211. model = self.env["report_general_ledger"]
  212. if self.filter_account_ids:
  213. account_ids = self.filter_account_ids
  214. else:
  215. account_ids = self.env["account.account"].search(
  216. [("company_id", "=", self.company_id.id)]
  217. )
  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.code_prefix,
  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, query_inject_account_params)
  378. def _update_account_group_child_values(self):
  379. """Compute values for report_trial_balance_account group 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_account
  386. WHERE report_id = %s
  387. GROUP BY report_trial_balance_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_account p USING (parent_id)
  394. WHERE p.report_id = %s
  395. )
  396. SELECT account_group_id, code,
  397. sum(initial_balance) AS initial_balance,
  398. sum(initial_balance_foreign_currency) AS initial_balance_foreign_currency,
  399. sum(debit) AS debit,
  400. sum(credit) AS credit,
  401. sum(debit) - sum(credit) AS period_balance,
  402. sum(final_balance) AS final_balance,
  403. sum(final_balance_foreign_currency) AS final_balance_foreign_currency
  404. FROM cte
  405. GROUP BY cte.account_group_id, cte.code
  406. ORDER BY account_group_id
  407. )
  408. UPDATE report_trial_balance_account
  409. SET initial_balance = computed.initial_balance,
  410. initial_balance_foreign_currency =
  411. computed.initial_balance_foreign_currency,
  412. debit = computed.debit,
  413. credit = computed.credit,
  414. period_balance = computed.period_balance,
  415. final_balance = computed.final_balance,
  416. final_balance_foreign_currency =
  417. computed.final_balance_foreign_currency
  418. FROM computed
  419. WHERE report_trial_balance_account.account_group_id = computed.account_group_id
  420. AND report_trial_balance_account.report_id = %s
  421. """
  422. query_update_account_params = (
  423. self.id,
  424. self.id,
  425. self.id,
  426. )
  427. self.env.cr.execute(query_update_account_group, query_update_account_params)
  428. def _add_account_group_account_values(self):
  429. """Compute values for report_trial_balance_account group in child."""
  430. query_update_account_group = """
  431. DROP AGGREGATE IF EXISTS array_concat_agg(anyarray);
  432. CREATE AGGREGATE array_concat_agg(anyarray) (
  433. SFUNC = array_cat,
  434. STYPE = anyarray
  435. );
  436. WITH aggr AS(WITH computed AS (WITH RECURSIVE cte AS (
  437. SELECT account_group_id, account_group_id AS parent_id,
  438. ARRAY[account_id]::int[] as child_account_ids
  439. FROM report_trial_balance_account
  440. WHERE report_id = %s
  441. GROUP BY report_trial_balance_account.id
  442. UNION ALL
  443. SELECT c.account_group_id, p.account_group_id, ARRAY[p.account_id]::int[]
  444. FROM cte c
  445. JOIN report_trial_balance_account p USING (parent_id)
  446. WHERE p.report_id = %s
  447. )
  448. SELECT account_group_id,
  449. array_concat_agg(DISTINCT child_account_ids)::int[] as child_account_ids
  450. FROM cte
  451. GROUP BY cte.account_group_id, cte.child_account_ids
  452. ORDER BY account_group_id
  453. )
  454. SELECT account_group_id,
  455. array_concat_agg(DISTINCT child_account_ids)::int[]
  456. AS child_account_ids from computed
  457. GROUP BY account_group_id)
  458. UPDATE report_trial_balance_account
  459. SET child_account_ids = aggr.child_account_ids
  460. FROM aggr
  461. WHERE report_trial_balance_account.account_group_id = aggr.account_group_id
  462. AND report_trial_balance_account.report_id = %s
  463. """
  464. query_update_account_params = (
  465. self.id,
  466. self.id,
  467. self.id,
  468. )
  469. self.env.cr.execute(query_update_account_group, query_update_account_params)
  470. def _update_account_group_computed_values(self):
  471. """Compute values for report_trial_balance_account group in compute."""
  472. query_update_account_group = """
  473. WITH RECURSIVE accgroup AS
  474. (SELECT
  475. accgroup.id,
  476. sum(coalesce(ra.initial_balance, 0)) as initial_balance,
  477. sum(coalesce(ra.initial_balance_foreign_currency, 0))
  478. as initial_balance_foreign_currency,
  479. sum(coalesce(ra.debit, 0)) as debit,
  480. sum(coalesce(ra.credit, 0)) as credit,
  481. sum(coalesce(ra.debit, 0)) - sum(coalesce(ra.credit, 0)) as period_balance,
  482. sum(coalesce(ra.final_balance, 0)) as final_balance,
  483. sum(coalesce(ra.final_balance_foreign_currency, 0))
  484. as final_balance_foreign_currency
  485. FROM
  486. account_group accgroup
  487. LEFT OUTER JOIN account_account AS acc
  488. ON strpos(acc.code, accgroup.code_prefix) = 1
  489. LEFT OUTER JOIN report_trial_balance_account AS ra
  490. ON ra.account_id = acc.id
  491. WHERE ra.report_id = %s
  492. GROUP BY accgroup.id
  493. )
  494. UPDATE report_trial_balance_account
  495. SET initial_balance = accgroup.initial_balance,
  496. initial_balance_foreign_currency =
  497. accgroup.initial_balance_foreign_currency,
  498. debit = accgroup.debit,
  499. credit = accgroup.credit,
  500. period_balance = accgroup.period_balance,
  501. final_balance = accgroup.final_balance,
  502. final_balance_foreign_currency =
  503. accgroup.final_balance_foreign_currency
  504. FROM accgroup
  505. WHERE report_trial_balance_account.account_group_id = accgroup.id
  506. """
  507. query_update_account_params = (self.id,)
  508. self.env.cr.execute(query_update_account_group, query_update_account_params)
  509. def _update_account_sequence(self):
  510. """Compute sequence, level for report_trial_balance_account account."""
  511. query_update_account_group = """
  512. UPDATE report_trial_balance_account
  513. SET sequence = CONCAT(newline.sequence, newline.code),
  514. level = newline.level + 1
  515. FROM report_trial_balance_account as newline
  516. WHERE newline.account_group_id = report_trial_balance_account.parent_id
  517. AND report_trial_balance_account.report_id = newline.report_id
  518. AND report_trial_balance_account.account_id is not null
  519. AND report_trial_balance_account.report_id = %s"""
  520. query_update_account_params = (self.id,)
  521. self.env.cr.execute(query_update_account_group, query_update_account_params)
  522. def _compute_group_accounts(self):
  523. groups = self.account_ids.filtered(lambda a: a.account_group_id is not False)
  524. for group in groups:
  525. if self.hierarchy_on == "computed":
  526. group.compute_account_ids = group.account_group_id.compute_account_ids
  527. else:
  528. if group.child_account_ids:
  529. chacc = (
  530. group.child_account_ids.replace("}", "")
  531. .replace("{", "")
  532. .split(",")
  533. )
  534. if "NULL" in chacc:
  535. chacc.remove("NULL")
  536. if chacc:
  537. group.compute_account_ids = [(6, 0, [int(g) for g in chacc])]