diff --git a/addons/account/models/account.py b/addons/account/models/account.py
index 3379aa344cc..e9f51aefc74 100644
--- a/addons/account/models/account.py
+++ b/addons/account/models/account.py
@@ -487,10 +487,10 @@ class AccountJournal(models.Model):
bank_id = fields.Many2one('res.bank', related='bank_account_id.bank_id', readonly=False)
post_at_bank_rec = fields.Boolean(string="Post At Bank Reconciliation", help="Whether or not the payments made in this journal should be generated in draft state, so that the related journal entries are only posted when performing bank reconciliation.")
- # alias configuration for 'purchase' type journals
+ # alias configuration for journals
alias_id = fields.Many2one('mail.alias', string='Alias', copy=False)
alias_domain = fields.Char('Alias domain', compute='_compute_alias_domain', default=lambda self: self.env["ir.config_parameter"].sudo().get_param("mail.catchall.domain"))
- alias_name = fields.Char('Alias Name for Vendor Bills', related='alias_id.alias_name', help="It creates draft vendor bill by sending an email.", readonly=False)
+ alias_name = fields.Char('Alias Name', related='alias_id.alias_name', help="It creates draft invoices and bills by sending an email.", readonly=False)
journal_group_ids = fields.Many2many('account.journal.group', string="Journal Groups")
@@ -583,13 +583,13 @@ class AccountJournal(models.Model):
self.default_debit_account_id = self.default_credit_account_id
@api.multi
- def _get_alias_values(self, alias_name=None):
+ def _get_alias_values(self, type, alias_name=None):
if not alias_name:
alias_name = self.name
if self.company_id != self.env.ref('base.main_company'):
alias_name += '-' + str(self.company_id.name)
return {
- 'alias_defaults': {'type': 'in_invoice', 'company_id': self.company_id.id},
+ 'alias_defaults': {'type': type == 'purchase' and 'in_invoice' or 'out_invoice', 'company_id': self.company_id.id},
'alias_parent_thread_id': self.id,
'alias_name': re.sub(r'[^\w]+', '-', alias_name)
}
@@ -617,7 +617,7 @@ class AccountJournal(models.Model):
def _update_mail_alias(self, vals):
self.ensure_one()
- alias_values = self._get_alias_values(alias_name=vals.get('alias_name'))
+ alias_values = self._get_alias_values(type=vals.get('type') or self.type, alias_name=vals.get('alias_name'))
if self.alias_id:
self.alias_id.write(alias_values)
else:
@@ -663,7 +663,7 @@ class AccountJournal(models.Model):
bank_account = self.env['res.partner.bank'].browse(vals['bank_account_id'])
if bank_account.partner_id != company.partner_id:
raise UserError(_("The partners of the journal's company and the related bank account mismatch."))
- if vals.get('type') == 'purchase':
+ if 'alias_name' in vals:
journal._update_mail_alias(vals)
result = super(AccountJournal, self).write(vals)
@@ -787,8 +787,7 @@ class AccountJournal(models.Model):
if vals.get('type') in ('sale', 'purchase') and vals.get('refund_sequence') and not vals.get('refund_sequence_id'):
vals.update({'refund_sequence_id': self.sudo()._create_sequence(vals, refund=True).id})
journal = super(AccountJournal, self.with_context(mail_create_nolog=True)).create(vals)
- if journal.type == 'purchase':
- # create a mail alias for purchase journals (always, deactivated if alias_name isn't set)
+ if 'alias_name' in vals:
journal._update_mail_alias(vals)
# Create the bank_account_id if necessary
diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py
index 4d8e5583f08..0b1595efd3b 100644
--- a/addons/account/models/account_invoice.py
+++ b/addons/account/models/account_invoice.py
@@ -1000,7 +1000,7 @@ class AccountInvoice(models.Model):
# lots of duplicate calls to action_invoice_open, so we remove those already open
to_open_invoices = self.filtered(lambda inv: inv.state != 'open')
if to_open_invoices.filtered(lambda inv: not inv.partner_id):
- raise UserError(_("The field Vendor is required, please complete it to validate the Vendor Bill."))
+ raise UserError(_("The field Vendor/Customer is required, please complete it to validate the Vendor Bill/Customer Invoice."))
if to_open_invoices.filtered(lambda inv: inv.state != 'draft'):
raise UserError(_("Invoice must be in draft state in order to validate it."))
if to_open_invoices.filtered(lambda inv: float_compare(inv.amount_total, 0.0, precision_rounding=inv.currency_id.rounding) == -1):
diff --git a/addons/account/models/account_invoice_import_wizard.py b/addons/account/models/account_invoice_import_wizard.py
index 0e401842287..5753e36ac2b 100644
--- a/addons/account/models/account_invoice_import_wizard.py
+++ b/addons/account/models/account_invoice_import_wizard.py
@@ -9,10 +9,10 @@ class ImportInvoiceImportWizard(models.TransientModel):
_description = 'Import Your Vendor Bills from Files.'
def _get_default_journal_id(self):
- return self.env['account.journal'].search([('type', '=', 'purchase')], limit=1)
+ return self.env['account.journal'].search([('type', '=', self.env.context.get('journal_type'))], limit=1)
attachment_ids = fields.Many2many('ir.attachment', string='Files')
- journal_id = fields.Many2one(string="Journal", comodel_name="account.journal", required=True, domain="[('type', '=', 'purchase')]", default=_get_default_journal_id, help="Journal where to generate the bills")
+ journal_id = fields.Many2one(string="Journal", comodel_name="account.journal", required=True, domain="[('type', 'in', ('sale', 'purchase'))]", default=_get_default_journal_id, help="Journal where to generate the bills")
@api.multi
def _create_invoice_from_file(self, attachment):
@@ -35,12 +35,14 @@ class ImportInvoiceImportWizard(models.TransientModel):
for attachment in self.attachment_ids:
invoices += self._create_invoice_from_file(attachment)
+ form_view = self.env.context.get('journal_type') == 'purchase' and self.env.ref('account.invoice_supplier_form').id or self.env.ref('account.invoice_form').id
+ tree_view = self.env.context.get('journal_type') == 'purchase' and self.env.ref('account.invoice_supplier_tree').id or self.env.ref('account.invoice_tree').id
action_vals = {
'name': _('Invoices'),
'domain': [('id', 'in', invoices.ids)],
'view_type': 'form',
'res_model': 'account.invoice',
- 'view_id': False,
+ 'views': [[tree_view, "tree"], [form_view, "form"]],
'type': 'ir.actions.act_window',
}
if len(invoices) == 1:
diff --git a/addons/account/static/src/js/bills_tree_upload.js b/addons/account/static/src/js/bills_tree_upload.js
index edc043a5978..5171051bb97 100644
--- a/addons/account/static/src/js/bills_tree_upload.js
+++ b/addons/account/static/src/js/bills_tree_upload.js
@@ -22,7 +22,6 @@ odoo.define('account.bills.tree', function (require) {
this.$buttons.on('click', '.o_button_upload_bill', function () {
var state = self.model.get(self.handle, {raw: true});
var context = state.getContext()
- context['type'] = 'in_invoice'
self.do_action({
type: 'ir.actions.act_window',
res_model: 'account.invoice.import.wizard',
@@ -41,5 +40,5 @@ odoo.define('account.bills.tree', function (require) {
}),
});
- viewRegistry.add('account_bills_tree', BillsListView);
+ viewRegistry.add('account_tree', BillsListView);
});
diff --git a/addons/account/views/account_invoice_view.xml b/addons/account/views/account_invoice_view.xml
index 634ee330c9d..288327fdd4d 100644
--- a/addons/account/views/account_invoice_view.xml
+++ b/addons/account/views/account_invoice_view.xml
@@ -153,7 +153,7 @@
account.invoice.treeaccount.invoice
-
+
@@ -233,7 +233,7 @@
account.invoice.supplier.treeaccount.invoice
-
+
@@ -402,7 +402,7 @@
-
+
diff --git a/addons/account_facturx/__manifest__.py b/addons/account_facturx/__manifest__.py
index a615e3f9a45..49de3248eec 100644
--- a/addons/account_facturx/__manifest__.py
+++ b/addons/account_facturx/__manifest__.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
{
- 'name' : 'Import Vendor Bills From XML',
+ 'name' : 'Import Bills/Invoices From XML',
'version' : '1.0',
'category': 'Accounting',
'depends' : ['account'],
diff --git a/addons/account_facturx/models/account_invoice.py b/addons/account_facturx/models/account_invoice.py
index 3b5e080162e..8d7c959506e 100644
--- a/addons/account_facturx/models/account_invoice.py
+++ b/addons/account_facturx/models/account_invoice.py
@@ -12,6 +12,9 @@ from collections import namedtuple
import io
import base64
+import logging
+_logger = logging.getLogger(__name__)
+
DEFAULT_FACTURX_DATE_FORMAT = '%Y%m%d'
@@ -56,22 +59,23 @@ class AccountInvoice(models.Model):
# type must be present in the context to get the right behavior of the _default_journal method (account.invoice).
# journal_id must be present in the context to get the right behavior of the _default_account method (account.invoice.line).
- self_ctx = self.with_context(type='in_invoice')
- journal_id = self_ctx._default_journal().id
- self_ctx = self_ctx.with_context(journal_id=journal_id)
+ journal_id = self._default_journal()
+ self_ctx = self.with_context(journal_id=journal_id.id)
# self could be a single record (editing) or be empty (new).
- with Form(self_ctx, view='account.invoice_supplier_form') as invoice_form:
+ view = journal_id.type == 'purchase' and 'account.invoice_supplier_form' or 'account.invoice_form'
+ with Form(self_ctx, view=view) as invoice_form:
# Partner (first step to avoid warning 'Warning! You must first select a partner.').
- elements = tree.xpath('//ram:SellerTradeParty/ram:SpecifiedTaxRegistration/ram:ID', namespaces=tree.nsmap)
+ partner_type = journal_id.type == 'purchase' and 'SellerTradeParty' or 'BuyerTradeParty'
+ elements = tree.xpath('//ram:'+partner_type+'/ram:SpecifiedTaxRegistration/ram:ID', namespaces=tree.nsmap)
partner = elements and self.env['res.partner'].search([('vat', '=', elements[0].text)], limit=1)
if not partner:
- elements = tree.xpath('//ram:SellerTradeParty/ram:Name', namespaces=tree.nsmap)
+ elements = tree.xpath('//ram:'+partner_type+'/ram:Name', namespaces=tree.nsmap)
partner_name = elements and elements[0].text
partner = elements and self.env['res.partner'].search([('name', 'ilike', partner_name)], limit=1)
if not partner:
- elements = tree.xpath('//ram:SellerTradeParty//ram:URIID[@schemeID=\'SMTP\']', namespaces=tree.nsmap)
+ elements = tree.xpath('//ram:'+partner_type+'//ram:URIID[@schemeID=\'SMTP\']', namespaces=tree.nsmap)
partner = elements and self.env['res.partner'].search([('email', '=', elements[0].text)], limit=1)
if partner:
invoice_form.partner_id = partner
@@ -98,7 +102,7 @@ class AccountInvoice(models.Model):
# To handle both, we consider the 'a' mode and switch to 'b' if a negative amount is encountered.
elements = tree.xpath('//rsm:ExchangedDocument/ram:TypeCode', namespaces=tree.nsmap)
type_code = elements[0].text
- refund_sign = 1
+ refund_sign = type_code == '380' and 1 or -1
# Total amount.
elements = tree.xpath('//ram:GrandTotalAmount', namespaces=tree.nsmap)
@@ -131,6 +135,8 @@ class AccountInvoice(models.Model):
if elements:
date_str = elements[0].text
date_obj = datetime.strptime(date_str, DEFAULT_FACTURX_DATE_FORMAT)
+ # Set to empty record set to avoid readonly on date_due, can not set to False or None in a Form
+ invoice_form.payment_term_id = self.env['account.payment.term']
invoice_form.date_due = date_obj.strftime(DEFAULT_SERVER_DATE_FORMAT)
# Invoice lines.
@@ -163,7 +169,7 @@ class AccountInvoice(models.Model):
# Quantity.
line_elements = element.xpath('.//ram:SpecifiedLineTradeDelivery/ram:BilledQuantity', namespaces=tree.nsmap)
if line_elements:
- invoice_line_form.quantity = float(line_elements[0].text) * refund_sign
+ invoice_line_form.quantity = float(line_elements[0].text)
# Price Unit.
line_elements = element.xpath('.//ram:GrossPriceProductTradePrice/ram:ChargeAmount', namespaces=tree.nsmap)
@@ -188,7 +194,7 @@ class AccountInvoice(models.Model):
tax = self.env['account.tax'].search([
('company_id', '=', invoice_form.company_id.id),
('amount_type', '=', 'percent'),
- ('type_tax_use', '=', 'purchase'),
+ ('type_tax_use', '=', journal_id.type),
('amount', '=', percentage),
], limit=1)
@@ -202,7 +208,10 @@ class AccountInvoice(models.Model):
invoice_line_form.price_unit = amount_total_import
# Refund.
- invoice_form.type = 'in_refund' if refund_sign == -1 else 'in_invoice'
+ if self_ctx.env.context['journal_type'] == 'purchase':
+ invoice_form.type = 'in_refund' if refund_sign == -1 else 'in_invoice'
+ else:
+ invoice_form.type = 'out_refund' if refund_sign == -1 else 'out_invoice'
return invoice_form.save()
@@ -221,7 +230,7 @@ class AccountInvoice(models.Model):
# 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._context and len(self) == 1 and self.state == 'draft':
# Get attachments.
# - 'attachments' is a namedtuple defined in mail.thread looking like:
# _Attachment = namedtuple('Attachment', ('fname', 'content', 'info'))
@@ -260,8 +269,9 @@ class AccountInvoice(models.Model):
self._import_facturx_invoice(tree)
buffer.close()
return res
- except:
+ except Exception as e:
# Malformed PDF.
+ _logger.exception(e)
pass
buffer.close()
return res