[IMP] account, _facturx, l10n[be,it]_edi: remove account.invoice.import.wizard
Since the wizard is not shown anymore (skipped to go right to file selection), we can remove it. Small refactoring had to be done for l10n_it in order to upload files with multiple invoices inside and to detect format closes odoo/odoo#34145 Signed-off-by: Quentin De Paoli (qdp) <qdp@openerp.com>
This commit is contained in:
@@ -24,7 +24,6 @@ You could use this simplified accounting in case you work with an (external) acc
|
||||
'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',
|
||||
|
||||
@@ -9,6 +9,7 @@ from odoo.tools.float_utils import float_round as round, float_compare
|
||||
from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.tests.common import Form
|
||||
|
||||
|
||||
class AccountAccountType(models.Model):
|
||||
@@ -953,6 +954,34 @@ class AccountJournal(models.Model):
|
||||
# We simply call the setup bar function.
|
||||
return self.env['res.company'].setting_init_bank_account_action()
|
||||
|
||||
def create_invoice_from_attachment(self, attachment_ids=[]):
|
||||
''' Create the invoices from files.
|
||||
:return: A action redirecting to account.move tree/form view.
|
||||
'''
|
||||
attachments = self.env['ir.attachment'].browse(attachment_ids)
|
||||
if not attachments:
|
||||
raise UserError(_("No attachment was provided"))
|
||||
|
||||
invoices = self.env['account.move']
|
||||
for attachment in attachments:
|
||||
invoice = self.env['account.move'].create({})
|
||||
attachment.write({'res_model': 'account.move', 'res_id': invoice.id})
|
||||
invoice.message_post(attachment_ids=[attachment.id])
|
||||
invoices += invoice
|
||||
|
||||
action_vals = {
|
||||
'name': _('Generated Documents'),
|
||||
'domain': [('id', 'in', invoices.ids)],
|
||||
'res_model': 'account.move',
|
||||
'views': [[False, "tree"], [False, "form"]],
|
||||
'type': 'ir.actions.act_window',
|
||||
}
|
||||
if len(invoices) == 1:
|
||||
action_vals.update({'res_id': invoices[0].id, 'view_mode': 'form'})
|
||||
else:
|
||||
action_vals['view_mode'] = 'tree,form'
|
||||
return action_vals
|
||||
|
||||
|
||||
class ResPartnerBank(models.Model):
|
||||
_inherit = "res.partner.bank"
|
||||
|
||||
@@ -32,15 +32,12 @@ odoo.define('account.upload.bill.mixin', function (require) {
|
||||
var attachent_ids = attachments.reduce(function(filtered, record) {
|
||||
if (record.id) {
|
||||
filtered.push(record.id);
|
||||
}
|
||||
}
|
||||
return filtered;
|
||||
}, []);
|
||||
if (!attachent_ids.length) {
|
||||
return self.do_notify(_t("Error"), _t("An error occurred during the upload"));
|
||||
}
|
||||
return this._rpc({
|
||||
model: 'account.invoice.import.wizard',
|
||||
method: 'create_invoices',
|
||||
model: 'account.journal',
|
||||
method: 'create_invoice_from_attachment',
|
||||
args: ["", attachent_ids],
|
||||
context: this.initialState.context,
|
||||
}).then(function(result) {
|
||||
@@ -50,9 +47,6 @@ odoo.define('account.upload.bill.mixin', function (require) {
|
||||
|
||||
_onUpload: function (event) {
|
||||
var self = this;
|
||||
if (!this.initialState.context.journal_type) {
|
||||
this.initialState.context.journal_type = event.currentTarget.attributes['journal_type'].value;
|
||||
}
|
||||
// If hidden upload form don't exists, create it
|
||||
var $formContainer = this.$('.o_content').find('.o_vendor_bill_upload');
|
||||
if (!$formContainer.length) {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
<t t-name="account.BillsHiddenUploadForm">
|
||||
<div class="d-none o_vendor_bill_upload">
|
||||
<t t-call="HiddenInputFile">
|
||||
<t t-set="multi_upload" t-value="true"/>
|
||||
<t t-set="fileupload_id" t-value="widget.fileUploadID"/>
|
||||
<t t-set="fileupload_action" t-translation="off">/web/binary/upload_attachment</t>
|
||||
<input type="hidden" name="model" t-att-value="'account.move'"/>
|
||||
@@ -19,11 +20,9 @@
|
||||
r.push(context.engine.tools.call(context, 'ImportView.import_button', dict));
|
||||
};
|
||||
</t>
|
||||
<t t-if="widget.initialState.context.journal_type === 'purchase'">
|
||||
<button type="button" class="btn btn-secondary o_button_upload_bill">
|
||||
Upload
|
||||
</button>
|
||||
</t>
|
||||
<button type="button" class="btn btn-secondary o_button_upload_bill">
|
||||
Upload
|
||||
</button>
|
||||
</t>
|
||||
</t>
|
||||
</templates>
|
||||
</templates>
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
|
||||
from . import account_unreconcile
|
||||
from . import account_invoice_import
|
||||
from . import account_validate_account_move
|
||||
from . import pos_box
|
||||
from . import account_move_reversal
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.tests.common import Form
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class ImportInvoiceImportWizard(models.TransientModel):
|
||||
_name = 'account.invoice.import.wizard'
|
||||
_description = 'Import Your Vendor Bills from Files.'
|
||||
|
||||
attachment_ids = fields.Many2many('ir.attachment', string='Files')
|
||||
|
||||
def _create_invoice_from_file(self, attachment):
|
||||
invoice = self.env['account.move'].create({})
|
||||
attachment.write({'res_model': 'account.move', 'res_id': invoice.id})
|
||||
invoice.message_post(attachment_ids=[attachment.id])
|
||||
return invoice
|
||||
|
||||
def _create_invoice(self, attachment):
|
||||
return self._create_invoice_from_file(attachment)
|
||||
|
||||
def create_invoices(self, attachment_ids=[]):
|
||||
''' Create the invoices from files.
|
||||
:return: A action redirecting to account.invoice tree/form view.
|
||||
'''
|
||||
attachments = self.attachment_ids or self.env['ir.attachment'].browse(attachment_ids)
|
||||
if not attachments:
|
||||
raise UserError(_("No attachment was provided"))
|
||||
|
||||
invoices = self.env['account.move']
|
||||
for attachment in attachments:
|
||||
invoices += self._create_invoice(attachment)
|
||||
|
||||
action_vals = {
|
||||
'name': _('Generated Documents'),
|
||||
'domain': [('id', 'in', invoices.ids)],
|
||||
'view_type': 'form',
|
||||
'res_model': 'account.move',
|
||||
'views': [[False, "tree"], [False, "form"]],
|
||||
'type': 'ir.actions.act_window',
|
||||
'context': self.env.context,
|
||||
}
|
||||
if len(invoices) == 1:
|
||||
action_vals.update({'res_id': invoices[0].id, 'view_mode': 'form'})
|
||||
else:
|
||||
action_vals['view_mode'] = 'tree,form'
|
||||
return action_vals
|
||||
@@ -1,29 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
<record id="account_invoice_import_wizard_form_view" model="ir.ui.view">
|
||||
<field name="name">account.invoice.import.wizard.form</field>
|
||||
<field name="model">account.invoice.import.wizard</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<group>
|
||||
<field name="attachment_ids" widget="many2many_binary" colspan="2"
|
||||
string="Upload Files" nolabel="1"/>
|
||||
</group>
|
||||
<footer>
|
||||
<button string="Import" name="create_invoices" type="object" class="oe_highlight"/>
|
||||
<button string="Cancel" class="btn btn-secondary" special="cancel"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="account_invoice_import_wizard_action" model="ir.actions.act_window">
|
||||
<field name="name">Import Vendor Bills</field>
|
||||
<field name="res_model">account.invoice.import.wizard</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="target">new</field>
|
||||
<field name="context">{'default_type': 'in_invoice', 'default_journal_id': active_id}</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -291,7 +291,7 @@ class AccountMove(models.Model):
|
||||
|
||||
: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:
|
||||
* check_func: A function taking an etree and a file name 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.
|
||||
@@ -310,20 +310,20 @@ class AccountMove(models.Model):
|
||||
raise UserError(_('The xml file is badly formatted : {}').format(attachment.name))
|
||||
|
||||
for xml_type, check_func, decode_func in decoders:
|
||||
check_res = check_func(tree)
|
||||
check_res = check_func(tree, attachment.name)
|
||||
|
||||
if check_res.get('flag') and not check_res.get('error'):
|
||||
invoice = decode_func(tree)
|
||||
if invoice:
|
||||
invoice_ids = decode_func(tree)
|
||||
if invoice_ids:
|
||||
try:
|
||||
# don't propose to send to ocr
|
||||
invoice.extract_state = 'done'
|
||||
invoice_ids.write({'extract_state': 'done'})
|
||||
except AttributeError:
|
||||
# account_invoice_exctract not installed
|
||||
pass
|
||||
break
|
||||
|
||||
try:
|
||||
return invoice
|
||||
return invoice_ids
|
||||
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.name))
|
||||
|
||||
@@ -25,7 +25,7 @@ class AccountMove(models.Model):
|
||||
return namespaces
|
||||
|
||||
@api.model
|
||||
def _detect_ubl_2_1(self, tree):
|
||||
def _detect_ubl_2_1(self, tree, file_name):
|
||||
# Quick check the tree looks like an UBL 2.1 file.
|
||||
flag = tree.tag == '{urn:oasis:names:specification:ubl:schema:xsd:Invoice-2}Invoice'
|
||||
error = None
|
||||
|
||||
@@ -290,23 +290,22 @@ class AccountMove(models.Model):
|
||||
)
|
||||
self.l10n_it_send_state = 'invalid'
|
||||
|
||||
def _import_xml_invoice(self, content, attachment):
|
||||
def _import_xml_invoice(self, tree):
|
||||
''' Extract invoice values from the E-Invoice xml tree passed as parameter.
|
||||
|
||||
:param content: The tree of the xml file.
|
||||
:return: A dictionary containing account.invoice values to create/update it.
|
||||
'''
|
||||
|
||||
try:
|
||||
tree = etree.fromstring(content)
|
||||
except:
|
||||
_logger.info('Error during decoding XML file')
|
||||
return self.env['account.move']
|
||||
|
||||
invoices = self.env['account.move']
|
||||
multi = False
|
||||
|
||||
# possible to have multiple invoices in the case of an invoice batch, the batch itself is repeated for every invoice of the batch
|
||||
for body_tree in tree.xpath('//FatturaElettronicaBody', namespaces=tree.nsmap):
|
||||
if multi:
|
||||
# make sure all the iterations create a new invoice record (except the first which could have already created one)
|
||||
self = self.env['account.move']
|
||||
multi = True
|
||||
|
||||
elements = tree.xpath('//DatiGeneraliDocumento/TipoDocumento', namespaces=tree.nsmap)
|
||||
if elements and elements[0].text and elements[0].text == 'TD01':
|
||||
@@ -654,7 +653,8 @@ class AccountMove(models.Model):
|
||||
})
|
||||
|
||||
# default_res_id is had to context to avoid facturx to import his content
|
||||
new_invoice.with_context(default_res_id=new_invoice.id).message_post(
|
||||
# no_new_invoice to prevent from looping on the message_post that would create a new invoice without it
|
||||
new_invoice.with_context(no_new_invoice=True, default_res_id=new_invoice.id).message_post(
|
||||
body=(_("Attachment from XML")),
|
||||
attachment_ids=[attachment_64.id]
|
||||
)
|
||||
@@ -662,10 +662,6 @@ class AccountMove(models.Model):
|
||||
for message in message_to_log:
|
||||
new_invoice.message_post(body=message)
|
||||
|
||||
if attachment:
|
||||
new_invoice.l10n_it_einvoice_name = attachment.name
|
||||
attachment.write({'res_model': 'account.move', 'res_id': new_invoice.id})
|
||||
new_invoice.message_post(attachment_ids=[attachment.id])
|
||||
invoices += new_invoice
|
||||
return invoices
|
||||
|
||||
@@ -695,6 +691,20 @@ class AccountMove(models.Model):
|
||||
output_str += "<li>%s: %s</li>" % (element.tag, text)
|
||||
return output_str + "</ul>"
|
||||
|
||||
@api.model
|
||||
def _get_xml_decoders(self):
|
||||
# Override
|
||||
ubl_decoders = [('Italy', self._detect_italy_edi, self._import_xml_invoice)]
|
||||
return super(AccountMove, self)._get_xml_decoders() + ubl_decoders
|
||||
|
||||
@api.model
|
||||
def _detect_italy_edi(self, tree, file_name):
|
||||
# Quick check the file name looks like the one of an Italian EDI XML.
|
||||
flag = re.search("([A-Z]{2}[A-Za-z0-9]{2,28}_[A-Za-z0-9]{0,5}.(xml.p7m|xml))", file_name)
|
||||
error = None
|
||||
|
||||
return {'flag': flag, 'error': error}
|
||||
|
||||
class AccountTax(models.Model):
|
||||
_name = "account.tax"
|
||||
_inherit = "account.tax"
|
||||
@@ -730,18 +740,3 @@ class AccountTax(models.Model):
|
||||
raise ValidationError("If the tax has exoneration, you must enter a kind of exoneration, a law reference and the amount of the tax must be 0.0.")
|
||||
if tax.l10n_it_kind_exoneration == 'N6' and tax.l10n_it_vat_due_date == 'S':
|
||||
raise UserError(_("'Scissione dei pagamenti' is not compatible with exoneration of kind 'N6'"))
|
||||
|
||||
class ImportInvoiceImportWizard(models.TransientModel):
|
||||
_name = 'account.invoice.import.wizard'
|
||||
_inherit = 'account.invoice.import.wizard'
|
||||
|
||||
def _create_invoice_from_file(self, attachment):
|
||||
if attachment.mimetype == 'application/xml' and re.search("([A-Z]{2}[A-Za-z0-9]{2,28}_[A-Za-z0-9]{0,5}.(xml.p7m|xml))", attachment.name):
|
||||
if self.env['account.move'].search([('l10n_it_einvoice_name', '=', attachment.name)], limit=1):
|
||||
# invoice already exist
|
||||
raise UserError(_('E-invoice already exist: %s') % attachment.name)
|
||||
self = self.with_context(default_journal_id= self.journal_id.id)
|
||||
invoice = self.env['account.move']._import_xml_invoice(base64.decodestring(attachment.datas), attachment)
|
||||
else:
|
||||
invoice = super(ImportInvoiceImportWizard, self)._create_invoice_from_file(attachment)
|
||||
return invoice
|
||||
|
||||
@@ -19,7 +19,7 @@ from lxml import etree
|
||||
from datetime import datetime
|
||||
|
||||
from odoo import api, fields, models, tools, _
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo.exceptions import ValidationError, UserError
|
||||
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
@@ -137,7 +137,12 @@ class FetchmailServer(models.Model):
|
||||
'type': 'binary',
|
||||
})
|
||||
|
||||
invoice = self.env['account.move']._import_xml_invoice(att_content, invoice_attachment)
|
||||
try:
|
||||
tree = etree.fromstring(att_content)
|
||||
except Exception:
|
||||
raise UserError(_('The xml file is badly formatted : {}').format(att_name))
|
||||
|
||||
invoice = self.env['account.move']._import_xml_invoice(tree)
|
||||
invoice.l10n_it_send_state = "new"
|
||||
invoice.source_email = from_address
|
||||
self._cr.commit()
|
||||
|
||||
Reference in New Issue
Block a user