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.

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