Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

E invoice improvements #6

Merged
merged 2 commits into from
Dec 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading