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.

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