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.

637 lines
19 KiB

  1. # -*- coding: utf-8 -*-
  2. # © 2016 Julien Coux (Camptocamp)
  3. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
  4. from odoo import models, fields, api
  5. class AgedPartnerBalanceReport(models.TransientModel):
  6. """ Here, we just define class fields.
  7. For methods, go more bottom at this file.
  8. The class hierarchy is :
  9. * AgedPartnerBalanceReport
  10. ** AgedPartnerBalanceReportAccount
  11. *** AgedPartnerBalanceReportPartner
  12. **** AgedPartnerBalanceReportLine
  13. **** AgedPartnerBalanceReportMoveLine
  14. If "show_move_line_details" is selected
  15. """
  16. _name = 'report_aged_partner_balance_qweb'
  17. _inherit = 'report_qweb_abstract'
  18. # Filters fields, used for data computation
  19. date_at = fields.Date()
  20. only_posted_moves = fields.Boolean()
  21. company_id = fields.Many2one(comodel_name='res.company')
  22. filter_account_ids = fields.Many2many(comodel_name='account.account')
  23. filter_partner_ids = fields.Many2many(comodel_name='res.partner')
  24. show_move_line_details = fields.Boolean()
  25. # Open Items Report Data fields, used as base for compute the data reports
  26. open_items_id = fields.Many2one(comodel_name='report_open_items_qweb')
  27. # Data fields, used to browse report data
  28. account_ids = fields.One2many(
  29. comodel_name='report_aged_partner_balance_qweb_account',
  30. inverse_name='report_id'
  31. )
  32. class AgedPartnerBalanceReportAccount(models.TransientModel):
  33. _name = 'report_aged_partner_balance_qweb_account'
  34. _inherit = 'report_qweb_abstract'
  35. _order = 'code ASC'
  36. report_id = fields.Many2one(
  37. comodel_name='report_aged_partner_balance_qweb',
  38. ondelete='cascade',
  39. index=True
  40. )
  41. # Data fields, used to keep link with real object
  42. account_id = fields.Many2one(
  43. 'account.account',
  44. index=True
  45. )
  46. # Data fields, used for report display
  47. code = fields.Char()
  48. name = fields.Char()
  49. cumul_amount_residual = fields.Float(digits=(16, 2))
  50. cumul_current = fields.Float(digits=(16, 2))
  51. cumul_age_30_days = fields.Float(digits=(16, 2))
  52. cumul_age_60_days = fields.Float(digits=(16, 2))
  53. cumul_age_90_days = fields.Float(digits=(16, 2))
  54. cumul_age_120_days = fields.Float(digits=(16, 2))
  55. cumul_older = fields.Float(digits=(16, 2))
  56. percent_current = fields.Float(digits=(16, 2))
  57. percent_age_30_days = fields.Float(digits=(16, 2))
  58. percent_age_60_days = fields.Float(digits=(16, 2))
  59. percent_age_90_days = fields.Float(digits=(16, 2))
  60. percent_age_120_days = fields.Float(digits=(16, 2))
  61. percent_older = fields.Float(digits=(16, 2))
  62. # Data fields, used to browse report data
  63. partner_ids = fields.One2many(
  64. comodel_name='report_aged_partner_balance_qweb_partner',
  65. inverse_name='report_account_id'
  66. )
  67. class AgedPartnerBalanceReportPartner(models.TransientModel):
  68. _name = 'report_aged_partner_balance_qweb_partner'
  69. _inherit = 'report_qweb_abstract'
  70. report_account_id = fields.Many2one(
  71. comodel_name='report_aged_partner_balance_qweb_account',
  72. ondelete='cascade',
  73. index=True
  74. )
  75. # Data fields, used to keep link with real object
  76. partner_id = fields.Many2one(
  77. 'res.partner',
  78. index=True
  79. )
  80. # Data fields, used for report display
  81. name = fields.Char()
  82. # Data fields, used to browse report data
  83. move_line_ids = fields.One2many(
  84. comodel_name='report_aged_partner_balance_qweb_move_line',
  85. inverse_name='report_partner_id'
  86. )
  87. line_ids = fields.One2many(
  88. comodel_name='report_aged_partner_balance_qweb_line',
  89. inverse_name='report_partner_id'
  90. )
  91. @api.model
  92. def _generate_order_by(self, order_spec, query):
  93. """Custom order to display "No partner allocated" at last position."""
  94. return """
  95. ORDER BY
  96. CASE
  97. WHEN
  98. "report_aged_partner_balance_qweb_partner"."partner_id" IS NOT NULL
  99. THEN 0
  100. ELSE 1
  101. END,
  102. "report_aged_partner_balance_qweb_partner"."name"
  103. """
  104. class AgedPartnerBalanceReportLine(models.TransientModel):
  105. _name = 'report_aged_partner_balance_qweb_line'
  106. _inherit = 'report_qweb_abstract'
  107. report_partner_id = fields.Many2one(
  108. comodel_name='report_aged_partner_balance_qweb_partner',
  109. ondelete='cascade',
  110. index=True
  111. )
  112. # Data fields, used for report display
  113. partner = fields.Char()
  114. amount_residual = fields.Float(digits=(16, 2))
  115. current = fields.Float(digits=(16, 2))
  116. age_30_days = fields.Float(digits=(16, 2))
  117. age_60_days = fields.Float(digits=(16, 2))
  118. age_90_days = fields.Float(digits=(16, 2))
  119. age_120_days = fields.Float(digits=(16, 2))
  120. older = fields.Float(digits=(16, 2))
  121. class AgedPartnerBalanceReportMoveLine(models.TransientModel):
  122. _name = 'report_aged_partner_balance_qweb_move_line'
  123. _inherit = 'report_qweb_abstract'
  124. report_partner_id = fields.Many2one(
  125. comodel_name='report_aged_partner_balance_qweb_partner',
  126. ondelete='cascade',
  127. index=True
  128. )
  129. # Data fields, used to keep link with real object
  130. move_line_id = fields.Many2one('account.move.line')
  131. # Data fields, used for report display
  132. date = fields.Date()
  133. date_due = fields.Date()
  134. entry = fields.Char()
  135. journal = fields.Char()
  136. account = fields.Char()
  137. partner = fields.Char()
  138. label = fields.Char()
  139. amount_residual = fields.Float(digits=(16, 2))
  140. current = fields.Float(digits=(16, 2))
  141. age_30_days = fields.Float(digits=(16, 2))
  142. age_60_days = fields.Float(digits=(16, 2))
  143. age_90_days = fields.Float(digits=(16, 2))
  144. age_120_days = fields.Float(digits=(16, 2))
  145. older = fields.Float(digits=(16, 2))
  146. class AgedPartnerBalanceReportCompute(models.TransientModel):
  147. """ Here, we just define methods.
  148. For class fields, go more top at this file.
  149. """
  150. _inherit = 'report_aged_partner_balance_qweb'
  151. @api.multi
  152. def print_report(self, report_type):
  153. self.ensure_one()
  154. if report_type == 'xlsx':
  155. report_name = 'account_financial_report_qweb.' \
  156. 'report_aged_partner_balance_xlsx'
  157. else:
  158. report_name = 'account_financial_report_qweb.' \
  159. 'report_aged_partner_balance_qweb'
  160. return self.env['report'].get_action(docids=self.ids,
  161. report_name=report_name)
  162. def _get_html(self):
  163. result = {}
  164. rcontext = {}
  165. context = dict(self.env.context)
  166. report = self.browse(context.get('active_id'))
  167. if report:
  168. rcontext['o'] = report
  169. result['html'] = self.env.ref(
  170. 'account_financial_report_qweb.'
  171. 'report_aged_partner_balance_html').render(rcontext)
  172. return result
  173. @api.model
  174. def get_html(self, given_context=None):
  175. return self._get_html()
  176. def _prepare_report_open_items(self):
  177. self.ensure_one()
  178. return {
  179. 'date_at': self.date_at,
  180. 'only_posted_moves': self.only_posted_moves,
  181. 'company_id': self.company_id.id,
  182. 'filter_account_ids': [(6, 0, self.filter_account_ids.ids)],
  183. 'filter_partner_ids': [(6, 0, self.filter_partner_ids.ids)],
  184. }
  185. @api.multi
  186. def compute_data_for_report(self):
  187. self.ensure_one()
  188. # Compute Open Items Report Data.
  189. # The data of Aged Partner Balance Report
  190. # are based on Open Items Report data.
  191. model = self.env['report_open_items_qweb']
  192. self.open_items_id = model.create(self._prepare_report_open_items())
  193. self.open_items_id.compute_data_for_report()
  194. # Compute report data
  195. self._inject_account_values()
  196. self._inject_partner_values()
  197. self._inject_line_values()
  198. self._inject_line_values(only_empty_partner_line=True)
  199. if self.show_move_line_details:
  200. self._inject_move_line_values()
  201. self._inject_move_line_values(only_empty_partner_line=True)
  202. self._compute_accounts_cumul()
  203. # Refresh cache because all data are computed with SQL requests
  204. self.invalidate_cache()
  205. def _inject_account_values(self):
  206. """Inject report values for report_aged_partner_balance_qweb_account"""
  207. query_inject_account = """
  208. INSERT INTO
  209. report_aged_partner_balance_qweb_account
  210. (
  211. report_id,
  212. create_uid,
  213. create_date,
  214. account_id,
  215. code,
  216. name
  217. )
  218. SELECT
  219. %s AS report_id,
  220. %s AS create_uid,
  221. NOW() AS create_date,
  222. rao.account_id,
  223. rao.code,
  224. rao.name
  225. FROM
  226. report_open_items_qweb_account rao
  227. WHERE
  228. rao.report_id = %s
  229. """
  230. query_inject_account_params = (
  231. self.id,
  232. self.env.uid,
  233. self.open_items_id.id,
  234. )
  235. self.env.cr.execute(query_inject_account, query_inject_account_params)
  236. def _inject_partner_values(self):
  237. """Inject report values for report_aged_partner_balance_qweb_partner"""
  238. query_inject_partner = """
  239. INSERT INTO
  240. report_aged_partner_balance_qweb_partner
  241. (
  242. report_account_id,
  243. create_uid,
  244. create_date,
  245. partner_id,
  246. name
  247. )
  248. SELECT
  249. ra.id AS report_account_id,
  250. %s AS create_uid,
  251. NOW() AS create_date,
  252. rpo.partner_id,
  253. rpo.name
  254. FROM
  255. report_open_items_qweb_partner rpo
  256. INNER JOIN
  257. report_open_items_qweb_account rao ON rpo.report_account_id = rao.id
  258. INNER JOIN
  259. report_aged_partner_balance_qweb_account ra ON rao.code = ra.code
  260. WHERE
  261. rao.report_id = %s
  262. AND ra.report_id = %s
  263. """
  264. query_inject_partner_params = (
  265. self.env.uid,
  266. self.open_items_id.id,
  267. self.id,
  268. )
  269. self.env.cr.execute(query_inject_partner, query_inject_partner_params)
  270. def _inject_line_values(self, only_empty_partner_line=False):
  271. """ Inject report values for report_aged_partner_balance_qweb_line.
  272. The "only_empty_partner_line" value is used
  273. to compute data without partner.
  274. """
  275. query_inject_line = """
  276. WITH
  277. date_range AS
  278. (
  279. SELECT
  280. DATE %s AS date_current,
  281. DATE %s - INTEGER '30' AS date_less_30_days,
  282. DATE %s - INTEGER '60' AS date_less_60_days,
  283. DATE %s - INTEGER '90' AS date_less_90_days,
  284. DATE %s - INTEGER '120' AS date_less_120_days
  285. )
  286. INSERT INTO
  287. report_aged_partner_balance_qweb_line
  288. (
  289. report_partner_id,
  290. create_uid,
  291. create_date,
  292. partner,
  293. amount_residual,
  294. current,
  295. age_30_days,
  296. age_60_days,
  297. age_90_days,
  298. age_120_days,
  299. older
  300. )
  301. SELECT
  302. rp.id AS report_partner_id,
  303. %s AS create_uid,
  304. NOW() AS create_date,
  305. rp.name,
  306. SUM(ROUND(rlo.amount_residual, 2)) AS amount_residual,
  307. SUM(
  308. CASE
  309. WHEN rlo.date_due >= date_range.date_current
  310. THEN ROUND(rlo.amount_residual, 2)
  311. END
  312. ) AS current,
  313. SUM(
  314. CASE
  315. WHEN
  316. rlo.date_due >= date_range.date_less_30_days
  317. AND rlo.date_due < date_range.date_current
  318. THEN ROUND(rlo.amount_residual, 2)
  319. END
  320. ) AS age_30_days,
  321. SUM(
  322. CASE
  323. WHEN
  324. rlo.date_due >= date_range.date_less_60_days
  325. AND rlo.date_due < date_range.date_less_30_days
  326. THEN ROUND(rlo.amount_residual, 2)
  327. END
  328. ) AS age_60_days,
  329. SUM(
  330. CASE
  331. WHEN
  332. rlo.date_due >= date_range.date_less_90_days
  333. AND rlo.date_due < date_range.date_less_60_days
  334. THEN ROUND(rlo.amount_residual, 2)
  335. END
  336. ) AS age_90_days,
  337. SUM(
  338. CASE
  339. WHEN
  340. rlo.date_due >= date_range.date_less_120_days
  341. AND rlo.date_due < date_range.date_less_90_days
  342. THEN ROUND(rlo.amount_residual, 2)
  343. END
  344. ) AS age_120_days,
  345. SUM(
  346. CASE
  347. WHEN rlo.date_due < date_range.date_less_120_days
  348. THEN ROUND(rlo.amount_residual, 2)
  349. END
  350. ) AS older
  351. FROM
  352. date_range,
  353. report_open_items_qweb_move_line rlo
  354. INNER JOIN
  355. report_open_items_qweb_partner rpo ON rlo.report_partner_id = rpo.id
  356. INNER JOIN
  357. report_open_items_qweb_account rao ON rpo.report_account_id = rao.id
  358. INNER JOIN
  359. report_aged_partner_balance_qweb_account ra ON rao.code = ra.code
  360. INNER JOIN
  361. report_aged_partner_balance_qweb_partner rp
  362. ON
  363. ra.id = rp.report_account_id
  364. """
  365. if not only_empty_partner_line:
  366. query_inject_line += """
  367. AND rpo.partner_id = rp.partner_id
  368. """
  369. elif only_empty_partner_line:
  370. query_inject_line += """
  371. AND rpo.partner_id IS NULL
  372. AND rp.partner_id IS NULL
  373. """
  374. query_inject_line += """
  375. WHERE
  376. rao.report_id = %s
  377. AND ra.report_id = %s
  378. GROUP BY
  379. rp.id
  380. """
  381. query_inject_line_params = (self.date_at,) * 5
  382. query_inject_line_params += (
  383. self.env.uid,
  384. self.open_items_id.id,
  385. self.id,
  386. )
  387. self.env.cr.execute(query_inject_line, query_inject_line_params)
  388. def _inject_move_line_values(self, only_empty_partner_line=False):
  389. """ Inject report values for report_aged_partner_balance_qweb_move_line
  390. The "only_empty_partner_line" value is used
  391. to compute data without partner.
  392. """
  393. query_inject_move_line = """
  394. WITH
  395. date_range AS
  396. (
  397. SELECT
  398. DATE %s AS date_current,
  399. DATE %s - INTEGER '30' AS date_less_30_days,
  400. DATE %s - INTEGER '60' AS date_less_60_days,
  401. DATE %s - INTEGER '90' AS date_less_90_days,
  402. DATE %s - INTEGER '120' AS date_less_120_days
  403. )
  404. INSERT INTO
  405. report_aged_partner_balance_qweb_move_line
  406. (
  407. report_partner_id,
  408. create_uid,
  409. create_date,
  410. move_line_id,
  411. date,
  412. date_due,
  413. entry,
  414. journal,
  415. account,
  416. partner,
  417. label,
  418. amount_residual,
  419. current,
  420. age_30_days,
  421. age_60_days,
  422. age_90_days,
  423. age_120_days,
  424. older
  425. )
  426. SELECT
  427. rp.id AS report_partner_id,
  428. %s AS create_uid,
  429. NOW() AS create_date,
  430. rlo.move_line_id,
  431. rlo.date,
  432. rlo.date_due,
  433. rlo.entry,
  434. rlo.journal,
  435. rlo.account,
  436. rlo.partner,
  437. rlo.label,
  438. rlo.amount_residual AS amount_residual,
  439. CASE
  440. WHEN rlo.date_due >= date_range.date_current
  441. THEN rlo.amount_residual
  442. END AS current,
  443. CASE
  444. WHEN
  445. rlo.date_due >= date_range.date_less_30_days
  446. AND rlo.date_due < date_range.date_current
  447. THEN rlo.amount_residual
  448. END AS age_30_days,
  449. CASE
  450. WHEN
  451. rlo.date_due >= date_range.date_less_60_days
  452. AND rlo.date_due < date_range.date_less_30_days
  453. THEN rlo.amount_residual
  454. END AS age_60_days,
  455. CASE
  456. WHEN
  457. rlo.date_due >= date_range.date_less_90_days
  458. AND rlo.date_due < date_range.date_less_60_days
  459. THEN rlo.amount_residual
  460. END AS age_90_days,
  461. CASE
  462. WHEN
  463. rlo.date_due >= date_range.date_less_120_days
  464. AND rlo.date_due < date_range.date_less_90_days
  465. THEN rlo.amount_residual
  466. END AS age_120_days,
  467. CASE
  468. WHEN rlo.date_due < date_range.date_less_120_days
  469. THEN rlo.amount_residual
  470. END AS older
  471. FROM
  472. date_range,
  473. report_open_items_qweb_move_line rlo
  474. INNER JOIN
  475. report_open_items_qweb_partner rpo ON rlo.report_partner_id = rpo.id
  476. INNER JOIN
  477. report_open_items_qweb_account rao ON rpo.report_account_id = rao.id
  478. INNER JOIN
  479. report_aged_partner_balance_qweb_account ra ON rao.code = ra.code
  480. INNER JOIN
  481. report_aged_partner_balance_qweb_partner rp
  482. ON
  483. ra.id = rp.report_account_id
  484. """
  485. if not only_empty_partner_line:
  486. query_inject_move_line += """
  487. AND rpo.partner_id = rp.partner_id
  488. """
  489. elif only_empty_partner_line:
  490. query_inject_move_line += """
  491. AND rpo.partner_id IS NULL
  492. AND rp.partner_id IS NULL
  493. """
  494. query_inject_move_line += """
  495. WHERE
  496. rao.report_id = %s
  497. AND ra.report_id = %s
  498. """
  499. query_inject_move_line_params = (self.date_at,) * 5
  500. query_inject_move_line_params += (
  501. self.env.uid,
  502. self.open_items_id.id,
  503. self.id,
  504. )
  505. self.env.cr.execute(query_inject_move_line,
  506. query_inject_move_line_params)
  507. def _compute_accounts_cumul(self):
  508. """ Compute cumulative amount for
  509. report_aged_partner_balance_qweb_account.
  510. """
  511. query_compute_accounts_cumul = """
  512. WITH
  513. cumuls AS
  514. (
  515. SELECT
  516. ra.id AS report_account_id,
  517. SUM(ROUND(rl.amount_residual, 2)) AS cumul_amount_residual,
  518. SUM(ROUND(rl.current, 2)) AS cumul_current,
  519. SUM(ROUND(rl.age_30_days, 2)) AS cumul_age_30_days,
  520. SUM(ROUND(rl.age_60_days, 2)) AS cumul_age_60_days,
  521. SUM(ROUND(rl.age_90_days, 2)) AS cumul_age_90_days,
  522. SUM(ROUND(rl.age_120_days, 2)) AS cumul_age_120_days,
  523. SUM(ROUND(rl.older, 2)) AS cumul_older
  524. FROM
  525. report_aged_partner_balance_qweb_line rl
  526. INNER JOIN
  527. report_aged_partner_balance_qweb_partner rp
  528. ON rl.report_partner_id = rp.id
  529. INNER JOIN
  530. report_aged_partner_balance_qweb_account ra
  531. ON rp.report_account_id = ra.id
  532. WHERE
  533. ra.report_id = %s
  534. GROUP BY
  535. ra.id
  536. )
  537. UPDATE
  538. report_aged_partner_balance_qweb_account
  539. SET
  540. cumul_amount_residual = c.cumul_amount_residual,
  541. cumul_current = c.cumul_current,
  542. cumul_age_30_days = c.cumul_age_30_days,
  543. cumul_age_60_days = c.cumul_age_60_days,
  544. cumul_age_90_days = c.cumul_age_90_days,
  545. cumul_age_120_days = c.cumul_age_120_days,
  546. cumul_older = c.cumul_older,
  547. percent_current =
  548. CASE
  549. WHEN c.cumul_amount_residual != 0
  550. THEN 100 * c.cumul_current / c.cumul_amount_residual
  551. END,
  552. percent_age_30_days =
  553. CASE
  554. WHEN c.cumul_amount_residual != 0
  555. THEN 100 * c.cumul_age_30_days / c.cumul_amount_residual
  556. END,
  557. percent_age_60_days =
  558. CASE
  559. WHEN c.cumul_amount_residual != 0
  560. THEN 100 * c.cumul_age_60_days / c.cumul_amount_residual
  561. END,
  562. percent_age_90_days =
  563. CASE
  564. WHEN c.cumul_amount_residual != 0
  565. THEN 100 * c.cumul_age_90_days / c.cumul_amount_residual
  566. END,
  567. percent_age_120_days =
  568. CASE
  569. WHEN c.cumul_amount_residual != 0
  570. THEN 100 * c.cumul_age_120_days / c.cumul_amount_residual
  571. END,
  572. percent_older =
  573. CASE
  574. WHEN c.cumul_amount_residual != 0
  575. THEN 100 * c.cumul_older / c.cumul_amount_residual
  576. END
  577. FROM
  578. cumuls c
  579. WHERE
  580. id = c.report_account_id
  581. """
  582. params_compute_accounts_cumul = (self.id,)
  583. self.env.cr.execute(query_compute_accounts_cumul,
  584. params_compute_accounts_cumul)