[ADD] l10n_eu_service

As of January 1rst, 2015, telecommunications, broadcasting
and electronic services sold within the European Union
have to be always taxed in the country where the customer
belongs. In order to simplify the application of this EU
directive, the Mini One Stop Shop (MOSS) registration scheme
allows businesses to make a unique tax declaration.

This module makes it possible by helping with the creation
of the required EU fiscal positions and taxes in order to
automatically apply and record the required taxes.

This module installs a wizard to help setup fiscal positions
and taxes for selling electronic services inside EU.

The wizard lets you select:
 - the EU countries to which you are selling these
   services
 - your national VAT tax for services, to be mapped
   to the target country's tax
 - optionally: a template fiscal position, in order
   to copy the account mapping. Should be your
   existing B2C Intra-EU fiscal position. (defaults
   to no account mapping)
 - optionally: an account to use for collecting the
   tax amounts (defaults to the account used by your
   national VAT tax for services)

It creates the corresponding fiscal positions and taxes,
automatically applicable for EU sales with a customer
in the selected countries.
The wizard can be run again for adding more countries.

References
++++++++++
- Directive 2008/8/EC
- Council Implementing Regulation (EU) No 1042/2013

Closes #5229
This commit is contained in:
Goffin Simon 2015-02-06 08:39:27 +01:00 committed by Olivier Dony
parent af94e5af6b
commit 60a8f894f9
10 changed files with 567 additions and 0 deletions

View File

@ -0,0 +1,2 @@
import wizard
import models

View File

@ -0,0 +1,85 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# Odoo, Open Source Business Applications
# Copyright (C) 2015 Odoo S.A. <http://www.odoo.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
{
'name': 'EU Mini One Stop Shop (MOSS)',
'version': '1.0',
'author': 'Odoo SA',
'website': 'http://www.odoo.com',
'category': '',
'description': """
EU Mini One Stop Shop (MOSS) VAT for telecommunications, broadcasting and electronic services
=============================================================================================
As of January 1rst, 2015, telecommunications, broadcasting
and electronic services sold within the European Union
have to be always taxed in the country where the customer
belongs. In order to simplify the application of this EU
directive, the Mini One Stop Shop (MOSS) registration scheme
allows businesses to make a unique tax declaration.
This module makes it possible by helping with the creation
of the required EU fiscal positions and taxes in order to
automatically apply and record the required taxes.
This module installs a wizard to help setup fiscal positions
and taxes for selling electronic services inside EU.
The wizard lets you select:
- the EU countries to which you are selling these
services
- your national VAT tax for services, to be mapped
to the target country's tax
- optionally: a template fiscal position, in order
to copy the account mapping. Should be your
existing B2C Intra-EU fiscal position. (defaults
to no account mapping)
- optionally: an account to use for collecting the
tax amounts (defaults to the account used by your
national VAT tax for services)
It creates the corresponding fiscal positions and taxes,
automatically applicable for EU sales with a customer
in the selected countries.
The wizard can be run again for adding more countries.
The wizard creates a separate Chart of Taxes for collecting the
VAT amounts of the MOSS declaration, so extracting the MOSS
data should be easy.
Look for a Chart of Taxes named "EU MOSS VAT Chart" in the
Taxes Report menu (Generic Accounting Report).
References
++++++++++
- Directive 2008/8/EC
- Council Implementing Regulation (EU) No 1042/2013
""",
'depends': ['account_accountant'],
'data': [
'security/ir.model.access.csv',
'wizard/wizard.xml',
'wizard/l10n_eu_service.service_tax_rate.csv'
],
'test': [],
'demo': [],
'auto_install': False,
'installable': True,
}

View File

@ -0,0 +1,184 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * l10n_eu_service
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 8.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-02-17 14:34+0000\n"
"PO-Revision-Date: 2015-02-17 14:34+0000\n"
"Last-Translator: <>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: l10n_eu_service
#: field:l10n_eu_service.wizard,done_country_ids:0
msgid "Already Supported"
msgstr ""
#. module: l10n_eu_service
#: code:addons/l10n_eu_service/wizard/wizard.py:128
#, python-format
msgid "Base - VAT for EU Services to %(country_name)s"
msgstr ""
#. module: l10n_eu_service
#: view:l10n_eu_service.wizard:l10n_eu_service.eu_service_view
msgid "Based on the options selected below, this wizard will create one\n"
" fiscal position mapping for each EU country to which you are selling\n"
" services.\n"
" Each fiscal position will automatically map your national VAT tax for\n"
" services to the corresponding VAT tax in the country your customer\n"
" belongs to."
msgstr ""
#. module: l10n_eu_service
#: view:l10n_eu_service.wizard:l10n_eu_service.eu_service_view
msgid "Cancel"
msgstr ""
#. module: l10n_eu_service
#: field:l10n_eu_service.wizard,chart_id:0
msgid "Chart of Accounts"
msgstr ""
#. module: l10n_eu_service
#: field:l10n_eu_service.service_tax_rate,country_id:0
msgid "Country"
msgstr ""
#. module: l10n_eu_service
#: view:l10n_eu_service.wizard:l10n_eu_service.eu_service_view
msgid "Create Fiscal Positions and Taxes"
msgstr ""
#. module: l10n_eu_service
#: model:ir.model,name:l10n_eu_service.model_l10n_eu_service_wizard
msgid "Create fiscal positions for EU Service VAT"
msgstr ""
#. module: l10n_eu_service
#: field:l10n_eu_service.service_tax_rate,create_uid:0
#: field:l10n_eu_service.wizard,create_uid:0
msgid "Created by"
msgstr ""
#. module: l10n_eu_service
#: field:l10n_eu_service.service_tax_rate,create_date:0
#: field:l10n_eu_service.wizard,create_date:0
msgid "Created on"
msgstr ""
#. module: l10n_eu_service
#: view:l10n_eu_service.wizard:l10n_eu_service.eu_service_view
msgid "Current EU B2C Fiscal Position, if any"
msgstr ""
#. module: l10n_eu_service
#: field:l10n_eu_service.wizard,todo_country_ids:0
msgid "EU Customers From"
msgstr ""
#. module: l10n_eu_service
#: code:addons/l10n_eu_service/wizard/wizard.py:110
#, python-format
msgid "EU MOSS VAT Chart - %(company)s"
msgstr ""
#. module: l10n_eu_service
#: field:l10n_eu_service.wizard,fiscal_position_id:0
msgid "Fiscal Position"
msgstr ""
#. module: l10n_eu_service
#: field:l10n_eu_service.service_tax_rate,id:0
#: field:l10n_eu_service.wizard,id:0
msgid "ID"
msgstr ""
#. module: l10n_eu_service
#: code:addons/l10n_eu_service/wizard/wizard.py:161
#, python-format
msgid "Intra-EU B2C in %(country_name)s"
msgstr ""
#. module: l10n_eu_service
#: view:l10n_eu_service.wizard:l10n_eu_service.eu_service_view
msgid "Keep empty to use current Service VAT account"
msgstr ""
#. module: l10n_eu_service
#: field:l10n_eu_service.service_tax_rate,write_uid:0
#: field:l10n_eu_service.wizard,write_uid:0
msgid "Last Updated by"
msgstr ""
#. module: l10n_eu_service
#: field:l10n_eu_service.service_tax_rate,write_date:0
#: field:l10n_eu_service.wizard,write_date:0
msgid "Last Updated on"
msgstr ""
#. module: l10n_eu_service
#: help:l10n_eu_service.wizard,account_collected_id:0
msgid "Optional account to use for collecting tax amounts when selling services in each EU country selected below. If not set, the current collecting account of your Service VAT will be used."
msgstr ""
#. module: l10n_eu_service
#: help:l10n_eu_service.wizard,fiscal_position_id:0
msgid "Optional fiscal position to use as template for general account mapping. Should usually be your current Intra-EU B2B fiscal position. If not set, no general account mapping will be configured for EU fiscal positions."
msgstr ""
#. module: l10n_eu_service
#: help:l10n_eu_service.wizard,tax_id:0
msgid "Select your current VAT tax for services. This is the tax that will be mapped to the corresponding VAT tax in each EU country selected below."
msgstr ""
#. module: l10n_eu_service
#: field:l10n_eu_service.wizard,tax_id:0
msgid "Service VAT"
msgstr ""
#. module: l10n_eu_service
#: view:account.config.settings:l10n_eu_service.view_account_config_settings_inherit
#: model:ir.actions.act_window,name:l10n_eu_service.action_eu_service
#: view:l10n_eu_service.wizard:l10n_eu_service.eu_service_view
msgid "Setup EU MOSS Taxes"
msgstr ""
#. module: l10n_eu_service
#: field:l10n_eu_service.wizard,account_collected_id:0
msgid "Tax Collection Account"
msgstr ""
#. module: l10n_eu_service
#: code:addons/l10n_eu_service/wizard/wizard.py:36
#, python-format
msgid "The Europe country group cannot be found. Please update the base module."
msgstr ""
#. module: l10n_eu_service
#: field:l10n_eu_service.service_tax_rate,rate:0
msgid "VAT Rate"
msgstr ""
#. module: l10n_eu_service
#: code:addons/l10n_eu_service/wizard/wizard.py:132
#, python-format
msgid "VAT for EU Services to %(country_name)s"
msgstr ""
#. module: l10n_eu_service
#: view:l10n_eu_service.wizard:l10n_eu_service.eu_service_view
msgid "You can use the wizard again later to add more countries."
msgstr ""
#. module: l10n_eu_service
#: view:l10n_eu_service.wizard:l10n_eu_service.eu_service_view
msgid "or"
msgstr ""

View File

@ -0,0 +1 @@
import eu_service_tax_rate

View File

@ -0,0 +1,7 @@
from openerp import fields, models, api
class service_tax_rate(models.Model):
_name = "l10n_eu_service.service_tax_rate"
country_id = fields.Many2one('res.country', string='Country')
rate = fields.Float(string="VAT Rate")

View File

@ -0,0 +1,3 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_l10n_eu_service_service_tax_rate,access_l10n_eu_service_service_tax_rate,model_l10n_eu_service_service_tax_rate,,1,0,0,0
access_l10n_eu_service_service_tax_rate,access_l10n_eu_service_service_tax_rate,model_l10n_eu_service_service_tax_rate,account.group_account_manager,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_l10n_eu_service_service_tax_rate access_l10n_eu_service_service_tax_rate model_l10n_eu_service_service_tax_rate 1 0 0 0
3 access_l10n_eu_service_service_tax_rate access_l10n_eu_service_service_tax_rate model_l10n_eu_service_service_tax_rate account.group_account_manager 1 1 1 1

View File

@ -0,0 +1 @@
import wizard

View File

@ -0,0 +1,30 @@
id,country_id:id,rate
eu_rate_at,base.at,0.21
eu_rate_be,base.be,0.21
eu_rate_bg,base.bg,0.20
eu_rate_hr,base.hr,0.25
eu_rate_cy,base.cy,0.21
eu_rate_cz,base.cz,0.21
eu_rate_dk,base.dk,0.25
eu_rate_ee,base.ee,0.20
eu_rate_fi,base.fi,0.24
eu_rate_fr,base.fr,0.20
eu_rate_de,base.de,0.19
eu_rate_gr,base.gr,0.23
eu_rate_hu,base.hu,0.27
eu_rate_ie,base.ie,0.23
eu_rate_it,base.it,0.22
eu_rate_lv,base.lv,0.21
eu_rate_lt,base.lt,0.21
eu_rate_lu,base.lu,0.17
eu_rate_mt,base.mt,0.18
eu_rate_nl,base.nl,0.21
eu_rate_pl,base.pl,0.23
eu_rate_pt,base.pt,0.23
eu_rate_ro,base.ro,0.24
eu_rate_pt,base.pt,0.23
eu_rate_sk,base.sk,0.20
eu_rate_si,base.si,0.22
eu_rate_es,base.es,0.21
eu_rate_se,base.se,0.24
eu_rate_uk,base.uk,0.20
1 id country_id:id rate
2 eu_rate_at base.at 0.21
3 eu_rate_be base.be 0.21
4 eu_rate_bg base.bg 0.20
5 eu_rate_hr base.hr 0.25
6 eu_rate_cy base.cy 0.21
7 eu_rate_cz base.cz 0.21
8 eu_rate_dk base.dk 0.25
9 eu_rate_ee base.ee 0.20
10 eu_rate_fi base.fi 0.24
11 eu_rate_fr base.fr 0.20
12 eu_rate_de base.de 0.19
13 eu_rate_gr base.gr 0.23
14 eu_rate_hu base.hu 0.27
15 eu_rate_ie base.ie 0.23
16 eu_rate_it base.it 0.22
17 eu_rate_lv base.lv 0.21
18 eu_rate_lt base.lt 0.21
19 eu_rate_lu base.lu 0.17
20 eu_rate_mt base.mt 0.18
21 eu_rate_nl base.nl 0.21
22 eu_rate_pl base.pl 0.23
23 eu_rate_pt base.pt 0.23
24 eu_rate_ro base.ro 0.24
25 eu_rate_pt base.pt 0.23
26 eu_rate_sk base.sk 0.20
27 eu_rate_si base.si 0.22
28 eu_rate_es base.es 0.21
29 eu_rate_se base.se 0.24
30 eu_rate_uk base.uk 0.20

View File

@ -0,0 +1,176 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Odoo, Open Source Business Applications
# Copyright (C) 2015 Odoo S.A. <http://www.odoo.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp.exceptions import Warning
from openerp import fields, models, api
from openerp.tools.translate import _
class l10n_eu_service(models.TransientModel):
"""Create fiscal positions for EU Service VAT"""
_name = "l10n_eu_service.wizard"
_description = __doc__
def _get_eu_res_country_group(self):
eu_group = self.env.ref("base.europe", raise_if_not_found=False)
if not eu_group:
raise Warning(_('The Europe country group cannot be found. '
'Please update the base module.'))
return eu_group
def _default_chart_id(self):
user = self.env.user
return self.env['account.account'].search(
[('company_id', '=', user.company_id.id), ('parent_id', '=', False)], limit=1)
def _default_fiscal_position_id(self):
user = self.env.user
eu_id = self._get_eu_res_country_group()
return self.env['account.fiscal.position'].search(
[('company_id', '=', user.company_id.id), ('vat_required', '=', True),
('country_group_id.id', '=', eu_id.id)], limit=1)
def _default_tax_id(self):
user = self.env.user
return self.env['account.tax'].search(
[('company_id', '=', user.company_id.id), ('type_tax_use', '=', 'sale'),
('type', '=', 'percent'), ('account_collected_id', '!=', False),
('tax_code_id', '!=', False)], limit=1, order='amount desc')
def _default_done_country_ids(self):
user = self.env.user
eu_country_group = self._get_eu_res_country_group()
return eu_country_group.country_ids - self._default_todo_country_ids() - user.company_id.country_id
def _default_todo_country_ids(self):
user = self.env.user
eu_country_group = self._get_eu_res_country_group()
eu_fiscal = self.env['account.fiscal.position'].search(
[('country_id', 'in', eu_country_group.country_ids.ids),
('vat_required', '=', False), ('auto_apply', '=', True),
('company_id', '=', user.company_id.id)])
return eu_country_group.country_ids - eu_fiscal.mapped('country_id') - user.company_id.country_id
chart_id = fields.Many2one(
"account.account", string="Chart of Accounts", required=True, default=_default_chart_id)
company_id = fields.Many2one(
'res.company', string='Company', required=True,
related='chart_id.company_id', readonly=True)
fiscal_position_id = fields.Many2one(
'account.fiscal.position', string='Fiscal Position', default=_default_fiscal_position_id,
help="Optional fiscal position to use as template for general account mapping. "
"Should usually be your current Intra-EU B2B fiscal position. "
"If not set, no general account mapping will be configured for EU fiscal positions.")
tax_id = fields.Many2one(
'account.tax', string='Service VAT', required=True, default=_default_tax_id,
help="Select your current VAT tax for services. This is the tax that will be mapped "
"to the corresponding VAT tax in each EU country selected below.")
account_collected_id = fields.Many2one(
"account.account", string="Tax Collection Account",
help="Optional account to use for collecting tax amounts when selling services in "
"each EU country selected below. If not set, the current collecting account of "
"your Service VAT will be used.")
done_country_ids = fields.Many2many(
'res.country', 'l10n_eu_service_country_rel_done', default=_default_done_country_ids,
string='Already Supported')
todo_country_ids = fields.Many2many(
'res.country', 'l10n_eu_service_country_rel_todo', default=_default_todo_country_ids,
string='EU Customers From', required=True)
@api.multi
def generate_eu_service(self):
imd = self.env['ir.model.data']
tax_code = self.env['account.tax.code']
tax_rate = self.env["l10n_eu_service.service_tax_rate"]
account_tax = self.env['account.tax']
fpos = self.env['account.fiscal.position']
chart_xid = 'l10n_eu_service.tax_chart_service_eu_company_%s' % self.company_id.name
chart = self.env.ref(chart_xid, raise_if_not_found=False)
if not chart:
vals = {
'name': _("EU MOSS VAT Chart - %(company)s") % {'company': self.company_id.name},
'company_id': self.company_id.id,
'parent_id': False
}
chart_id = tax_code.create(vals).id
vals_data = {
'name': 'tax_chart_service_eu_company_%s'%(self.company_id.name),
'model': 'account.tax.code',
'module': 'l10n_eu_service',
'res_id': chart_id,
'noupdate': True, # Don't drop it when module is updated
}
imd.create(vals_data)
else:
chart_id = chart.id
for country in self.todo_country_ids:
format_params = {'country_name': country.name}
tx_base_code_data = {
'name': _("Base - VAT for EU Services to %(country_name)s") % format_params,
'code': "BASE-EU-VAT-%s" % country.code,
'parent_id': chart_id,
}
tax_name = _("VAT for EU Services to %(country_name)s") % format_params
tx_code_data = {
'name': tax_name,
'code': "EU-VAT-%s" % country.code,
'parent_id': chart_id,
}
tx_base_code = tax_code.create(tx_base_code_data)
tx_code = tax_code.create(tx_code_data)
#create a new tax based on the selected service tax
data_tax = {
'name': tax_name,
'amount': tax_rate.search([('country_id', '=', country.id)]).rate,
'base_code_id': self.tax_id.base_code_id.id,
'account_collected_id': self.account_collected_id.id or self.tax_id.account_collected_id.id,
'account_paid_id': self.account_collected_id.id or self.tax_id.account_collected_id.id,
'type_tax_use': 'sale',
'base_code_id': tx_base_code.id,
'ref_base_code_id': tx_base_code.id,
'tax_code_id': tx_code.id,
'ref_tax_code_id': tx_code.id,
'ref_base_sign': -1,
'ref_tax_sign': -1
}
tax = account_tax.create(data_tax)
if self.fiscal_position_id:
account_ids = [(6, 0, self.fiscal_position_id.account_ids.ids)]
else:
account_ids = False
#create a fiscal position for the country
fiscal_pos_name = _("Intra-EU B2C in %(country_name)s") % {'country_name': country.name}
fiscal_pos_name += " (EU-VAT-%s)" % country.code
data_fiscal = {
'name': fiscal_pos_name,
'company_id': self.chart_id.company_id.id,
'vat_required': False,
'auto_apply': True,
'country_id': country.id,
'account_ids': account_ids,
'tax_ids': [(0, 0, {'tax_src_id': self.tax_id.id, 'tax_dest_id': tax.id})],
}
fpos.create(data_fiscal)
return {'type': 'ir.actions.act_window_close'}

View File

@ -0,0 +1,78 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="eu_service_view" model="ir.ui.view">
<field name="name">l10n_eu_service.wizard.form</field>
<field name="model">l10n_eu_service.wizard</field>
<field name="arch" type="xml">
<form string='Setup EU MOSS Taxes'>
<p>
Based on the options selected below, this wizard will create one
fiscal position mapping for each EU country to which you are selling
services.
Each fiscal position will automatically map your national VAT tax for
services to the corresponding VAT tax in the country your customer
belongs to.
<br/>
You can use the wizard again later to add more countries.
</p>
<group>
<field name="chart_id" domain="[('parent_id','=', False)]"
context="{'company_id': company_id}" options="{'no_create': True}"/>
<field name="company_id" invisible="1"/>
<field name="fiscal_position_id" domain="[('company_id','=', company_id)]"
options="{'no_create': True}"
placeholder="Current EU B2C Fiscal Position, if any"/>
<field name="tax_id"
domain="[('type_tax_use', '=', 'sale'),
('type', '=', 'percent'),
('account_collected_id', '!=', False),
('tax_code_id', '!=', False),
('company_id','=', company_id)]"
options="{'no_create': True}"/>
<field name="account_collected_id"
placeholder="Keep empty to use current Service VAT account"/>
<field name="done_country_ids" widget="many2many_tags" readonly="1"
attrs="{'invisible': [('done_country_ids', '=', [(6, False, [])])]}"/>
<field name="todo_country_ids" widget="many2many_tags"
domain="[('country_group_ids', 'in', %(base.europe)d)]"/>
</group>
<footer>
<button string="Create Fiscal Positions and Taxes"
name="generate_eu_service" type="object" class="oe_highlight"/>
or
<button string="Cancel" class="oe_link" special="cancel"/>
</footer>
</form>
</field>
</record>
<record id="action_eu_service" model="ir.actions.act_window">
<field name="name">Setup EU MOSS Taxes</field>
<field name="res_model">l10n_eu_service.wizard</field>
<field name="view_mode">form</field>
<field name="view_id" ref="eu_service_view"/>
<field name="target">new</field>
</record>
<record id="open_l10n_eu_service" model="ir.actions.todo">
<field name="action_id" ref="action_eu_service"/>
<field name="type">automatic</field>
<field name="state">open</field>
</record>
<record id="view_account_config_settings_inherit" model="ir.ui.view">
<field name="name">account settings</field>
<field name="model">account.config.settings</field>
<field name="inherit_id" ref="account.view_account_config_settings"/>
<field name="arch" type="xml">
<field name="default_sale_tax" position="after">
<button type="action" name="%(action_eu_service)d"
string="Setup EU MOSS Taxes" class="oe_link"/>
</field>
</field>
</record>
</data>
</openerp>