mirror of
https://github.com/bringout/oca-financial.git
synced 2026-04-26 18:42:06 +02:00
255 lines
9.7 KiB
Python
255 lines
9.7 KiB
Python
# Copyright 2004-2010 Tiny SPRL (http://tiny.be).
|
|
# Copyright 2010-2011 Elico Corp.
|
|
# Copyright 2016 Acsone (https://www.acsone.eu/)
|
|
# Copyright 2017 Eficent Business and IT Consulting Services S.L.
|
|
# (http://www.eficent.com)
|
|
# Copyright 2019 Okia SPRL
|
|
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
|
|
|
import numbers
|
|
|
|
from odoo import api, models
|
|
from odoo.tools import float_is_zero
|
|
|
|
|
|
class AccountMove(models.Model):
|
|
_inherit = "account.move"
|
|
|
|
@api.model
|
|
def _get_invoice_key_cols(self):
|
|
return [
|
|
"partner_id",
|
|
"user_id",
|
|
"move_type",
|
|
"currency_id",
|
|
"journal_id",
|
|
"company_id",
|
|
"partner_bank_id",
|
|
]
|
|
|
|
@api.model
|
|
def _get_invoice_line_key_cols(self):
|
|
fields = [
|
|
"name",
|
|
"discount",
|
|
"tax_ids",
|
|
"price_unit",
|
|
"product_id",
|
|
"account_id",
|
|
"analytic_distribution",
|
|
"product_uom_id",
|
|
]
|
|
for field in [
|
|
"sale_line_ids", # odoo/sale
|
|
"purchase_line_id", # odoo/purchase
|
|
"purchase_price", # OCA/account_invoice_margin
|
|
]:
|
|
if field in self.env["account.move.line"]._fields:
|
|
fields.append(field)
|
|
return fields
|
|
|
|
@api.model
|
|
def _get_first_invoice_fields(self, invoice):
|
|
return {
|
|
"invoice_origin": "%s" % (invoice.invoice_origin or "",),
|
|
"partner_id": invoice.partner_id.id,
|
|
"journal_id": invoice.journal_id.id,
|
|
"user_id": invoice.user_id.id,
|
|
"currency_id": invoice.currency_id.id,
|
|
"company_id": invoice.company_id.id,
|
|
"move_type": invoice.move_type,
|
|
"state": "draft",
|
|
"payment_reference": "%s" % (invoice.payment_reference or "",),
|
|
"name": "%s" % (invoice.name or "",),
|
|
"fiscal_position_id": invoice.fiscal_position_id.id,
|
|
"invoice_payment_term_id": invoice.invoice_payment_term_id.id,
|
|
"invoice_line_ids": {},
|
|
"partner_bank_id": invoice.partner_bank_id.id,
|
|
}
|
|
|
|
@api.model
|
|
def _get_sum_fields(self):
|
|
return ["quantity"]
|
|
|
|
@api.model
|
|
def _get_invoice_line_vals(self, line):
|
|
field_names = self._get_invoice_line_key_cols() + self._get_sum_fields()
|
|
vals = {}
|
|
origin_vals = line._convert_to_write(line._cache)
|
|
for field_name, val in origin_vals.items():
|
|
if field_name in field_names:
|
|
vals[field_name] = val
|
|
return vals
|
|
|
|
def _get_draft_invoices(self):
|
|
"""Overridable function to return draft invoices to merge"""
|
|
return self.filtered(lambda x: x.state == "draft")
|
|
|
|
def make_key(self, br, fields):
|
|
"""
|
|
Return a hashable key
|
|
"""
|
|
list_key = []
|
|
for field in fields:
|
|
field_val = getattr(br, field)
|
|
if isinstance(field_val, dict):
|
|
field_val = str(field_val)
|
|
elif isinstance(field_val, models.Model):
|
|
field_val = tuple(sorted(field_val.ids))
|
|
list_key.append((field, field_val))
|
|
list_key.sort()
|
|
return tuple(list_key)
|
|
|
|
# flake8: noqa: C901
|
|
def do_merge(
|
|
self, keep_references=True, date_invoice=False, remove_empty_invoice_lines=True
|
|
):
|
|
"""
|
|
To merge similar type of account invoices.
|
|
Invoices will only be merged if:
|
|
* Account invoices are in draft
|
|
* Account invoices belong to the same partner
|
|
* Account invoices are have same company, partner, address, currency,
|
|
journal, currency, salesman, account, type
|
|
Lines will only be merged if:
|
|
* Invoice lines are exactly the same except for the quantity and unit
|
|
|
|
@param self: The object pointer.
|
|
@param keep_references: If True, keep reference of original invoices
|
|
|
|
@return: new account invoice id
|
|
|
|
"""
|
|
|
|
# compute what the new invoices should contain
|
|
new_invoices = {}
|
|
seen_origins = {}
|
|
seen_client_refs = {}
|
|
sum_fields = self._get_sum_fields()
|
|
|
|
for account_invoice in self._get_draft_invoices():
|
|
invoice_key = self.make_key(account_invoice, self._get_invoice_key_cols())
|
|
new_invoice = new_invoices.setdefault(invoice_key, ({}, []))
|
|
origins = seen_origins.setdefault(invoice_key, set())
|
|
client_refs = seen_client_refs.setdefault(invoice_key, set())
|
|
new_invoice[1].append(account_invoice.id)
|
|
invoice_infos = new_invoice[0]
|
|
if not invoice_infos:
|
|
invoice_infos.update(self._get_first_invoice_fields(account_invoice))
|
|
origins.add(account_invoice.invoice_origin)
|
|
client_refs.add(account_invoice.payment_reference)
|
|
if not keep_references:
|
|
invoice_infos.pop("name")
|
|
else:
|
|
if (
|
|
account_invoice.name
|
|
and keep_references
|
|
and invoice_infos.get("name") != account_invoice.name
|
|
):
|
|
invoice_infos["name"] = (
|
|
(invoice_infos["name"] or "") + " " + account_invoice.name
|
|
)
|
|
if (
|
|
account_invoice.invoice_origin
|
|
and account_invoice.invoice_origin not in origins
|
|
):
|
|
invoice_infos["invoice_origin"] = (
|
|
(invoice_infos["invoice_origin"] or "")
|
|
+ " "
|
|
+ account_invoice.invoice_origin
|
|
)
|
|
origins.add(account_invoice.invoice_origin)
|
|
if (
|
|
account_invoice.payment_reference
|
|
and account_invoice.payment_reference not in client_refs
|
|
):
|
|
invoice_infos["payment_reference"] = (
|
|
(invoice_infos["payment_reference"] or "")
|
|
+ " "
|
|
+ account_invoice.payment_reference
|
|
)
|
|
client_refs.add(account_invoice.payment_reference)
|
|
|
|
for invoice_line in account_invoice.invoice_line_ids:
|
|
line_key = self.make_key(
|
|
invoice_line, self._get_invoice_line_key_cols()
|
|
)
|
|
o_line = invoice_infos["invoice_line_ids"].setdefault(line_key, {})
|
|
|
|
if o_line:
|
|
# merge the line with an existing line
|
|
for sum_field in sum_fields:
|
|
if sum_field in invoice_line._fields:
|
|
sum_val = invoice_line[sum_field]
|
|
if isinstance(sum_val, numbers.Number):
|
|
o_line[sum_field] += sum_val
|
|
else:
|
|
# append a new "standalone" line
|
|
o_line.update(self._get_invoice_line_vals(invoice_line))
|
|
|
|
allinvoices = []
|
|
allnewinvoices = []
|
|
invoices_info = {}
|
|
old_invoices = self.env["account.move"]
|
|
qty_prec = self.env["decimal.precision"].precision_get(
|
|
"Product Unit of Measure"
|
|
)
|
|
for invoice_key, (invoice_data, old_ids) in new_invoices.items():
|
|
# skip merges with only one invoice
|
|
if len(old_ids) < 2:
|
|
allinvoices += old_ids or []
|
|
continue
|
|
|
|
if remove_empty_invoice_lines:
|
|
invoice_data["invoice_line_ids"] = [
|
|
(0, 0, value)
|
|
for value in invoice_data["invoice_line_ids"].values()
|
|
if not float_is_zero(value["quantity"], precision_digits=qty_prec)
|
|
]
|
|
else:
|
|
invoice_data["invoice_line_ids"] = [
|
|
(0, 0, value) for value in invoice_data["invoice_line_ids"].values()
|
|
]
|
|
|
|
if date_invoice:
|
|
invoice_data["invoice_date"] = date_invoice
|
|
|
|
# create the new invoice
|
|
newinvoice = self.with_context(is_merge=True).create(invoice_data)
|
|
invoices_info.update({newinvoice.id: old_ids})
|
|
allinvoices.append(newinvoice.id)
|
|
allnewinvoices.append(newinvoice)
|
|
# cancel old invoices
|
|
old_invoices = self.env["account.move"].browse(old_ids)
|
|
old_invoices.with_context(is_merge=True).button_cancel()
|
|
self.merge_callback(invoices_info, old_invoices)
|
|
return invoices_info
|
|
|
|
@staticmethod
|
|
def order_line_update_invoice_lines(todos, all_old_inv_line):
|
|
for todo in todos:
|
|
for line in todo.order_line:
|
|
invoice_line = line.invoice_lines.filtered(
|
|
lambda x: x.parent_state != "cancel" or x.id not in all_old_inv_line
|
|
)
|
|
if invoice_line:
|
|
line.write({"invoice_lines": [(6, 0, invoice_line.ids)]})
|
|
|
|
@api.model
|
|
def merge_callback(self, invoices_info, old_invoices):
|
|
# Make link between original sale order
|
|
# None if sale is not installed
|
|
# None if purchase is not installed
|
|
if invoices_info:
|
|
all_old_inv_line = old_invoices.mapped("invoice_line_ids").ids
|
|
if "sale.order" in self.env.registry:
|
|
sale_todos = old_invoices.mapped(
|
|
"invoice_line_ids.sale_line_ids.order_id"
|
|
)
|
|
self.order_line_update_invoice_lines(sale_todos, all_old_inv_line)
|
|
|
|
if "purchase.order" in self.env.registry:
|
|
purchase_todos = old_invoices.mapped(
|
|
"invoice_line_ids.purchase_line_id.order_id"
|
|
)
|
|
self.order_line_update_invoice_lines(purchase_todos, all_old_inv_line)
|