mirror of
https://gitlab.com/flectra-hq/flectra.git
synced 2026-10-31 10:04:02 -05:00
[PATCH] Upstream patch - 19062022
This commit is contained in:
1 parent
d13fa4b670
commit
7fcc8ab048
22 files changed
+233
-109
No files matched your search
@@ -184,9 +184,9 @@ class AccountMove(models.Model):
|
||||
partner_id = fields.Many2one('res.partner', readonly=True, tracking=True,
|
||||
states={'draft': [('readonly', False)]},
|
||||
check_company=True,
|
||||
string='Partner', change_default=True)
|
||||
string='Partner', change_default=True, ondelete='restrict')
|
||||
commercial_partner_id = fields.Many2one('res.partner', string='Commercial Entity', store=True, readonly=True,
|
||||
compute='_compute_commercial_partner_id')
|
||||
compute='_compute_commercial_partner_id', ondelete='restrict')
|
||||
country_code = fields.Char(related='company_id.country_id.code', readonly=True)
|
||||
user_id = fields.Many2one(string='User', related='invoice_user_id',
|
||||
help='Technical field used to fit the generic behavior in mail templates.')
|
||||
|
||||
@@ -8,7 +8,7 @@ from psycopg2 import sql, DatabaseError
|
||||
|
||||
from flectra import api, fields, models, _
|
||||
from flectra.tools import DEFAULT_SERVER_DATETIME_FORMAT
|
||||
from flectra.exceptions import ValidationError
|
||||
from flectra.exceptions import ValidationError, UserError
|
||||
from flectra.addons.base.models.res_partner import WARNING_MESSAGE, WARNING_HELP
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
@@ -499,6 +499,17 @@ class ResPartner(models.Model):
|
||||
vals['supplier_rank'] = 1
|
||||
return super().create(vals_list)
|
||||
|
||||
def unlink(self):
|
||||
"""
|
||||
Prevent the deletion of a partner "Individual", child of a company if:
|
||||
- partner in 'account.move'
|
||||
- state: all states (draft and posted)
|
||||
"""
|
||||
moves = self.sudo().env['account.move'].search_count([('partner_id', 'in', self.ids), ('state', 'in', ['draft', 'posted'])])
|
||||
if moves:
|
||||
raise UserError(_("Record cannot be deleted. Partner used in Accounting"))
|
||||
return super(ResPartner, self).unlink()
|
||||
|
||||
def _increase_rank(self, field, n=1):
|
||||
if self.ids and field in ['customer_rank', 'supplier_rank']:
|
||||
try:
|
||||
|
||||
@@ -218,11 +218,12 @@ class AccountEdiFormat(models.Model):
|
||||
# Import methods to override based on EDI Format
|
||||
####################################################
|
||||
|
||||
def _create_invoice_from_xml_tree(self, filename, tree):
|
||||
def _create_invoice_from_xml_tree(self, filename, tree, journal=None):
|
||||
""" Create a new invoice with the data inside the xml.
|
||||
|
||||
:param filename: The name of the xml.
|
||||
:param tree: The tree of the xml to import.
|
||||
:param journal: The journal on which importing the invoice.
|
||||
:returns: The created invoice.
|
||||
"""
|
||||
# TO OVERRIDE
|
||||
@@ -504,96 +505,107 @@ class AccountEdiFormat(models.Model):
|
||||
element = xml_element.xpath(xpath, namespaces=namespaces)
|
||||
return element[0].text if element else None
|
||||
|
||||
def _retrieve_partner(self, name=None, phone=None, mail=None, vat=None):
|
||||
'''Search all partners and find one that matches one of the parameters.
|
||||
@api.model
|
||||
def _retrieve_partner_with_vat(self, vat, extra_domain):
|
||||
if not vat:
|
||||
return None
|
||||
|
||||
# Sometimes, the vat is specified with some whitespaces.
|
||||
normalized_vat = vat.replace(' ', '')
|
||||
country_prefix = re.match('^[a-zA-Z]{2}|^', vat).group()
|
||||
|
||||
partner = self.env['res.partner'].search(extra_domain + [('vat', 'in', (normalized_vat, vat))], limit=1)
|
||||
|
||||
# Try to remove the country code prefix from the vat.
|
||||
if not partner and country_prefix:
|
||||
partner = self.env['res.partner'].search(extra_domain + [
|
||||
('vat', 'in', (normalized_vat[2:], vat[2:])),
|
||||
('country_id.code', '=', country_prefix.upper()),
|
||||
], limit=1)
|
||||
|
||||
# The country could be not specified on the partner.
|
||||
if not partner:
|
||||
partner = self.env['res.partner'].search(extra_domain + [
|
||||
('vat', 'in', (normalized_vat[2:], vat[2:])),
|
||||
('country_id', '=', False),
|
||||
], limit=1)
|
||||
|
||||
# The vat could be a string of alphanumeric values without country code but with missing zeros at the
|
||||
# beginning.
|
||||
if not partner:
|
||||
try:
|
||||
vat_only_numeric = str(int(re.sub(r'^\D{2}', '', normalized_vat) or 0))
|
||||
except ValueError:
|
||||
vat_only_numeric = None
|
||||
|
||||
if vat_only_numeric:
|
||||
query = self.env['res.partner']._where_calc(extra_domain + [('active', '=', True)])
|
||||
tables, where_clause, where_params = query.get_sql()
|
||||
|
||||
if country_prefix:
|
||||
vat_prefix_regex = f'({country_prefix})?'
|
||||
else:
|
||||
vat_prefix_regex = '([A-z]{2})?'
|
||||
|
||||
self._cr.execute(f'''
|
||||
SELECT res_partner.id
|
||||
FROM {tables}
|
||||
WHERE {where_clause}
|
||||
AND res_partner.vat ~ %s
|
||||
LIMIT 1
|
||||
''', where_params + ['^%s0*%s$' % (vat_prefix_regex, vat_only_numeric)])
|
||||
partner_row = self._cr.fetchone()
|
||||
if partner_row:
|
||||
partner = self.env['res.partner'].browse(partner_row[0])
|
||||
|
||||
return partner
|
||||
|
||||
@api.model
|
||||
def _retrieve_partner_with_phone_mail(self, phone, mail, extra_domain):
|
||||
domains = []
|
||||
if phone:
|
||||
domains.append([('phone', '=', phone)])
|
||||
domains.append([('mobile', '=', phone)])
|
||||
if mail:
|
||||
domains.append([('email', '=', mail)])
|
||||
|
||||
if not domains:
|
||||
return None
|
||||
|
||||
domain = expression.OR(domains)
|
||||
if extra_domain:
|
||||
domain = expression.AND([domain, extra_domain])
|
||||
return self.env['res.partner'].search(domain, limit=1)
|
||||
|
||||
@api.model
|
||||
def _retrieve_partner_with_name(self, name, extra_domain):
|
||||
if not name:
|
||||
return None
|
||||
return self.env['res.partner'].search([('name', 'ilike', name)] + extra_domain, limit=1)
|
||||
|
||||
def _retrieve_partner(self, name=None, phone=None, mail=None, vat=None, domain=None):
|
||||
'''Search all partners and find one that matches one of the parameters.
|
||||
:param name: The name of the partner.
|
||||
:param phone: The phone or mobile of the partner.
|
||||
:param mail: The mail of the partner.
|
||||
:param vat: The vat number of the partner.
|
||||
:returns: A partner or an empty recordset if not found.
|
||||
'''
|
||||
|
||||
def search_with_vat(extra_domain):
|
||||
if not vat:
|
||||
return None
|
||||
|
||||
# Sometimes, the vat is specified with some whitespaces.
|
||||
normalized_vat = vat.replace(' ', '')
|
||||
country_prefix = re.match('^[a-zA-Z]{2}|^', vat).group()
|
||||
|
||||
partner = self.env['res.partner'].search(extra_domain + [('vat', 'in', (normalized_vat, vat))], limit=1)
|
||||
|
||||
# Try to remove the country code prefix from the vat.
|
||||
if not partner and country_prefix:
|
||||
partner = self.env['res.partner'].search(extra_domain + [
|
||||
('vat', 'in', (normalized_vat[2:], vat[2:])),
|
||||
('country_id.code', '=', country_prefix.upper()),
|
||||
], limit=1)
|
||||
|
||||
# The country could be not specified on the partner.
|
||||
if not partner:
|
||||
partner = self.env['res.partner'].search(extra_domain + [
|
||||
('vat', 'in', (normalized_vat[2:], vat[2:])),
|
||||
('country_id', '=', False),
|
||||
], limit=1)
|
||||
|
||||
# The vat could be a string of alphanumeric values without country code but with missing zeros at the
|
||||
# beginning.
|
||||
if not partner:
|
||||
try:
|
||||
vat_only_numeric = str(int(re.sub('^\D{2}', '', normalized_vat) or 0))
|
||||
except ValueError:
|
||||
vat_only_numeric = None
|
||||
|
||||
if vat_only_numeric:
|
||||
query = self.env['res.partner']._where_calc(extra_domain + [('active', '=', True)])
|
||||
tables, where_clause, where_params = query.get_sql()
|
||||
|
||||
if country_prefix:
|
||||
vat_prefix_regex = f'({country_prefix})?'
|
||||
else:
|
||||
vat_prefix_regex = '([A-z]{2})?'
|
||||
|
||||
self._cr.execute(f'''
|
||||
SELECT res_partner.id
|
||||
FROM {tables}
|
||||
WHERE {where_clause}
|
||||
AND res_partner.vat ~ %s
|
||||
LIMIT 1
|
||||
''', where_params + ['^%s0*%s$' % (vat_prefix_regex, vat_only_numeric)])
|
||||
partner_row = self._cr.fetchone()
|
||||
if partner_row:
|
||||
partner = self.env['res.partner'].browse(partner_row[0])
|
||||
|
||||
return partner
|
||||
return self._retrieve_partner_with_vat(vat, extra_domain)
|
||||
|
||||
def search_with_phone_mail(extra_domain):
|
||||
domains = []
|
||||
if phone:
|
||||
domains.append([('phone', '=', phone)])
|
||||
domains.append([('mobile', '=', phone)])
|
||||
if mail:
|
||||
domains.append([('email', '=', mail)])
|
||||
|
||||
if not domains:
|
||||
return None
|
||||
|
||||
domain = expression.OR(domains)
|
||||
if extra_domain:
|
||||
domain = expression.AND([domain, extra_domain])
|
||||
return self.env['res.partner'].search(domain, limit=1)
|
||||
return self._retrieve_partner_with_phone_mail(phone, mail, extra_domain)
|
||||
|
||||
def search_with_name(extra_domain):
|
||||
if not name:
|
||||
return None
|
||||
return self.env['res.partner'].search([('name', 'ilike', name)] + extra_domain, limit=1)
|
||||
return self._retrieve_partner_with_name(name, extra_domain)
|
||||
|
||||
for search_method in (search_with_vat, search_with_phone_mail, search_with_name):
|
||||
for extra_domain in ([('company_id', '=', self.env.company.id)], []):
|
||||
partner = search_method(extra_domain)
|
||||
if partner:
|
||||
return partner
|
||||
|
||||
return self.env['res.partner']
|
||||
|
||||
def _retrieve_product(self, name=None, default_code=None, barcode=None):
|
||||
@@ -648,3 +660,6 @@ class AccountEdiFormat(models.Model):
|
||||
def _format_error_message(self, error_title, errors):
|
||||
bullet_list_msg = ''.join('<li>%s</li>' % html_escape(msg) for msg in errors)
|
||||
return '%s<ul>%s</ul>' % (error_title, bullet_list_msg)
|
||||
|
||||
def _is_account_edi_ubl_cii_available(self):
|
||||
return hasattr(self, '_infer_xml_builder_from_tree')
|
||||
@@ -31,8 +31,12 @@ class AccountJournal(models.Model):
|
||||
('edi_format_id', 'in', diff_edi_format_ids.ids),
|
||||
('state', 'in', ('to_cancel', 'to_send')),
|
||||
])
|
||||
if documents:
|
||||
# If the formats we are unchecking do not need a webservice, we don't need them to be correctly sent
|
||||
if documents.filtered(lambda d: d.edi_format_id._needs_web_services()):
|
||||
raise UserError(_('Cannot deactivate (%s) on this journal because not all documents are synchronized', ', '.join(documents.edi_format_id.mapped('display_name'))))
|
||||
# remove these documents which: do not need a web service & are linked to the edi formats we are unchecking
|
||||
if documents:
|
||||
documents.unlink()
|
||||
return res
|
||||
else:
|
||||
return super().write(vals)
|
||||
|
||||
@@ -264,6 +264,34 @@ class AccountMove(models.Model):
|
||||
|
||||
return invoice_global_tax_details
|
||||
|
||||
def _prepare_edi_vals_to_export(self):
|
||||
''' The purpose of this helper is to prepare values in order to export an invoice through the EDI system.
|
||||
This includes the computation of the tax details for each invoice line that could be very difficult to
|
||||
handle regarding the computation of the base amount.
|
||||
:return: A python dict containing default pre-processed values.
|
||||
'''
|
||||
self.ensure_one()
|
||||
|
||||
res = {
|
||||
'record': self,
|
||||
'balance_multiplicator': -1 if self.is_inbound() else 1,
|
||||
'invoice_line_vals_list': [],
|
||||
}
|
||||
|
||||
# Invoice lines details.
|
||||
for index, line in enumerate(self.invoice_line_ids.filtered(lambda line: not line.display_type), start=1):
|
||||
line_vals = line._prepare_edi_vals_to_export()
|
||||
line_vals['index'] = index
|
||||
res['invoice_line_vals_list'].append(line_vals)
|
||||
|
||||
# Totals.
|
||||
res.update({
|
||||
'total_price_subtotal_before_discount': sum(x['price_subtotal_before_discount'] for x in res['invoice_line_vals_list']),
|
||||
'total_price_discount': sum(x['price_discount'] for x in res['invoice_line_vals_list']),
|
||||
})
|
||||
|
||||
return res
|
||||
|
||||
def _update_payments_edi_documents(self):
|
||||
''' Update the edi documents linked to the current journal entries. These journal entries must be linked to an
|
||||
account.payment of an account.bank.statement.line. This additional method is needed because the payment flow is
|
||||
@@ -416,6 +444,34 @@ class AccountMoveLine(models.Model):
|
||||
# Export Electronic Document
|
||||
####################################################
|
||||
|
||||
def _prepare_edi_vals_to_export(self):
|
||||
''' The purpose of this helper is the same as '_prepare_edi_vals_to_export' but for a single invoice line.
|
||||
This includes the computation of the tax details for each invoice line or the management of the discount.
|
||||
Indeed, in some EDI, we need to provide extra values depending the discount such as:
|
||||
- the discount as an amount instead of a percentage.
|
||||
- the price_unit but after subtraction of the discount.
|
||||
:return: A python dict containing default pre-processed values.
|
||||
'''
|
||||
self.ensure_one()
|
||||
|
||||
if self.discount == 100.0:
|
||||
gross_price_subtotal = self.currency_id.round(self.price_unit * self.quantity)
|
||||
else:
|
||||
gross_price_subtotal = self.currency_id.round(self.price_subtotal / (1 - self.discount / 100.0))
|
||||
|
||||
res = {
|
||||
'line': self,
|
||||
'price_unit_after_discount': self.currency_id.round(self.price_unit * (1 - (self.discount / 100.0))),
|
||||
'price_subtotal_before_discount': gross_price_subtotal,
|
||||
'price_subtotal_unit': self.currency_id.round(self.price_subtotal / self.quantity) if self.quantity else 0.0,
|
||||
'price_total_unit': self.currency_id.round(self.price_total / self.quantity) if self.quantity else 0.0,
|
||||
'price_discount': gross_price_subtotal - self.price_subtotal,
|
||||
'price_discount_unit': (gross_price_subtotal - self.price_subtotal) / self.quantity if self.quantity else 0.0,
|
||||
'gross_price_total_unit': self.currency_id.round(gross_price_subtotal / self.quantity) if self.quantity else 0.0,
|
||||
'unece_uom_code': self.product_id.product_tmpl_id.uom_id._get_unece_code(),
|
||||
}
|
||||
return res
|
||||
|
||||
def reconcile(self):
|
||||
# OVERRIDE
|
||||
# In some countries, the payments must be sent to the government under some condition. One of them could be
|
||||
|
||||
@@ -1 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import models
|
||||
from flectra import api, SUPERUSER_ID
|
||||
|
||||
|
||||
def _post_init_hook(cr, registry):
|
||||
env = api.Environment(cr, SUPERUSER_ID, {})
|
||||
modules = env['ir.module.module'].search([('name', '=', 'account_edi_ubl_cii'), ('state', '=', 'uninstalled')])
|
||||
modules.sudo().button_install()
|
||||
@@ -9,6 +9,7 @@
|
||||
'data/facturx_templates.xml',
|
||||
],
|
||||
'installable': True,
|
||||
'post_init_hook': '_post_init_hook',
|
||||
'application': False,
|
||||
'auto_install': True,
|
||||
'license': 'LGPL-3',
|
||||
|
||||
@@ -26,13 +26,13 @@ class AccountEdiFormat(models.Model):
|
||||
def _is_compatible_with_journal(self, journal):
|
||||
self.ensure_one()
|
||||
res = super()._is_compatible_with_journal(journal)
|
||||
if self.code != 'facturx_1_0_05':
|
||||
if self.code != 'facturx_1_0_05' or self._is_account_edi_ubl_cii_available():
|
||||
return res
|
||||
return journal.type == 'sale'
|
||||
|
||||
def _post_invoice_edi(self, invoices, test_mode=False):
|
||||
self.ensure_one()
|
||||
if self.code != 'facturx_1_0_05':
|
||||
if self.code != 'facturx_1_0_05' or self._is_account_edi_ubl_cii_available():
|
||||
return super()._post_invoice_edi(invoices, test_mode=test_mode)
|
||||
res = {}
|
||||
for invoice in invoices:
|
||||
@@ -53,7 +53,7 @@ class AccountEdiFormat(models.Model):
|
||||
|
||||
def _prepare_invoice_report(self, pdf_writer, edi_document):
|
||||
self.ensure_one()
|
||||
if self.code != 'facturx_1_0_05':
|
||||
if self.code != 'facturx_1_0_05' or self._is_account_edi_ubl_cii_available():
|
||||
return super()._prepare_invoice_report(pdf_writer, edi_document)
|
||||
if not edi_document.attachment_id:
|
||||
return
|
||||
@@ -175,15 +175,15 @@ class AccountEdiFormat(models.Model):
|
||||
def _is_facturx(self, filename, tree):
|
||||
return self.code == 'facturx_1_0_05' and tree.tag == '{urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100}CrossIndustryInvoice'
|
||||
|
||||
def _create_invoice_from_xml_tree(self, filename, tree):
|
||||
def _create_invoice_from_xml_tree(self, filename, tree, journal=None):
|
||||
self.ensure_one()
|
||||
if self._is_facturx(filename, tree):
|
||||
if self._is_facturx(filename, tree) and not self._is_account_edi_ubl_cii_available():
|
||||
return self._import_facturx(tree, self.env['account.move'])
|
||||
return super()._create_invoice_from_xml_tree(filename, tree)
|
||||
return super()._create_invoice_from_xml_tree(filename, tree, journal=journal)
|
||||
|
||||
def _update_invoice_from_xml_tree(self, filename, tree, invoice):
|
||||
self.ensure_one()
|
||||
if self._is_facturx(filename, tree):
|
||||
if self._is_facturx(filename, tree) and not self._is_account_edi_ubl_cii_available():
|
||||
return self._import_facturx(tree, invoice)
|
||||
return super()._update_invoice_from_xml_tree(filename, tree, invoice)
|
||||
|
||||
|
||||
@@ -11,6 +11,12 @@ class TestAccountEdiFacturx(AccountEdiTestCommon):
|
||||
def setUpClass(cls, chart_template_ref=None, edi_format_ref='account_edi_facturx.edi_facturx_1_0_05'):
|
||||
super().setUpClass(chart_template_ref=chart_template_ref, edi_format_ref=edi_format_ref)
|
||||
|
||||
if cls.env['ir.module.module'].search(
|
||||
[('name', '=', 'account_edi_ubl_cii'), ('state', '=', 'installed')],
|
||||
limit=1,
|
||||
):
|
||||
cls.skipTest(cls, "Factur-X Tests skipped because account_edi_ubl_cii is installed.")
|
||||
|
||||
# ==== Init ====
|
||||
|
||||
cls.tax_10_include = cls.env['account.tax'].create({
|
||||
|
||||
@@ -2,3 +2,10 @@
|
||||
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import models
|
||||
from flectra import api, SUPERUSER_ID
|
||||
|
||||
|
||||
def _post_init_hook(cr, registry):
|
||||
env = api.Environment(cr, SUPERUSER_ID, {})
|
||||
modules = env['ir.module.module'].search([('name', '=', 'account_edi_ubl_cii'), ('state', '=', 'uninstalled')])
|
||||
modules.sudo().button_install()
|
||||
@@ -17,5 +17,6 @@ Belgian e-invoicing uses the UBL 2.0 using the e-fff protocol.
|
||||
],
|
||||
'installable': True,
|
||||
'auto_install': True,
|
||||
'post_init_hook': '_post_init_hook',
|
||||
'license': 'LGPL-3',
|
||||
}
|
||||
@@ -11,28 +11,27 @@ class AccountEdiFormat(models.Model):
|
||||
def _is_efff(self, filename, tree):
|
||||
return self.code == 'efff_1' and tree.tag == '{urn:oasis:names:specification:ubl:schema:xsd:Invoice-2}Invoice'
|
||||
|
||||
def _create_invoice_from_xml_tree(self, filename, tree):
|
||||
def _create_invoice_from_xml_tree(self, filename, tree, journal=None):
|
||||
self.ensure_one()
|
||||
if self._is_efff(filename, tree):
|
||||
if self._is_efff(filename, tree) and not self._is_account_edi_ubl_cii_available():
|
||||
return self._create_invoice_from_ubl(tree)
|
||||
return super()._create_invoice_from_xml_tree(filename, tree)
|
||||
return super()._create_invoice_from_xml_tree(filename, tree, journal=journal)
|
||||
|
||||
def _update_invoice_from_xml_tree(self, filename, tree, invoice):
|
||||
self.ensure_one()
|
||||
if self._is_efff(filename, tree):
|
||||
if self._is_efff(filename, tree) and not self._is_account_edi_ubl_cii_available():
|
||||
return self._update_invoice_from_ubl(tree, invoice)
|
||||
return super()._update_invoice_from_xml_tree(filename, tree, invoice)
|
||||
|
||||
def _is_compatible_with_journal(self, journal):
|
||||
self.ensure_one()
|
||||
res = super()._is_compatible_with_journal(journal)
|
||||
if self.code != 'efff_1':
|
||||
return res
|
||||
if self.code != 'efff_1' or self._is_account_edi_ubl_cii_available():
|
||||
return super()._is_compatible_with_journal(journal)
|
||||
return journal.type == 'sale' and journal.country_code == 'BE'
|
||||
|
||||
def _post_invoice_edi(self, invoices, test_mode=False):
|
||||
self.ensure_one()
|
||||
if self.code != 'efff_1':
|
||||
if self.code != 'efff_1' or self._is_account_edi_ubl_cii_available():
|
||||
return super()._post_invoice_edi(invoices, test_mode=test_mode)
|
||||
res = {}
|
||||
for invoice in invoices:
|
||||
|
||||
@@ -14,6 +14,12 @@ class TestUBLBE(AccountEdiTestCommon):
|
||||
def setUpClass(cls, chart_template_ref='l10n_be.l10nbe_chart_template', edi_format_ref='l10n_be_edi.edi_efff_1'):
|
||||
super().setUpClass(chart_template_ref=chart_template_ref, edi_format_ref=edi_format_ref)
|
||||
|
||||
if cls.env['ir.module.module'].search(
|
||||
[('name', '=', 'account_edi_ubl_cii'), ('state', '=', 'installed')],
|
||||
limit=1,
|
||||
):
|
||||
cls.skipTest(cls, "L10n_be_edi Tests skipped because account_edi_ubl_cii is installed.")
|
||||
|
||||
cls.partner_a.write({
|
||||
'street': "Chaussée de Namur 40",
|
||||
'zip': "1367",
|
||||
|
||||
@@ -9,6 +9,12 @@ class TestUBL(AccountEdiTestCommon):
|
||||
def setUpClass(cls, chart_template_ref='l10n_be.l10nbe_chart_template', edi_format_ref='l10n_be_edi.edi_efff_1'):
|
||||
super().setUpClass(chart_template_ref=chart_template_ref, edi_format_ref=edi_format_ref)
|
||||
|
||||
if cls.env['ir.module.module'].search(
|
||||
[('name', '=', 'account_edi_ubl_cii'), ('state', '=', 'installed')],
|
||||
limit=1,
|
||||
):
|
||||
cls.skipTest(cls, "L10n_be_edi Tests skipped because account_edi_ubl_cii is installed.")
|
||||
|
||||
cls.partner_a.vat = 'BE0477472701'
|
||||
|
||||
def test_invoice_edi_xml_update(self):
|
||||
|
||||
@@ -169,8 +169,8 @@
|
||||
<AliquotaIVA t-esc="format_numbers(tax_dict['tax'].amount)"/>
|
||||
<Natura t-if="tax_dict['tax'].l10n_it_has_exoneration" t-esc="tax_dict['tax'].l10n_it_kind_exoneration"/>
|
||||
<Arrotondamento t-if="tax_dict.get('rounding')" t-esc="format_numbers(tax_dict['rounding'])"/>
|
||||
<ImponibileImporto t-esc="format_monetary(abs(tax_dict['base_amount']), currency)"/>
|
||||
<Imposta t-esc="format_monetary(abs(tax_dict['tax_amount']), currency)"/>
|
||||
<ImponibileImporto t-esc="format_monetary(abs(tax_dict['base_amount_currency']), currency)"/>
|
||||
<Imposta t-esc="format_monetary(abs(tax_dict['tax_amount_currency']), currency)"/>
|
||||
<EsigibilitaIVA t-if="not tax_dict['tax'].l10n_it_has_exoneration or tax_dict['tax'].l10n_it_kind_exoneration=='N6'" t-esc="tax_dict['tax'].l10n_it_vat_due_date"/>
|
||||
<RiferimentoNormativo t-if="tax_dict['tax'].l10n_it_has_exoneration" t-esc="format_alphanumeric(tax_dict['tax'].l10n_it_law_reference[:100])"/>
|
||||
</DatiRiepilogo>
|
||||
|
||||
@@ -233,11 +233,11 @@ class AccountEdiFormat(models.Model):
|
||||
def _is_fattura_pa(self, filename, tree):
|
||||
return self.code == 'fattura_pa' and self._check_filename_is_fattura_pa(filename)
|
||||
|
||||
def _create_invoice_from_xml_tree(self, filename, tree):
|
||||
def _create_invoice_from_xml_tree(self, filename, tree, journal=None):
|
||||
self.ensure_one()
|
||||
if self._is_fattura_pa(filename, tree):
|
||||
return self._import_fattura_pa(tree, self.env['account.move'])
|
||||
return super()._create_invoice_from_xml_tree(filename, tree)
|
||||
return super()._create_invoice_from_xml_tree(filename, tree, journal=journal)
|
||||
|
||||
def _update_invoice_from_xml_tree(self, filename, tree, invoice):
|
||||
self.ensure_one()
|
||||
|
||||
@@ -167,14 +167,14 @@ class AccountMove(models.Model):
|
||||
# taxable base = sum(taxable base for each unit) + Arrotondamento
|
||||
tax_details = self._prepare_edi_tax_details()
|
||||
for _tax_name, tax_dict in tax_details['tax_details'].items():
|
||||
base_amount = tax_dict['base_amount']
|
||||
tax_amount = tax_dict['tax_amount']
|
||||
base_amount = tax_dict['base_amount_currency']
|
||||
tax_amount = tax_dict['tax_amount_currency']
|
||||
tax_rate = tax_dict['tax'].amount
|
||||
if tax_dict['tax'].price_include and tax_dict['tax'].amount_type == 'percent':
|
||||
expected_base_amount = tax_amount * 100 / tax_rate if tax_rate else False
|
||||
if expected_base_amount and float_compare(base_amount, expected_base_amount, 2):
|
||||
tax_dict['rounding'] = base_amount - (tax_amount * 100 / tax_rate)
|
||||
tax_dict['base_amount'] = base_amount - tax_dict['rounding']
|
||||
tax_dict['base_amount_currency'] = base_amount - tax_dict['rounding']
|
||||
|
||||
# Create file content.
|
||||
template_values = {
|
||||
|
||||
@@ -28,5 +28,6 @@ class PosAdyenController(http.Controller):
|
||||
payment_method.adyen_latest_diagnosis = data['SaleToPOIResponse']['MessageHeader']['ServiceID']
|
||||
else:
|
||||
payment_method.adyen_latest_response = json.dumps(data)
|
||||
_logger.info('notification writed from adyen\n%s', data)
|
||||
else:
|
||||
_logger.error('received a message for a terminal not registered in Flectra: %s', terminal_identifier)
|
||||
@@ -79,6 +79,7 @@ class PosPaymentMethod(models.Model):
|
||||
}
|
||||
|
||||
def get_latest_adyen_status(self, pos_config_name):
|
||||
_logger.info('get_latest_adyen_status\n%s', pos_config_name)
|
||||
self.ensure_one()
|
||||
|
||||
# Poll the status of the terminal if there's no new
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
|
||||
from flectra import api, models
|
||||
from flectra import models
|
||||
|
||||
|
||||
class ResUsers(models.Model):
|
||||
@@ -8,4 +8,8 @@ class ResUsers(models.Model):
|
||||
|
||||
def _has_unsplash_key_rights(self):
|
||||
self.ensure_one()
|
||||
return self.has_group('base.group_erp_manager')
|
||||
# Website has no dependency to web_unsplash, we cannot warranty the order of the execution
|
||||
# of the overwrite done in 5ef8300.
|
||||
# So to avoid to create a new module bridge, with a lot of code, we prefer to make a check
|
||||
# here for website's user.
|
||||
return self.has_group('base.group_erp_manager') or self.has_group('website.group_website_designer')
|
||||
@@ -3,8 +3,10 @@
|
||||
|
||||
import logging
|
||||
|
||||
from werkzeug.urls import url_join
|
||||
|
||||
from flectra import api, fields, models, _
|
||||
from flectra.addons.http_routing.models.ir_http import url_for
|
||||
from flectra.http import request
|
||||
from flectra.osv import expression
|
||||
from flectra.exceptions import AccessError
|
||||
@@ -50,7 +52,7 @@ class SeoMetadata(models.AbstractModel):
|
||||
'og:type': 'website',
|
||||
'og:title': title,
|
||||
'og:site_name': company.name,
|
||||
'og:url': request.httprequest.url,
|
||||
'og:url': url_join(request.httprequest.url_root, url_for(request.httprequest.path)),
|
||||
'og:image': img,
|
||||
}
|
||||
# Default meta for Twitter
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
|
||||
import logging
|
||||
|
||||
from flectra import api, fields, models, tools, _
|
||||
from flectra import api, fields, models, _
|
||||
from flectra.exceptions import ValidationError
|
||||
from flectra.http import request
|
||||
|
||||
@@ -19,12 +19,6 @@ class ResUsers(models.Model):
|
||||
('login_key', 'unique (login, website_id)', 'You can not have two users with the same login!'),
|
||||
]
|
||||
|
||||
def _has_unsplash_key_rights(self):
|
||||
self.ensure_one()
|
||||
if self.has_group('website.group_website_designer'):
|
||||
return True
|
||||
return super(ResUsers, self)._has_unsplash_key_rights()
|
||||
|
||||
@api.constrains('login', 'website_id')
|
||||
def _check_login(self):
|
||||
""" Do not allow two users with the same login without website """
|
||||
|
||||
Reference in new issue
Block a user