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.

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