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.

294 lines
13 KiB

  1. # © 2016 Julien Coux (Camptocamp)
  2. # Copyright 2020 ForgeFlow S.L. (https://www.forgeflow.com)
  3. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
  4. from odoo import models, api
  5. from odoo.tools import float_is_zero
  6. from odoo.osv import expression
  7. from datetime import date, datetime
  8. import operator
  9. class OpenItemsReport(models.AbstractModel):
  10. _name = 'report.account_financial_report.open_items'
  11. _description = "Open Items Report"
  12. @api.model
  13. def get_html(self, given_context=None):
  14. return self._get_html()
  15. def _get_html(self):
  16. result = {}
  17. rcontext = {}
  18. context = dict(self.env.context)
  19. rcontext.update(context.get('data'))
  20. active_id = context.get('active_id')
  21. wiz = self.env['open.items.report.wizard'].browse(active_id)
  22. rcontext['o'] = wiz
  23. result['html'] = self.env.ref(
  24. 'account_financial_report.report_open_items').render(rcontext)
  25. return result
  26. def _get_account_partial_reconciled(self, move_lines_data, date_at_object):
  27. reconciled_ids = []
  28. for move_line in move_lines_data:
  29. if move_line['reconciled']:
  30. reconciled_ids += [move_line['id']]
  31. domain = [('max_date', '>=', date_at_object)]
  32. domain += expression.OR(
  33. [[('debit_move_id', 'in', reconciled_ids)],
  34. [('credit_move_id', 'in', reconciled_ids)]])
  35. fields = ['debit_move_id', 'credit_move_id', 'amount']
  36. accounts_partial_reconcile = \
  37. self.env['account.partial.reconcile'].search_read(
  38. domain=domain,
  39. fields=fields
  40. )
  41. debit_accounts_partial_amount = {}
  42. credit_accounts_partial_amount = {}
  43. for account_partial_reconcile_data in accounts_partial_reconcile:
  44. debit_move_id = account_partial_reconcile_data['debit_move_id'][0]
  45. credit_move_id = account_partial_reconcile_data['credit_move_id'][0]
  46. if debit_move_id not in debit_accounts_partial_amount.keys():
  47. debit_accounts_partial_amount[debit_move_id] = 0.0
  48. debit_accounts_partial_amount[debit_move_id] += \
  49. account_partial_reconcile_data['amount']
  50. if credit_move_id not in credit_accounts_partial_amount.keys():
  51. credit_accounts_partial_amount[credit_move_id] = 0.0
  52. credit_accounts_partial_amount[credit_move_id] += \
  53. account_partial_reconcile_data['amount']
  54. account_partial_reconcile_data.update({
  55. 'debit_move_id': debit_move_id,
  56. 'credit_move_id': credit_move_id,
  57. })
  58. return accounts_partial_reconcile, debit_accounts_partial_amount, \
  59. credit_accounts_partial_amount
  60. @api.model
  61. def _get_query_domain(self, account_ids, partner_ids, date_at_object,
  62. target_move, company_id, date_from):
  63. query = """
  64. WHERE aml.account_id in %s and aml.company_id = %s
  65. """ % (tuple(account_ids) if len(account_ids) > 1 else "(%s)" %
  66. account_ids[0], company_id)
  67. if date_from:
  68. query += " and aml.date >= '%s'" % date_from
  69. if partner_ids:
  70. query += " and aml.partner_id in %s" % (tuple(partner_ids),)
  71. if target_move == 'posted':
  72. query += " and am.state = 'posted'"
  73. if date_at_object >= date.today():
  74. query += " and aml.reconciled IS FALSE"
  75. else:
  76. query += """ and ((aml.reconciled IS FALSE OR aml.date >= '%s')
  77. OR aml.full_reconcile_id IS NOT NULL)""" % date_at_object
  78. return query
  79. @api.model
  80. def _get_query(self, account_ids, partner_ids, date_at_object,
  81. target_move, company_id, date_from):
  82. aml_fields = [
  83. 'id', 'date', 'move_id', 'journal_id', 'account_id', 'partner_id',
  84. 'ref', 'date_maturity', 'amount_residual', 'amount_currency',
  85. 'amount_residual_currency', 'debit', 'credit', 'currency_id',
  86. 'reconciled', 'full_reconcile_id']
  87. query = ""
  88. # SELECT
  89. for field in aml_fields:
  90. if not query:
  91. query = "SELECT aml.%s" % field
  92. else:
  93. query += ", aml.%s" % field
  94. # name from res_partner
  95. query += ", rp.name as partner_name"
  96. # name from res_currency
  97. query += ", rc.name as currency_name"
  98. # state and name from account_move
  99. query += ", am.state, am.name as move_name"
  100. # FROM
  101. query += """
  102. FROM account_move_line as aml
  103. LEFT JOIN res_partner as rp
  104. ON aml.partner_id = rp.id
  105. LEFT JOIN res_currency as rc
  106. ON aml.currency_id = rc.id
  107. LEFT JOIN account_move as am
  108. ON am.id = aml.move_id
  109. """
  110. # WHERE
  111. query += self._get_query_domain(account_ids, partner_ids,
  112. date_at_object, target_move,
  113. company_id, date_from)
  114. return query
  115. def _get_accounts_data(self, accounts_ids):
  116. accounts = self.env['account.account'].browse(accounts_ids)
  117. accounts_data = {}
  118. for account in accounts:
  119. accounts_data.update({account.id: {
  120. 'id': account.id,
  121. 'code': account.code,
  122. 'name': account.name,
  123. 'hide_account': False,
  124. 'currency_id': account.currency_id or False,
  125. 'currency_name': account.currency_id.name}
  126. })
  127. return accounts_data
  128. def _get_journals_data(self, journals_ids):
  129. journals = self.env['account.journal'].browse(journals_ids)
  130. journals_data = {}
  131. for journal in journals:
  132. journals_data.update({journal.id: {'id': journal.id,
  133. 'code': journal.code}})
  134. return journals_data
  135. def _get_data(self, account_ids, partner_ids, date_at_object, target_move,
  136. company_id, date_from):
  137. query = self._get_query(account_ids, partner_ids, date_at_object,
  138. target_move, company_id, date_from)
  139. self._cr.execute(query)
  140. move_lines_data = self._cr.dictfetchall()
  141. account_ids = map(operator.itemgetter('account_id'), move_lines_data)
  142. accounts_data = self._get_accounts_data(list(account_ids))
  143. journal_ids = map(operator.itemgetter('journal_id'), move_lines_data)
  144. journals_data = self._get_journals_data(list(journal_ids))
  145. if date_at_object < date.today():
  146. accounts_partial_reconcile, debit_accounts_partial_amount, \
  147. credit_accounts_partial_amount = \
  148. self._get_account_partial_reconciled(move_lines_data,
  149. date_at_object)
  150. if accounts_partial_reconcile:
  151. debit_ids = map(operator.itemgetter('debit_move_id'),
  152. accounts_partial_reconcile)
  153. credit_ids = map(operator.itemgetter('credit_move_id'),
  154. accounts_partial_reconcile)
  155. for move_line in move_lines_data:
  156. if move_line['id'] in debit_ids:
  157. move_line['amount_residual'] += \
  158. debit_accounts_partial_amount[move_line['id']]
  159. if move_line['id'] in credit_ids:
  160. move_line['amount_residual'] -= \
  161. credit_accounts_partial_amount[move_line['id']]
  162. moves_lines_to_remove = []
  163. for move_line in move_lines_data:
  164. if move_line['date'] > date_at_object or float_is_zero(
  165. move_line['amount_residual'], precision_digits=2):
  166. moves_lines_to_remove.append(move_line)
  167. if len(moves_lines_to_remove) > 0:
  168. for move_line_to_remove in moves_lines_to_remove:
  169. move_lines_data.remove(move_line_to_remove)
  170. partners_data = {
  171. 0: {
  172. 'id': 0,
  173. 'name': 'Missing Partner'
  174. }
  175. }
  176. open_items_move_lines_data = {}
  177. for move_line in move_lines_data:
  178. no_partner = True
  179. # Partners data
  180. if move_line['partner_id']:
  181. no_partner = False
  182. partners_data.update({
  183. move_line['partner_id']: {
  184. 'id': move_line['partner_id'],
  185. 'name': move_line['partner_name'],
  186. 'currency_id': accounts_data[move_line[
  187. 'account_id']]['currency_id'],
  188. }
  189. })
  190. else:
  191. partners_data[0]['currency_id'] = accounts_data[move_line[
  192. 'account_id']]['currency_id']
  193. # Move line update
  194. original = 0
  195. if not float_is_zero(move_line['credit'], precision_digits=2):
  196. original = move_line['credit']*(-1)
  197. if not float_is_zero(move_line['debit'], precision_digits=2):
  198. original = move_line['debit']
  199. move_line.update({
  200. 'date': move_line['date'].strftime("%d/%m/%Y"),
  201. 'date_maturity': move_line["date_maturity"]
  202. and move_line["date_maturity"].strftime("%d/%m/%Y"),
  203. 'original': original,
  204. 'partner_id': 0 if no_partner else move_line['partner_id'],
  205. 'partner_name': '' if no_partner else move_line['partner_name'],
  206. 'ref': '' if not move_line['ref'] else move_line['ref'],
  207. 'account': accounts_data[move_line['account_id']]['code'],
  208. 'journal': journals_data[move_line['journal_id']]['code'],
  209. })
  210. # Open Items Move Lines Data
  211. if move_line['account_id'] not in open_items_move_lines_data.keys():
  212. open_items_move_lines_data[move_line['account_id']] = {
  213. move_line['partner_id']: [move_line]}
  214. else:
  215. if move_line['partner_id'] not in \
  216. open_items_move_lines_data[move_line[
  217. 'account_id']].keys():
  218. open_items_move_lines_data[move_line['account_id']][
  219. move_line['partner_id']] = [move_line]
  220. else:
  221. open_items_move_lines_data[move_line['account_id']][
  222. move_line['partner_id']].append(move_line)
  223. return move_lines_data, partners_data, journals_data, accounts_data, \
  224. open_items_move_lines_data
  225. @api.model
  226. def _calculate_amounts(self, open_items_move_lines_data):
  227. total_amount = {}
  228. for account_id in open_items_move_lines_data.keys():
  229. total_amount[account_id] = {}
  230. total_amount[account_id]['residual'] = 0.0
  231. for partner_id in open_items_move_lines_data[account_id].keys():
  232. total_amount[account_id][partner_id] = {}
  233. total_amount[account_id][partner_id]['residual'] = 0.0
  234. for move_line in open_items_move_lines_data[account_id][
  235. partner_id]:
  236. total_amount[account_id][partner_id]['residual'] += \
  237. move_line['amount_residual']
  238. total_amount[account_id]['residual'] += move_line[
  239. 'amount_residual']
  240. return total_amount
  241. @api.multi
  242. def _get_report_values(self, docids, data):
  243. wizard_id = data['wizard_id']
  244. company = self.env['res.company'].browse(data['company_id'])
  245. company_id = data['company_id']
  246. account_ids = data['account_ids']
  247. partner_ids = data['partner_ids']
  248. date_at = data['date_at']
  249. date_at_object = datetime.strptime(date_at, '%Y-%m-%d').date()
  250. date_from = data['date_from']
  251. target_move = data['target_move']
  252. move_lines_data, partners_data, journals_data, accounts_data, \
  253. open_items_move_lines_data = self._get_data(
  254. account_ids, partner_ids, date_at_object,
  255. target_move, company_id, date_from)
  256. total_amount = self._calculate_amounts(open_items_move_lines_data)
  257. return{
  258. 'doc_ids': [wizard_id],
  259. 'doc_model': 'open.items.report.wizard',
  260. 'docs': self.env['open.items.report.wizard'].browse(wizard_id),
  261. 'foreign_currency': data['foreign_currency'],
  262. 'company_name': company.display_name,
  263. 'currency_name': company.currency_id.name,
  264. 'date_at': date_at_object.strftime("%d/%m/%Y"),
  265. 'hide_account_at_0': data['hide_account_at_0'],
  266. 'target_move': data['target_move'],
  267. 'partners_data': partners_data,
  268. 'accounts_data': accounts_data,
  269. 'total_amount': total_amount,
  270. 'Open_Items': open_items_move_lines_data,
  271. }