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.

363 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. import operator
  5. from datetime import date, datetime
  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. _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"
  25. ).render(rcontext)
  26. return result
  27. def _get_account_partial_reconciled(self, move_lines_data, date_at_object):
  28. reconciled_ids = []
  29. for move_line in move_lines_data:
  30. if move_line["reconciled"]:
  31. reconciled_ids += [move_line["id"]]
  32. domain = [("max_date", ">=", date_at_object)]
  33. domain += expression.OR(
  34. [
  35. [("debit_move_id", "in", reconciled_ids)],
  36. [("credit_move_id", "in", reconciled_ids)],
  37. ]
  38. )
  39. fields = ["debit_move_id", "credit_move_id", "amount"]
  40. accounts_partial_reconcile = self.env["account.partial.reconcile"].search_read(
  41. domain=domain, fields=fields
  42. )
  43. debit_accounts_partial_amount = {}
  44. credit_accounts_partial_amount = {}
  45. for account_partial_reconcile_data in accounts_partial_reconcile:
  46. debit_move_id = account_partial_reconcile_data["debit_move_id"][0]
  47. credit_move_id = account_partial_reconcile_data["credit_move_id"][0]
  48. if debit_move_id not in debit_accounts_partial_amount.keys():
  49. debit_accounts_partial_amount[debit_move_id] = 0.0
  50. debit_accounts_partial_amount[
  51. debit_move_id
  52. ] += account_partial_reconcile_data["amount"]
  53. if credit_move_id not in credit_accounts_partial_amount.keys():
  54. credit_accounts_partial_amount[credit_move_id] = 0.0
  55. credit_accounts_partial_amount[
  56. credit_move_id
  57. ] += account_partial_reconcile_data["amount"]
  58. account_partial_reconcile_data.update(
  59. {"debit_move_id": debit_move_id, "credit_move_id": credit_move_id}
  60. )
  61. return (
  62. accounts_partial_reconcile,
  63. debit_accounts_partial_amount,
  64. credit_accounts_partial_amount,
  65. )
  66. @api.model
  67. def _get_query_domain(
  68. self,
  69. account_ids,
  70. partner_ids,
  71. date_at_object,
  72. target_move,
  73. company_id,
  74. date_from,
  75. ):
  76. query = """
  77. WHERE aml.account_id in %s and aml.company_id = %s
  78. """ % (
  79. tuple(account_ids) if len(account_ids) > 1 else "(%s)" % account_ids[0],
  80. company_id,
  81. )
  82. if date_from:
  83. query += " and aml.date >= '%s'" % date_from
  84. if partner_ids:
  85. query += " and aml.partner_id in {}".format(tuple(partner_ids))
  86. if target_move == "posted":
  87. query += " and am.state = 'posted'"
  88. if date_at_object >= date.today():
  89. query += " and aml.reconciled IS FALSE"
  90. else:
  91. query += (
  92. """ and ((aml.reconciled IS FALSE OR aml.date >= '%s')
  93. OR aml.full_reconcile_id IS NOT NULL)"""
  94. % date_at_object
  95. )
  96. return query
  97. @api.model
  98. def _get_query(
  99. self,
  100. account_ids,
  101. partner_ids,
  102. date_at_object,
  103. target_move,
  104. company_id,
  105. date_from,
  106. ):
  107. aml_fields = [
  108. "id",
  109. "date",
  110. "move_id",
  111. "journal_id",
  112. "account_id",
  113. "partner_id",
  114. "ref",
  115. "date_maturity",
  116. "amount_residual",
  117. "amount_currency",
  118. "amount_residual_currency",
  119. "debit",
  120. "credit",
  121. "currency_id",
  122. "reconciled",
  123. "full_reconcile_id",
  124. ]
  125. query = ""
  126. # SELECT
  127. for field in aml_fields:
  128. if not query:
  129. query = "SELECT aml.%s" % field
  130. else:
  131. query += ", aml.%s" % field
  132. # name from res_partner
  133. query += ", rp.name as partner_name"
  134. # name from res_currency
  135. query += ", rc.name as currency_name"
  136. # state and name from account_move
  137. query += ", am.state, am.name as move_name"
  138. # FROM
  139. query += """
  140. FROM account_move_line as aml
  141. LEFT JOIN res_partner as rp
  142. ON aml.partner_id = rp.id
  143. LEFT JOIN res_currency as rc
  144. ON aml.currency_id = rc.id
  145. LEFT JOIN account_move as am
  146. ON am.id = aml.move_id
  147. """
  148. # WHERE
  149. query += self._get_query_domain(
  150. account_ids, partner_ids, date_at_object, target_move, company_id, date_from
  151. )
  152. return query
  153. def _get_accounts_data(self, accounts_ids):
  154. accounts = self.env["account.account"].browse(accounts_ids)
  155. accounts_data = {}
  156. for account in accounts:
  157. accounts_data.update(
  158. {
  159. account.id: {
  160. "id": account.id,
  161. "code": account.code,
  162. "name": account.name,
  163. "hide_account": False,
  164. "currency_id": account.currency_id or False,
  165. "currency_name": account.currency_id.name,
  166. }
  167. }
  168. )
  169. return accounts_data
  170. def _get_journals_data(self, journals_ids):
  171. journals = self.env["account.journal"].browse(journals_ids)
  172. journals_data = {}
  173. for journal in journals:
  174. journals_data.update({journal.id: {"id": journal.id, "code": journal.code}})
  175. return journals_data
  176. # flake8: noqa: C901
  177. def _get_data(
  178. self,
  179. account_ids,
  180. partner_ids,
  181. date_at_object,
  182. target_move,
  183. company_id,
  184. date_from,
  185. ):
  186. query = self._get_query(
  187. account_ids, partner_ids, date_at_object, target_move, company_id, date_from
  188. )
  189. self._cr.execute(query)
  190. move_lines_data = self._cr.dictfetchall()
  191. account_ids = map(lambda r: r["account_id"], move_lines_data)
  192. accounts_data = self._get_accounts_data(list(account_ids))
  193. journal_ids = map(lambda r: r["journal_id"], move_lines_data)
  194. journals_data = self._get_journals_data(list(journal_ids))
  195. if date_at_object < date.today():
  196. (
  197. accounts_partial_reconcile,
  198. debit_accounts_partial_amount,
  199. credit_accounts_partial_amount,
  200. ) = self._get_account_partial_reconciled(move_lines_data, date_at_object)
  201. if accounts_partial_reconcile:
  202. debit_ids = map(
  203. operator.itemgetter("debit_move_id"), accounts_partial_reconcile
  204. )
  205. credit_ids = map(
  206. operator.itemgetter("credit_move_id"), accounts_partial_reconcile
  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"]:
  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"]
  258. and move_line["date_maturity"].strftime("%d/%m/%Y"),
  259. "original": original,
  260. "partner_id": 0 if no_partner else move_line["partner_id"],
  261. "partner_name": "" if no_partner else move_line["partner_name"],
  262. "ref": "" if not move_line["ref"] else move_line["ref"],
  263. "account": accounts_data[move_line["account_id"]]["code"],
  264. "journal": journals_data[move_line["journal_id"]]["code"],
  265. }
  266. )
  267. # Open Items Move Lines Data
  268. if move_line["account_id"] not in open_items_move_lines_data.keys():
  269. open_items_move_lines_data[move_line["account_id"]] = {
  270. move_line["partner_id"]: [move_line]
  271. }
  272. else:
  273. if (
  274. move_line["partner_id"]
  275. not in open_items_move_lines_data[move_line["account_id"]].keys()
  276. ):
  277. open_items_move_lines_data[move_line["account_id"]][
  278. move_line["partner_id"]
  279. ] = [move_line]
  280. else:
  281. open_items_move_lines_data[move_line["account_id"]][
  282. move_line["partner_id"]
  283. ].append(move_line)
  284. return (
  285. move_lines_data,
  286. partners_data,
  287. journals_data,
  288. accounts_data,
  289. open_items_move_lines_data,
  290. )
  291. @api.model
  292. def _calculate_amounts(self, open_items_move_lines_data):
  293. total_amount = {}
  294. for account_id in open_items_move_lines_data.keys():
  295. total_amount[account_id] = {}
  296. total_amount[account_id]["residual"] = 0.0
  297. for partner_id in open_items_move_lines_data[account_id].keys():
  298. total_amount[account_id][partner_id] = {}
  299. total_amount[account_id][partner_id]["residual"] = 0.0
  300. for move_line in open_items_move_lines_data[account_id][partner_id]:
  301. total_amount[account_id][partner_id]["residual"] += move_line[
  302. "amount_residual"
  303. ]
  304. total_amount[account_id]["residual"] += move_line["amount_residual"]
  305. return total_amount
  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. }