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.

401 lines
15 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.tools import float_is_zero
  8. class OpenItemsReport(models.AbstractModel):
  9. _name = "report.account_financial_report.open_items"
  10. _description = "Open Items Report"
  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, company_id, date_at_object):
  27. domain = [("max_date", ">", date_at_object), ("company_id", "=", company_id)]
  28. fields = ["debit_move_id", "credit_move_id", "amount"]
  29. accounts_partial_reconcile = self.env["account.partial.reconcile"].search_read(
  30. domain=domain, fields=fields
  31. )
  32. debit_amount = {}
  33. credit_amount = {}
  34. for account_partial_reconcile_data in accounts_partial_reconcile:
  35. debit_move_id = account_partial_reconcile_data["debit_move_id"][0]
  36. credit_move_id = account_partial_reconcile_data["credit_move_id"][0]
  37. if debit_move_id not in debit_amount.keys():
  38. debit_amount[debit_move_id] = 0.0
  39. debit_amount[debit_move_id] += account_partial_reconcile_data["amount"]
  40. if credit_move_id not in credit_amount.keys():
  41. credit_amount[credit_move_id] = 0.0
  42. credit_amount[credit_move_id] += account_partial_reconcile_data["amount"]
  43. account_partial_reconcile_data.update(
  44. {"debit_move_id": debit_move_id, "credit_move_id": credit_move_id}
  45. )
  46. return accounts_partial_reconcile, debit_amount, credit_amount
  47. @api.model
  48. def _get_new_move_lines_domain(
  49. self, new_ml_ids, account_ids, company_id, partner_ids, only_posted_moves
  50. ):
  51. domain = [
  52. ("account_id", "in", account_ids),
  53. ("company_id", "=", company_id),
  54. ("id", "in", new_ml_ids),
  55. ]
  56. if partner_ids:
  57. domain += [("partner_id", "in", partner_ids)]
  58. if only_posted_moves:
  59. domain += [("move_id.state", "=", "posted")]
  60. else:
  61. domain += [("move_id.state", "in", ["posted", "draft"])]
  62. return domain
  63. def _recalculate_move_lines(
  64. self,
  65. move_lines,
  66. debit_ids,
  67. credit_ids,
  68. debit_amount,
  69. credit_amount,
  70. ml_ids,
  71. account_ids,
  72. company_id,
  73. partner_ids,
  74. only_posted_moves,
  75. ):
  76. debit_ids = set(debit_ids)
  77. credit_ids = set(credit_ids)
  78. in_credit_but_not_in_debit = credit_ids - debit_ids
  79. reconciled_ids = list(debit_ids) + list(in_credit_but_not_in_debit)
  80. reconciled_ids = set(reconciled_ids)
  81. ml_ids = set(ml_ids)
  82. new_ml_ids = reconciled_ids - ml_ids
  83. new_ml_ids = list(new_ml_ids)
  84. new_domain = self._get_new_move_lines_domain(
  85. new_ml_ids, account_ids, company_id, partner_ids, only_posted_moves
  86. )
  87. ml_fields = [
  88. "id",
  89. "name",
  90. "date",
  91. "move_id",
  92. "journal_id",
  93. "account_id",
  94. "partner_id",
  95. "amount_residual",
  96. "date_maturity",
  97. "ref",
  98. "debit",
  99. "credit",
  100. "reconciled",
  101. "currency_id",
  102. "amount_currency",
  103. "amount_residual_currency",
  104. ]
  105. new_move_lines = self.env["account.move.line"].search_read(
  106. domain=new_domain, fields=ml_fields
  107. )
  108. move_lines = move_lines + new_move_lines
  109. for move_line in move_lines:
  110. ml_id = move_line["id"]
  111. if ml_id in debit_ids:
  112. move_line["amount_residual"] += debit_amount[ml_id]
  113. if ml_id in credit_ids:
  114. move_line["amount_residual"] -= credit_amount[ml_id]
  115. return move_lines
  116. @api.model
  117. def _get_move_lines_domain(
  118. self, company_id, account_ids, partner_ids, only_posted_moves, date_from
  119. ):
  120. domain = [
  121. ("account_id", "in", account_ids),
  122. ("company_id", "=", company_id),
  123. ("reconciled", "=", False),
  124. ]
  125. if partner_ids:
  126. domain += [("partner_id", "in", partner_ids)]
  127. if only_posted_moves:
  128. domain += [("move_id.state", "=", "posted")]
  129. else:
  130. domain += [("move_id.state", "in", ["posted", "draft"])]
  131. if date_from:
  132. domain += [("date", ">", date_from)]
  133. return domain
  134. def _get_accounts_data(self, accounts_ids):
  135. accounts = self.env["account.account"].browse(accounts_ids)
  136. accounts_data = {}
  137. for account in accounts:
  138. accounts_data.update(
  139. {
  140. account.id: {
  141. "id": account.id,
  142. "code": account.code,
  143. "name": account.name,
  144. "hide_account": False,
  145. "currency_id": account.currency_id or False,
  146. "currency_name": account.currency_id.name,
  147. }
  148. }
  149. )
  150. return accounts_data
  151. def _get_journals_data(self, journals_ids):
  152. journals = self.env["account.journal"].browse(journals_ids)
  153. journals_data = {}
  154. for journal in journals:
  155. journals_data.update({journal.id: {"id": journal.id, "code": journal.code}})
  156. return journals_data
  157. def _get_data(
  158. self,
  159. account_ids,
  160. partner_ids,
  161. date_at_object,
  162. only_posted_moves,
  163. company_id,
  164. date_from,
  165. ):
  166. domain = self._get_move_lines_domain(
  167. company_id, account_ids, partner_ids, only_posted_moves, date_from
  168. )
  169. ml_fields = [
  170. "id",
  171. "name",
  172. "date",
  173. "move_id",
  174. "journal_id",
  175. "account_id",
  176. "partner_id",
  177. "amount_residual",
  178. "date_maturity",
  179. "ref",
  180. "debit",
  181. "credit",
  182. "reconciled",
  183. "currency_id",
  184. "amount_currency",
  185. "amount_residual_currency",
  186. ]
  187. move_lines = self.env["account.move.line"].search_read(
  188. domain=domain, fields=ml_fields
  189. )
  190. journals_ids = set()
  191. partners_ids = set()
  192. partners_data = {}
  193. if date_at_object < date.today():
  194. (
  195. acc_partial_rec,
  196. debit_amount,
  197. credit_amount,
  198. ) = self._get_account_partial_reconciled(company_id, date_at_object)
  199. if acc_partial_rec:
  200. ml_ids = list(map(operator.itemgetter("id"), move_lines))
  201. debit_ids = list(
  202. map(operator.itemgetter("debit_move_id"), acc_partial_rec)
  203. )
  204. credit_ids = list(
  205. map(operator.itemgetter("credit_move_id"), acc_partial_rec)
  206. )
  207. move_lines = self._recalculate_move_lines(
  208. move_lines,
  209. debit_ids,
  210. credit_ids,
  211. debit_amount,
  212. credit_amount,
  213. ml_ids,
  214. account_ids,
  215. company_id,
  216. partner_ids,
  217. only_posted_moves,
  218. )
  219. move_lines = [
  220. move_line
  221. for move_line in move_lines
  222. if move_line["date"] <= date_at_object
  223. and not float_is_zero(move_line["amount_residual"], precision_digits=2)
  224. ]
  225. open_items_move_lines_data = {}
  226. for move_line in move_lines:
  227. journals_ids.add(move_line["journal_id"][0])
  228. acc_id = move_line["account_id"][0]
  229. # Partners data
  230. if move_line["partner_id"]:
  231. prt_id = move_line["partner_id"][0]
  232. prt_name = move_line["partner_id"][1]
  233. else:
  234. prt_id = 0
  235. prt_name = "Missing Partner"
  236. if prt_id not in partners_ids:
  237. partners_data.update({prt_id: {"id": prt_id, "name": prt_name}})
  238. partners_ids.add(prt_id)
  239. # Move line update
  240. original = 0
  241. if not float_is_zero(move_line["credit"], precision_digits=2):
  242. original = move_line["credit"] * (-1)
  243. if not float_is_zero(move_line["debit"], precision_digits=2):
  244. original = move_line["debit"]
  245. if move_line["ref"] == move_line["name"]:
  246. if move_line["ref"]:
  247. ref_label = move_line["ref"]
  248. else:
  249. ref_label = ""
  250. elif not move_line["ref"]:
  251. ref_label = move_line["name"]
  252. elif not move_line["name"]:
  253. ref_label = move_line["ref"]
  254. else:
  255. ref_label = move_line["ref"] + str(" - ") + move_line["name"]
  256. move_line.update(
  257. {
  258. "date": move_line["date"],
  259. "date_maturity": move_line["date_maturity"]
  260. and move_line["date_maturity"].strftime("%d/%m/%Y"),
  261. "original": original,
  262. "partner_id": prt_id,
  263. "partner_name": prt_name,
  264. "ref_label": ref_label,
  265. "journal_id": move_line["journal_id"][0],
  266. "move_name": move_line["move_id"][1],
  267. "currency_id": move_line["currency_id"][0]
  268. if move_line["currency_id"]
  269. else False,
  270. "currency_name": move_line["currency_id"][1]
  271. if move_line["currency_id"]
  272. else False,
  273. }
  274. )
  275. # Open Items Move Lines Data
  276. if acc_id not in open_items_move_lines_data.keys():
  277. open_items_move_lines_data[acc_id] = {prt_id: [move_line]}
  278. else:
  279. if prt_id not in open_items_move_lines_data[acc_id].keys():
  280. open_items_move_lines_data[acc_id][prt_id] = [move_line]
  281. else:
  282. open_items_move_lines_data[acc_id][prt_id].append(move_line)
  283. journals_data = self._get_journals_data(list(journals_ids))
  284. accounts_data = self._get_accounts_data(open_items_move_lines_data.keys())
  285. return (
  286. move_lines,
  287. partners_data,
  288. journals_data,
  289. accounts_data,
  290. open_items_move_lines_data,
  291. )
  292. @api.model
  293. def _calculate_amounts(self, open_items_move_lines_data):
  294. total_amount = {}
  295. for account_id in open_items_move_lines_data.keys():
  296. total_amount[account_id] = {}
  297. total_amount[account_id]["residual"] = 0.0
  298. for partner_id in open_items_move_lines_data[account_id].keys():
  299. total_amount[account_id][partner_id] = {}
  300. total_amount[account_id][partner_id]["residual"] = 0.0
  301. for move_line in open_items_move_lines_data[account_id][partner_id]:
  302. total_amount[account_id][partner_id]["residual"] += move_line[
  303. "amount_residual"
  304. ]
  305. total_amount[account_id]["residual"] += move_line["amount_residual"]
  306. return total_amount
  307. @api.model
  308. def _order_open_items_by_date(
  309. self, open_items_move_lines_data, show_partner_details
  310. ):
  311. new_open_items = {}
  312. if not show_partner_details:
  313. for acc_id in open_items_move_lines_data.keys():
  314. new_open_items[acc_id] = {}
  315. move_lines = []
  316. for prt_id in open_items_move_lines_data[acc_id]:
  317. for move_line in open_items_move_lines_data[acc_id][prt_id]:
  318. move_lines += [move_line]
  319. move_lines = sorted(move_lines, key=lambda k: (k["date"]))
  320. new_open_items[acc_id] = move_lines
  321. else:
  322. for acc_id in open_items_move_lines_data.keys():
  323. new_open_items[acc_id] = {}
  324. for prt_id in open_items_move_lines_data[acc_id]:
  325. new_open_items[acc_id][prt_id] = {}
  326. move_lines = []
  327. for move_line in open_items_move_lines_data[acc_id][prt_id]:
  328. move_lines += [move_line]
  329. move_lines = sorted(move_lines, key=lambda k: (k["date"]))
  330. new_open_items[acc_id][prt_id] = move_lines
  331. return new_open_items
  332. def _get_report_values(self, docids, data):
  333. wizard_id = data["wizard_id"]
  334. company = self.env["res.company"].browse(data["company_id"])
  335. company_id = data["company_id"]
  336. account_ids = data["account_ids"]
  337. partner_ids = data["partner_ids"]
  338. date_at = data["date_at"]
  339. date_at_object = datetime.strptime(date_at, "%Y-%m-%d").date()
  340. date_from = data["date_from"]
  341. only_posted_moves = data["only_posted_moves"]
  342. show_partner_details = data["show_partner_details"]
  343. (
  344. move_lines_data,
  345. partners_data,
  346. journals_data,
  347. accounts_data,
  348. open_items_move_lines_data,
  349. ) = self._get_data(
  350. account_ids,
  351. partner_ids,
  352. date_at_object,
  353. only_posted_moves,
  354. company_id,
  355. date_from,
  356. )
  357. total_amount = self._calculate_amounts(open_items_move_lines_data)
  358. open_items_move_lines_data = self._order_open_items_by_date(
  359. open_items_move_lines_data, show_partner_details
  360. )
  361. return {
  362. "doc_ids": [wizard_id],
  363. "doc_model": "open.items.report.wizard",
  364. "docs": self.env["open.items.report.wizard"].browse(wizard_id),
  365. "foreign_currency": data["foreign_currency"],
  366. "show_partner_details": data["show_partner_details"],
  367. "company_name": company.display_name,
  368. "currency_name": company.currency_id.name,
  369. "date_at": date_at_object.strftime("%d/%m/%Y"),
  370. "hide_account_at_0": data["hide_account_at_0"],
  371. "target_move": data["target_move"],
  372. "journals_data": journals_data,
  373. "partners_data": partners_data,
  374. "accounts_data": accounts_data,
  375. "total_amount": total_amount,
  376. "Open_Items": open_items_move_lines_data,
  377. }