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.

296 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. _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 = pd.DataFrame(self._cr.dictfetchall())
  141. account_ids = set(move_lines_data.account_id.to_list())
  142. accounts_data = self._get_accounts_data(list(account_ids))
  143. journal_ids = set(move_lines_data.journal_id.to_list())
  144. journals_data = self._get_journals_data(list(journal_ids))
  145. move_lines_data = move_lines_data.fillna(0).to_dict(orient='records')
  146. if date_at_object < date.today():
  147. accounts_partial_reconcile, debit_accounts_partial_amount, \
  148. credit_accounts_partial_amount = \
  149. self._get_account_partial_reconciled(move_lines_data,
  150. date_at_object)
  151. if accounts_partial_reconcile:
  152. accounts_partial_reconcile_data = pd.DataFrame(
  153. accounts_partial_reconcile)
  154. debit_ids = set(accounts_partial_reconcile_data.debit_move_id
  155. .to_list())
  156. credit_ids = set(
  157. accounts_partial_reconcile_data.credit_move_id.to_list())
  158. for move_line in move_lines_data:
  159. if move_line['id'] in debit_ids:
  160. move_line['amount_residual'] += \
  161. debit_accounts_partial_amount[move_line['id']]
  162. if move_line['id'] in credit_ids:
  163. move_line['amount_residual'] -= \
  164. credit_accounts_partial_amount[move_line['id']]
  165. moves_lines_to_remove = []
  166. for move_line in move_lines_data:
  167. if move_line['date'] > date_at_object or float_is_zero(
  168. move_line['amount_residual'], precision_digits=2):
  169. moves_lines_to_remove.append(move_line)
  170. if len(moves_lines_to_remove) > 0:
  171. for move_line_to_remove in moves_lines_to_remove:
  172. move_lines_data.remove(move_line_to_remove)
  173. partners_data = {
  174. 0: {
  175. 'id': 0,
  176. 'name': 'Missing Partner'
  177. }
  178. }
  179. open_items_move_lines_data = {}
  180. for move_line in move_lines_data:
  181. no_partner = True
  182. # Partners data
  183. if move_line['partner_id'] and not pd.isna(move_line['partner_id']):
  184. no_partner = False
  185. partners_data.update({
  186. move_line['partner_id']: {
  187. 'id': move_line['partner_id'],
  188. 'name': move_line['partner_name'],
  189. 'currency_id': accounts_data[move_line[
  190. 'account_id']]['currency_id'],
  191. }
  192. })
  193. else:
  194. partners_data[0]['currency_id'] = accounts_data[move_line[
  195. 'account_id']]['currency_id']
  196. # Move line update
  197. original = 0
  198. if not float_is_zero(move_line['credit'], precision_digits=2):
  199. original = move_line['credit']*(-1)
  200. if not float_is_zero(move_line['debit'], precision_digits=2):
  201. original = move_line['debit']
  202. move_line.update({
  203. 'date': move_line['date'].strftime("%d/%m/%Y"),
  204. 'date_maturity': move_line['date_maturity'].strftime("%d/%m/%Y"),
  205. 'original': original,
  206. 'partner_id': 0 if no_partner else move_line['partner_id'],
  207. 'partner_name': '' if no_partner else move_line['partner_name'],
  208. 'ref': '' if not move_line['ref'] else move_line['ref'],
  209. 'account': accounts_data[move_line['account_id']]['code'],
  210. 'journal': journals_data[move_line['journal_id']]['code'],
  211. })
  212. # Open Items Move Lines Data
  213. if move_line['account_id'] not in open_items_move_lines_data.keys():
  214. open_items_move_lines_data[move_line['account_id']] = {
  215. move_line['partner_id']: [move_line]}
  216. else:
  217. if move_line['partner_id'] not in \
  218. open_items_move_lines_data[move_line[
  219. 'account_id']].keys():
  220. open_items_move_lines_data[move_line['account_id']][
  221. move_line['partner_id']] = [move_line]
  222. else:
  223. open_items_move_lines_data[move_line['account_id']][
  224. move_line['partner_id']].append(move_line)
  225. return move_lines_data, partners_data, journals_data, accounts_data, \
  226. open_items_move_lines_data
  227. @api.model
  228. def _calculate_amounts(self, open_items_move_lines_data):
  229. total_amount = {}
  230. for account_id in open_items_move_lines_data.keys():
  231. total_amount[account_id] = {}
  232. total_amount[account_id]['residual'] = 0.0
  233. for partner_id in open_items_move_lines_data[account_id].keys():
  234. total_amount[account_id][partner_id] = {}
  235. total_amount[account_id][partner_id]['residual'] = 0.0
  236. for move_line in open_items_move_lines_data[account_id][
  237. partner_id]:
  238. total_amount[account_id][partner_id]['residual'] += \
  239. move_line['amount_residual']
  240. total_amount[account_id]['residual'] += move_line[
  241. 'amount_residual']
  242. return total_amount
  243. @api.multi
  244. def _get_report_values(self, docids, data):
  245. wizard_id = data['wizard_id']
  246. company = self.env['res.company'].browse(data['company_id'])
  247. company_id = data['company_id']
  248. account_ids = data['account_ids']
  249. partner_ids = data['partner_ids']
  250. date_at = data['date_at']
  251. date_at_object = datetime.strptime(date_at, '%Y-%m-%d').date()
  252. date_from = data['date_from']
  253. target_move = data['target_move']
  254. move_lines_data, partners_data, journals_data, accounts_data, \
  255. open_items_move_lines_data = self._get_data(
  256. account_ids, partner_ids, date_at_object,
  257. target_move, company_id, date_from)
  258. total_amount = self._calculate_amounts(open_items_move_lines_data)
  259. return{
  260. 'doc_ids': [wizard_id],
  261. 'doc_model': 'open.items.report.wizard',
  262. 'docs': self.env['open.items.report.wizard'].browse(wizard_id),
  263. 'foreign_currency': data['foreign_currency'],
  264. 'company_name': company.display_name,
  265. 'currency_name': company.currency_id.name,
  266. 'date_at': date_at_object.strftime("%d/%m/%Y"),
  267. 'hide_account_at_0': data['hide_account_at_0'],
  268. 'target_move': data['target_move'],
  269. 'partners_data': partners_data,
  270. 'accounts_data': accounts_data,
  271. 'total_amount': total_amount,
  272. 'Open_Items': open_items_move_lines_data,
  273. }