# © 2016 Julien Coux (Camptocamp) # Copyright 2020 ForgeFlow S.L. (https://www.forgeflow.com) # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). from datetime import date, datetime import pandas as pd from odoo import api, models from odoo.osv import expression from odoo.tools import float_is_zero class OpenItemsReport(models.AbstractModel): _name = "report.account_financial_report.open_items" @api.model def get_html(self, given_context=None): return self._get_html() def _get_html(self): result = {} rcontext = {} context = dict(self.env.context) rcontext.update(context.get("data")) active_id = context.get("active_id") wiz = self.env["open.items.report.wizard"].browse(active_id) rcontext["o"] = wiz result["html"] = self.env.ref( "account_financial_report.report_open_items" ).render(rcontext) return result def _get_account_partial_reconciled(self, move_lines_data, date_at_object): reconciled_ids = [] for move_line in move_lines_data: if move_line["reconciled"]: reconciled_ids += [move_line["id"]] domain = [("max_date", ">=", date_at_object)] domain += expression.OR( [ [("debit_move_id", "in", reconciled_ids)], [("credit_move_id", "in", reconciled_ids)], ] ) fields = ["debit_move_id", "credit_move_id", "amount"] accounts_partial_reconcile = self.env["account.partial.reconcile"].search_read( domain=domain, fields=fields ) debit_accounts_partial_amount = {} credit_accounts_partial_amount = {} for account_partial_reconcile_data in accounts_partial_reconcile: debit_move_id = account_partial_reconcile_data["debit_move_id"][0] credit_move_id = account_partial_reconcile_data["credit_move_id"][0] if debit_move_id not in debit_accounts_partial_amount.keys(): debit_accounts_partial_amount[debit_move_id] = 0.0 debit_accounts_partial_amount[ debit_move_id ] += account_partial_reconcile_data["amount"] if credit_move_id not in credit_accounts_partial_amount.keys(): credit_accounts_partial_amount[credit_move_id] = 0.0 credit_accounts_partial_amount[ credit_move_id ] += account_partial_reconcile_data["amount"] account_partial_reconcile_data.update( {"debit_move_id": debit_move_id, "credit_move_id": credit_move_id,} ) return ( accounts_partial_reconcile, debit_accounts_partial_amount, credit_accounts_partial_amount, ) @api.model def _get_query_domain( self, account_ids, partner_ids, date_at_object, target_move, company_id, date_from, ): query = """ WHERE aml.account_id in %s and aml.company_id = %s """ % ( tuple(account_ids) if len(account_ids) > 1 else "(%s)" % account_ids[0], company_id, ) if date_from: query += " and aml.date >= '%s'" % date_from if partner_ids: query += " and aml.partner_id in {}".format(tuple(partner_ids)) if target_move == "posted": query += " and am.state = 'posted'" if date_at_object >= date.today(): query += " and aml.reconciled IS FALSE" else: query += ( """ and ((aml.reconciled IS FALSE OR aml.date >= '%s') OR aml.full_reconcile_id IS NOT NULL)""" % date_at_object ) return query @api.model def _get_query( self, account_ids, partner_ids, date_at_object, target_move, company_id, date_from, ): aml_fields = [ "id", "date", "move_id", "journal_id", "account_id", "partner_id", "ref", "date_maturity", "amount_residual", "amount_currency", "amount_residual_currency", "debit", "credit", "currency_id", "reconciled", "full_reconcile_id", ] query = "" # SELECT for field in aml_fields: if not query: query = "SELECT aml.%s" % field else: query += ", aml.%s" % field # name from res_partner query += ", rp.name as partner_name" # name from res_currency query += ", rc.name as currency_name" # state and name from account_move query += ", am.state, am.name as move_name" # FROM query += """ FROM account_move_line as aml LEFT JOIN res_partner as rp ON aml.partner_id = rp.id LEFT JOIN res_currency as rc ON aml.currency_id = rc.id LEFT JOIN account_move as am ON am.id = aml.move_id """ # WHERE query += self._get_query_domain( account_ids, partner_ids, date_at_object, target_move, company_id, date_from ) return query def _get_accounts_data(self, accounts_ids): accounts = self.env["account.account"].browse(accounts_ids) accounts_data = {} for account in accounts: accounts_data.update( { account.id: { "id": account.id, "code": account.code, "name": account.name, "hide_account": False, "currency_id": account.currency_id or False, "currency_name": account.currency_id.name, } } ) return accounts_data def _get_journals_data(self, journals_ids): journals = self.env["account.journal"].browse(journals_ids) journals_data = {} for journal in journals: journals_data.update({journal.id: {"id": journal.id, "code": journal.code}}) return journals_data def _get_data( self, account_ids, partner_ids, date_at_object, target_move, company_id, date_from, ): query = self._get_query( account_ids, partner_ids, date_at_object, target_move, company_id, date_from ) self._cr.execute(query) move_lines_data = pd.DataFrame(self._cr.dictfetchall()) account_ids = set(move_lines_data.account_id.to_list()) accounts_data = self._get_accounts_data(list(account_ids)) journal_ids = set(move_lines_data.journal_id.to_list()) journals_data = self._get_journals_data(list(journal_ids)) move_lines_data = move_lines_data.fillna(0).to_dict(orient="records") if date_at_object < date.today(): ( accounts_partial_reconcile, debit_accounts_partial_amount, credit_accounts_partial_amount, ) = self._get_account_partial_reconciled(move_lines_data, date_at_object) if accounts_partial_reconcile: accounts_partial_reconcile_data = pd.DataFrame( accounts_partial_reconcile ) debit_ids = set(accounts_partial_reconcile_data.debit_move_id.to_list()) credit_ids = set( accounts_partial_reconcile_data.credit_move_id.to_list() ) for move_line in move_lines_data: if move_line["id"] in debit_ids: move_line["amount_residual"] += debit_accounts_partial_amount[ move_line["id"] ] if move_line["id"] in credit_ids: move_line["amount_residual"] -= credit_accounts_partial_amount[ move_line["id"] ] moves_lines_to_remove = [] for move_line in move_lines_data: if move_line["date"] > date_at_object or float_is_zero( move_line["amount_residual"], precision_digits=2 ): moves_lines_to_remove.append(move_line) if len(moves_lines_to_remove) > 0: for move_line_to_remove in moves_lines_to_remove: move_lines_data.remove(move_line_to_remove) partners_data = {0: {"id": 0, "name": "Missing Partner"}} open_items_move_lines_data = {} for move_line in move_lines_data: no_partner = True # Partners data if move_line["partner_id"] and not pd.isna(move_line["partner_id"]): no_partner = False partners_data.update( { move_line["partner_id"]: { "id": move_line["partner_id"], "name": move_line["partner_name"], "currency_id": accounts_data[move_line["account_id"]][ "currency_id" ], } } ) else: partners_data[0]["currency_id"] = accounts_data[ move_line["account_id"] ]["currency_id"] # Move line update original = 0 if not float_is_zero(move_line["credit"], precision_digits=2): original = move_line["credit"] * (-1) if not float_is_zero(move_line["debit"], precision_digits=2): original = move_line["debit"] move_line.update( { "date": move_line["date"].strftime("%d/%m/%Y"), "date_maturity": move_line["date_maturity"].strftime("%d/%m/%Y"), "original": original, "partner_id": 0 if no_partner else move_line["partner_id"], "partner_name": "" if no_partner else move_line["partner_name"], "ref": "" if not move_line["ref"] else move_line["ref"], "account": accounts_data[move_line["account_id"]]["code"], "journal": journals_data[move_line["journal_id"]]["code"], } ) # Open Items Move Lines Data if move_line["account_id"] not in open_items_move_lines_data.keys(): open_items_move_lines_data[move_line["account_id"]] = { move_line["partner_id"]: [move_line] } else: if ( move_line["partner_id"] not in open_items_move_lines_data[move_line["account_id"]].keys() ): open_items_move_lines_data[move_line["account_id"]][ move_line["partner_id"] ] = [move_line] else: open_items_move_lines_data[move_line["account_id"]][ move_line["partner_id"] ].append(move_line) return ( move_lines_data, partners_data, journals_data, accounts_data, open_items_move_lines_data, ) @api.model def _calculate_amounts(self, open_items_move_lines_data): total_amount = {} for account_id in open_items_move_lines_data.keys(): total_amount[account_id] = {} total_amount[account_id]["residual"] = 0.0 for partner_id in open_items_move_lines_data[account_id].keys(): total_amount[account_id][partner_id] = {} total_amount[account_id][partner_id]["residual"] = 0.0 for move_line in open_items_move_lines_data[account_id][partner_id]: total_amount[account_id][partner_id]["residual"] += move_line[ "amount_residual" ] total_amount[account_id]["residual"] += move_line["amount_residual"] return total_amount @api.multi def _get_report_values(self, docids, data): wizard_id = data["wizard_id"] company = self.env["res.company"].browse(data["company_id"]) company_id = data["company_id"] account_ids = data["account_ids"] partner_ids = data["partner_ids"] date_at = data["date_at"] date_at_object = datetime.strptime(date_at, "%Y-%m-%d").date() date_from = data["date_from"] target_move = data["target_move"] ( move_lines_data, partners_data, journals_data, accounts_data, open_items_move_lines_data, ) = self._get_data( account_ids, partner_ids, date_at_object, target_move, company_id, date_from ) total_amount = self._calculate_amounts(open_items_move_lines_data) return { "doc_ids": [wizard_id], "doc_model": "open.items.report.wizard", "docs": self.env["open.items.report.wizard"].browse(wizard_id), "foreign_currency": data["foreign_currency"], "company_name": company.display_name, "currency_name": company.currency_id.name, "date_at": date_at_object.strftime("%d/%m/%Y"), "hide_account_at_0": data["hide_account_at_0"], "target_move": data["target_move"], "partners_data": partners_data, "accounts_data": accounts_data, "total_amount": total_amount, "Open_Items": open_items_move_lines_data, }