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.

365 lines
14 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 datetime import date, datetime
  5. import pandas as pd
  6. from odoo import api, models
  7. from odoo.osv import expression
  8. from odoo.tools import float_is_zero
  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"
  24. ).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. [
  34. [("debit_move_id", "in", reconciled_ids)],
  35. [("credit_move_id", "in", reconciled_ids)],
  36. ]
  37. )
  38. fields = ["debit_move_id", "credit_move_id", "amount"]
  39. accounts_partial_reconcile = self.env["account.partial.reconcile"].search_read(
  40. domain=domain, fields=fields
  41. )
  42. debit_accounts_partial_amount = {}
  43. credit_accounts_partial_amount = {}
  44. for account_partial_reconcile_data in accounts_partial_reconcile:
  45. debit_move_id = account_partial_reconcile_data["debit_move_id"][0]
  46. credit_move_id = account_partial_reconcile_data["credit_move_id"][0]
  47. if debit_move_id not in debit_accounts_partial_amount.keys():
  48. debit_accounts_partial_amount[debit_move_id] = 0.0
  49. debit_accounts_partial_amount[
  50. debit_move_id
  51. ] += account_partial_reconcile_data["amount"]
  52. if credit_move_id not in credit_accounts_partial_amount.keys():
  53. credit_accounts_partial_amount[credit_move_id] = 0.0
  54. credit_accounts_partial_amount[
  55. credit_move_id
  56. ] += account_partial_reconcile_data["amount"]
  57. account_partial_reconcile_data.update(
  58. {"debit_move_id": debit_move_id, "credit_move_id": credit_move_id,}
  59. )
  60. return (
  61. accounts_partial_reconcile,
  62. debit_accounts_partial_amount,
  63. credit_accounts_partial_amount,
  64. )
  65. @api.model
  66. def _get_query_domain(
  67. self,
  68. account_ids,
  69. partner_ids,
  70. date_at_object,
  71. target_move,
  72. company_id,
  73. date_from,
  74. ):
  75. query = """
  76. WHERE aml.account_id in %s and aml.company_id = %s
  77. """ % (
  78. tuple(account_ids) if len(account_ids) > 1 else "(%s)" % account_ids[0],
  79. company_id,
  80. )
  81. if date_from:
  82. query += " and aml.date >= '%s'" % date_from
  83. if partner_ids:
  84. query += " and aml.partner_id in {}".format(tuple(partner_ids))
  85. if target_move == "posted":
  86. query += " and am.state = 'posted'"
  87. if date_at_object >= date.today():
  88. query += " and aml.reconciled IS FALSE"
  89. else:
  90. query += (
  91. """ and ((aml.reconciled IS FALSE OR aml.date >= '%s')
  92. OR aml.full_reconcile_id IS NOT NULL)"""
  93. % date_at_object
  94. )
  95. return query
  96. @api.model
  97. def _get_query(
  98. self,
  99. account_ids,
  100. partner_ids,
  101. date_at_object,
  102. target_move,
  103. company_id,
  104. date_from,
  105. ):
  106. aml_fields = [
  107. "id",
  108. "date",
  109. "move_id",
  110. "journal_id",
  111. "account_id",
  112. "partner_id",
  113. "ref",
  114. "date_maturity",
  115. "amount_residual",
  116. "amount_currency",
  117. "amount_residual_currency",
  118. "debit",
  119. "credit",
  120. "currency_id",
  121. "reconciled",
  122. "full_reconcile_id",
  123. ]
  124. query = ""
  125. # SELECT
  126. for field in aml_fields:
  127. if not query:
  128. query = "SELECT aml.%s" % field
  129. else:
  130. query += ", aml.%s" % field
  131. # name from res_partner
  132. query += ", rp.name as partner_name"
  133. # name from res_currency
  134. query += ", rc.name as currency_name"
  135. # state and name from account_move
  136. query += ", am.state, am.name as move_name"
  137. # FROM
  138. query += """
  139. FROM account_move_line as aml
  140. LEFT JOIN res_partner as rp
  141. ON aml.partner_id = rp.id
  142. LEFT JOIN res_currency as rc
  143. ON aml.currency_id = rc.id
  144. LEFT JOIN account_move as am
  145. ON am.id = aml.move_id
  146. """
  147. # WHERE
  148. query += self._get_query_domain(
  149. account_ids, partner_ids, date_at_object, target_move, company_id, date_from
  150. )
  151. return query
  152. def _get_accounts_data(self, accounts_ids):
  153. accounts = self.env["account.account"].browse(accounts_ids)
  154. accounts_data = {}
  155. for account in accounts:
  156. accounts_data.update(
  157. {
  158. account.id: {
  159. "id": account.id,
  160. "code": account.code,
  161. "name": account.name,
  162. "hide_account": False,
  163. "currency_id": account.currency_id or False,
  164. "currency_name": account.currency_id.name,
  165. }
  166. }
  167. )
  168. return accounts_data
  169. def _get_journals_data(self, journals_ids):
  170. journals = self.env["account.journal"].browse(journals_ids)
  171. journals_data = {}
  172. for journal in journals:
  173. journals_data.update({journal.id: {"id": journal.id, "code": journal.code}})
  174. return journals_data
  175. def _get_data(
  176. self,
  177. account_ids,
  178. partner_ids,
  179. date_at_object,
  180. target_move,
  181. company_id,
  182. date_from,
  183. ):
  184. query = self._get_query(
  185. account_ids, partner_ids, date_at_object, target_move, company_id, date_from
  186. )
  187. self._cr.execute(query)
  188. move_lines_data = pd.DataFrame(self._cr.dictfetchall())
  189. account_ids = set(move_lines_data.account_id.to_list())
  190. accounts_data = self._get_accounts_data(list(account_ids))
  191. journal_ids = set(move_lines_data.journal_id.to_list())
  192. journals_data = self._get_journals_data(list(journal_ids))
  193. move_lines_data = move_lines_data.fillna(0).to_dict(orient="records")
  194. if date_at_object < date.today():
  195. (
  196. accounts_partial_reconcile,
  197. debit_accounts_partial_amount,
  198. credit_accounts_partial_amount,
  199. ) = self._get_account_partial_reconciled(move_lines_data, date_at_object)
  200. if accounts_partial_reconcile:
  201. accounts_partial_reconcile_data = pd.DataFrame(
  202. accounts_partial_reconcile
  203. )
  204. debit_ids = set(accounts_partial_reconcile_data.debit_move_id.to_list())
  205. credit_ids = set(
  206. accounts_partial_reconcile_data.credit_move_id.to_list()
  207. )
  208. for move_line in move_lines_data:
  209. if move_line["id"] in debit_ids:
  210. move_line["amount_residual"] += debit_accounts_partial_amount[
  211. move_line["id"]
  212. ]
  213. if move_line["id"] in credit_ids:
  214. move_line["amount_residual"] -= credit_accounts_partial_amount[
  215. move_line["id"]
  216. ]
  217. moves_lines_to_remove = []
  218. for move_line in move_lines_data:
  219. if move_line["date"] > date_at_object or float_is_zero(
  220. move_line["amount_residual"], precision_digits=2
  221. ):
  222. moves_lines_to_remove.append(move_line)
  223. if len(moves_lines_to_remove) > 0:
  224. for move_line_to_remove in moves_lines_to_remove:
  225. move_lines_data.remove(move_line_to_remove)
  226. partners_data = {0: {"id": 0, "name": "Missing Partner"}}
  227. open_items_move_lines_data = {}
  228. for move_line in move_lines_data:
  229. no_partner = True
  230. # Partners data
  231. if move_line["partner_id"] and not pd.isna(move_line["partner_id"]):
  232. no_partner = False
  233. partners_data.update(
  234. {
  235. move_line["partner_id"]: {
  236. "id": move_line["partner_id"],
  237. "name": move_line["partner_name"],
  238. "currency_id": accounts_data[move_line["account_id"]][
  239. "currency_id"
  240. ],
  241. }
  242. }
  243. )
  244. else:
  245. partners_data[0]["currency_id"] = accounts_data[
  246. move_line["account_id"]
  247. ]["currency_id"]
  248. # Move line update
  249. original = 0
  250. if not float_is_zero(move_line["credit"], precision_digits=2):
  251. original = move_line["credit"] * (-1)
  252. if not float_is_zero(move_line["debit"], precision_digits=2):
  253. original = move_line["debit"]
  254. move_line.update(
  255. {
  256. "date": move_line["date"].strftime("%d/%m/%Y"),
  257. "date_maturity": move_line["date_maturity"].strftime("%d/%m/%Y"),
  258. "original": original,
  259. "partner_id": 0 if no_partner else move_line["partner_id"],
  260. "partner_name": "" if no_partner else move_line["partner_name"],
  261. "ref": "" if not move_line["ref"] else move_line["ref"],
  262. "account": accounts_data[move_line["account_id"]]["code"],
  263. "journal": journals_data[move_line["journal_id"]]["code"],
  264. }
  265. )
  266. # Open Items Move Lines Data
  267. if move_line["account_id"] not in open_items_move_lines_data.keys():
  268. open_items_move_lines_data[move_line["account_id"]] = {
  269. move_line["partner_id"]: [move_line]
  270. }
  271. else:
  272. if (
  273. move_line["partner_id"]
  274. not in open_items_move_lines_data[move_line["account_id"]].keys()
  275. ):
  276. open_items_move_lines_data[move_line["account_id"]][
  277. move_line["partner_id"]
  278. ] = [move_line]
  279. else:
  280. open_items_move_lines_data[move_line["account_id"]][
  281. move_line["partner_id"]
  282. ].append(move_line)
  283. return (
  284. move_lines_data,
  285. partners_data,
  286. journals_data,
  287. accounts_data,
  288. open_items_move_lines_data,
  289. )
  290. @api.model
  291. def _calculate_amounts(self, open_items_move_lines_data):
  292. total_amount = {}
  293. for account_id in open_items_move_lines_data.keys():
  294. total_amount[account_id] = {}
  295. total_amount[account_id]["residual"] = 0.0
  296. for partner_id in open_items_move_lines_data[account_id].keys():
  297. total_amount[account_id][partner_id] = {}
  298. total_amount[account_id][partner_id]["residual"] = 0.0
  299. for move_line in open_items_move_lines_data[account_id][partner_id]:
  300. total_amount[account_id][partner_id]["residual"] += move_line[
  301. "amount_residual"
  302. ]
  303. total_amount[account_id]["residual"] += move_line["amount_residual"]
  304. return total_amount
  305. @api.multi
  306. def _get_report_values(self, docids, data):
  307. wizard_id = data["wizard_id"]
  308. company = self.env["res.company"].browse(data["company_id"])
  309. company_id = data["company_id"]
  310. account_ids = data["account_ids"]
  311. partner_ids = data["partner_ids"]
  312. date_at = data["date_at"]
  313. date_at_object = datetime.strptime(date_at, "%Y-%m-%d").date()
  314. date_from = data["date_from"]
  315. target_move = data["target_move"]
  316. (
  317. move_lines_data,
  318. partners_data,
  319. journals_data,
  320. accounts_data,
  321. open_items_move_lines_data,
  322. ) = self._get_data(
  323. account_ids, partner_ids, date_at_object, target_move, company_id, date_from
  324. )
  325. total_amount = self._calculate_amounts(open_items_move_lines_data)
  326. return {
  327. "doc_ids": [wizard_id],
  328. "doc_model": "open.items.report.wizard",
  329. "docs": self.env["open.items.report.wizard"].browse(wizard_id),
  330. "foreign_currency": data["foreign_currency"],
  331. "company_name": company.display_name,
  332. "currency_name": company.currency_id.name,
  333. "date_at": date_at_object.strftime("%d/%m/%Y"),
  334. "hide_account_at_0": data["hide_account_at_0"],
  335. "target_move": data["target_move"],
  336. "partners_data": partners_data,
  337. "accounts_data": accounts_data,
  338. "total_amount": total_amount,
  339. "Open_Items": open_items_move_lines_data,
  340. }