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.

776 lines
22 KiB

  1. # © 2016 Julien Coux (Camptocamp)
  2. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
  3. from odoo import models, fields, api, _
  4. class OpenItemsReport(models.TransientModel):
  5. """ Here, we just define class fields.
  6. For methods, go more bottom at this file.
  7. The class hierarchy is :
  8. * OpenItemsReport
  9. ** OpenItemsReportAccount
  10. *** OpenItemsReportPartner
  11. **** OpenItemsReportMoveLine
  12. """
  13. _name = 'report_open_items'
  14. # Filters fields, used for data computation
  15. date_at = fields.Date()
  16. only_posted_moves = fields.Boolean()
  17. hide_account_balance_at_0 = fields.Boolean()
  18. company_id = fields.Many2one(comodel_name='res.company')
  19. filter_account_ids = fields.Many2many(comodel_name='account.account')
  20. filter_partner_ids = fields.Many2many(comodel_name='res.partner')
  21. # Flag fields, used for report display
  22. has_second_currency = fields.Boolean()
  23. # Data fields, used to browse report data
  24. account_ids = fields.One2many(
  25. comodel_name='report_open_items_account',
  26. inverse_name='report_id'
  27. )
  28. class OpenItemsReportAccount(models.TransientModel):
  29. _name = 'report_open_items_account'
  30. _order = 'code ASC'
  31. report_id = fields.Many2one(
  32. comodel_name='report_open_items',
  33. ondelete='cascade',
  34. index=True
  35. )
  36. # Data fields, used to keep link with real object
  37. account_id = fields.Many2one(
  38. 'account.account',
  39. index=True
  40. )
  41. # Data fields, used for report display
  42. code = fields.Char()
  43. name = fields.Char()
  44. final_amount_residual = fields.Float(digits=(16, 2))
  45. # Data fields, used to browse report data
  46. partner_ids = fields.One2many(
  47. comodel_name='report_open_items_partner',
  48. inverse_name='report_account_id'
  49. )
  50. class OpenItemsReportPartner(models.TransientModel):
  51. _name = 'report_open_items_partner'
  52. report_account_id = fields.Many2one(
  53. comodel_name='report_open_items_account',
  54. ondelete='cascade',
  55. index=True
  56. )
  57. # Data fields, used to keep link with real object
  58. partner_id = fields.Many2one(
  59. 'res.partner',
  60. index=True
  61. )
  62. # Data fields, used for report display
  63. name = fields.Char()
  64. final_amount_residual = fields.Float(digits=(16, 2))
  65. # Data fields, used to browse report data
  66. move_line_ids = fields.One2many(
  67. comodel_name='report_open_items_move_line',
  68. inverse_name='report_partner_id'
  69. )
  70. @api.model
  71. def _generate_order_by(self, order_spec, query):
  72. """Custom order to display "No partner allocated" at last position."""
  73. return """
  74. ORDER BY
  75. CASE
  76. WHEN "report_open_items_partner"."partner_id" IS NOT NULL
  77. THEN 0
  78. ELSE 1
  79. END,
  80. "report_open_items_partner"."name"
  81. """
  82. class OpenItemsReportMoveLine(models.TransientModel):
  83. _name = 'report_open_items_move_line'
  84. report_partner_id = fields.Many2one(
  85. comodel_name='report_open_items_partner',
  86. ondelete='cascade',
  87. index=True
  88. )
  89. # Data fields, used to keep link with real object
  90. move_line_id = fields.Many2one('account.move.line')
  91. # Data fields, used for report display
  92. date = fields.Date()
  93. date_due = fields.Date()
  94. entry = fields.Char()
  95. journal = fields.Char()
  96. account = fields.Char()
  97. partner = fields.Char()
  98. label = fields.Char()
  99. amount_total_due = fields.Float(digits=(16, 2))
  100. amount_residual = fields.Float(digits=(16, 2))
  101. currency_id = fields.Many2one('res.currency')
  102. amount_total_due_currency = fields.Float(digits=(16, 2))
  103. amount_residual_currency = fields.Float(digits=(16, 2))
  104. class OpenItemsReportCompute(models.TransientModel):
  105. """ Here, we just define methods.
  106. For class fields, go more top at this file.
  107. """
  108. _inherit = 'report_open_items'
  109. @api.multi
  110. def print_report(self, report_type):
  111. self.ensure_one()
  112. if report_type == 'xlsx':
  113. report_name = 'a_f_r.report_open_items_xlsx'
  114. else:
  115. report_name = 'account_financial_report.' \
  116. 'report_open_items_qweb'
  117. return self.env['ir.actions.report'].search(
  118. [('report_name', '=', report_name),
  119. ('report_type', '=', report_type)], limit=1).report_action(self)
  120. def _get_html(self):
  121. result = {}
  122. rcontext = {}
  123. context = dict(self.env.context)
  124. report = self.browse(context.get('active_id'))
  125. if report:
  126. rcontext['o'] = report
  127. result['html'] = self.env.ref(
  128. 'account_financial_report.report_open_items').render(
  129. rcontext)
  130. return result
  131. @api.model
  132. def get_html(self, given_context=None):
  133. return self._get_html()
  134. @api.multi
  135. def compute_data_for_report(self):
  136. self.ensure_one()
  137. # Compute report data
  138. self._inject_account_values()
  139. self._inject_partner_values()
  140. self._inject_line_values()
  141. self._inject_line_values(only_empty_partner_line=True)
  142. self._clean_partners_and_accounts()
  143. self._compute_partners_and_accounts_cumul()
  144. if self.hide_account_balance_at_0:
  145. self._clean_partners_and_accounts(
  146. only_delete_account_balance_at_0=True
  147. )
  148. # Compute display flag
  149. self._compute_has_second_currency()
  150. # Refresh cache because all data are computed with SQL requests
  151. self.refresh()
  152. def _inject_account_values(self):
  153. """Inject report values for report_open_items_account."""
  154. query_inject_account = """
  155. WITH
  156. accounts AS
  157. (
  158. SELECT
  159. a.id,
  160. a.code,
  161. a.name,
  162. a.user_type_id
  163. FROM
  164. account_account a
  165. INNER JOIN
  166. account_move_line ml ON a.id = ml.account_id AND ml.date <= %s
  167. """
  168. if self.filter_partner_ids:
  169. query_inject_account += """
  170. INNER JOIN
  171. res_partner p ON ml.partner_id = p.id
  172. """
  173. if self.only_posted_moves:
  174. query_inject_account += """
  175. INNER JOIN
  176. account_move m ON ml.move_id = m.id AND m.state = 'posted'
  177. """
  178. query_inject_account += """
  179. WHERE
  180. a.company_id = %s
  181. AND a.reconcile IS true
  182. """
  183. if self.filter_account_ids:
  184. query_inject_account += """
  185. AND
  186. a.id IN %s
  187. """
  188. if self.filter_partner_ids:
  189. query_inject_account += """
  190. AND
  191. p.id IN %s
  192. """
  193. query_inject_account += """
  194. GROUP BY
  195. a.id
  196. )
  197. INSERT INTO
  198. report_open_items_account
  199. (
  200. report_id,
  201. create_uid,
  202. create_date,
  203. account_id,
  204. code,
  205. name
  206. )
  207. SELECT
  208. %s AS report_id,
  209. %s AS create_uid,
  210. NOW() AS create_date,
  211. a.id AS account_id,
  212. a.code,
  213. a.name
  214. FROM
  215. accounts a
  216. """
  217. query_inject_account_params = (
  218. self.date_at,
  219. self.company_id.id,
  220. )
  221. if self.filter_account_ids:
  222. query_inject_account_params += (
  223. tuple(self.filter_account_ids.ids),
  224. )
  225. if self.filter_partner_ids:
  226. query_inject_account_params += (
  227. tuple(self.filter_partner_ids.ids),
  228. )
  229. query_inject_account_params += (
  230. self.id,
  231. self.env.uid,
  232. )
  233. self.env.cr.execute(query_inject_account, query_inject_account_params)
  234. def _inject_partner_values(self):
  235. """ Inject report values for report_open_items_partner. """
  236. # pylint: disable=sql-injection
  237. query_inject_partner = """
  238. WITH
  239. accounts_partners AS
  240. (
  241. SELECT
  242. ra.id AS report_account_id,
  243. a.id AS account_id,
  244. at.include_initial_balance AS include_initial_balance,
  245. p.id AS partner_id,
  246. COALESCE(
  247. CASE
  248. WHEN
  249. NULLIF(p.name, '') IS NOT NULL
  250. AND NULLIF(p.ref, '') IS NOT NULL
  251. THEN p.name || ' (' || p.ref || ')'
  252. ELSE p.name
  253. END,
  254. '""" + _('No partner allocated') + """'
  255. ) AS partner_name
  256. FROM
  257. report_open_items_account ra
  258. INNER JOIN
  259. account_account a ON ra.account_id = a.id
  260. INNER JOIN
  261. account_account_type at ON a.user_type_id = at.id
  262. INNER JOIN
  263. account_move_line ml ON a.id = ml.account_id AND ml.date <= %s
  264. """
  265. if self.only_posted_moves:
  266. query_inject_partner += """
  267. INNER JOIN
  268. account_move m ON ml.move_id = m.id AND m.state = 'posted'
  269. """
  270. query_inject_partner += """
  271. LEFT JOIN
  272. res_partner p ON ml.partner_id = p.id
  273. WHERE
  274. ra.report_id = %s
  275. """
  276. if self.filter_partner_ids:
  277. query_inject_partner += """
  278. AND
  279. p.id IN %s
  280. """
  281. query_inject_partner += """
  282. GROUP BY
  283. ra.id,
  284. a.id,
  285. p.id,
  286. at.include_initial_balance
  287. )
  288. INSERT INTO
  289. report_open_items_partner
  290. (
  291. report_account_id,
  292. create_uid,
  293. create_date,
  294. partner_id,
  295. name
  296. )
  297. SELECT
  298. ap.report_account_id,
  299. %s AS create_uid,
  300. NOW() AS create_date,
  301. ap.partner_id,
  302. ap.partner_name
  303. FROM
  304. accounts_partners ap
  305. """
  306. query_inject_partner_params = (
  307. self.date_at,
  308. self.id,
  309. )
  310. if self.filter_partner_ids:
  311. query_inject_partner_params += (
  312. tuple(self.filter_partner_ids.ids),
  313. )
  314. query_inject_partner_params += (
  315. self.env.uid,
  316. )
  317. self.env.cr.execute(query_inject_partner, query_inject_partner_params)
  318. def _get_line_sub_query_move_lines(self,
  319. only_empty_partner_line=False,
  320. positive_balance=True):
  321. """ Return subquery used to compute sum amounts on lines """
  322. sub_query = """
  323. SELECT
  324. ml.id,
  325. ml.balance,
  326. SUM(
  327. CASE
  328. WHEN ml_past.id IS NOT NULL
  329. THEN pr.amount
  330. ELSE NULL
  331. END
  332. ) AS partial_amount,
  333. ml.amount_currency,
  334. SUM(
  335. CASE
  336. WHEN ml_past.id IS NOT NULL
  337. THEN pr.amount_currency
  338. ELSE NULL
  339. END
  340. ) AS partial_amount_currency,
  341. ml.currency_id
  342. FROM
  343. report_open_items_partner rp
  344. INNER JOIN
  345. report_open_items_account ra
  346. ON rp.report_account_id = ra.id
  347. INNER JOIN
  348. account_move_line ml
  349. ON ra.account_id = ml.account_id
  350. """
  351. if not only_empty_partner_line:
  352. sub_query += """
  353. AND rp.partner_id = ml.partner_id
  354. """
  355. elif only_empty_partner_line:
  356. sub_query += """
  357. AND ml.partner_id IS NULL
  358. """
  359. if not positive_balance:
  360. sub_query += """
  361. LEFT JOIN
  362. account_partial_reconcile pr
  363. ON ml.balance < 0 AND pr.credit_move_id = ml.id
  364. LEFT JOIN
  365. account_move_line ml_future
  366. ON ml.balance < 0 AND pr.debit_move_id = ml_future.id
  367. AND ml_future.date > %s
  368. LEFT JOIN
  369. account_move_line ml_past
  370. ON ml.balance < 0 AND pr.debit_move_id = ml_past.id
  371. AND ml_past.date <= %s
  372. """
  373. else:
  374. sub_query += """
  375. LEFT JOIN
  376. account_partial_reconcile pr
  377. ON ml.balance > 0 AND pr.debit_move_id = ml.id
  378. LEFT JOIN
  379. account_move_line ml_future
  380. ON ml.balance > 0 AND pr.credit_move_id = ml_future.id
  381. AND ml_future.date > %s
  382. LEFT JOIN
  383. account_move_line ml_past
  384. ON ml.balance > 0 AND pr.credit_move_id = ml_past.id
  385. AND ml_past.date <= %s
  386. """
  387. sub_query += """
  388. WHERE
  389. ra.report_id = %s
  390. GROUP BY
  391. ml.id,
  392. ml.balance,
  393. ml.amount_currency
  394. HAVING
  395. (
  396. ml.full_reconcile_id IS NULL
  397. OR MAX(ml_future.id) IS NOT NULL
  398. )
  399. """
  400. return sub_query
  401. def _inject_line_values(self, only_empty_partner_line=False):
  402. """ Inject report values for report_open_items_move_line.
  403. The "only_empty_partner_line" value is used
  404. to compute data without partner.
  405. """
  406. query_inject_move_line = """
  407. WITH
  408. move_lines_amount AS
  409. (
  410. """
  411. query_inject_move_line += self._get_line_sub_query_move_lines(
  412. only_empty_partner_line=only_empty_partner_line,
  413. positive_balance=True
  414. )
  415. query_inject_move_line += """
  416. UNION
  417. """
  418. query_inject_move_line += self._get_line_sub_query_move_lines(
  419. only_empty_partner_line=only_empty_partner_line,
  420. positive_balance=False
  421. )
  422. query_inject_move_line += """
  423. ),
  424. move_lines AS
  425. (
  426. SELECT
  427. id,
  428. CASE
  429. WHEN SUM(partial_amount) > 0
  430. THEN
  431. CASE
  432. WHEN balance > 0
  433. THEN balance - SUM(partial_amount)
  434. ELSE balance + SUM(partial_amount)
  435. END
  436. ELSE balance
  437. END AS amount_residual,
  438. CASE
  439. WHEN SUM(partial_amount_currency) > 0
  440. THEN
  441. CASE
  442. WHEN amount_currency > 0
  443. THEN amount_currency - SUM(partial_amount_currency)
  444. ELSE amount_currency + SUM(partial_amount_currency)
  445. END
  446. ELSE amount_currency
  447. END AS amount_residual_currency,
  448. currency_id
  449. FROM
  450. move_lines_amount
  451. GROUP BY
  452. id,
  453. balance,
  454. amount_currency,
  455. currency_id
  456. )
  457. INSERT INTO
  458. report_open_items_move_line
  459. (
  460. report_partner_id,
  461. create_uid,
  462. create_date,
  463. move_line_id,
  464. date,
  465. date_due,
  466. entry,
  467. journal,
  468. account,
  469. partner,
  470. label,
  471. amount_total_due,
  472. amount_residual,
  473. currency_id,
  474. amount_total_due_currency,
  475. amount_residual_currency
  476. )
  477. SELECT
  478. rp.id AS report_partner_id,
  479. %s AS create_uid,
  480. NOW() AS create_date,
  481. ml.id AS move_line_id,
  482. ml.date,
  483. ml.date_maturity,
  484. m.name AS entry,
  485. j.code AS journal,
  486. a.code AS account,
  487. """
  488. if not only_empty_partner_line:
  489. query_inject_move_line += """
  490. CASE
  491. WHEN
  492. NULLIF(p.name, '') IS NOT NULL
  493. AND NULLIF(p.ref, '') IS NOT NULL
  494. THEN p.name || ' (' || p.ref || ')'
  495. ELSE p.name
  496. END AS partner,
  497. """
  498. elif only_empty_partner_line:
  499. query_inject_move_line += """
  500. '""" + _('No partner allocated') + """' AS partner,
  501. """
  502. query_inject_move_line += """
  503. CONCAT_WS(' - ', NULLIF(ml.ref, ''), NULLIF(ml.name, '')) AS label,
  504. ml.balance,
  505. ml2.amount_residual,
  506. c.id AS currency_id,
  507. ml.amount_currency,
  508. ml2.amount_residual_currency
  509. FROM
  510. report_open_items_partner rp
  511. INNER JOIN
  512. report_open_items_account ra ON rp.report_account_id = ra.id
  513. INNER JOIN
  514. account_move_line ml ON ra.account_id = ml.account_id
  515. INNER JOIN
  516. move_lines ml2
  517. ON ml.id = ml2.id
  518. AND ml2.amount_residual IS NOT NULL
  519. AND ml2.amount_residual != 0
  520. INNER JOIN
  521. account_move m ON ml.move_id = m.id
  522. INNER JOIN
  523. account_journal j ON ml.journal_id = j.id
  524. INNER JOIN
  525. account_account a ON ml.account_id = a.id
  526. """
  527. if not only_empty_partner_line:
  528. query_inject_move_line += """
  529. INNER JOIN
  530. res_partner p
  531. ON ml.partner_id = p.id AND rp.partner_id = p.id
  532. """
  533. query_inject_move_line += """
  534. LEFT JOIN
  535. account_full_reconcile fr ON ml.full_reconcile_id = fr.id
  536. LEFT JOIN
  537. res_currency c ON ml2.currency_id = c.id
  538. WHERE
  539. ra.report_id = %s
  540. AND
  541. ml.date <= %s
  542. """
  543. if self.only_posted_moves:
  544. query_inject_move_line += """
  545. AND
  546. m.state = 'posted'
  547. """
  548. if only_empty_partner_line:
  549. query_inject_move_line += """
  550. AND
  551. ml.partner_id IS NULL
  552. AND
  553. rp.partner_id IS NULL
  554. """
  555. if not only_empty_partner_line:
  556. query_inject_move_line += """
  557. ORDER BY
  558. a.code, p.name, ml.date, ml.id
  559. """
  560. elif only_empty_partner_line:
  561. query_inject_move_line += """
  562. ORDER BY
  563. a.code, ml.date, ml.id
  564. """
  565. self.env.cr.execute(
  566. query_inject_move_line,
  567. (self.date_at,
  568. self.date_at,
  569. self.id,
  570. self.date_at,
  571. self.date_at,
  572. self.id,
  573. self.env.uid,
  574. self.id,
  575. self.date_at,)
  576. )
  577. def _compute_partners_and_accounts_cumul(self):
  578. """ Compute cumulative amount for
  579. report_open_items_partner and report_open_items_account.
  580. """
  581. query_compute_partners_cumul = """
  582. UPDATE
  583. report_open_items_partner
  584. SET
  585. final_amount_residual =
  586. (
  587. SELECT
  588. SUM(rml.amount_residual) AS final_amount_residual
  589. FROM
  590. report_open_items_move_line rml
  591. WHERE
  592. rml.report_partner_id = report_open_items_partner.id
  593. )
  594. WHERE
  595. id IN
  596. (
  597. SELECT
  598. rp.id
  599. FROM
  600. report_open_items_account ra
  601. INNER JOIN
  602. report_open_items_partner rp
  603. ON ra.id = rp.report_account_id
  604. WHERE
  605. ra.report_id = %s
  606. )
  607. """
  608. params_compute_partners_cumul = (self.id,)
  609. self.env.cr.execute(query_compute_partners_cumul,
  610. params_compute_partners_cumul)
  611. query_compute_accounts_cumul = """
  612. UPDATE
  613. report_open_items_account
  614. SET
  615. final_amount_residual =
  616. (
  617. SELECT
  618. SUM(rp.final_amount_residual) AS final_amount_residual
  619. FROM
  620. report_open_items_partner rp
  621. WHERE
  622. rp.report_account_id = report_open_items_account.id
  623. )
  624. WHERE
  625. report_id = %s
  626. """
  627. params_compute_accounts_cumul = (self.id,)
  628. self.env.cr.execute(query_compute_accounts_cumul,
  629. params_compute_accounts_cumul)
  630. def _clean_partners_and_accounts(self,
  631. only_delete_account_balance_at_0=False):
  632. """ Delete empty data for
  633. report_open_items_partner and report_open_items_account.
  634. The "only_delete_account_balance_at_0" value is used
  635. to delete also the data with cumulative amounts at 0.
  636. """
  637. query_clean_partners = """
  638. DELETE FROM
  639. report_open_items_partner
  640. WHERE
  641. id IN
  642. (
  643. SELECT
  644. DISTINCT rp.id
  645. FROM
  646. report_open_items_account ra
  647. INNER JOIN
  648. report_open_items_partner rp
  649. ON ra.id = rp.report_account_id
  650. LEFT JOIN
  651. report_open_items_move_line rml
  652. ON rp.id = rml.report_partner_id
  653. WHERE
  654. ra.report_id = %s
  655. """
  656. if not only_delete_account_balance_at_0:
  657. query_clean_partners += """
  658. AND rml.id IS NULL
  659. """
  660. elif only_delete_account_balance_at_0:
  661. query_clean_partners += """
  662. AND (
  663. rp.final_amount_residual IS NULL
  664. OR rp.final_amount_residual = 0
  665. )
  666. """
  667. query_clean_partners += """
  668. )
  669. """
  670. params_clean_partners = (self.id,)
  671. self.env.cr.execute(query_clean_partners, params_clean_partners)
  672. query_clean_accounts = """
  673. DELETE FROM
  674. report_open_items_account
  675. WHERE
  676. id IN
  677. (
  678. SELECT
  679. DISTINCT ra.id
  680. FROM
  681. report_open_items_account ra
  682. LEFT JOIN
  683. report_open_items_partner rp
  684. ON ra.id = rp.report_account_id
  685. WHERE
  686. ra.report_id = %s
  687. """
  688. if not only_delete_account_balance_at_0:
  689. query_clean_accounts += """
  690. AND rp.id IS NULL
  691. """
  692. elif only_delete_account_balance_at_0:
  693. query_clean_accounts += """
  694. AND (
  695. ra.final_amount_residual IS NULL
  696. OR ra.final_amount_residual = 0
  697. )
  698. """
  699. query_clean_accounts += """
  700. )
  701. """
  702. params_clean_accounts = (self.id,)
  703. self.env.cr.execute(query_clean_accounts, params_clean_accounts)
  704. def _compute_has_second_currency(self):
  705. """ Compute "has_second_currency" flag which will used for display."""
  706. query_update_has_second_currency = """
  707. UPDATE
  708. report_open_items
  709. SET
  710. has_second_currency =
  711. (
  712. SELECT
  713. TRUE
  714. FROM
  715. report_open_items_move_line l
  716. INNER JOIN
  717. report_open_items_partner p
  718. ON l.report_partner_id = p.id
  719. INNER JOIN
  720. report_open_items_account a
  721. ON p.report_account_id = a.id
  722. WHERE
  723. a.report_id = %s
  724. AND l.currency_id IS NOT NULL
  725. LIMIT 1
  726. )
  727. WHERE id = %s
  728. """
  729. params = (self.id,) * 2
  730. self.env.cr.execute(query_update_has_second_currency, params)