[PATCH] Upstream patch - 14032022

This commit is contained in:
Parthiv Patel committed 2022-03-14 08:35:47 +00:00
1 parent 626e6d1890
commit 2608624af7
22 files changed
+666 -102

No files matched your search

+26 -60
View File
@@ -3101,6 +3101,20 @@ class AccountMove(models.Model):
"""
return []
def _notify_get_groups(self, msg_vals=None):
""" Give access button to users and portal customer as portal is integrated
in account. Customer and portal group have probably no right to see
the document so they don't have the access button. """
groups = super(AccountMove, self)._notify_get_groups(msg_vals=msg_vals)
self.ensure_one()
if self.move_type != 'entry':
for group_name, _group_method, group_data in groups:
if group_name == 'portal_customer':
group_data['has_button_access'] = True
return groups
class AccountMoveLine(models.Model):
_name = "account.move.line"
_description = "Journal Item"
@@ -3341,67 +3355,20 @@ class AccountMoveLine(models.Model):
if not self.product_id:
return 0.0
company = self.move_id.company_id
currency = self.move_id.currency_id
company_currency = company.currency_id
product_uom = self.product_id.uom_id
fiscal_position = self.move_id.fiscal_position_id
is_refund_document = self.move_id.move_type in ('out_refund', 'in_refund')
move_date = self.move_id.date or fields.Date.context_today(self)
if self.move_id.is_sale_document(include_receipts=True):
product_price_unit = self.product_id.lst_price
product_taxes = self.product_id.taxes_id
document_type = 'sale'
elif self.move_id.is_purchase_document(include_receipts=True):
product_price_unit = self.product_id.standard_price
product_taxes = self.product_id.supplier_taxes_id
document_type = 'purchase'
else:
return 0.0
product_taxes = product_taxes.filtered(lambda tax: tax.company_id == company)
# Apply unit of measure.
if self.product_uom_id and self.product_uom_id != product_uom:
product_price_unit = product_uom._compute_price(product_price_unit, self.product_uom_id)
# Apply fiscal position.
if product_taxes and fiscal_position:
product_taxes_after_fp = fiscal_position.map_tax(product_taxes, partner=self.partner_id)
if set(product_taxes.ids) != set(product_taxes_after_fp.ids):
flattened_taxes_before_fp = product_taxes._origin.flatten_taxes_hierarchy()
if any(tax.price_include for tax in flattened_taxes_before_fp):
taxes_res = flattened_taxes_before_fp.compute_all(
product_price_unit,
quantity=1.0,
currency=company_currency,
product=self.product_id,
partner=self.partner_id,
is_refund=is_refund_document,
)
product_price_unit = company_currency.round(taxes_res['total_excluded'])
flattened_taxes_after_fp = product_taxes_after_fp._origin.flatten_taxes_hierarchy()
if any(tax.price_include for tax in flattened_taxes_after_fp):
taxes_res = flattened_taxes_after_fp.compute_all(
product_price_unit,
quantity=1.0,
currency=company_currency,
product=self.product_id,
partner=self.partner_id,
is_refund=is_refund_document,
handle_price_include=False,
)
for tax_res in taxes_res['taxes']:
tax = self.env['account.tax'].browse(tax_res['id'])
if tax.price_include:
product_price_unit += tax_res['amount']
# Apply currency rate.
if currency and currency != company_currency:
product_price_unit = company_currency._convert(product_price_unit, currency, company, move_date)
return product_price_unit
document_type = 'other'
return self.product_id._get_tax_included_unit_price(
self.move_id.company_id,
self.move_id.currency_id,
self.move_id.date,
document_type,
fiscal_position=self.move_id.fiscal_position_id,
product_uom=self.product_uom_id
)
def _get_computed_account(self):
self.ensure_one()
@@ -3544,8 +3511,7 @@ class AccountMoveLine(models.Model):
# Compute 'price_total'.
if taxes:
force_sign = -1 if move_type in ('out_invoice', 'in_refund', 'out_receipt') else 1
taxes_res = taxes._origin.with_context(force_sign=force_sign).compute_all(line_discount_price_unit,
taxes_res = taxes._origin.with_context(force_sign=1).compute_all(line_discount_price_unit,
quantity=quantity, currency=currency, product=product, partner=partner, is_refund=move_type in ('out_refund', 'in_refund'))
res['price_subtotal'] = taxes_res['total_excluded']
res['price_total'] = taxes_res['total_included']
@@ -459,6 +459,23 @@ class AccountReconcileModel(models.Model):
return lines_vals_list + writeoff_vals_list
def _prepare_widget_writeoff_vals(self, st_line_id, write_off_vals):
fixed_write_off_vals = dict(write_off_vals, currency_id=st_line_id.company_id.currency_id.id)
counterpart_vals = st_line_id._prepare_counterpart_move_line_vals(fixed_write_off_vals)
return {
'name': counterpart_vals['name'],
'balance': counterpart_vals['amount_currency'],
'debit': counterpart_vals['debit'],
'credit': counterpart_vals['credit'],
'account_id': counterpart_vals['account_id'],
'currency_id': counterpart_vals['currency_id'],
'analytic_account_id': counterpart_vals.get('analytic_account_id'),
'analytic_tag_ids': counterpart_vals.get('analytic_tag_ids', []),
'reconcile_model_id': self.id,
'journal_id': counterpart_vals['journal_id']
}
####################################################
# RECONCILIATION CRITERIA
####################################################
+2 -2
View File
@@ -530,7 +530,7 @@ class AccountTax(models.Model):
price_include = self._context.get('force_price_include', tax.price_include)
#compute the tax_amount
if not skip_checkpoint and price_include and total_included_checkpoints.get(i) and sum_repartition_factor != 0:
if not skip_checkpoint and price_include and total_included_checkpoints.get(i) is not None and sum_repartition_factor != 0:
# We know the total to reach for that tax, so we make a substraction to avoid any rounding issues
tax_amount = total_included_checkpoints[i] - (base + cumulated_tax_included_amount)
cumulated_tax_included_amount = 0
@@ -542,7 +542,7 @@ class AccountTax(models.Model):
tax_amount = round(tax_amount, precision_rounding=prec)
factorized_tax_amount = round(tax_amount * sum_repartition_factor, precision_rounding=prec)
if price_include and not total_included_checkpoints.get(i):
if price_include and total_included_checkpoints.get(i) is None:
cumulated_tax_included_amount += factorized_tax_amount
# If the tax affects the base of subsequent taxes, its tax move lines must
+73
View File
@@ -59,3 +59,76 @@ class ProductProduct(models.Model):
def _get_product_accounts(self):
return self.product_tmpl_id._get_product_accounts()
@api.model
def _get_tax_included_unit_price(self, company, currency, document_date, document_type,
is_refund_document=False, product_uom=None, product_currency=None,
product_price_unit=None, product_taxes=None, fiscal_position=None
):
""" Helper to get the price unit from different models.
This is needed to compute the same unit price in different models (sale order, account move, etc.) with same parameters.
"""
product = self
assert document_type
if product_uom is None:
product_uom = product.uom_id
if not product_currency:
if document_type == 'sale':
product_currency = product.currency_id
elif document_type == 'purchase':
product_currency = company.currency_id
if product_price_unit is None:
if document_type == 'sale':
product_price_unit = product.with_company(company).lst_price
elif document_type == 'purchase':
product_price_unit = product.with_company(company).standard_price
else:
return 0.0
if product_taxes is None:
if document_type == 'sale':
product_taxes = product.taxes_id.filtered(lambda x: x.company_id == company)
elif document_type == 'purchase':
product_taxes = product.supplier_taxes_id.filtered(lambda x: x.company_id == company)
# Apply unit of measure.
if product_uom and product.uom_id != product_uom:
product_price_unit = product.uom_id._compute_price(product_price_unit, product_uom)
# Apply fiscal position.
if product_taxes and fiscal_position:
product_taxes_after_fp = fiscal_position.map_tax(product_taxes)
if set(product_taxes.ids) != set(product_taxes_after_fp.ids):
flattened_taxes_before_fp = product_taxes._origin.flatten_taxes_hierarchy()
if any(tax.price_include for tax in flattened_taxes_before_fp):
taxes_res = flattened_taxes_before_fp.compute_all(
product_price_unit,
quantity=1.0,
currency=currency,
product=product,
is_refund=is_refund_document,
)
product_price_unit = taxes_res['total_excluded']
flattened_taxes_after_fp = product_taxes_after_fp._origin.flatten_taxes_hierarchy()
if any(tax.price_include for tax in flattened_taxes_after_fp):
taxes_res = flattened_taxes_after_fp.compute_all(
product_price_unit,
quantity=1.0,
currency=currency,
product=product,
is_refund=is_refund_document,
handle_price_include=False,
)
for tax_res in taxes_res['taxes']:
tax = self.env['account.tax'].browse(tax_res['id'])
if tax.price_include:
product_price_unit += tax_res['amount']
# Apply currency rate.
if currency != product_currency:
product_price_unit = product_currency._convert(product_price_unit, currency, company, document_date)
return product_price_unit
@@ -1802,6 +1802,75 @@ class TestAccountMoveOutInvoiceOnchanges(AccountTestInvoicingCommon):
'amount_total': 260.01,
})
def test_out_invoice_line_tax_fixed_price_include_free_product(self):
''' Check that fixed tax include are correctly computed while the price_unit is 0
'''
fixed_tax_price_include = self.env['account.tax'].create({
'name': 'BEBAT 0.05',
'type_tax_use': 'sale',
'amount_type': 'fixed',
'amount': 0.05,
'price_include': True,
'include_base_amount': True,
})
invoice = self.env['account.move'].create({
'move_type': 'out_invoice',
'invoice_date': '2022-03-03',
'date': '2022-03-03',
'partner_id': self.partner_a.id,
'invoice_line_ids': [(0, 0, {
'name': 'Free product',
'price_unit': 0.0,
'account_id': self.company_data['default_account_revenue'].id,
'tax_ids': [(6, 0, fixed_tax_price_include.ids)],
})],
})
self.assertRecordValues(invoice, [{
'amount_untaxed': -0.05,
'amount_tax': 0.05,
'amount_total': 0.0,
}])
def test_out_invoice_line_taxes_fixed_price_include_free_product(self):
''' Check that fixed tax include are correctly computed while the price_unit is 0
'''
# please ensure this test remains consistent with
# test_free_product_and_price_include_fixed_tax in the sale module
fixed_tax_price_include_1 = self.env['account.tax'].create({
'name': 'BEBAT 0.05',
'type_tax_use': 'sale',
'amount_type': 'fixed',
'amount': 0.05,
'price_include': True,
'include_base_amount': True,
})
fixed_tax_price_include_2 = self.env['account.tax'].create({
'name': 'Recupel 0.25',
'type_tax_use': 'sale',
'amount_type': 'fixed',
'amount': 0.25,
'price_include': True,
'include_base_amount': True,
})
invoice = self.env['account.move'].create({
'move_type': 'out_invoice',
'invoice_date': '2022-03-03',
'date': '2022-03-03',
'partner_id': self.partner_a.id,
'invoice_line_ids': [(0, 0, {
'name': 'Free product',
'price_unit': 0.0,
'account_id': self.company_data['default_account_revenue'].id,
'tax_ids': [(6, 0, (fixed_tax_price_include_1 + fixed_tax_price_include_2).ids)],
})],
})
self.assertRecordValues(invoice, [{
'amount_untaxed': -0.30,
'amount_tax': 0.30,
'amount_total': 0.0,
}])
def test_out_invoice_create_refund(self):
self.invoice.action_post()
+4 -3
View File
@@ -149,14 +149,15 @@ class ResPartner(models.Model):
# This is for API pushes from external platforms where you have no control over VAT numbers.
if self.env.context.get('no_vat_validation'):
return
partners_with_vat = self.filtered('vat')
if not partners_with_vat:
return
if self.env.context.get('company_id'):
company = self.env['res.company'].browse(self.env.context['company_id'])
else:
company = self.env.company
eu_countries = self.env.ref('base.europe').country_ids
for partner in self:
if not partner.vat:
continue
for partner in partners_with_vat:
is_eu_country = partner.commercial_partner_id.country_id in eu_countries
if company.vat_check_vies and is_eu_country and partner.is_company:
# force full VIES online check
@@ -46,7 +46,7 @@
help="Worked hours last month">
<div class="o_field_widget o_stat_info">
<span class="o_stat_value">
<field name="hours_last_month_display"/> Hours
<field name="hours_last_month_display" widget="float_time"/> Hours
</span>
<span class="o_stat_text">
Last Month
@@ -100,7 +100,7 @@
help="Worked hours last month">
<div class="o_field_widget o_stat_info">
<span class="o_stat_value">
<field name="hours_last_month_display"/> Hours
<field name="hours_last_month_display" widget="float_time"/> Hours
</span>
<span class="o_stat_text">
Last Month
@@ -4,5 +4,6 @@
from . import chart_template
from . import eu_service_tax_rate
from . import eu_tax_map
from . import eu_tag_map
from . import res_company
from . import res_config_settings
+214
View File
@@ -0,0 +1,214 @@
# -*- coding: utf-8 -*-
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
"""
The EU_TAG_MAP answers the question: "which tag should I apply on the OSS tax repartition line?"
{
'fiscal_country_code': {
'invoice_base_tag': xml_id_of_the_tag or None,
'invoice_tax_tag': xml_id_of_the_tag or None,
'refund_base_tag': xml_id_of_the_tag or None,
'refund_tax_tag': xml_id_of_the_tag or None,
},
}
"""
EU_TAG_MAP = {
# Austria
'l10n_at.l10n_at_chart_template': {
'invoice_base_tag': None,
'invoice_tax_tag': None,
'refund_base_tag': None,
'refund_tax_tag': None,
},
# Belgium
'l10n_be.l10nbe_chart_template': {
'invoice_base_tag': 'l10n_be.tax_report_line_47',
'invoice_tax_tag': None,
'refund_base_tag': 'l10n_be.tax_report_line_49',
'refund_tax_tag': None,
},
# Bulgaria
'BG': {
'invoice_base_tag': None,
'invoice_tax_tag': None,
'refund_base_tag': None,
'refund_tax_tag': None,
},
# Croatia
'l10n_hr.l10n_hr_chart_template_rrif': {
'invoice_base_tag': None,
'invoice_tax_tag': None,
'refund_base_tag': None,
'refund_tax_tag': None,
},
# Cyprus
'CY': {
'invoice_base_tag': None,
'invoice_tax_tag': None,
'refund_base_tag': None,
'refund_tax_tag': None,
},
# Czech - Done in 13.0 - CoA not available yet
'l10n_cz.cz_chart_template': {
'invoice_base_tag': None,
'invoice_tax_tag': None,
'refund_base_tag': None,
'refund_tax_tag': None,
},
# Denmark
'l10n_dk.dk_chart_template': {
'invoice_base_tag': None,
'invoice_tax_tag': None,
'refund_base_tag': None,
'refund_tax_tag': None,
},
# Estonia - Done in 13.0 - CoA not available yet
'EE': {
'invoice_base_tag': None,
'invoice_tax_tag': None,
'refund_base_tag': None,
'refund_tax_tag': None,
},
# Finland
'l10n_fi.fi_chart_template': {
'invoice_base_tag': None,
'invoice_tax_tag': None,
'refund_base_tag': None,
'refund_tax_tag': None,
},
# France
'l10n_fr.l10n_fr_pcg_chart_template': {
'invoice_base_tag': None,
'invoice_tax_tag': None,
'refund_base_tag': None,
'refund_tax_tag': None,
},
# Germany SKR03
'l10n_de_skr03.l10n_de_chart_template': {
'invoice_base_tag': None,
'invoice_tax_tag': None,
'refund_base_tag': None,
'refund_tax_tag': None,
},
# Germany SKR04
'l10n_de_skr04.l10n_chart_de_skr04': {
'invoice_base_tag': None,
'invoice_tax_tag': None,
'refund_base_tag': None,
'refund_tax_tag': None,
},
# Greece
'l10n_gr.l10n_gr_chart_template': {
'invoice_base_tag': None,
'invoice_tax_tag': None,
'refund_base_tag': None,
'refund_tax_tag': None,
},
# Hungary
'l10n_hu.hungarian_chart_template': {
'invoice_base_tag': None,
'invoice_tax_tag': None,
'refund_base_tag': None,
'refund_tax_tag': None,
},
# Ireland
'l10n_ie.l10n_ie': {
'invoice_base_tag': None,
'invoice_tax_tag': None,
'refund_base_tag': None,
'refund_tax_tag': None,
},
# Italy
'l10n_it.l10n_it_chart_template_generic': {
'invoice_base_tag': None,
'invoice_tax_tag': None,
'refund_base_tag': None,
'refund_tax_tag': None,
},
# Latvia
'LV': {
'invoice_base_tag': None,
'invoice_tax_tag': None,
'refund_base_tag': None,
'refund_tax_tag': None,
},
# Lithuania
'l10n_lt.account_chart_template_lithuania': {
'invoice_base_tag': None,
'invoice_tax_tag': None,
'refund_base_tag': None,
'refund_tax_tag': None,
},
# Luxembourg
'l10n_lu.lu_2011_chart_1': {
'invoice_base_tag': None,
'invoice_tax_tag': None,
'refund_base_tag': None,
'refund_tax_tag': None,
},
# Malta - Done in 13.0 - CoA not available yet
'MT': {
'invoice_base_tag': None,
'invoice_tax_tag': None,
'refund_base_tag': None,
'refund_tax_tag': None,
},
# Netherlands
'l10n_nl.l10nnl_chart_template': {
'invoice_base_tag': None,
'invoice_tax_tag': None,
'refund_base_tag': None,
'refund_tax_tag': None,
},
# Poland
'l10n_pl.pl_chart_template': {
'invoice_base_tag': None,
'invoice_tax_tag': None,
'refund_base_tag': None,
'refund_tax_tag': None,
},
# Portugal
'l10n_pt.pt_chart_template': {
'invoice_base_tag': None,
'invoice_tax_tag': None,
'refund_base_tag': None,
'refund_tax_tag': None,
},
# Romania
'l10n_ro.ro_chart_template': {
'invoice_base_tag': None,
'invoice_tax_tag': None,
'refund_base_tag': None,
'refund_tax_tag': None,
},
# Slovakia - Done in 13.0 - CoA not available yet
'l10n_sk.sk_chart_template': {
'invoice_base_tag': None,
'invoice_tax_tag': None,
'refund_base_tag': None,
'refund_tax_tag': None,
},
# Slovenia
'l10n_si.gd_chart': {
'invoice_base_tag': None,
'invoice_tax_tag': None,
'refund_base_tag': None,
'refund_tax_tag': None,
},
# Spain
'l10n_es.account_chart_template_common': {
'invoice_base_tag': None,
'invoice_tax_tag': None,
'refund_base_tag': None,
'refund_tax_tag': None,
},
# Sweden
'l10n_se.l10nse_chart_template': {
'invoice_base_tag': None,
'invoice_tax_tag': None,
'refund_base_tag': None,
'refund_tax_tag': None,
},
}
+45 -21
View File
@@ -3,6 +3,7 @@
from flectra import api, fields, models, _
from .eu_tax_map import EU_TAX_MAP
from .eu_tag_map import EU_TAG_MAP
class Company(models.Model):
@@ -28,19 +29,19 @@ class Company(models.Model):
taxes = self.env['account.tax'].search([
('type_tax_use', '=', 'sale'),
('amount_type', '=', 'percent'),
('company_id','=', company.id),
('company_id', '=', company.id),
('tax_group_id', 'not in', oss_tax_groups.mapped('res_id'))])
for country in eu_countries - company.account_tax_fiscal_country_id:
for destination_country in eu_countries - company.account_tax_fiscal_country_id:
mapping = []
fpos = self.env['account.fiscal.position'].search([
('country_id', '=', country.id),
('country_id', '=', destination_country.id),
('company_id', '=', company.id),
('auto_apply', '=', True),
('vat_required', '=', False)], limit=1)
if not fpos:
fpos = self.env['account.fiscal.position'].create({
'name': 'OSS B2C %s' % country.name,
'country_id': country.id,
'name': f'OSS B2C {destination_country.name}',
'country_id': destination_country.id,
'company_id': company.id,
'auto_apply': True,
})
@@ -48,25 +49,26 @@ class Company(models.Model):
foreign_taxes = {tax.amount: tax for tax in fpos.tax_ids.tax_dest_id if tax.amount_type == 'percent'}
for domestic_tax in taxes:
tax_amount = EU_TAX_MAP.get((company.account_tax_fiscal_country_id.code, domestic_tax.amount, country.code), False)
tax_amount = EU_TAX_MAP.get((company.account_tax_fiscal_country_id.code, domestic_tax.amount, destination_country.code), False)
if tax_amount and domestic_tax not in fpos.tax_ids.tax_src_id:
if not foreign_taxes.get(tax_amount, False):
if not self.env['ir.model.data'].xmlid_to_object('l10n_eu_service.oss_tax_group_%s' % str(tax_amount).replace('.','_')):
oss_tax_group_local_xml_id = f"oss_tax_group_{str(tax_amount).replace('.', '_')}"
if not self.env['ir.model.data'].xmlid_to_object(f"l10n_eu_service.{oss_tax_group_local_xml_id}"):
self.env['ir.model.data'].create({
'name': 'oss_tax_group_%s' % str(tax_amount).replace('.','_'),
'name': oss_tax_group_local_xml_id,
'module': 'l10n_eu_service',
'model': 'account.tax.group',
'res_id': self.env['account.tax.group'].create({'name': 'OSS %s%%' % tax_amount}).id,
'res_id': self.env['account.tax.group'].create({'name': f'OSS {tax_amount}%'}).id,
'noupdate': True,
})
})
foreign_taxes[tax_amount] = self.env['account.tax'].create({
'name': '%(rate)s%% %(country)s %(label)s' % {'rate': tax_amount, 'country': country.code, 'label': country.vat_label},
'name': f'{tax_amount}% {destination_country.code} {destination_country.vat_label}',
'amount': tax_amount,
'invoice_repartition_line_ids': invoice_repartition_lines,
'refund_repartition_line_ids': refund_repartition_lines,
'type_tax_use': 'sale',
'description': "%s%%" % tax_amount,
'tax_group_id': self.env.ref('l10n_eu_service.oss_tax_group_%s' % str(tax_amount).replace('.','_')).id,
'description': f"{tax_amount}%",
'tax_group_id': self.env.ref(f'l10n_eu_service.{oss_tax_group_local_xml_id}').id,
'sequence': 1000,
'company_id': company.id,
})
@@ -79,15 +81,19 @@ class Company(models.Model):
def _get_repartition_lines_oss(self):
self.ensure_one()
defaults = self.env['account.tax'].with_company(self).default_get(['invoice_repartition_line_ids', 'refund_repartition_line_ids'])
oss_account = self._get_oss_account()
if oss_account:
defaults['invoice_repartition_line_ids'][1][2]['account_id'] = oss_account.id
defaults['refund_repartition_line_ids'][1][2]['account_id'] = oss_account.id
oss_account, oss_tags = self._get_oss_account(), self._get_oss_tags()
base_line, tax_line, vals = 0, 1, 2
for doc_type in 'invoice', 'refund':
if oss_account:
defaults[f'{doc_type}_repartition_line_ids'][tax_line][vals]['account_id'] = oss_account.id
if oss_tags:
defaults[f'{doc_type}_repartition_line_ids'][base_line][vals]['tag_ids'] += [(4, oss_tags[f'{doc_type}_base_tag'].id, 0)] if oss_tags[f'{doc_type}_base_tag'] else []
defaults[f'{doc_type}_repartition_line_ids'][tax_line][vals]['tag_ids'] += [(4, oss_tags[f'{doc_type}_tax_tag'].id, 0)] if oss_tags[f'{doc_type}_tax_tag'] else []
return defaults['invoice_repartition_line_ids'], defaults['refund_repartition_line_ids']
def _get_oss_account(self):
self.ensure_one()
if not self.env['ir.model.data'].xmlid_to_object('l10n_eu_service.oss_tax_account_company_%s' % self.id):
if not self.env['ir.model.data'].xmlid_to_object(f'l10n_eu_service.oss_tax_account_company_{self.id}'):
sales_tax_accounts = self.env['account.tax'].search([
('type_tax_use', '=', 'sale'),
('company_id', '=', self.id)
@@ -96,16 +102,34 @@ class Company(models.Model):
return False
new_code = self.env['account.account']._search_new_account_code(self, len(sales_tax_accounts[0].code), sales_tax_accounts[0].code[:-2])
oss_account = self.env['account.account'].create({
'name': '%s OSS' % sales_tax_accounts[0].name,
'name': f'{sales_tax_accounts[0].name} OSS',
'code': new_code,
'user_type_id': sales_tax_accounts[0].user_type_id.id,
'company_id': self.id,
})
self.env['ir.model.data'].create({
'name': 'oss_tax_account_company_%s' % self.id,
'name': f'oss_tax_account_company_{self.id}',
'module': 'l10n_eu_service',
'model': 'account.account',
'res_id': oss_account.id,
'noupdate': True,
})
return self.env.ref('l10n_eu_service.oss_tax_account_company_%s' % self.id)
return self.env.ref(f'l10n_eu_service.oss_tax_account_company_{self.id}')
def _get_oss_tags(self):
[chart_template_xml_id] = self.chart_template_id.get_xml_id().values()
tag_for_country = EU_TAG_MAP.get(chart_template_xml_id, {
'invoice_base_tag': None,
'invoice_tax_tag': None,
'refund_base_tag': None,
'refund_tax_tag': None,
})
return {
repartition_line_key: (
self.env.ref(tag_xml_id).tag_ids.filtered(lambda t: not t.tax_negate)
if tag_xml_id
else None
)
for repartition_line_key, tag_xml_id in tag_for_country.items()
}
+3
View File
@@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
from . import test_oss
+77
View File
@@ -0,0 +1,77 @@
# -*- coding: utf-8 -*-
from flectra.addons.l10n_eu_service.models.eu_tag_map import EU_TAG_MAP
from flectra.addons.account.tests.common import AccountTestInvoicingCommon
from flectra.tests import tagged
@tagged('post_install', 'post_install_l10n', '-at_install')
class TestOSSBelgium(AccountTestInvoicingCommon):
@classmethod
def setUpClass(self, chart_template_ref='l10n_be.l10nbe_chart_template'):
try:
super().setUpClass(chart_template_ref=chart_template_ref)
except ValueError as e:
if e.args[0] == "External ID not found in the system: l10n_be.l10nbe_chart_template":
self.skipTest(self, reason="Belgian CoA is required for this testSuite but l10n_be isn't installed")
else:
raise e
self.company_data['company'].country_id = self.env.ref('base.be')
self.company_data['company']._map_eu_taxes()
def test_country_tag_from_belgium(self):
# get an eu country which isn't the current one:
another_eu_country_code = (self.env.ref('base.europe').country_ids - self.company_data['company'].country_id)[0].code
tax_oss = self.env['account.tax'].search([('name', 'ilike', f'%{another_eu_country_code}%')], limit=1)
for doc_type, report_line_xml_id in (
("invoice", "l10n_be.tax_report_line_47"),
("refund", "l10n_be.tax_report_line_49"),
):
with self.subTest(doc_type=doc_type, report_line_xml_id=report_line_xml_id):
oss_tag_id = tax_oss[f"{doc_type}_repartition_line_ids"]\
.filtered(lambda x: x.repartition_type == 'base')\
.tag_ids
expected_tag_id = self.env.ref(report_line_xml_id)\
.tag_ids\
.filtered(lambda t: not t.tax_negate)
self.assertIn(expected_tag_id, oss_tag_id, f"{doc_type} tag from Belgian CoA not correctly linked")
@tagged('post_install', 'post_install_l10n', '-at_install')
class TestOSSUSA(AccountTestInvoicingCommon):
@classmethod
def setUpClass(self, chart_template_ref=None):
super().setUpClass(chart_template_ref=chart_template_ref)
self.company_data['company'].country_id = self.env.ref('base.us')
self.company_data['company']._map_eu_taxes()
def test_no_oss_tax(self):
# get an eu country which isn't the current one:
another_eu_country_code = (self.env.ref('base.europe').country_ids - self.company_data['company'].country_id)[0].code
tax_oss = self.env['account.tax'].search([('name', 'ilike', f'%{another_eu_country_code}%')], limit=1)
self.assertFalse(len(tax_oss), "OSS tax shouldn't be instanced on a US company")
@tagged('post_install', 'post_install_l10n', '-at_install')
class TestOSSMap(AccountTestInvoicingCommon):
def test_oss_eu_tag_map(self):
""" Checks that the xml_id referenced in the map are correct.
In case of failure display the couple (chart_template_xml_id, tax_report_line_xml_id).
The test doesn't fail for unreferenced char_template or unreferenced tax_report_line.
"""
chart_templates = self.env['account.chart.template'].search([])
for chart_template in chart_templates:
[chart_template_xml_id] = chart_template.get_xml_id().values()
oss_tags = EU_TAG_MAP.get(chart_template_xml_id, {})
for tax_report_line_xml_id in filter(lambda d: d, oss_tags.values()):
with self.subTest(chart_template_xml_id=chart_template_xml_id, tax_report_line_xml_id=tax_report_line_xml_id):
tag = self.env.ref(tax_report_line_xml_id, raise_if_not_found=False)
self.assertIsNotNone(tag, f"The following xml_id is incorrect in EU_TAG_MAP.py:{tax_report_line_xml_id}")
+1 -1
View File
@@ -19,7 +19,7 @@ class LunchController(http.Controller):
lines = self._get_current_lines(user.id)
if lines:
lines = [{'id': line.id,
'product': (line.product_id.id, line.product_id.name, float_repr(float_round(line.product_id.price, 2), 2)),
'product': (line.product_id.id, line.product_id.name, float_repr(float_round(line.price, 2), 2)),
'toppings': [(topping.name, float_repr(float_round(topping.price, 2), 2))
for topping in line.topping_ids_1 | line.topping_ids_2 | line.topping_ids_3],
'quantity': line.quantity,
+1 -1
View File
@@ -96,7 +96,7 @@ class MailBlackList(models.Model):
def mail_action_blacklist_remove(self):
return {
'name': 'Are you sure you want to unblacklist this Email Address?',
'name': _('Are you sure you want to unblacklist this Email Address?'),
'type': 'ir.actions.act_window',
'view_mode': 'form',
'res_model': 'mail.blacklist.remove',
+2 -2
View File
@@ -116,11 +116,11 @@ class MailBlackListMixin(models.AbstractModel):
can_access = self.env['mail.blacklist'].check_access_rights('write', raise_exception=False)
if can_access:
return {
'name': 'Are you sure you want to unblacklist this Email Address?',
'name': _('Are you sure you want to unblacklist this Email Address?'),
'type': 'ir.actions.act_window',
'view_mode': 'form',
'res_model': 'mail.blacklist.remove',
'target': 'new',
}
else:
raise AccessError("You do not have the access right to unblacklist emails. Please contact your administrator.")
raise AccessError(_("You do not have the access right to unblacklist emails. Please contact your administrator."))
@@ -637,6 +637,9 @@
year = parts[3] || '';
if (year.length > 0) {
sep = ' / ';
// Force year to 2 digits
if (year.length === 4 )
year = year.substr(-2);
} else if (sep === ' /') {
mon = mon.substring(0, 1);
sep = '';
+2 -2
View File
@@ -30,8 +30,8 @@ class AcquirerSips(models.Model):
provider = fields.Selection(selection_add=[('sips', 'Sips')], ondelete={'sips': 'set default'})
sips_merchant_id = fields.Char('Merchant ID', required_if_provider='sips', groups='base.group_user')
sips_secret = fields.Char('Secret Key', size=64, required_if_provider='sips', groups='base.group_user')
sips_test_url = fields.Char("Test url", required_if_provider='sips', default='https://payment-webinit.simu.sips-atos.com/paymentInit')
sips_prod_url = fields.Char("Production url", required_if_provider='sips', default='https://payment-webinit.sips-atos.com/paymentInit')
sips_test_url = fields.Char("Test url", required_if_provider='sips', default='https://payment-webinit.simu.sips-services.com/paymentInit')
sips_prod_url = fields.Char("Production url", required_if_provider='sips', default='https://payment-webinit.sips-services.com/paymentInit')
sips_version = fields.Char("Interface Version", required_if_provider='sips', default='HP_2.31')
sips_key_version = fields.Integer("Secret Key Version", required_if_provider='sips', default=2)
+18 -2
View File
@@ -1716,7 +1716,15 @@ class SaleOrderLine(models.Model):
self._compute_tax_id()
if self.order_id.pricelist_id and self.order_id.partner_id:
vals['price_unit'] = self.env['account.tax']._fix_tax_included_price_company(self._get_display_price(product), product.taxes_id, self.tax_id, self.company_id)
vals['price_unit'] = product._get_tax_included_unit_price(
self.company_id,
self.order_id.currency_id,
self.order_id.date_order,
'sale',
fiscal_position=self.order_id.fiscal_position_id,
product_price_unit=self._get_display_price(product),
product_currency=self.currency_id
)
self.update(vals)
title = False
@@ -1749,7 +1757,15 @@ class SaleOrderLine(models.Model):
uom=self.product_uom.id,
fiscal_position=self.env.context.get('fiscal_position')
)
self.price_unit = self.env['account.tax']._fix_tax_included_price_company(self._get_display_price(product), product.taxes_id, self.tax_id, self.company_id)
self.price_unit = product._get_tax_included_unit_price(
self.company_id,
self.order_id.currency_id,
self.order_id.date_order,
'sale',
fiscal_position=self.order_id.fiscal_position_id,
product_price_unit=self._get_display_price(product),
product_currency=self.currency_id
)
def name_get(self):
result = []
+56
View File
@@ -65,6 +65,62 @@ class TestOnchangeProductId(TransactionCase):
# Check the unit price of SO line
self.assertEqual(100, sale_order.order_line[0].price_unit, "The included tax must be subtracted to the price")
def test_fiscalposition_application(self):
"""Test application of a fiscal position mapping
price included to price included tax
"""
uom = self.product_uom_model.search([('name', '=', 'Units')])
pricelist = self.pricelist_model.search([('name', '=', 'Public Pricelist')])
partner = self.res_partner_model.create({
'name': "George"
})
tax_include_src = self.tax_model.create({
'name': "Include tax",
'amount': 21.00,
'price_include': True,
})
tax_include_dst = self.tax_model.create({
'name': "Exclude tax",
'amount': 6.00,
'price_include': True,
})
product_tmpl = self.product_tmpl_model.create({
'name': "Voiture",
'list_price': 121,
'taxes_id': [(6, 0, [tax_include_src.id])]
})
product_product = product_tmpl.product_variant_id
fpos = self.fiscal_position_model.create({
'name': "fiscal position",
'sequence': 1
})
fpos_tax = self.fiscal_position_tax_model.create({
'position_id' :fpos.id,
'tax_src_id': tax_include_src.id,
'tax_dest_id': tax_include_dst.id
})
# Create the SO with one SO line and apply a pricelist and fiscal position on it
order_form = Form(self.env['sale.order'].with_context(tracking_disable=True))
order_form.partner_id = partner
order_form.pricelist_id = pricelist
order_form.fiscal_position_id = fpos
with order_form.order_line.new() as line:
line.name = product_product.name
line.product_id = product_product
line.product_uom_qty = 1.0
line.product_uom = uom
sale_order = order_form.save()
# Check the unit price of SO line
self.assertRecordValues(sale_order.order_line, [{'price_unit': 106}])
def test_pricelist_application(self):
""" Test different prices are correctly applied based on dates """
support_product = self.env['product.product'].create({
+40
View File
@@ -583,6 +583,46 @@ class TestSaleOrder(TestSaleCommon):
self.assertEqual(line.price_subtotal, 17527.41)
self.assertEqual(line.untaxed_amount_to_invoice, line.price_subtotal)
def test_free_product_and_price_include_fixed_tax(self):
""" Check that fixed tax include are correctly computed while the price_unit is 0
"""
# please ensure this test remains consistent with
# test_out_invoice_line_onchange_2_taxes_fixed_price_include_free_product in account module
sale_order = self.env['sale.order'].create({
'partner_id': self.partner_a.id,
'order_line': [(0, 0, {
'product_id': self.company_data['product_order_no'].id,
'product_uom_qty': 1,
'price_unit': 0.0,
})]
})
sale_order.action_confirm()
line = sale_order.order_line
line.tax_id = [
(0, 0, {
'name': 'BEBAT 0.05',
'type_tax_use': 'sale',
'amount_type': 'fixed',
'amount': 0.05,
'price_include': True,
'include_base_amount': True,
}),
(0, 0, {
'name': 'Recupel 0.25',
'type_tax_use': 'sale',
'amount_type': 'fixed',
'amount': 0.25,
'price_include': True,
'include_base_amount': True,
}),
]
sale_order.action_confirm()
self.assertRecordValues(sale_order, [{
'amount_untaxed': -0.30,
'amount_tax': 0.30,
'amount_total': 0.0,
}])
def test_sol_name_search(self):
# Shouldn't raise
self.env['sale.order']._search([('order_line', 'ilike', 'acoustic')])
@@ -55,7 +55,7 @@
</field>
</record>
<record model="ir.ui.view" id="sale_margin_sale_order_pivot">
<record model="ir.ui.view" id="sale_margin_sale_order_graph">
<field name="name">sale.order.margin.view.graph</field>
<field name="model">sale.order</field>
<field name="inherit_id" ref="sale.view_sale_order_graph"/>
+9 -5
View File
@@ -40,7 +40,7 @@ class Survey(http.Controller):
], limit=1)
return survey_sudo, answer_sudo
def _check_validity(self, survey_token, answer_token, ensure_token=True):
def _check_validity(self, survey_token, answer_token, ensure_token=True, check_partner=True):
""" Check survey is open and can be taken. This does not checks for
security rules, only functional / business rules. It returns a string key
allowing further manipulation of validity issues
@@ -57,6 +57,9 @@ class Survey(http.Controller):
:param ensure_token: whether user input existence based on given access token
should be enforced or not, depending on the route requesting a token or
allowing external world calls;
:param check_partner: Whether we must check that the partner associated to the target
answer corresponds to the active user.
"""
survey_sudo, answer_sudo = self._fetch_from_access_token(survey_token, answer_token)
@@ -80,7 +83,7 @@ class Survey(http.Controller):
if (not survey_sudo.page_ids and survey_sudo.questions_layout == 'page_per_section') or not survey_sudo.question_ids:
return 'survey_void'
if answer_sudo:
if answer_sudo and check_partner:
if request.env.user._is_public() and answer_sudo.partner_id:
# answers from public user should not have any partner_id; this indicates probably a cookie issue
return 'answer_wrong_user'
@@ -93,16 +96,17 @@ class Survey(http.Controller):
return True
def _get_access_data(self, survey_token, answer_token, ensure_token=True):
def _get_access_data(self, survey_token, answer_token, ensure_token=True, check_partner=True):
""" Get back data related to survey and user input, given the ID and access
token provided by the route.
: param ensure_token: whether user input existence should be enforced or not(see ``_check_validity``)
: param check_partner: whether the partner of the target answer should be checked (see ``_check_validity``)
"""
survey_sudo, answer_sudo = request.env['survey.survey'].sudo(), request.env['survey.user_input'].sudo()
has_survey_access, can_answer = False, False
validity_code = self._check_validity(survey_token, answer_token, ensure_token=ensure_token)
validity_code = self._check_validity(survey_token, answer_token, ensure_token=ensure_token, check_partner=check_partner)
if validity_code != 'survey_wrong':
survey_sudo, answer_sudo = self._fetch_from_access_token(survey_token, answer_token)
try:
@@ -569,7 +573,7 @@ class Survey(http.Controller):
def survey_print(self, survey_token, review=False, answer_token=None, **post):
'''Display an survey in printable view; if <answer_token> is set, it will
grab the answers of the user_input_id that has <answer_token>.'''
access_data = self._get_access_data(survey_token, answer_token, ensure_token=False)
access_data = self._get_access_data(survey_token, answer_token, ensure_token=False, check_partner=False)
if access_data['validity_code'] is not True and (
access_data['has_survey_access'] or
access_data['validity_code'] not in ['token_required', 'survey_closed', 'survey_void']):