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.

633 lines
19 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 AgedPartnerBalanceReport(models.TransientModel):
  5. """ Here, we just define class fields.
  6. For methods, go more bottom at this file.
  7. The class hierarchy is :
  8. * AgedPartnerBalanceReport
  9. ** AgedPartnerBalanceReportAccount
  10. *** AgedPartnerBalanceReportPartner
  11. **** AgedPartnerBalanceReportLine
  12. **** AgedPartnerBalanceReportMoveLine
  13. If "show_move_line_details" is selected
  14. """
  15. _name = 'report_aged_partner_balance'
  16. _inherit = 'account_financial_report_abstract'
  17. # Filters fields, used for data computation
  18. date_at = fields.Date()
  19. only_posted_moves = fields.Boolean()
  20. company_id = fields.Many2one(comodel_name='res.company')
  21. filter_account_ids = fields.Many2many(comodel_name='account.account')
  22. filter_partner_ids = fields.Many2many(comodel_name='res.partner')
  23. show_move_line_details = fields.Boolean()
  24. # Open Items Report Data fields, used as base for compute the data reports
  25. open_items_id = fields.Many2one(comodel_name='report_open_items')
  26. # Data fields, used to browse report data
  27. account_ids = fields.One2many(
  28. comodel_name='report_aged_partner_balance_account',
  29. inverse_name='report_id'
  30. )
  31. class AgedPartnerBalanceReportAccount(models.TransientModel):
  32. _name = 'report_aged_partner_balance_account'
  33. _inherit = 'account_financial_report_abstract'
  34. _order = 'code ASC'
  35. report_id = fields.Many2one(
  36. comodel_name='report_aged_partner_balance',
  37. ondelete='cascade',
  38. index=True
  39. )
  40. # Data fields, used to keep link with real object
  41. account_id = fields.Many2one(
  42. 'account.account',
  43. index=True
  44. )
  45. # Data fields, used for report display
  46. code = fields.Char()
  47. name = fields.Char()
  48. cumul_amount_residual = fields.Float(digits=(16, 2))
  49. cumul_current = fields.Float(digits=(16, 2))
  50. cumul_age_30_days = fields.Float(digits=(16, 2))
  51. cumul_age_60_days = fields.Float(digits=(16, 2))
  52. cumul_age_90_days = fields.Float(digits=(16, 2))
  53. cumul_age_120_days = fields.Float(digits=(16, 2))
  54. cumul_older = fields.Float(digits=(16, 2))
  55. percent_current = fields.Float(digits=(16, 2))
  56. percent_age_30_days = fields.Float(digits=(16, 2))
  57. percent_age_60_days = fields.Float(digits=(16, 2))
  58. percent_age_90_days = fields.Float(digits=(16, 2))
  59. percent_age_120_days = fields.Float(digits=(16, 2))
  60. percent_older = fields.Float(digits=(16, 2))
  61. # Data fields, used to browse report data
  62. partner_ids = fields.One2many(
  63. comodel_name='report_aged_partner_balance_partner',
  64. inverse_name='report_account_id'
  65. )
  66. class AgedPartnerBalanceReportPartner(models.TransientModel):
  67. _name = 'report_aged_partner_balance_partner'
  68. _inherit = 'account_financial_report_abstract'
  69. report_account_id = fields.Many2one(
  70. comodel_name='report_aged_partner_balance_account',
  71. ondelete='cascade',
  72. index=True
  73. )
  74. # Data fields, used to keep link with real object
  75. partner_id = fields.Many2one(
  76. 'res.partner',
  77. index=True
  78. )
  79. # Data fields, used for report display
  80. name = fields.Char()
  81. # Data fields, used to browse report data
  82. move_line_ids = fields.One2many(
  83. comodel_name='report_aged_partner_balance_move_line',
  84. inverse_name='report_partner_id'
  85. )
  86. line_ids = fields.One2many(
  87. comodel_name='report_aged_partner_balance_line',
  88. inverse_name='report_partner_id'
  89. )
  90. @api.model
  91. def _generate_order_by(self, order_spec, query):
  92. """Custom order to display "No partner allocated" at last position."""
  93. return """
  94. ORDER BY
  95. CASE
  96. WHEN
  97. "report_aged_partner_balance_partner"."partner_id" IS NOT NULL
  98. THEN 0
  99. ELSE 1
  100. END,
  101. "report_aged_partner_balance_partner"."name"
  102. """
  103. class AgedPartnerBalanceReportLine(models.TransientModel):
  104. _name = 'report_aged_partner_balance_line'
  105. _inherit = 'account_financial_report_abstract'
  106. report_partner_id = fields.Many2one(
  107. comodel_name='report_aged_partner_balance_partner',
  108. ondelete='cascade',
  109. index=True
  110. )
  111. # Data fields, used for report display
  112. partner = fields.Char()
  113. amount_residual = fields.Float(digits=(16, 2))
  114. current = fields.Float(digits=(16, 2))
  115. age_30_days = fields.Float(digits=(16, 2))
  116. age_60_days = fields.Float(digits=(16, 2))
  117. age_90_days = fields.Float(digits=(16, 2))
  118. age_120_days = fields.Float(digits=(16, 2))
  119. older = fields.Float(digits=(16, 2))
  120. class AgedPartnerBalanceReportMoveLine(models.TransientModel):
  121. _name = 'report_aged_partner_balance_move_line'
  122. _inherit = 'account_financial_report_abstract'
  123. report_partner_id = fields.Many2one(
  124. comodel_name='report_aged_partner_balance_partner',
  125. ondelete='cascade',
  126. index=True
  127. )
  128. # Data fields, used to keep link with real object
  129. move_line_id = fields.Many2one('account.move.line')
  130. # Data fields, used for report display
  131. date = fields.Date()
  132. date_due = fields.Date()
  133. entry = fields.Char()
  134. journal = fields.Char()
  135. account = fields.Char()
  136. partner = fields.Char()
  137. label = fields.Char()
  138. amount_residual = fields.Float(digits=(16, 2))
  139. current = fields.Float(digits=(16, 2))
  140. age_30_days = fields.Float(digits=(16, 2))
  141. age_60_days = fields.Float(digits=(16, 2))
  142. age_90_days = fields.Float(digits=(16, 2))
  143. age_120_days = fields.Float(digits=(16, 2))
  144. older = fields.Float(digits=(16, 2))
  145. class AgedPartnerBalanceReportCompute(models.TransientModel):
  146. """ Here, we just define methods.
  147. For class fields, go more top at this file.
  148. """
  149. _inherit = 'report_aged_partner_balance'
  150. @api.multi
  151. def print_report(self, report_type):
  152. self.ensure_one()
  153. if report_type == 'xlsx':
  154. report_name = 'a_f_r.report_aged_partner_balance_xlsx'
  155. else:
  156. report_name = 'account_financial_report.' \
  157. 'report_aged_partner_balance_qweb'
  158. report = self.env['ir.actions.report'].search(
  159. [('report_name', '=', report_name),
  160. ('report_type', '=', report_type)], limit=1)
  161. return report.report_action(self, config=False)
  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.report_aged_partner_balance').render(
  171. 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']
  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_account"""
  207. query_inject_account = """
  208. INSERT INTO
  209. report_aged_partner_balance_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_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_partner"""
  238. query_inject_partner = """
  239. INSERT INTO
  240. report_aged_partner_balance_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_partner rpo
  256. INNER JOIN
  257. report_open_items_account rao ON rpo.report_account_id = rao.id
  258. INNER JOIN
  259. report_aged_partner_balance_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_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_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(rlo.amount_residual) AS amount_residual,
  307. SUM(
  308. CASE
  309. WHEN rlo.date_due >= date_range.date_current
  310. THEN rlo.amount_residual
  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 rlo.amount_residual
  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 rlo.amount_residual
  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 rlo.amount_residual
  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 rlo.amount_residual
  343. END
  344. ) AS age_120_days,
  345. SUM(
  346. CASE
  347. WHEN rlo.date_due < date_range.date_less_120_days
  348. THEN rlo.amount_residual
  349. END
  350. ) AS older
  351. FROM
  352. date_range,
  353. report_open_items_move_line rlo
  354. INNER JOIN
  355. report_open_items_partner rpo ON rlo.report_partner_id = rpo.id
  356. INNER JOIN
  357. report_open_items_account rao ON rpo.report_account_id = rao.id
  358. INNER JOIN
  359. report_aged_partner_balance_account ra ON rao.code = ra.code
  360. INNER JOIN
  361. report_aged_partner_balance_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_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_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_move_line rlo
  474. INNER JOIN
  475. report_open_items_partner rpo ON rlo.report_partner_id = rpo.id
  476. INNER JOIN
  477. report_open_items_account rao ON rpo.report_account_id = rao.id
  478. INNER JOIN
  479. report_aged_partner_balance_account ra ON rao.code = ra.code
  480. INNER JOIN
  481. report_aged_partner_balance_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_account.
  510. """
  511. query_compute_accounts_cumul = """
  512. WITH
  513. cumuls AS
  514. (
  515. SELECT
  516. ra.id AS report_account_id,
  517. SUM(rl.amount_residual) AS cumul_amount_residual,
  518. SUM(rl.current) AS cumul_current,
  519. SUM(rl.age_30_days) AS cumul_age_30_days,
  520. SUM(rl.age_60_days) AS cumul_age_60_days,
  521. SUM(rl.age_90_days) AS cumul_age_90_days,
  522. SUM(rl.age_120_days) AS cumul_age_120_days,
  523. SUM(rl.older) AS cumul_older
  524. FROM
  525. report_aged_partner_balance_line rl
  526. INNER JOIN
  527. report_aged_partner_balance_partner rp
  528. ON rl.report_partner_id = rp.id
  529. INNER JOIN
  530. report_aged_partner_balance_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_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)