Skip to content
Permalink
Browse files

import_xml -> facturx

  • Loading branch information...
william-andre committed Mar 25, 2019
1 parent d55c51e commit fc347f421beff8abab40e1a94470c856d5879ea0
Showing with 356 additions and 49,391 deletions.
  1. +1 −1 addons/account/__manifest__.py
  2. +0 −1 addons/account/models/__init__.py
  3. +0 −1 addons/account/models/res_config_settings.py
  4. +0 −11 addons/account/views/res_config_settings_views.xml
  5. +1 −0 addons/account/wizard/__init__.py
  6. 0 addons/account/{models/account_invoice_import_wizard.py → wizard/account_invoice_import.py}
  7. 0 ...s/account/{views/account_invoice_import_wizard_view.xml → wizard/account_invoice_import_view.xml}
  8. +95 −42 addons/account_facturx/models/account_invoice.py
  9. +0 −3 addons/account_invoice_import_xml/__init__.py
  10. +0 −12 addons/account_invoice_import_xml/__manifest__.py
  11. +0 −3 addons/account_invoice_import_xml/models/__init__.py
  12. +0 −66 addons/account_invoice_import_xml/models/import_invoice_xml_wizard.py
  13. +1 −0 addons/l10n_be_edi/__init__.py
  14. +2 −1 addons/l10n_be_edi/__manifest__.py
  15. +0 −731 addons/l10n_be_edi/data/xsd/2.1/common/CCTS_CCT_SchemaModule-2.1.xsd
  16. +0 −39,799 addons/l10n_be_edi/data/xsd/2.1/common/UBL-CommonAggregateComponents-2.1.xsd
  17. +0 −5,389 addons/l10n_be_edi/data/xsd/2.1/common/UBL-CommonBasicComponents-2.1.xsd
  18. +0 −223 addons/l10n_be_edi/data/xsd/2.1/common/UBL-CommonExtensionComponents-2.1.xsd
  19. +0 −101 addons/l10n_be_edi/data/xsd/2.1/common/UBL-CommonSignatureComponents-2.1.xsd
  20. +0 −63 addons/l10n_be_edi/data/xsd/2.1/common/UBL-CoreComponentParameters-2.1.xsd
  21. +0 −89 addons/l10n_be_edi/data/xsd/2.1/common/UBL-ExtensionContentDataType-2.1.xsd
  22. +0 −69 addons/l10n_be_edi/data/xsd/2.1/common/UBL-QualifiedDataTypes-2.1.xsd
  23. +0 −138 addons/l10n_be_edi/data/xsd/2.1/common/UBL-SignatureAggregateComponents-2.1.xsd
  24. +0 −78 addons/l10n_be_edi/data/xsd/2.1/common/UBL-SignatureBasicComponents-2.1.xsd
  25. +0 −553 addons/l10n_be_edi/data/xsd/2.1/common/UBL-UnqualifiedDataTypes-2.1.xsd
  26. +0 −476 addons/l10n_be_edi/data/xsd/2.1/common/UBL-XAdESv132-2.1.xsd
  27. +0 −25 addons/l10n_be_edi/data/xsd/2.1/common/UBL-XAdESv141-2.1.xsd
  28. +0 −330 addons/l10n_be_edi/data/xsd/2.1/common/UBL-xmldsig-core-schema-2.1.xsd
  29. +0 −1,002 addons/l10n_be_edi/data/xsd/2.1/maindoc/UBL-Invoice-2.1.xsd
  30. +67 −0 addons/l10n_be_edi/hooks.py
  31. +0 −1 addons/l10n_be_edi/models/__init__.py
  32. +189 −7 addons/l10n_be_edi/models/account_invoice.py
  33. +0 −176 addons/l10n_be_edi/models/import_invoice_xml_wizard.py
@@ -24,6 +24,7 @@
'data/digest_data.xml',
'views/account_menuitem.xml',
'views/account_payment_view.xml',
'wizard/account_invoice_import_view.xml',
'wizard/account_unreconcile_view.xml',
'wizard/account_move_reversal_view.xml',
'views/account_move_views.xml',
@@ -49,7 +50,6 @@
'views/tax_adjustments.xml',
'wizard/wizard_tax_adjustments_view.xml',
'views/res_config_settings_views.xml',
'views/account_invoice_import_wizard_view.xml',
'views/account_journal_dashboard_view.xml',
'views/account_portal_templates.xml',
'views/report_payment_receipt_templates.xml',
@@ -19,4 +19,3 @@
from . import reconciliation_widget
from . import account_incoterms
from . import digest
from . import account_invoice_import_wizard
@@ -75,7 +75,6 @@ class ResConfigSettings(models.TransientModel):
module_product_margin = fields.Boolean(string="Allow Product Margin")
module_l10n_eu_service = fields.Boolean(string="EU Digital Goods VAT")
module_account_taxcloud = fields.Boolean(string="Account TaxCloud")
module_account_invoice_import_xml = fields.Boolean(string='Import XML Bills')
module_account_invoice_extract = fields.Boolean(string="Bill Digitalization")
module_snailmail_account = fields.Boolean(string="Snailmail")
tax_exigibility = fields.Boolean(string='Cash Basis', related='company_id.tax_exigibility', readonly=False)
@@ -398,17 +398,6 @@
</div>
</div>
</div>
<div class="col-xs-12 col-md-6 o_setting_box">
<div class="o_setting_left_pane">
<field name="module_account_invoice_import_xml" widget="upgrade_boolean"/>
</div>
<div class="o_setting_right_pane">
<label for="module_account_invoice_import_xml"/>
<div class="text-muted">
Allows to import vendor bills in XML format.
</div>
</div>
</div>
</div>
<h2>Vendor Payments</h2>
<div class="row mt16 o_settings_container">
@@ -3,6 +3,7 @@


from . import account_unreconcile
from . import account_invoice_import
from . import account_invoice_refund
from . import account_invoice_state
from . import account_validate_account_move
@@ -213,15 +213,7 @@ def message_post(self, **kwargs):
# /!\ 'default_res_id' in self._context is used to don't process attachment when using a form view.
res = super(AccountInvoice, self).message_post(**kwargs)

def _get_attachment_filename(attachment):
# Handle both _Attachment namedtuple in mail.thread or ir.attachment.
return hasattr(attachment, 'fname') and getattr(attachment, 'fname') or attachment.name

def _get_attachment_content(attachment):
# Handle both _Attachment namedtuple in mail.thread or ir.attachment.
return hasattr(attachment, 'content') and getattr(attachment, 'content') or base64.b64decode(attachment.datas)

if 'default_res_id' not in self._context and len(self) == 1 and self.state == 'draft' and self.type in ('in_invoice', 'in_refund'):
if 'default_res_id' not in self.env.context and 'no_new_invoice' not in self.env.context and len(self) == 1 and self.state == 'draft' and self.type in ('in_invoice', 'in_refund'):
# Get attachments.
# - 'attachments' is a namedtuple defined in mail.thread looking like:
# _Attachment = namedtuple('Attachment', ('fname', 'content', 'info'))
@@ -231,37 +223,98 @@ def _get_attachment_content(attachment):
attachments += self.env['ir.attachment'].browse(kwargs['attachment_ids'])

for attachment in attachments:
filename = _get_attachment_filename(attachment)
content = _get_attachment_content(attachment)

# Check if the attachment is a pdf.
if not filename.endswith('.pdf'):
continue

buffer = io.BytesIO(content)
try:
reader = PdfFileReader(buffer)

# Search for Factur-x embedded file.
if reader.trailer['/Root'].get('/Names') and reader.trailer['/Root']['/Names'].get('/EmbeddedFiles'):
# N.B: embedded_files looks like:
# ['file.xml', {'/Type': '/Filespec', '/F': 'file.xml', '/EF': {'/F': IndirectObject(22, 0)}}]
embedded_files = reader.trailer['/Root']['/Names']['/EmbeddedFiles']['/Names']
# '[::2]' because it's a list [fn_1, content_1, fn_2, content_2, ..., fn_n, content_2]
for filename_obj, content_obj in list(zip(embedded_files, embedded_files[1:]))[::2]:
content = content_obj.getObject()['/EF']['/F'].getData()

if filename_obj == 'factur-x.xml':
try:
tree = etree.fromstring(content)
except:
continue

self._import_facturx_invoice(tree)
buffer.close()
return res
except:
# Malformed PDF.
pass
buffer.close()
self._create_invoice_from_attachment(attachment)
return res

@api.one
def _create_invoice_from_attachment(self, attachment):
print(attachment.name)
if 'pdf' in attachment.mimetype:
self._create_invoice_from_pdf(attachment)
if 'xml' in attachment.mimetype:
self._create_invoice_from_xml(attachment)

def _create_invoice_from_pdf(self, attachment):
def _get_attachment_filename(attachment):
# Handle both _Attachment namedtuple in mail.thread or ir.attachment.
return hasattr(attachment, 'fname') and getattr(attachment, 'fname') or attachment.name

def _get_attachment_content(attachment):
# Handle both _Attachment namedtuple in mail.thread or ir.attachment.
return hasattr(attachment, 'content') and getattr(attachment, 'content') or base64.b64decode(attachment.datas)
filename = _get_attachment_filename(attachment)
content = _get_attachment_content(attachment)

# Check if the attachment is a pdf.
if not filename.endswith('.pdf'):
return

with io.BytesIO(content) as buffer:
try:
reader = PdfFileReader(buffer)

# Search for Factur-x embedded file.
if reader.trailer['/Root'].get('/Names') and reader.trailer['/Root']['/Names'].get('/EmbeddedFiles'):
# N.B: embedded_files looks like:
# ['file.xml', {'/Type': '/Filespec', '/F': 'file.xml', '/EF': {'/F': IndirectObject(22, 0)}}]
embedded_files = reader.trailer['/Root']['/Names']['/EmbeddedFiles']['/Names']
# '[::2]' because it's a list [fn_1, content_1, fn_2, content_2, ..., fn_n, content_2]
for filename_obj, content_obj in list(zip(embedded_files, embedded_files[1:]))[::2]:
content = content_obj.getObject()['/EF']['/F'].getData()

if filename_obj == 'factur-x.xml':
try:
tree = etree.fromstring(content)
except Exception:
continue

self._import_facturx_invoice(tree)
buffer.close()
return res
except Exception:
# Malformed pdf
pass

@api.model
def _get_xml_decoders(self):
''' List of usable decoders to extract invoice from attachments.
:return: a list of triplet (xml_type, check_func, decode_func)
* xml_type: The format name, e.g 'UBL 2.1'
* check_func: A function taking an etree as parameter and returning a dict:
* flag: The etree is part of this format.
* error: Error message.
* decode_func: A function taking an etree as parameter and returning an invoice record.
'''
# TO BE OVERWRITTEN
return []

@api.multi
def _create_invoice_from_xml(self, attachment):
decoders = self._get_xml_decoders()

# Convert attachment -> etree
content = base64.b64decode(attachment.datas)
try:
tree = etree.fromstring(content)
except Exception:
raise UserError(_('The xml file is badly formatted : {}').format(attachment.datas_fname))

for xml_type, check_func, decode_func in decoders:
check_res = check_func(tree)

if check_res.get('flag') and not check_res.get('error'):
invoice = decode_func(tree)
if invoice:
try:
# don't propose to send to ocr
invoice.extract_state = 'done'
except AttributeError:
# account_invoice_exctract not installed
pass
break

try:
return invoice
except UnboundLocalError:
raise UserError(_('No decoder was found for the xml file: {}. The file is badly formatted, not supported or the decoder is not installed').format(attachment.datas_fname))

This file was deleted.

Oops, something went wrong.

This file was deleted.

Oops, something went wrong.

This file was deleted.

Oops, something went wrong.

This file was deleted.

Oops, something went wrong.
@@ -2,3 +2,4 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.

from . import models
from .hooks import post_init_hook
@@ -11,10 +11,11 @@
(cf the `official announce <http://www.prweb.com/releases/2016/01/prweb13186919.htm>`_).
Belgian e-invoicing uses the UBL 2.0 using the e-fff protocol.
""",
'depends': ['account', 'l10n_be', 'account_invoice_import_xml'],
'depends': ['account', 'l10n_be', 'account_facturx'],
'data': [
'data/ubl_invoice_templates.xml',
],
'installable': True,
'auto_install': False,
'post_init_hook': 'post_init_hook',
}
Oops, something went wrong.

0 comments on commit fc347f4

Please sign in to comment.
You can’t perform that action at this time.