Skip to content

Commit

Permalink
Merge pull request #6 from AmetrasIntelligence/e_invoice_improvements
Browse files Browse the repository at this point in the history
E invoice improvements
  • Loading branch information
baguenth authored Dec 11, 2024
2 parents 3817747 + 9bfeecd commit 8844259
Show file tree
Hide file tree
Showing 8 changed files with 287 additions and 6 deletions.
6 changes: 5 additions & 1 deletion account_invoice_import/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

{
"name": "Account Invoice Import",
"version": "13.0.1.0.1",
"version": "13.0.1.1.0",
"category": "Accounting & Finance",
"license": "AGPL-3",
"summary": "Import supplier invoices/refunds as PDF or XML files",
Expand All @@ -16,7 +16,11 @@
"base_iban",
"base_business_document_import",
"onchange_helper",
"account_operating_unit",
"partner_identification",
"partner_identification_gln",
],
"external_dependencies": {"python": ["factur-x==3.1"]},
"data": [
"security/ir.model.access.csv",
"security/rule.xml",
Expand Down
1 change: 1 addition & 0 deletions account_invoice_import/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
from . import account_invoice_import_config
from . import account_move
from . import account_journal
from . import product_product
19 changes: 19 additions & 0 deletions account_invoice_import/models/product_product.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from odoo import models


class ProductProduct(models.Model):
_inherit = "product.product"

def _get_e_invoice_default_code_search_args(
self, default_code, wildcard_default_code
):
"""
Return default search query for product search by default code in e invoice
"""
return [("default_code", "=", default_code)]

def _get_e_invoice_barcode_search_args(self, barcode, wildcard_barcode):
"""
Return default search query for product search by barcode in e invoice
"""
return [("barcode", "=", barcode)]
37 changes: 37 additions & 0 deletions account_invoice_import/models/res_partner.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,40 @@ def show_account_invoice_import_config(self):
"invoice_import_config_main_view": True,
}
return action

def _get_e_invoice_vat_search_args(self, vat, wildcard_vat):
"""
Return default search query for partner search by vat
"""
normalized_vat = vat.replace(" ", "")
return [
("vat", "in", [normalized_vat, vat]),
("tax_number", "in", [normalized_vat, vat]),
]

def _get_e_invoice_tax_number_search_args(self, tax_number, wildcard_tax_number):
"""
Return default search query for partner search by tax_number in e invoice
"""
normalized_tax_number = tax_number.replace(" ", "")
return [
("vat", "in", [normalized_tax_number, tax_number]),
("tax_number", "in", [normalized_tax_number, tax_number]),
]

def _get_e_invoice_gln_search_args(self, gln, wildcard_gln):
"""
Return default search query for partner search by gln in e invoice
"""
PartnerIDNumber = self.env["res.partner.id_number"]
normalized_gln = gln.replace(" ", "")
partner_ids = PartnerIDNumber.search(
[
("active", "=", True),
("category_id.code", "=", "gln_id_number"),
("name", "in", [normalized_gln, gln]),
]
).mapped("partner_id")
if partner_ids:
return [("id", "in", partner_ids.ids)]
return []
130 changes: 127 additions & 3 deletions account_invoice_import/wizard/account_invoice_import.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,25 @@

from odoo import _, api, fields, models
from odoo.exceptions import UserError
from odoo.osv.expression import AND, OR
from odoo.tools import config, float_compare, float_is_zero, float_round
from odoo.tools.misc import format_amount

logger = logging.getLogger(__name__)


try:
from facturx import get_xml_from_pdf
except ImportError:
logger.debug("Cannot import facturx")


def get_wildcard_string(val):
if val and val[-1] != "%":
val += "%"
return val


class AccountInvoiceImport(models.TransientModel):
_name = "account.invoice.import"
_inherit = ["business.document.import", "mail.thread"]
Expand Down Expand Up @@ -83,6 +96,16 @@ def default_get(self, fields_list):
def parse_xml_invoice(self, xml_root):
return False

@api.model
def get_xml_files_from_pdf(self, file_data):
"""
Overwrite the original function to use the facturx libraray to extract
xml from pdf
"""
data = get_xml_from_pdf(pdf_file=file_data, check_xsd=False)
xml_root = etree.fromstring(data[1])
return {data[0]: xml_root}

@api.model
def parse_pdf_invoice(self, file_data):
"""This method must be inherited by additional modules with
Expand Down Expand Up @@ -254,7 +277,9 @@ def _prepare_create_invoice_vals(self, parsed_inv, import_config):
assert parsed_inv.get("pre-processed"), "pre-processing not done"
amo = self.env["account.move"]
company = (
self.env["res.company"].browse(self.env.context.get("force_company"))
self.env["res.company"].browse(
parsed_inv.get("company_id") or self.env.context.get("force_company")
)
or self.env.company
)
vals = {
Expand All @@ -267,6 +292,8 @@ def _prepare_create_invoice_vals(self, parsed_inv, import_config):
"invoice_payment_ref": parsed_inv.get("payment_reference"),
"invoice_line_ids": [],
}
if parsed_inv.get("operating_unit_id"):
vals["operating_unit_id"] = parsed_inv.get("operating_unit_id")
if parsed_inv["type"] in ("out_invoice", "out_refund"):
partner_type = "customer"
else:
Expand Down Expand Up @@ -525,7 +552,9 @@ def parse_invoice(self, invoice_file_b64, invoice_filename, email_from=None):
parsed_inv["partner"]["name"] = partner_name
# pre_process_parsed_inv() will be called again a second time,
# but it's OK
pp_parsed_inv = self.pre_process_parsed_inv(parsed_inv)
pp_parsed_inv = self.with_context(
edi_skip_company_check=True
).pre_process_parsed_inv(parsed_inv)
return pp_parsed_inv

@api.model
Expand Down Expand Up @@ -797,14 +826,109 @@ def new_partner(self):
# If you have an idea on how to fix this problem, please tell me!
return action

@api.model
def _match_product_search(self, product_dict):
"""
Extend the product search function to find products with additional
parameters.
"""
default_code = product_dict.get("code")
barcode = product_dict.get("barcode")
ProductProduct = self.env["product.product"]
args = []
if default_code:
args += ProductProduct._get_e_invoice_default_code_search_args(
default_code, get_wildcard_string(default_code)
)
if barcode:
args += ProductProduct._get_e_invoice_barcode_search_args(
barcode, get_wildcard_string(barcode)
)
if args:
domain = AND(
[
OR([arg] for arg in args),
[("company_id", "in", [False, self.env.company.id])],
]
)
product_variant_id = ProductProduct.search(domain, limit=1)
if product_variant_id:
return product_variant_id
return super()._match_product_search(product_dict=product_dict)

@api.model
def _hook_match_partner(self, partner_dict, chatter_msg, domain, order):
"""
Custom partner search function to find partners with additional
parameters.
"""
gln = partner_dict.get("gln")
vat = partner_dict.get("vat")
tax_number = partner_dict.get("tax_number")
ResPartner = self.env["res.partner"]
args = []
if gln:
args += ResPartner._get_e_invoice_gln_search_args(
gln, get_wildcard_string(gln)
)
if vat:
args += ResPartner._get_e_invoice_vat_search_args(
vat, get_wildcard_string(vat)
)
if tax_number:
args += ResPartner._get_e_invoice_tax_number_search_args(
tax_number, get_wildcard_string(tax_number)
)
if args:
domain = AND(
[
OR([arg] for arg in args),
[("company_id", "in", [False, self.env.company.id])],
]
)
partner_id = ResPartner.search(domain, limit=1)
if partner_id:
return partner_id
return super()._hook_match_partner(
partner_dict=partner_dict,
chatter_msg=chatter_msg,
domain=domain,
order=order,
)

def _get_operating_unit(self, parsed_inv):
"""
Find the buyer party partner and get the operating unit from it
"""
OperatingUnit = self.env["operating.unit"]
try:
partner_id = self._match_partner(
parsed_inv["company"], parsed_inv["chatter_msg"], raise_exception=False
)
if partner_id:
return self.env["operating.unit"].search(
[("partner_id", "=", partner_id.id)], limit=1
)
except Exception:
...
return OperatingUnit

def import_invoice(self):
"""Method called by the button of the wizard
(import step AND config step)"""
self.ensure_one()
amo = self.env["account.move"]
aiico = self.env["account.invoice.import.config"]
company_id = self.env.context.get("force_company") or self.env.company.id
parsed_inv = self.get_parsed_invoice()
operating_unit_id = self._get_operating_unit(parsed_inv)
if operating_unit_id:
parsed_inv["operating_unit_id"] = operating_unit_id.id
company_id = (
operating_unit_id.company_id.id
or self.env.context.get("force_company")
or self.env.company.id
)
parsed_inv["company_id"] = company_id
if not self.partner_id:
if parsed_inv.get("partner"):
try:
Expand Down
2 changes: 1 addition & 1 deletion account_invoice_import_facturx/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

{
"name": "Account Invoice Import Factur-X",
"version": "13.0.1.0.0",
"version": "13.0.1.1.0",
"category": "Invoicing Management",
"license": "AGPL-3",
"summary": "Import Factur-X/ZUGFeRD supplier invoices/refunds",
Expand Down
Loading

0 comments on commit 8844259

Please sign in to comment.