Skip to content

Commit 754e9be

Browse files
Bastian GuentherBastian Guenther
Bastian Guenther
authored and
Bastian Guenther
committed
[IMP] account_invoice_import:
* Improved matching of partner * Improved matching of products * Added matching of operating unit * Improved account move line name
1 parent 3817747 commit 754e9be

File tree

6 files changed

+190
-4
lines changed

6 files changed

+190
-4
lines changed

account_invoice_import/__manifest__.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
{
66
"name": "Account Invoice Import",
7-
"version": "13.0.1.0.1",
7+
"version": "13.0.1.1.0",
88
"category": "Accounting & Finance",
99
"license": "AGPL-3",
1010
"summary": "Import supplier invoices/refunds as PDF or XML files",
@@ -16,7 +16,11 @@
1616
"base_iban",
1717
"base_business_document_import",
1818
"onchange_helper",
19+
"account_operating_unit",
20+
"partner_identification",
21+
"partner_identification_gln",
1922
],
23+
"external_dependencies": {"python": ["factur-x==3.1"]},
2024
"data": [
2125
"security/ir.model.access.csv",
2226
"security/rule.xml",

account_invoice_import/models/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@
33
from . import account_invoice_import_config
44
from . import account_move
55
from . import account_journal
6+
from . import product_product
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
from odoo import models
2+
3+
4+
class ProductProduct(models.Model):
5+
_inherit = "product.product"
6+
7+
def _get_e_invoice_default_code_search_args(
8+
self, default_code, wildcard_default_code
9+
):
10+
"""
11+
Return default search query for product search by default code in e invoice
12+
"""
13+
return [("default_code", "=", default_code)]
14+
15+
def _get_e_invoice_barcode_search_args(self, barcode, wildcard_barcode):
16+
"""
17+
Return default search query for product search by barcode in e invoice
18+
"""
19+
return [("barcode", "=", barcode)]

account_invoice_import/models/res_partner.py

+37
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,40 @@ def show_account_invoice_import_config(self):
4444
"invoice_import_config_main_view": True,
4545
}
4646
return action
47+
48+
def _get_e_invoice_vat_search_args(self, vat, wildcard_vat):
49+
"""
50+
Return default search query for partner search by vat
51+
"""
52+
normalized_vat = vat.replace(" ", "")
53+
return [
54+
("vat", "in", [normalized_vat, vat]),
55+
("tax_number", "in", [normalized_vat, vat]),
56+
]
57+
58+
def _get_e_invoice_tax_number_search_args(self, tax_number, wildcard_tax_number):
59+
"""
60+
Return default search query for partner search by tax_number in e invoice
61+
"""
62+
normalized_tax_number = tax_number.replace(" ", "")
63+
return [
64+
("vat", "in", [normalized_tax_number, tax_number]),
65+
("tax_number", "in", [normalized_tax_number, tax_number]),
66+
]
67+
68+
def _get_e_invoice_gln_search_args(self, gln, wildcard_gln):
69+
"""
70+
Return default search query for partner search by gln in e invoice
71+
"""
72+
PartnerIDNumber = self.env["res.partner.id_number"]
73+
normalized_gln = gln.replace(" ", "")
74+
partner_ids = PartnerIDNumber.search(
75+
[
76+
("active", "=", True),
77+
("category_id.code", "=", "gln_id_number"),
78+
("name", "in", [normalized_gln, gln]),
79+
]
80+
).mapped("partner_id")
81+
if partner_ids:
82+
return [("id", "in", partner_ids.ids)]
83+
return []

account_invoice_import/wizard/account_invoice_import.py

+127-3
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,25 @@
1414

1515
from odoo import _, api, fields, models
1616
from odoo.exceptions import UserError
17+
from odoo.osv.expression import AND, OR
1718
from odoo.tools import config, float_compare, float_is_zero, float_round
1819
from odoo.tools.misc import format_amount
1920

2021
logger = logging.getLogger(__name__)
2122

2223

24+
try:
25+
from facturx import get_xml_from_pdf
26+
except ImportError:
27+
logger.debug("Cannot import facturx")
28+
29+
30+
def get_wildcard_string(val):
31+
if val and val[-1] != "%":
32+
val += "%"
33+
return val
34+
35+
2336
class AccountInvoiceImport(models.TransientModel):
2437
_name = "account.invoice.import"
2538
_inherit = ["business.document.import", "mail.thread"]
@@ -83,6 +96,16 @@ def default_get(self, fields_list):
8396
def parse_xml_invoice(self, xml_root):
8497
return False
8598

99+
@api.model
100+
def get_xml_files_from_pdf(self, file_data):
101+
"""
102+
Overwrite the original function to use the facturx libraray to extract
103+
xml from pdf
104+
"""
105+
data = get_xml_from_pdf(pdf_file=file_data, check_xsd=False)
106+
xml_root = etree.fromstring(data[1])
107+
return {data[0]: xml_root}
108+
86109
@api.model
87110
def parse_pdf_invoice(self, file_data):
88111
"""This method must be inherited by additional modules with
@@ -254,7 +277,9 @@ def _prepare_create_invoice_vals(self, parsed_inv, import_config):
254277
assert parsed_inv.get("pre-processed"), "pre-processing not done"
255278
amo = self.env["account.move"]
256279
company = (
257-
self.env["res.company"].browse(self.env.context.get("force_company"))
280+
self.env["res.company"].browse(
281+
parsed_inv.get("company_id") or self.env.context.get("force_company")
282+
)
258283
or self.env.company
259284
)
260285
vals = {
@@ -267,6 +292,8 @@ def _prepare_create_invoice_vals(self, parsed_inv, import_config):
267292
"invoice_payment_ref": parsed_inv.get("payment_reference"),
268293
"invoice_line_ids": [],
269294
}
295+
if parsed_inv.get("operating_unit_id"):
296+
vals["operating_unit_id"] = parsed_inv.get("operating_unit_id")
270297
if parsed_inv["type"] in ("out_invoice", "out_refund"):
271298
partner_type = "customer"
272299
else:
@@ -525,7 +552,9 @@ def parse_invoice(self, invoice_file_b64, invoice_filename, email_from=None):
525552
parsed_inv["partner"]["name"] = partner_name
526553
# pre_process_parsed_inv() will be called again a second time,
527554
# but it's OK
528-
pp_parsed_inv = self.pre_process_parsed_inv(parsed_inv)
555+
pp_parsed_inv = self.with_context(
556+
edi_skip_company_check=True
557+
).pre_process_parsed_inv(parsed_inv)
529558
return pp_parsed_inv
530559

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

829+
@api.model
830+
def _match_product_search(self, product_dict):
831+
"""
832+
Extend the product search function to find products with additional
833+
parameters.
834+
"""
835+
default_code = product_dict.get("code")
836+
barcode = product_dict.get("barcode")
837+
ProductProduct = self.env["product.product"]
838+
args = []
839+
if default_code:
840+
args += ProductProduct._get_e_invoice_default_code_search_args(
841+
default_code, get_wildcard_string(default_code)
842+
)
843+
if barcode:
844+
args += ProductProduct._get_e_invoice_barcode_search_args(
845+
barcode, get_wildcard_string(barcode)
846+
)
847+
if args:
848+
domain = AND(
849+
[
850+
OR([arg] for arg in args),
851+
[("company_id", "in", [False, self.env.company.id])],
852+
]
853+
)
854+
product_variant_id = ProductProduct.search(domain, limit=1)
855+
if product_variant_id:
856+
return product_variant_id
857+
return super()._match_product_search(product_dict=product_dict)
858+
859+
@api.model
860+
def _hook_match_partner(self, partner_dict, chatter_msg, domain, order):
861+
"""
862+
Custom partner search function to find partners with additional
863+
parameters.
864+
"""
865+
gln = partner_dict.get("gln")
866+
vat = partner_dict.get("vat")
867+
tax_number = partner_dict.get("tax_number")
868+
ResPartner = self.env["res.partner"]
869+
args = []
870+
if gln:
871+
args += ResPartner._get_e_invoice_gln_search_args(
872+
gln, get_wildcard_string(gln)
873+
)
874+
if vat:
875+
args += ResPartner._get_e_invoice_vat_search_args(
876+
vat, get_wildcard_string(vat)
877+
)
878+
if tax_number:
879+
args += ResPartner._get_e_invoice_tax_number_search_args(
880+
tax_number, get_wildcard_string(tax_number)
881+
)
882+
if args:
883+
domain = AND(
884+
[
885+
OR([arg] for arg in args),
886+
[("company_id", "in", [False, self.env.company.id])],
887+
]
888+
)
889+
partner_id = ResPartner.search(domain, limit=1)
890+
if partner_id:
891+
return partner_id
892+
return super()._hook_match_partner(
893+
partner_dict=partner_dict,
894+
chatter_msg=chatter_msg,
895+
domain=domain,
896+
order=order,
897+
)
898+
899+
def _get_operating_unit(self, parsed_inv):
900+
"""
901+
Find the buyer party partner and get the operating unit from it
902+
"""
903+
OperatingUnit = self.env["operating.unit"]
904+
try:
905+
partner_id = self._match_partner(
906+
parsed_inv["company"], parsed_inv["chatter_msg"], raise_exception=False
907+
)
908+
if partner_id:
909+
return self.env["operating.unit"].search(
910+
[("partner_id", "=", partner_id.id)], limit=1
911+
)
912+
except Exception:
913+
...
914+
return OperatingUnit
915+
800916
def import_invoice(self):
801917
"""Method called by the button of the wizard
802918
(import step AND config step)"""
803919
self.ensure_one()
804920
amo = self.env["account.move"]
805921
aiico = self.env["account.invoice.import.config"]
806-
company_id = self.env.context.get("force_company") or self.env.company.id
807922
parsed_inv = self.get_parsed_invoice()
923+
operating_unit_id = self._get_operating_unit(parsed_inv)
924+
if operating_unit_id:
925+
parsed_inv["operating_unit_id"] = operating_unit_id.id
926+
company_id = (
927+
operating_unit_id.company_id.id
928+
or self.env.context.get("force_company")
929+
or self.env.company.id
930+
)
931+
parsed_inv["company_id"] = company_id
808932
if not self.partner_id:
809933
if parsed_inv.get("partner"):
810934
try:

oca_dependencies.txt

+1
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ stock-logistics-workflow
1010
connector
1111
storage
1212
server-auth
13+
operating-unit

0 commit comments

Comments
 (0)