From 5f4747e8184178bca76f2a035fc095fe26d11464 Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Fri, 24 Jan 2020 14:34:17 +0100 Subject: [PATCH 01/58] l10n_fr_das2: initial check-in of the module --- l10n_fr_das2/__init__.py | 4 + l10n_fr_das2/__manifest__.py | 25 ++ .../data/account_account_template.xml | 32 +++ l10n_fr_das2/models/__init__.py | 7 + l10n_fr_das2/models/account_account.py | 29 +++ .../models/account_config_settings.py | 13 ++ l10n_fr_das2/models/company.py | 19 ++ l10n_fr_das2/models/l10n_fr_das2.py | 213 ++++++++++++++++++ l10n_fr_das2/post_install.py | 39 ++++ l10n_fr_das2/security/das2_security.xml | 19 ++ l10n_fr_das2/security/ir.model.access.csv | 3 + l10n_fr_das2/views/account_account.xml | 46 ++++ .../views/account_config_settings.xml | 36 +++ l10n_fr_das2/views/l10n_fr_das2.xml | 160 +++++++++++++ 14 files changed, 645 insertions(+) create mode 100644 l10n_fr_das2/__init__.py create mode 100644 l10n_fr_das2/__manifest__.py create mode 100644 l10n_fr_das2/data/account_account_template.xml create mode 100644 l10n_fr_das2/models/__init__.py create mode 100644 l10n_fr_das2/models/account_account.py create mode 100644 l10n_fr_das2/models/account_config_settings.py create mode 100644 l10n_fr_das2/models/company.py create mode 100644 l10n_fr_das2/models/l10n_fr_das2.py create mode 100644 l10n_fr_das2/post_install.py create mode 100644 l10n_fr_das2/security/das2_security.xml create mode 100644 l10n_fr_das2/security/ir.model.access.csv create mode 100644 l10n_fr_das2/views/account_account.xml create mode 100644 l10n_fr_das2/views/account_config_settings.xml create mode 100644 l10n_fr_das2/views/l10n_fr_das2.xml diff --git a/l10n_fr_das2/__init__.py b/l10n_fr_das2/__init__.py new file mode 100644 index 000000000..a223b2f37 --- /dev/null +++ b/l10n_fr_das2/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- + +from . import models +from .post_install import setup_das2_accounts diff --git a/l10n_fr_das2/__manifest__.py b/l10n_fr_das2/__manifest__.py new file mode 100644 index 000000000..19220a742 --- /dev/null +++ b/l10n_fr_das2/__manifest__.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Copyright 2020 Akretion France (http://www.akretion.com/) +# @author: Alexis de Lattre +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + 'name': 'DAS2', + 'version': '10.0.1.0.0', + 'category': 'Accounting', + 'license': 'AGPL-3', + 'summary': 'DAS2 (France)', + 'author': 'Akretion,Odoo Community Association (OCA)', + 'website': 'https://github.com/OCA/l10n-france', + 'depends': ['account_fiscal_year', 'l10n_fr', 'l10n_fr_siret'], + 'data': [ + 'security/das2_security.xml', + 'security/ir.model.access.csv', + 'data/account_account_template.xml', + 'views/l10n_fr_das2.xml', + 'views/account_account.xml', + ], + 'post_init_hook': 'setup_das2_accounts', + 'installable': True, + 'application': True, +} diff --git a/l10n_fr_das2/data/account_account_template.xml b/l10n_fr_das2/data/account_account_template.xml new file mode 100644 index 000000000..45254b8fe --- /dev/null +++ b/l10n_fr_das2/data/account_account_template.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/l10n_fr_das2/models/__init__.py b/l10n_fr_das2/models/__init__.py new file mode 100644 index 000000000..0d009227c --- /dev/null +++ b/l10n_fr_das2/models/__init__.py @@ -0,0 +1,7 @@ +# -*- coding: utf-8 -*- + +from . import account_account +from . import l10n_fr_das2 +from . import company +from . import account_config_settings + diff --git a/l10n_fr_das2/models/account_account.py b/l10n_fr_das2/models/account_account.py new file mode 100644 index 000000000..673802e0d --- /dev/null +++ b/l10n_fr_das2/models/account_account.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +# Copyright 2020 Akretion France (http://www.akretion.com/) +# @author: Alexis de Lattre +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import api, fields, models, _ +from odoo.exceptions import ValidationError + + +class AccountAccount(models.Model): + _inherit = 'account.account' + + fr_das2 = fields.Boolean(string='DAS2') + + @api.constrains('fr_das2', 'user_type_id') + def das2_check(self): + exp_acc_type = self.env.ref('account.data_account_type_expenses') + for account in self: + if account.fr_das2 and account.user_type_id != exp_acc_type: + raise ValidationError(_( + "The DAS2 option cannot be activated on account '%s' " + "because it is not an expense account.") + % account.display_name) + + +class AccountAccountTemplate(models.Model): + _inherit = 'account.account.template' + + fr_das2 = fields.Boolean(string='DAS2') diff --git a/l10n_fr_das2/models/account_config_settings.py b/l10n_fr_das2/models/account_config_settings.py new file mode 100644 index 000000000..a1dc976f4 --- /dev/null +++ b/l10n_fr_das2/models/account_config_settings.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- +# Copyright 2020 Akretion France (http://www.akretion.com/) +# @author: Alexis de Lattre +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class AccountConfigSettings(models.TransientModel): + _inherit = 'account.config.settings' + + fr_das2_partner_declare_threshold = fields.Monetary( + related='company_id.fr_das2_partner_declare_threshold', readonly=False) diff --git a/l10n_fr_das2/models/company.py b/l10n_fr_das2/models/company.py new file mode 100644 index 000000000..e2edacab8 --- /dev/null +++ b/l10n_fr_das2/models/company.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Copyright 2020 Akretion France (http://www.akretion.com/) +# @author: Alexis de Lattre +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class ResCompany(models.Model): + _inherit = 'res.company' + + fr_das2_partner_declare_threshold = fields.Monetary( + string='DAS2 Partner Declaration Threshold', + default=1200) + + _sql_constraints = [( + 'fr_das2_partner_declare_threshold_positive', + 'CHECK(fr_das2_partner_declare_threshold >= 0)', + 'The DAS2 partner declaration threshold must be positive!')] diff --git a/l10n_fr_das2/models/l10n_fr_das2.py b/l10n_fr_das2/models/l10n_fr_das2.py new file mode 100644 index 000000000..a321c0a10 --- /dev/null +++ b/l10n_fr_das2/models/l10n_fr_das2.py @@ -0,0 +1,213 @@ +# -*- coding: utf-8 -*- +# Copyright 2020 Akretion France (http://www.akretion.com/) +# @author: Alexis de Lattre +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import api, fields, models, _ +from odoo.exceptions import UserError, ValidationError +from odoo.tools import float_compare +from dateutil.relativedelta import relativedelta + + +class L10nFrDas2(models.Model): + _name = 'l10n.fr.das2' + _inherit = ['mail.thread'] + _order = 'date_start desc' + _description = 'DAS2' + + date_range_id = fields.Many2one( + 'date.range', string='Fiscal Year', ondelete='restrict', copy=False, + required=True, domain="[('type_id.fiscal_year', '=', True)]", + states={'done': [('readonly', True)]}, track_visibility='onchange', + default=lambda self: self._default_date_range()) + date_start = fields.Date( + related='date_range_id.date_start', store=True, readonly=True) + date_end = fields.Date( + related='date_range_id.date_end', store=True, readonly=True) + state = fields.Selection([ + ('draft', 'Draft'), + ('done', 'Done'), + ], default='draft', readonly=True, string='State') + company_id = fields.Many2one( + 'res.company', string='Company', + ondelete='cascade', required=True, + states={'done': [('readonly', True)]}, + default=lambda self: self.env['res.company']._company_default_get()) + total_amount = fields.Monetary( + compute='_compute_total_amount', currency_field='currency_id', + string='Total Amount To Declare', store=True, readonly=True, + track_visibility='onchange') + currency_id = fields.Many2one( + related='company_id.currency_id', readonly=True, store=True, + string='Company Currency') + source_journal_ids = fields.Many2many( + 'account.journal', + string='Source Journals', required=True, + default=lambda self: self._default_source_journals(), + states={'done': [('readonly', True)]}) + line_ids = fields.One2many( + 'l10n.fr.das2.line', 'parent_id', string='Lines', + states={'done': [('readonly', True)]}) + # option for draft moves ? + + _sql_constraints = [( + 'fiscal_year_company_uniq', + 'unique(company_id, date_range_id)', + 'A DAS2 already exists for this fiscal year!')] + + def _default_source_journals(self): + res = [] + src_journals = self.env['account.journal'].search([ + ('type', '=', 'purchase'), + ('company_id', '=', self.env.user.company_id.id)]) + if src_journals: + res = src_journals.ids + return res + + @api.depends('line_ids.amount') + def _compute_total_amount(self): + rg_res = self.env['l10n.fr.das2.line'].read_group( + [('parent_id', 'in', self.ids), ('to_declare', '=', True)], + ['parent_id', 'amount'], ['parent_id']) + mapped_data = dict([(x['parent_id'][0], x['amount']) for x in rg_res]) + for rec in self: + rec.total_amount = mapped_data.get(rec.id, 0) + + @api.model + def _default_date_range(self): + date_range = self.env['date.range'].search([ + ('company_id', '=', self.env.user.company_id.id), + ('type_id.fiscal_year', '=', True), + ('date_end', '<', fields.Date.context_today(self)), + ], order='date_end desc', limit=1) + return date_range + + def done(self): + self.state = 'done' + return + + def back2draft(self): + self.state = 'draft' + return + + def generate_lines(self): + self.ensure_one() + smo = self.env['account.move'] + lfdlo = self.env['l10n.fr.das2.line'] + if not self.company_id.country_id: + raise UserError(_( + "Country not set on company '%s'.") + % self.company_id.display_name) + if ( + self.company_id.country_id.code not in + ('FR', 'GP', 'MQ', 'GF', 'RE', 'YT')): + raise UserError(_( + "Company '%s' is configured in country '%s'. The DAS2 is " + "only for France and it's oversea territories.") + % (self.company_id.display_name, + self.company_id.country_id.name)) + if self.company_id.currency_id != self.env.ref('base.EUR'): + raise UserError(_( + "Company '%s' is configured with currency '%s'. " + "It should be EUR.") + % (self.company_id.display_name, + self.company_id.currency_id.name)) + if self.company_id.fr_das2_partner_declare_threshold <= 0: + raise UserError(_( + "The DAS2 partner declaration threshold is not set on " + "company '%s'.") % self.company_id.display_name) + threshold = self.company_id.fr_das2_partner_declare_threshold + das2_accounts = self.env['account.account'].search([ + ('company_id', '=', self.company_id.id), + ('fr_das2', '=', True), + ]) + if not das2_accounts: + raise UserError(_( + "There are no accounts flagged for DAS2 in company '%s'.") + % self.company_id.display_name) + # delete existing lines + self.line_ids.unlink() + date_end_scan_dt = fields.Date.from_string(self.date_end)\ + + relativedelta(months=5) + date_start_scan_dt = fields.Date.from_string(self.date_start)\ + - relativedelta(years=1) + rg_res = self.env['account.move.line'].read_group([ + ('company_id', '=', self.company_id.id), + ('date', '>=', fields.Date.to_string(date_start_scan_dt)), + ('date', '<=', fields.Date.to_string(date_end_scan_dt)), + ('account_id', 'in', das2_accounts.ids), + ('journal_id', 'in', self.source_journal_ids.ids), + ], ['move_id'], ['move_id']) + payable_acc_type = self.env.ref('account.data_account_type_payable') + res = {} + for rg_re in rg_res: + move_id = rg_re['move_id'][0] + move = smo.browse(move_id) + for line in move.line_ids: + if ( + line.account_id.user_type_id == payable_acc_type and + line.partner_id and + line.full_reconcile_id): + for rec_line in line.full_reconcile_id.reconciled_line_ids: + if ( + rec_line != line and + rec_line.journal_id.type in ('bank', 'cash') + and + rec_line.date >= self.date_start and + rec_line.date <= self.date_end and + rec_line.partner_id == line.partner_id): + if rec_line.partner_id in res: + res[rec_line.partner_id] += rec_line.balance + else: + res[rec_line.partner_id] = rec_line.balance + for partner, amount in res.items(): + vals = { + 'parent_id': self.id, + 'partner_id': partner.id, + 'amount': amount, + } + if partner.siren and partner.nic: + vals['partner_siret'] = partner.siret + if float_compare(amount, threshold, precision_digits=0) >= 0: + vals['to_declare'] = True + lfdlo.create(vals) + + def generate_file(self): + self.ensure_one() + raise UserError(_("This feature is not implemented yet.")) + + def button_lines_fullscreen(self): + self.ensure_one() + action = self.env['ir.actions.act_window'].for_xml_id( + 'l10n_fr_das2', 'l10n_fr_das2_line_action') + action.update({ + 'domain': [('parent_id', '=', self.id)], + 'views': False, + }) + return action + + +class L10nFrDas2Line(models.Model): + _name = 'l10n.fr.das2.line' + _description = 'DAS2 line' + + parent_id = fields.Many2one( + 'l10n.fr.das2', string='DAS2 Report', ondelete='cascade') + partner_id = fields.Many2one( + 'res.partner', string='Supplier', ondelete='restrict', + states={'done': [('readonly', True)]}, required=True) + partner_siret = fields.Char( + string='Partner SIRET', states={'done': [('readonly', True)]}) + currency_id = fields.Many2one( + related='parent_id.company_id.currency_id', store=True, readonly=True, + string='Company Currency') + amount = fields.Monetary(states={'done': [('readonly', True)]}) + to_declare = fields.Boolean( + string='To Declare', states={'done': [('readonly', True)]}) + state = fields.Selection( + related='parent_id.state', store=True, readonly=True) + + _sql_constraints = [( + 'amount_positive', + 'CHECK(amount >= 0)', + 'The amount of a DAS2 line must be positive!')] diff --git a/l10n_fr_das2/post_install.py b/l10n_fr_das2/post_install.py new file mode 100644 index 000000000..a775f1082 --- /dev/null +++ b/l10n_fr_das2/post_install.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +# © 2016-2017 Akretion (Alexis de Lattre ) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import api, SUPERUSER_ID +from odoo.tools import file_open +from lxml import etree +import logging +logger = logging.getLogger(__name__) + + +def setup_das2_accounts(cr, registry): + f = file_open( + 'l10n_fr_das2/data/account_account_template.xml', 'rb') + xml_root = etree.parse(f) + data = {} + for record in xml_root.xpath('//record'): + xmlid = record.attrib['id'] + account_code = xmlid.split('_')[-1] + data[account_code] = True + logger.debug('set_unece_on_taxes data=%s', data) + with api.Environment.manage(): + env = api.Environment(cr, SUPERUSER_ID, {}) + companies = env['res.company'].search([]) + aao = env['account.account'] + for company in companies: + logger.debug( + 'setup_das2_accounts working on company %s ID %d', + company.display_name, company.id) + if company.country_id and company.country_id != env.ref('base.fr'): + continue + for account_code in data.keys(): + accounts = aao.search([ + ('company_id', '=', company.id), + ('code', '=ilike', account_code + '%')]) + accounts.write({'fr_das2': True}) + logger.debug( + 'Set fr_das2=True on account IDs %s', accounts.ids) + return diff --git a/l10n_fr_das2/security/das2_security.xml b/l10n_fr_das2/security/das2_security.xml new file mode 100644 index 000000000..d6e9b4a9a --- /dev/null +++ b/l10n_fr_das2/security/das2_security.xml @@ -0,0 +1,19 @@ + + + + + + DAS2 multi-company + + ['|', ('company_id', '=', False), ('company_id', 'child_of', [user.company_id.id])] + + + + + DAS2 Line multi-company + + ['|', ('company_id', '=', False), ('company_id', 'child_of', [user.company_id.id])] + + + + diff --git a/l10n_fr_das2/security/ir.model.access.csv b/l10n_fr_das2/security/ir.model.access.csv new file mode 100644 index 000000000..6601efbb3 --- /dev/null +++ b/l10n_fr_das2/security/ir.model.access.csv @@ -0,0 +1,3 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_l10n_fr_das2,Full access to l10n.fr.das2 to accountant,model_l10n_fr_das2,account.group_account_user,1,1,1,1 +access_l10n_fr_das2_line,Full access to l10n.fr.das2.line to accoutant,model_l10n_fr_das2_line,account.group_account_user,1,1,1,1 diff --git a/l10n_fr_das2/views/account_account.xml b/l10n_fr_das2/views/account_account.xml new file mode 100644 index 000000000..5504e201d --- /dev/null +++ b/l10n_fr_das2/views/account_account.xml @@ -0,0 +1,46 @@ + + + + + + + + l10n.fr.das2.account.account.form + account.account + + + + + + + + + + l10n.fr.das2.account.account.search + account.account + + + + + + + + + + + l10n.fr.das2.account.account.template.form + account.account.template + + + + + + + + + + diff --git a/l10n_fr_das2/views/account_config_settings.xml b/l10n_fr_das2/views/account_config_settings.xml new file mode 100644 index 000000000..f2e028576 --- /dev/null +++ b/l10n_fr_das2/views/account_config_settings.xml @@ -0,0 +1,36 @@ + + + + + + + chorus.account.config.settings.form + account.config.settings + + + + + + + + + + + + + + + + + + diff --git a/l10n_fr_das2/views/l10n_fr_das2.xml b/l10n_fr_das2/views/l10n_fr_das2.xml new file mode 100644 index 000000000..e81c7fb4c --- /dev/null +++ b/l10n_fr_das2/views/l10n_fr_das2.xml @@ -0,0 +1,160 @@ + + + + + + + + l10n.fr.das2.form + l10n.fr.das2 + +
+
+
+ +
+ +
+ + + + + + + + + + + + + + + + +
+
+ + +
+
+
+
+ + + + l10n.fr.das2.tree + l10n.fr.das2 + + + + + + + + + + + + + l10n.fr.das2.search + l10n.fr.das2 + + + + + + + + + + l10n.fr.das2.graph + l10n.fr.das2 + + + + + + + + + + DAS2 + l10n.fr.das2 + tree,form,graph + + + + + + + l10n.fr.das2.line.form + l10n.fr.das2.line + +
+ + + + + + + + +
+
+
+ + + l10n.fr.das2.line.tree + l10n.fr.das2.line + + + + + + + + + + + + + + l10n.fr.das2.line.search + l10n.fr.das2.line + + + + + + + + + + + + DAS2 Lines + l10n.fr.das2.line + tree,form + + + +
From 90c4a31ee7fbbbf5c0565bb30c6f919e8b52f66c Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Fri, 24 Jan 2020 15:26:51 +0100 Subject: [PATCH 02/58] l10n_fr_das2: add field on configuration page Add company_id field on lines --- l10n_fr_das2/__manifest__.py | 1 + l10n_fr_das2/models/l10n_fr_das2.py | 2 ++ .../views/account_config_settings.xml | 20 ++++--------------- 3 files changed, 7 insertions(+), 16 deletions(-) diff --git a/l10n_fr_das2/__manifest__.py b/l10n_fr_das2/__manifest__.py index 19220a742..99f183cb8 100644 --- a/l10n_fr_das2/__manifest__.py +++ b/l10n_fr_das2/__manifest__.py @@ -18,6 +18,7 @@ 'data/account_account_template.xml', 'views/l10n_fr_das2.xml', 'views/account_account.xml', + 'views/account_config_settings.xml', ], 'post_init_hook': 'setup_das2_accounts', 'installable': True, diff --git a/l10n_fr_das2/models/l10n_fr_das2.py b/l10n_fr_das2/models/l10n_fr_das2.py index a321c0a10..c08f44c9f 100644 --- a/l10n_fr_das2/models/l10n_fr_das2.py +++ b/l10n_fr_das2/models/l10n_fr_das2.py @@ -198,6 +198,8 @@ class L10nFrDas2Line(models.Model): states={'done': [('readonly', True)]}, required=True) partner_siret = fields.Char( string='Partner SIRET', states={'done': [('readonly', True)]}) + company_id = fields.Many2one( + related='parent_id.company_id', store=True, readonly=True) currency_id = fields.Many2one( related='parent_id.company_id.currency_id', store=True, readonly=True, string='Company Currency') diff --git a/l10n_fr_das2/views/account_config_settings.xml b/l10n_fr_das2/views/account_config_settings.xml index f2e028576..db3f81f01 100644 --- a/l10n_fr_das2/views/account_config_settings.xml +++ b/l10n_fr_das2/views/account_config_settings.xml @@ -1,6 +1,7 @@ @@ -12,21 +13,8 @@ - - - - - - - - - From d43891f7f940eba591fe01c888c43cd72a76def0 Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Fri, 31 Jan 2020 00:31:46 +0100 Subject: [PATCH 03/58] l10n_fr_das2: add support for all different kinds of expense of the official report --- l10n_fr_das2/__manifest__.py | 3 + .../data/account_account_template.xml | 10 +- l10n_fr_das2/models/account_account.py | 24 +- .../models/account_config_settings.py | 2 +- l10n_fr_das2/models/company.py | 2 +- l10n_fr_das2/models/l10n_fr_das2.py | 575 +++++++++++++++--- l10n_fr_das2/post_install.py | 14 +- l10n_fr_das2/views/account_account.xml | 5 +- l10n_fr_das2/views/l10n_fr_das2.xml | 45 +- 9 files changed, 590 insertions(+), 90 deletions(-) diff --git a/l10n_fr_das2/__manifest__.py b/l10n_fr_das2/__manifest__.py index 99f183cb8..3f3c079c4 100644 --- a/l10n_fr_das2/__manifest__.py +++ b/l10n_fr_das2/__manifest__.py @@ -12,6 +12,9 @@ 'author': 'Akretion,Odoo Community Association (OCA)', 'website': 'https://github.com/OCA/l10n-france', 'depends': ['account_fiscal_year', 'l10n_fr', 'l10n_fr_siret'], + 'external_dependencies': { + 'python': ['unidecode'], + }, 'data': [ 'security/das2_security.xml', 'security/ir.model.access.csv', diff --git a/l10n_fr_das2/data/account_account_template.xml b/l10n_fr_das2/data/account_account_template.xml index 45254b8fe..30150aa02 100644 --- a/l10n_fr_das2/data/account_account_template.xml +++ b/l10n_fr_das2/data/account_account_template.xml @@ -9,23 +9,23 @@ - + commission - + fee - + commission - + attendance_fee - + copyright_royalties diff --git a/l10n_fr_das2/models/account_account.py b/l10n_fr_das2/models/account_account.py index 673802e0d..0d7f6e64e 100644 --- a/l10n_fr_das2/models/account_account.py +++ b/l10n_fr_das2/models/account_account.py @@ -10,7 +10,23 @@ class AccountAccount(models.Model): _inherit = 'account.account' - fr_das2 = fields.Boolean(string='DAS2') + fr_das2 = fields.Selection('get_das2_types', string='DAS2') + + @api.model + def get_das2_types(self): + res = [ + ('fee', u'Honoraires et vacations'), + ('commission', u'Commissions'), + ('brokerage', u'Courtages'), + ('discount', u'Ristournes'), + ('attendance_fee', u'Jetons de présence'), + ('copyright_royalties', u"Droits d'auteur"), + ('licence_royalties', u"Droits d'inventeur"), + ('other_income', u'Autre rémunérations'), + ('allowance', u'Indemnités et remboursements'), + ('withholding_tax', u'Retenue à la source'), + ] + return res @api.constrains('fr_das2', 'user_type_id') def das2_check(self): @@ -26,4 +42,8 @@ def das2_check(self): class AccountAccountTemplate(models.Model): _inherit = 'account.account.template' - fr_das2 = fields.Boolean(string='DAS2') + fr_das2 = fields.Selection('get_das2_types', string='DAS2') + + @api.model + def get_das2_types(self): + return self.env['account.account'].get_das2_types() diff --git a/l10n_fr_das2/models/account_config_settings.py b/l10n_fr_das2/models/account_config_settings.py index a1dc976f4..f9e3e6a0a 100644 --- a/l10n_fr_das2/models/account_config_settings.py +++ b/l10n_fr_das2/models/account_config_settings.py @@ -9,5 +9,5 @@ class AccountConfigSettings(models.TransientModel): _inherit = 'account.config.settings' - fr_das2_partner_declare_threshold = fields.Monetary( + fr_das2_partner_declare_threshold = fields.Integer( related='company_id.fr_das2_partner_declare_threshold', readonly=False) diff --git a/l10n_fr_das2/models/company.py b/l10n_fr_das2/models/company.py index e2edacab8..20f0432bd 100644 --- a/l10n_fr_das2/models/company.py +++ b/l10n_fr_das2/models/company.py @@ -9,7 +9,7 @@ class ResCompany(models.Model): _inherit = 'res.company' - fr_das2_partner_declare_threshold = fields.Monetary( + fr_das2_partner_declare_threshold = fields.Integer( string='DAS2 Partner Declaration Threshold', default=1200) diff --git a/l10n_fr_das2/models/l10n_fr_das2.py b/l10n_fr_das2/models/l10n_fr_das2.py index c08f44c9f..4736ef4d3 100644 --- a/l10n_fr_das2/models/l10n_fr_das2.py +++ b/l10n_fr_das2/models/l10n_fr_das2.py @@ -5,8 +5,29 @@ from odoo import api, fields, models, _ from odoo.exceptions import UserError, ValidationError -from odoo.tools import float_compare +from odoo.tools import float_compare, float_is_zero, float_round from dateutil.relativedelta import relativedelta +from odoo.addons.l10n_fr_siret.models.partner import _check_luhn +from unidecode import unidecode +import base64 +import logging + +logger = logging.getLogger(__name__) + + +try: + from unidecode import unidecode +except ImportError: + unidecode = False + logger.debug('Cannot import unidecode') + + +FRANCE_CODES = ('FR', 'GP', 'MQ', 'GF', 'RE', 'YT') +AMOUNT_FIELDS = [ + 'fee_amount', 'commission_amount', 'brokerage_amount', + 'discount_amount', 'attendance_fee_amount', 'copyright_royalties_amount', + 'licence_royalties_amount', 'other_income_amount', 'allowance_amount', + 'benefits_in_kind_amount', 'withholding_tax_amount'] class L10nFrDas2(models.Model): @@ -33,10 +54,6 @@ class L10nFrDas2(models.Model): ondelete='cascade', required=True, states={'done': [('readonly', True)]}, default=lambda self: self.env['res.company']._company_default_get()) - total_amount = fields.Monetary( - compute='_compute_total_amount', currency_field='currency_id', - string='Total Amount To Declare', store=True, readonly=True, - track_visibility='onchange') currency_id = fields.Many2one( related='company_id.currency_id', readonly=True, store=True, string='Company Currency') @@ -48,7 +65,16 @@ class L10nFrDas2(models.Model): line_ids = fields.One2many( 'l10n.fr.das2.line', 'parent_id', string='Lines', states={'done': [('readonly', True)]}) + partner_declare_threshold = fields.Integer( + string='Partner Declaration Threshold', readonly=True) # option for draft moves ? + contact_id = fields.Many2one( + 'res.partner', string='Administrative Contact', + states={'done': [('readonly', True)]}, + default=lambda self: self.env.user.partner_id.id, + help='Contact in the company for the fiscal administration: the name, email and phone number of this partner will be used in the file.') + attachment_id = fields.Many2one( + 'ir.attachment', string='File Export', readonly=True) _sql_constraints = [( 'fiscal_year_company_uniq', @@ -64,15 +90,6 @@ def _default_source_journals(self): res = src_journals.ids return res - @api.depends('line_ids.amount') - def _compute_total_amount(self): - rg_res = self.env['l10n.fr.das2.line'].read_group( - [('parent_id', 'in', self.ids), ('to_declare', '=', True)], - ['parent_id', 'amount'], ['parent_id']) - mapped_data = dict([(x['parent_id'][0], x['amount']) for x in rg_res]) - for rec in self: - rec.total_amount = mapped_data.get(rec.id, 0) - @api.model def _default_date_range(self): date_range = self.env['date.range'].search([ @@ -82,6 +99,13 @@ def _default_date_range(self): ], order='date_end desc', limit=1) return date_range + @api.depends('date_range_id') + def name_get(self): + res = [] + for rec in self: + res.append((rec.id, 'DAS2 %s' % rec.date_range_id.name)) + return res + def done(self): self.state = 'done' return @@ -92,89 +116,434 @@ def back2draft(self): def generate_lines(self): self.ensure_one() - smo = self.env['account.move'] + amo = self.env['account.move'] + ato = self.env['account.tax'] + amlo = self.env['account.move.line'] + aao = self.env['account.account'] lfdlo = self.env['l10n.fr.das2.line'] - if not self.company_id.country_id: + company = self.company_id + ccur_prec = company.currency_id.rounding + if not company.country_id: raise UserError(_( - "Country not set on company '%s'.") - % self.company_id.display_name) - if ( - self.company_id.country_id.code not in - ('FR', 'GP', 'MQ', 'GF', 'RE', 'YT')): + "Country not set on company '%s'.") % company.display_name) + if company.country_id.code not in FRANCE_CODES: raise UserError(_( "Company '%s' is configured in country '%s'. The DAS2 is " "only for France and it's oversea territories.") - % (self.company_id.display_name, - self.company_id.country_id.name)) - if self.company_id.currency_id != self.env.ref('base.EUR'): + % (company.display_name, + company.country_id.name)) + if company.currency_id != self.env.ref('base.EUR'): raise UserError(_( "Company '%s' is configured with currency '%s'. " "It should be EUR.") - % (self.company_id.display_name, - self.company_id.currency_id.name)) - if self.company_id.fr_das2_partner_declare_threshold <= 0: + % (company.display_name, + company.currency_id.name)) + if company.fr_das2_partner_declare_threshold <= 0: raise UserError(_( "The DAS2 partner declaration threshold is not set on " - "company '%s'.") % self.company_id.display_name) - threshold = self.company_id.fr_das2_partner_declare_threshold + "company '%s'.") % company.display_name) + self.partner_declare_threshold =\ + company.fr_das2_partner_declare_threshold das2_accounts = self.env['account.account'].search([ - ('company_id', '=', self.company_id.id), - ('fr_das2', '=', True), + ('company_id', '=', company.id), + ('fr_das2', '!=', False), ]) if not das2_accounts: raise UserError(_( - "There are no accounts flagged for DAS2 in company '%s'.") - % self.company_id.display_name) + "There are no expense accounts for DAS2 in company '%s'.") + % company.display_name) + das2acc2type = {} + for das2_account in das2_accounts: + das2acc2type[das2_account] = das2_account.fr_das2 # delete existing lines self.line_ids.unlink() + vat_deduc_accounts = aao.search([ + '|', '|', + ('code', '=like', '4452%'), # also include intracom due VAT + ('code', '=like', '44562%'), + ('code', '=like', '44566%'), + ('internal_type', '=', 'other'), + ('company_id', '=', company.id)]) + if not vat_deduc_accounts: + raise UserError(_('No VAT deductible accounts found')) + logger.info( + 'VAT deductible accounts: %s', [a.code for a in vat_deduc_accounts]) + supplier_accounts = aao.search([ + ('company_id', '=', company.id), + ('reconcile', '=', True), + ('internal_type', '=', 'payable'), + ]) + if not supplier_accounts: + raise UserError(_('No supplier accounts found.')) + expense_accounts = aao.search([ + ('company_id', '=', company.id), + ('internal_type', '=', 'other'), + '|', ('code', '=like', '6%'), ('code', '=like', '2%'), + ]) + # I include 2% accounts so that VAT prorata works + if not expense_accounts: + raise UserError(_("No expense accounts found.")) + speed_vattax2rate = {} + vattaxes = ato.search([ + ('company_id', '=', company.id), + ('type_tax_use', '=', 'purchase'), + ('amount_type', '=', 'percent'), + ('amount', '!=', False)]) + for vattax in vattaxes: + if not float_is_zero(vattax.amount, precision_digits=4): + speed_vattax2rate[vattax.id] = vattax.amount + date_end_scan_dt = fields.Date.from_string(self.date_end)\ + relativedelta(months=5) date_start_scan_dt = fields.Date.from_string(self.date_start)\ - relativedelta(years=1) - rg_res = self.env['account.move.line'].read_group([ + base_domain = [ ('company_id', '=', self.company_id.id), ('date', '>=', fields.Date.to_string(date_start_scan_dt)), ('date', '<=', fields.Date.to_string(date_end_scan_dt)), - ('account_id', 'in', das2_accounts.ids), ('journal_id', 'in', self.source_journal_ids.ids), - ], ['move_id'], ['move_id']) - payable_acc_type = self.env.ref('account.data_account_type_payable') - res = {} + ] + res = {} # key = partner, value = {field: amount, 'note': []} + + rg_res = amlo.read_group( + base_domain + [('account_id', 'in', das2_accounts.ids)], + ['move_id'], ['move_id']) for rg_re in rg_res: move_id = rg_re['move_id'][0] - move = smo.browse(move_id) + move = amo.browse(move_id) + logger.info('Processing move %s ID %d', move.name, move.id) + # Inspired by account_vat_prorata + tmp = { + 'total_vat': 0.0, + 'total_with_vat': 0.0, + 'total_exp': 0.0, + 'exps_have_tax_ids': False, + 'partner': False, + 'exps': {}, # expenses + # key = id + # value = {'weight_tax_ids', 'weight_no_tax_ids', 'bal': account_id + 'payments': [], + 'total_payments': 0.0, + 'total_theoric_vat': 0.0, # for prorata + } for line in move.line_ids: - if ( - line.account_id.user_type_id == payable_acc_type and - line.partner_id and - line.full_reconcile_id): - for rec_line in line.full_reconcile_id.reconciled_line_ids: - if ( - rec_line != line and - rec_line.journal_id.type in ('bank', 'cash') - and - rec_line.date >= self.date_start and - rec_line.date <= self.date_end and - rec_line.partner_id == line.partner_id): - if rec_line.partner_id in res: - res[rec_line.partner_id] += rec_line.balance + if float_is_zero(line.balance, precision_rounding=ccur_prec): + continue + # VAT line + if line.account_id in vat_deduc_accounts: + tmp['total_vat'] += line.balance + # Payable line + elif line.account_id in supplier_accounts: + if not line.partner_id: + raise UserError(_( + "Move line '%s' with account '%s' " + "dated %s (ID %d) has no partner.") % ( + line.name, line.account_id.display_name, + line.date, line.id)) + tmp['total_with_vat'] -= line.balance + partner = line.partner_id + tmp['partner'] = partner + if line.full_reconcile_id: + for rec_line in line.full_reconcile_id.reconciled_line_ids: + if ( + rec_line != line and + rec_line.journal_id.type != 'purchase' + and + rec_line.date >= self.date_start and + rec_line.date <= self.date_end and + rec_line.partner_id == partner): + tmp['payments'].append({ + 'date': rec_line.date, + 'amount': rec_line.balance}) + tmp['total_payments'] += rec_line.balance + # Expense line + elif line.account_id in expense_accounts: + tmp['total_exp'] += line.balance + if line.tax_ids and len(line.tax_ids) == 1 and line.tax_ids[0].id in speed_vattax2rate: + + tmp['exps_have_tax_ids'] = True + theoric_vat_amount = speed_vattax2rate[line.tax_ids[0].id] * line.balance / 100.0 + tmp['exps'][line.id] = { + 'bal': line.balance, + 'theoric_vat_amount': theoric_vat_amount, + 'account': line.account_id, + 'das2type': das2acc2type.get(line.account_id), + 'date': line.date, + 'name': line.name, + } + tmp['total_theoric_vat'] += theoric_vat_amount + else: + tmp['exps'][line.id] = { + 'bal': line.balance, + 'account': line.account_id, + 'das2type': das2acc2type.get(line.account_id), + 'theoric_vat_amount': 0, + 'date': line.date, + 'name': line.name, + } + + # Check + if float_compare(tmp['total_with_vat'], tmp['total_exp'] + tmp['total_vat'], precision_rounding=ccur_prec): + raise UserError(_("Move %s (ID %d) has a total with VAT (%s) with is different from the sum of VAT amount (%s) plus total expense (%s). This should not happen in a purchase journal.") % (move.name, move.id, tmp['total_with_vat'], tmp['total_vat'], tmp['total_exp'])) + + # process + if not float_is_zero(tmp['total_with_vat'], precision_rounding=ccur_prec) and tmp['payments'] and not float_is_zero(tmp['total_exp'], precision_rounding=ccur_prec): + payment_ratio = 100 + if float_compare(tmp['total_payments'], tmp['total_with_vat'], precision_rounding=ccur_prec): + payment_ratio = round(100 * tmp['total_payments'] / tmp['total_with_vat'], 2) + logger.info('Move ID %s selected with payment_ratio %s', move.id, payment_ratio) + for exp_line_id, exp_line_val in tmp['exps'].items(): + if exp_line_val['das2type']: + if tmp['exps_have_tax_ids'] and not float_is_zero(tmp['total_theoric_vat'], precision_rounding=ccur_prec): + bal_with_vat = float_round(exp_line_val['bal'] + tmp['total_vat'] * exp_line_val['theoric_vat_amount'] / tmp['total_theoric_vat'], precision_rounding=ccur_prec) + else: + bal_with_vat = float_round(exp_line_val['bal'] + tmp['total_vat'] * exp_line_val['bal'] / tmp['total_exp'], precision_rounding=ccur_prec) + paid_amount = bal_with_vat * payment_ratio / 100.0 + note = _( + u"Expense Line ID %d '%s' supplier '%s' dated %s " + u"account %s DAS2 type '%s': " + u"amount without VAT: %s €, amount with VAT: %s €, " + u"payment ratio %s %%, paid amount with VAT: %s €. " + u"Payments of related move: %s.") % ( + exp_line_id, + exp_line_val['name'], + tmp['partner'].display_name, + exp_line_val['date'], + exp_line_val['account'].code, + exp_line_val['das2type'], + exp_line_val['bal'], + bal_with_vat, + payment_ratio, + paid_amount, + ','.join([ + _(u'%s € on %s') % (x['amount'], x['date']) + for x in tmp['payments']]), + ) + + field_name = '%s_amount' % exp_line_val['das2type'] + if partner in res: + res[partner]['note'].append(note) + if field_name in res[partner]: + res[partner][field_name] += paid_amount else: - res[rec_line.partner_id] = rec_line.balance - for partner, amount in res.items(): - vals = { + res[partner][field_name] = paid_amount + else: + res[partner] = {field_name: paid_amount, 'note': [note]} + + for partner, vals in res.items(): + for key, value in vals.items(): + if key.endswith('_amount'): + vals[key] = int(round(value)) + vals.update({ 'parent_id': self.id, 'partner_id': partner.id, - 'amount': amount, - } + 'note': '\n'.join(vals['note']) + }) if partner.siren and partner.nic: vals['partner_siret'] = partner.siret - if float_compare(amount, threshold, precision_digits=0) >= 0: - vals['to_declare'] = True lfdlo.create(vals) + @api.model + def _prepare_field(self, field_name, partner, value, size, required=False, numeric=False): + '''This function is designed to be inherited.''' + if numeric: + if not value: + value = 0 + if not isinstance(value, int): + try: + value = int(value) + except Exception: + raise UserError(_( + "Failed to convert field '%s' (partner %s) to integer.") + % (field_name, partner.display_name)) + value = str(value) + if len(value) > size: + raise UserError(_( + "Field %s (partner %s) has value %s: " + "it is bigger than the maximum size " + "(%d characters)") + % (field_name, partner.display_name, value, size)) + if len(value) < size: + value = value.rjust(size, '0') + return value + if required and not value: + raise UserError(_( + "The field '%s' (partner %s) is empty or 0. It should have a non-null " "value.") % (field_name, partner.display_name)) + if not value: + value = ' ' * size + # Cut if too long + value = value[0:size] + # enlarge if too small + if len(value) < size: + value = value.ljust(size, ' ') + assert len(value) == size, 'The length of the field is wrong' + return value + + def _prepare_address(self, partner): + cstreet2 = self._prepare_field('Street2', partner, partner.street2, 32) + cstreet = self._prepare_field('Street', partner, partner.street, 26) + ccity = self._prepare_field('City', partner, partner.city, 26, True) + czip = self._prepare_field('Zip', partner, partner.zip, 5, True) + + caddress = cstreet2 + ' ' + '0' * 4 + ' ' + ' ' + cstreet + '0' * 5 + ' ' + ccity + czip + ' ' + ' ' * 26 + assert len(caddress) == 129 + return caddress + def generate_file(self): self.ensure_one() - raise UserError(_("This feature is not implemented yet.")) + company = self.company_id + cpartner = company.partner_id + if not self.line_ids: + raise UserError(_("The DAS2 has no lines.")) + if not company.siret: + raise UserError(_( + "Missing SIRET on company '%s'.") % company.display_name) + if not company.ape: + raise UserError(_( + "Missing APE on company '%s'.") % company.display_name) + if not company.street: + raise UserError(_( + "Missing Street on company '%s'") % company.display_name) + contact = self.contact_id + if not contact: + raise UserError(_("Missing administrative contact.")) + if not contact.email: + raise UserError(_( + "The email is not set on the administrative contact " + "partner '%s'.") % contact.display_name) + if not contact.phone and not contact.mobile: + raise UserError(_( + "The phone number is not set on the administrative contact " + "partner '%s'.") % contact.display_name) + if self.attachment_id: + raise UserError(_( + "An export file already exists. First, delete it via the " + "attachments and then re-generate it.")) + + csiren = self._prepare_field('SIREN', cpartner, cpartner.siren, 9, True) + csiret = self._prepare_field('SIRET', cpartner, cpartner.siret, 14, True) + cape = self._prepare_field('APE', cpartner, company.ape, 5, True) + cname = self._prepare_field('Name', cpartner, company.name, 50, True) + file_type = 'X' # tous déclarants honoraires seuls + year = str(fields.Date.from_string(self.date_start).year) + caddress = self._prepare_address(cpartner) + cprefix = csiret + '01' + year + '5' + # line 010 Company header + flines = [] + flines.append( + csiren + '0' * 12 + '010' + ' ' * 14 + cape + ' ' * 4 + + cname + caddress + ' ' * 8 + file_type + csiret + ' ' * 5 + + caddress + ' ' + ' ' * 288) + + # ligne 020 Etablissement header + flines.append( + cprefix + '020' + ' ' * 14 + cape + '0' * 14 + ' ' * 41 + + cname + caddress + ' ' * 40 + ' ' * 53 + 'N' * 6 + ' ' * 296) + + for line in self.line_ids: + partner = line.partner_id + if ( + partner.country_id and + partner.country_id.code in FRANCE_CODES and + not line.partner_siret): + raise UserError(_( + "Missing SIRET for french partner %s.") + % partner.display_name) + # ligne 210 honoraire + if partner.is_company: + partner_name = self._prepare_field('Partner name', partner, partner.name, 50, True) + lastname = ' ' * 30 + firstname = ' ' * 20 + else: + partner_name = ' ' * 50 + if hasattr(partner, 'firstname') and partner.firstname: + lastname = self._prepare_field('Partner lastname', partner, partner.lastname, 30, True) + firstname = self._prepare_field('Partner firstname', partner, partner.firstname, 20, True) + else: + lastname = self._prepare_field('Partner name', partner, partner.name, 30, True) + firstname = ' ' * 20 + address = self._prepare_address(partner) + partner_siret = self._prepare_field('SIRET', partner, line.partner_siret, 14) + job = self._prepare_field('Profession', partner, line.job, 30) + amount_fields_list = [ + self._prepare_field(x, partner, line[x], 10, numeric=True) + for x in AMOUNT_FIELDS] + if line.benefits_in_kind_amount: + bik_letters = '' + bik_letters += line.benefits_in_kind_food and 'N' or ' ' + bik_letters += line.benefits_in_kind_accomodation and 'L' or ' ' + bik_letters += line.benefits_in_kind_car and 'V' or ' ' + bik_letters += line.benefits_in_kind_other and 'A' or ' ' + bik_letters += line.benefits_in_kind_nict and 'T' or ' ' + else: + bik_letters = ' ' * 5 + if line.allowance_amount: + allow_letters = '' + allow_letters += line.allowance_fixed and 'F' or ' ' + allow_letters += line.allowance_real and 'R' or ' ' + allow_letters += line.allowance_employer and 'P' or ' ' + else: + allow_letters = ' ' * 3 + flines.append( + cprefix + '210' + partner_siret + partner_name + firstname + + lastname + job + address + ''.join(amount_fields_list) + + bik_letters + allow_letters + + ' ' * 2 + '0' * 10 + ' ' * 245) + rg = self.env['l10n.fr.das2.line'].read_group([('parent_id', '=', self.id)], AMOUNT_FIELDS, [])[0] + total_fields_list = [ + self._prepare_field(x, cpartner, rg[x], 12, numeric=True) + for x in AMOUNT_FIELDS] + contact_name = self._prepare_field('Administrative contact name', contact, contact.name, 50) + contact_email = self._prepare_field('Administrative contact email', contact, contact.email, 60) + phone = contact.phone or contact.mobile + phone = phone.replace(' ', '').replace('.', '').replace('-', '') + if phone.startswith('+33'): + phone = '0%s' % phone[3:] + contact_phone = self._prepare_field('Administrative contact phone', contact, phone, 10) + flines.append( + cprefix + '300' + ' ' * 36 + '0' * 12 * 9 + + ''.join(total_fields_list) + ' ' * 12 + '0' * 12 * 2 + '0' * 6 + + '0' * 12 * 5 + ' ' * 74 + + contact_name + contact_phone + contact_email + ' ' * 76) + + lines_number = self._prepare_field('Number of lines', cpartner, len(self.line_ids), 6, numeric=True) + flines.append( + csiren + '9' * 12 + '310' + '00001' + '0' * 6 + lines_number + + '0' * 6 * 3 + ' ' * 18 + '9' * 12 * 9 + + ''.join(total_fields_list) + ' ' * 12 + '0' * 12 * 2 + '0' * 6 + + '0' * 12 * 5 + ' ' * 253) + for fline in flines: + if len(fline) != 672: + raise UserError(_( + "One of the lines has a length of %d. " + "All lines should have a length of 672. Line: %s.") + % (len(fline), fline)) + file_content = '\n'.join(flines) + file_content_encoded = file_content.encode('latin1') + filename = 'DAS2_%s.txt' % ( + unidecode(self.date_range_id.name.replace(' ', '_'))) + attach = self.env['ir.attachment'].create({ + 'name': filename, + 'res_id': self.id, + 'res_model': self._name, + 'datas': base64.encodestring(file_content_encoded), + 'datas_fname': filename, + }) + self.attachment_id = attach.id + action = { + 'type': 'ir.actions.act_window', + 'name': _('DAS2 Export File'), + 'view_mode': 'form', + 'res_model': 'ir.attachment', + 'target': 'current', + 'res_id': attach.id, + } + try: + action['view_id'] = self.env.ref( + 'account_payment_order.view_attachment_simplified_form').id + except Exception: + pass + return action def button_lines_fullscreen(self): self.ensure_one() @@ -195,21 +564,91 @@ class L10nFrDas2Line(models.Model): 'l10n.fr.das2', string='DAS2 Report', ondelete='cascade') partner_id = fields.Many2one( 'res.partner', string='Supplier', ondelete='restrict', + domain=[('parent_id', '=', False)], states={'done': [('readonly', True)]}, required=True) partner_siret = fields.Char( - string='Partner SIRET', states={'done': [('readonly', True)]}) + string='Partner SIRET', size=14, states={'done': [('readonly', True)]}) company_id = fields.Many2one( related='parent_id.company_id', store=True, readonly=True) currency_id = fields.Many2one( related='parent_id.company_id.currency_id', store=True, readonly=True, string='Company Currency') - amount = fields.Monetary(states={'done': [('readonly', True)]}) + fee_amount = fields.Integer(string=u'Honoraires et vacations', states={'done': [('readonly', True)]}) + commission_amount = fields.Integer(string=u'Commissions', states={'done': [('readonly', True)]}) + brokerage_amount = fields.Integer(string=u'Courtages', states={'done': [('readonly', True)]}) + discount_amount = fields.Integer(string=u'Ristournes', states={'done': [('readonly', True)]}) + attendance_fee_amount = fields.Integer(string=u'Jetons de présence', states={'done': [('readonly', True)]}) + copyright_royalties_amount = fields.Integer(string=u"Droits d'auteur", states={'done': [('readonly', True)]}) + licence_royalties_amount = fields.Integer(string=u"Droits d'inventeur", states={'done': [('readonly', True)]}) + other_income_amount = fields.Integer(string=u'Autre rémunérations', states={'done': [('readonly', True)]}) + allowance_amount = fields.Integer(string=u'Indemnités et remboursements', states={'done': [('readonly', True)]}) + benefits_in_kind_amount = fields.Integer(string='Avantages en nature', states={'done': [('readonly', True)]}) + withholding_tax_amount = fields.Integer(string=u'Retenue à la source', states={'done': [('readonly', True)]}) + total_amount = fields.Integer( + compute='_compute_total_amount', string='Total Amount', store=True, readonly=True) to_declare = fields.Boolean( - string='To Declare', states={'done': [('readonly', True)]}) + compute='_compute_total_amount', string='To Declare', readonly=True) + allowance_fixed = fields.Boolean(u'Allocation forfaitaire', states={'done': [('readonly', True)]}) + allowance_real = fields.Boolean(u'Sur frais réels', states={'done': [('readonly', True)]}) + allowance_employer = fields.Boolean(u"Prise en charge directe par l'employeur", states={'done': [('readonly', True)]}) + benefits_in_kind_food = fields.Boolean(u'Nourriture', states={'done': [('readonly', True)]}) + benefits_in_kind_accomodation = fields.Boolean(u'Logement', states={'done': [('readonly', True)]}) + benefits_in_kind_car = fields.Boolean(u'Voiture', states={'done': [('readonly', True)]}) + benefits_in_kind_other = fields.Boolean(u'Autres', states={'done': [('readonly', True)]}) + benefits_in_kind_nict = fields.Boolean(u'Outils issus des NTIC', states={'done': [('readonly', True)]}) state = fields.Selection( related='parent_id.state', store=True, readonly=True) + note = fields.Text() + job = fields.Char(string='Profession', size=30) - _sql_constraints = [( - 'amount_positive', - 'CHECK(amount >= 0)', - 'The amount of a DAS2 line must be positive!')] + _sql_constraints = [ + ('fee_amount_positive', 'CHECK(fee_amount >= 0)', 'Negative amounts not allowed!'), + ('commission_amount_positive', 'CHECK(commission_amount >= 0)', 'Negative amounts not allowed!'), + ('brokerage_amount_positive', 'CHECK(brokerage_amount >= 0)', 'Negative amounts not allowed!'), + ('discount_amount_positive', 'CHECK(discount_amount >= 0)', 'Negative amounts not allowed!'), + ('attendance_fee_amount_positive', 'CHECK(attendance_fee_amount >= 0)', 'Negative amounts not allowed!'), + ('copyright_royalties_amount_positive', 'CHECK(copyright_royalties_amount >= 0)', 'Negative amounts not allowed!'), + ('licence_royalties_amount_positive', 'CHECK(licence_royalties_amount >= 0)', 'Negative amounts not allowed!'), + ('other_income_amount_positive', 'CHECK(other_income_amount >= 0)', 'Negative amounts not allowed!'), + ('allowance_amount_positive', 'CHECK(allowance_amount >= 0)', 'Negative amounts not allowed!'), + ('benefits_in_kind_amount_positive', 'CHECK(benefits_in_kind_amount >= 0)', 'Negative amounts not allowed!'), + ('withholding_tax_amount_positive', 'CHECK(withholding_tax_amount >= 0)', 'Negative amounts not allowed!'), + ] + + @api.depends( + 'parent_id.partner_declare_threshold', + 'fee_amount', 'commission_amount', + 'brokerage_amount', 'discount_amount', + 'attendance_fee_amount', 'copyright_royalties_amount', + 'licence_royalties_amount', 'other_income_amount', + 'allowance_amount', 'benefits_in_kind_amount', + 'withholding_tax_amount') + def _compute_total_amount(self): + for line in self: + amount_total = 0 + for field_name in AMOUNT_FIELDS: + amount_total += line[field_name] + to_declare = False + if line.parent_id: + if amount_total >= line.parent_id.partner_declare_threshold: + to_declare = True + line.to_declare = to_declare + line.amount_total = amount_total + + @api.constrains('partner_siret') + def check_siret(self): + for line in self: + if line.partner_siret: + if len(line.partner_siret) != 14: + raise ValidationError(_( + "SIRET %s is invalid: it must have 14 digits.") + % line.partner_siret) + if not _check_luhn(line.partner_siret): + raise ValidationError(_( + "SIRET %s is invalid: the checksum is wrong.") + % line.partner_siret) + + @api.onchange('partner_id') + def partner_id_change(self): + if self.partner_id and self.partner_id.siren and self.partner_id.nic: + self.partner_siret = self.partner_id.siret diff --git a/l10n_fr_das2/post_install.py b/l10n_fr_das2/post_install.py index a775f1082..20bb8c193 100644 --- a/l10n_fr_das2/post_install.py +++ b/l10n_fr_das2/post_install.py @@ -17,8 +17,9 @@ def setup_das2_accounts(cr, registry): for record in xml_root.xpath('//record'): xmlid = record.attrib['id'] account_code = xmlid.split('_')[-1] - data[account_code] = True - logger.debug('set_unece_on_taxes data=%s', data) + for xfield in record.xpath('field'): + data[account_code] = xfield.text + logger.info('setup_das2_accounts data=%s', data) with api.Environment.manage(): env = api.Environment(cr, SUPERUSER_ID, {}) companies = env['res.company'].search([]) @@ -29,11 +30,12 @@ def setup_das2_accounts(cr, registry): company.display_name, company.id) if company.country_id and company.country_id != env.ref('base.fr'): continue - for account_code in data.keys(): + for account_code, fr_das2 in data.items(): accounts = aao.search([ ('company_id', '=', company.id), ('code', '=ilike', account_code + '%')]) - accounts.write({'fr_das2': True}) - logger.debug( - 'Set fr_das2=True on account IDs %s', accounts.ids) + accounts.write({'fr_das2': fr_das2}) + logger.info( + 'Company %s: set fr_das2=%s on account IDs %s', + company.display_name, fr_das2, accounts.ids) return diff --git a/l10n_fr_das2/views/account_account.xml b/l10n_fr_das2/views/account_account.xml index 5504e201d..788608289 100644 --- a/l10n_fr_das2/views/account_account.xml +++ b/l10n_fr_das2/views/account_account.xml @@ -26,7 +26,10 @@ - + + + + diff --git a/l10n_fr_das2/views/l10n_fr_das2.xml b/l10n_fr_das2/views/l10n_fr_das2.xml index e81c7fb4c..39542b264 100644 --- a/l10n_fr_das2/views/l10n_fr_das2.xml +++ b/l10n_fr_das2/views/l10n_fr_das2.xml @@ -40,8 +40,10 @@ - + + + @@ -65,7 +67,6 @@ - @@ -83,21 +84,22 @@ + DAS2 l10n.fr.das2 - tree,form,graph + tree,form - + + + + + + + + + + + + + + + + + + + + + + @@ -129,7 +152,17 @@ - + + + + + + + + + + + From a3348e6187f1e13667af5836f79af2b0cb9d8455 Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Thu, 6 Feb 2020 19:02:13 +0100 Subject: [PATCH 04/58] Complete re-architecture of the module following a discussion with another accountant Add module l10n_fr_cog --- l10n_fr_cog/__init__.py | 4 + l10n_fr_cog/__manifest__.py | 21 + l10n_fr_cog/data/country.xml | 948 ++++++++++++++++++ l10n_fr_cog/models/__init__.py | 3 + l10n_fr_cog/models/country.py | 14 + l10n_fr_cog/post_install.py | 35 + l10n_fr_cog/views/country.xml | 23 + l10n_fr_das2/__manifest__.py | 11 +- .../data/account_account_template.xml | 32 - l10n_fr_das2/models/__init__.py | 2 +- l10n_fr_das2/models/account_account.py | 49 - l10n_fr_das2/models/l10n_fr_das2.py | 393 +++----- l10n_fr_das2/models/partner.py | 25 + l10n_fr_das2/views/account_account.xml | 49 - l10n_fr_das2/views/l10n_fr_das2.xml | 23 +- l10n_fr_das2/views/partner.xml | 39 + 16 files changed, 1280 insertions(+), 391 deletions(-) create mode 100644 l10n_fr_cog/__init__.py create mode 100644 l10n_fr_cog/__manifest__.py create mode 100644 l10n_fr_cog/data/country.xml create mode 100644 l10n_fr_cog/models/__init__.py create mode 100644 l10n_fr_cog/models/country.py create mode 100644 l10n_fr_cog/post_install.py create mode 100644 l10n_fr_cog/views/country.xml delete mode 100644 l10n_fr_das2/data/account_account_template.xml delete mode 100644 l10n_fr_das2/models/account_account.py create mode 100644 l10n_fr_das2/models/partner.py delete mode 100644 l10n_fr_das2/views/account_account.xml create mode 100644 l10n_fr_das2/views/partner.xml diff --git a/l10n_fr_cog/__init__.py b/l10n_fr_cog/__init__.py new file mode 100644 index 000000000..2e1c9a971 --- /dev/null +++ b/l10n_fr_cog/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- + +from .post_install import set_fr_cog +from . import models diff --git a/l10n_fr_cog/__manifest__.py b/l10n_fr_cog/__manifest__.py new file mode 100644 index 000000000..63cc1c099 --- /dev/null +++ b/l10n_fr_cog/__manifest__.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Copyright 2020 Akretion France (http://www.akretion.com/) +# @author: Alexis de Lattre +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + 'name': u'Code Officiel Géographique', + 'summary': u'Add Code Officiel Géographique (COG) on countries', + 'version': '10.0.1.0.0', + 'category': 'French Localization', + 'author': "Akretion,Odoo Community Association (OCA)", + 'website': 'https://github.com/OCA/l10n-france', + 'license': 'AGPL-3', + 'depends': ['base'], + 'data': [ + 'data/country.xml', + 'views/country.xml', + ], + 'post_init_hook': 'set_fr_cog', + 'installable': True, +} diff --git a/l10n_fr_cog/data/country.xml b/l10n_fr_cog/data/country.xml new file mode 100644 index 000000000..074559aa0 --- /dev/null +++ b/l10n_fr_cog/data/country.xml @@ -0,0 +1,948 @@ + + + + + + + + 99130 + + + + 99247 + + + + 99212 + + + + 99441 + + + + 99425 + + + + 99125 + + + + 99252 + + + + 99431 + + + + 99395 + + + + 99415 + + + + 99505 + + + + 99110 + + + + 99501 + + + + 99135 + + + + 99253 + + + + 99118 + + + + 99434 + + + + 99246 + + + + 99131 + + + + 99331 + + + + 99111 + + + + 99249 + + + + 99321 + + + + 99327 + + + + 99425 + + + + 99225 + + + + 99418 + + + + 99443 + + + + 99416 + + + + 99436 + + + + 99214 + + + + 99103 + + + + 99347 + + + + 99148 + + + + 99429 + + + + 99401 + + + + 99501 + + + + 99323 + + + + 99312 + + + + 99324 + + + + 99140 + + + + 99326 + + + + 99502 + + + + 99417 + + + + 99322 + + + + 99216 + + + + 99419 + + + + 99406 + + + + 99407 + + + + 99396 + + + + 99444 + + + + 99501 + + + + 99254 + + + + 99116 + + + + 99109 + + + + 99399 + + + + 99101 + + + + 99438 + + + + 99408 + + + + 99352 + + + + 99420 + + + + 99106 + + + + 99301 + + + + 99389 + + + + 99317 + + + + 99134 + + + + 99315 + + + + 99105 + + + + 99508 + + + + 99427 + + + + 99516 + + + + 99101 + + + + 99328 + + + + 99435 + + + + 99255 + + + + 99329 + + + + 99133 + + + + 99132 + + + + 99430 + + + + 99304 + + + + 99330 + + + + 99314 + + + + 99126 + + + + 99427 + + + + 99409 + + + + 99505 + + + + 99392 + + + + 99428 + + + + 99230 + + + + 99501 + + + + 99411 + + + + 99119 + + + + 99410 + + + + 99112 + + + + 99231 + + + + 99136 + + + + 99207 + + + + 99132 + + + + 99223 + + + + 99308 + + + + 99203 + + + + 99204 + + + + 99102 + + + + 99127 + + + + 99132 + + + + 99426 + + + + 99222 + + + + 99217 + + + + 99332 + + + + 99257 + + + + 99234 + + + + 99513 + + + + 99397 + + + + 99442 + + + + 99238 + + + + 99239 + + + + 99240 + + + + 99425 + + + + 99256 + + + + 99241 + + + + 99205 + + + + 99439 + + + + 99113 + + + + 99235 + + + + 99302 + + + + 99348 + + + + 99108 + + + + 99137 + + + + 99107 + + + + 99316 + + + + 99350 + + + + 99138 + + + + 99151 + + + + 99120 + + + + 99333 + + + + 99515 + + + + 99156 + + + + 99335 + + + + 99224 + + + + 99242 + + + + 99232 + + + + 99505 + + + + 99336 + + + + 99425 + + + + 99144 + + + + 99390 + + + + 99229 + + + + 99334 + + + + 99405 + + + + 99227 + + + + 99393 + + + + 99311 + + + + 99337 + + + + 99501 + + + + 99338 + + + + 99412 + + + + 99135 + + + + 99103 + + + + 99215 + + + + 99507 + + + + 99502 + + + + 99502 + + + + 99250 + + + + 99413 + + + + 99422 + + + + 99510 + + + + 99220 + + + + 99213 + + + + 99122 + + + + 99503 + + + + 99432 + + + + 99261 + + + + 99139 + + + + 99517 + + + + 99421 + + + + 99248 + + + + 99114 + + + + 99121 + + + + 99123 + + + + 99340 + + + + 99201 + + + + 99512 + + + + 99398 + + + + 99343 + + + + 99104 + + + + 99226 + + + + 99306 + + + + 99145 + + + + 99103 + + + + 99117 + + + + 99342 + + + + 99128 + + + + 99341 + + + + 99318 + + + + 99437 + + + + 99349 + + + + 99394 + + + + 99414 + + + + 99445 + + + + 99206 + + + + 99391 + + + + 99425 + + + + 99344 + + + + 99345 + + + + 99219 + + + + 99259 + + + + 99502 + + + + 99260 + + + + 99351 + + + + 99509 + + + + 99208 + + + + 99433 + + + + 99511 + + + + 99236 + + + + 99309 + + + + 99155 + + + + 99339 + + + + 99132 + + + + 99404 + + + + 99423 + + + + 99258 + + + + 99129 + + + + 99440 + + + + 99424 + + + + 99425 + + + + 99432 + + + + 99243 + + + + 99514 + + + + 99506 + + + + 99251 + + + + 99303 + + + + 99346 + + + + 99310 + + + + 99157 + + + + diff --git a/l10n_fr_cog/models/__init__.py b/l10n_fr_cog/models/__init__.py new file mode 100644 index 000000000..f2d611a89 --- /dev/null +++ b/l10n_fr_cog/models/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import country diff --git a/l10n_fr_cog/models/country.py b/l10n_fr_cog/models/country.py new file mode 100644 index 000000000..0b2231828 --- /dev/null +++ b/l10n_fr_cog/models/country.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- +# Copyright 2020 Akretion France (http://www.akretion.com/) +# @author: Alexis de Lattre +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class ResCountry(models.Model): + _inherit = 'res.country' + + fr_cog = fields.Integer( + string=u'Code Officiel Géographique', + help=u"Code Officiel Géographique, by INSEE") diff --git a/l10n_fr_cog/post_install.py b/l10n_fr_cog/post_install.py new file mode 100644 index 000000000..76757e98d --- /dev/null +++ b/l10n_fr_cog/post_install.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +# Copyright 2020 Akretion France (http://www.akretion.com/) +# @author: Alexis de Lattre +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import api, SUPERUSER_ID +from odoo.tools import file_open +from lxml import etree +import logging +logger = logging.getLogger(__name__) + + +# Countries data is provided in the base module with noupdate="1" +# That's why we need this post-install script +def set_fr_cog(cr, registry): + f = file_open( + 'l10n_fr_cog/data/country.xml', 'rb') + xml_root = etree.parse(f) + data = {} + for record in xml_root.xpath('//record'): + xmlid = record.attrib['id'] + data[xmlid] = {} + for xfield in record.xpath('field'): + if xfield.attrib and xfield.attrib.get('name') == 'fr_cog': + data[xmlid] = int(xfield.text) + logger.debug('set_fr_cog data=%s', data) + with api.Environment.manage(): + env = api.Environment(cr, SUPERUSER_ID, {}) + for xmlid, fr_cog in data.items(): + country = env.ref(xmlid) + country.fr_cog = fr_cog + logger.debug( + 'Country ID %d xmlid %s updated with fr_cog=%d', + country.id, xmlid, fr_cog) + return diff --git a/l10n_fr_cog/views/country.xml b/l10n_fr_cog/views/country.xml new file mode 100644 index 000000000..0373c4728 --- /dev/null +++ b/l10n_fr_cog/views/country.xml @@ -0,0 +1,23 @@ + + + + + + + + l10n_fr_cog.res.country.form + res.country + + + + + + + + + + diff --git a/l10n_fr_das2/__manifest__.py b/l10n_fr_das2/__manifest__.py index 3f3c079c4..8a7da1a3c 100644 --- a/l10n_fr_das2/__manifest__.py +++ b/l10n_fr_das2/__manifest__.py @@ -11,19 +11,22 @@ 'summary': 'DAS2 (France)', 'author': 'Akretion,Odoo Community Association (OCA)', 'website': 'https://github.com/OCA/l10n-france', - 'depends': ['account_fiscal_year', 'l10n_fr', 'l10n_fr_siret'], + 'depends': [ + 'account_fiscal_year', + 'l10n_fr', + 'l10n_fr_siret', + 'l10n_fr_cog', + ], 'external_dependencies': { 'python': ['unidecode'], }, 'data': [ 'security/das2_security.xml', 'security/ir.model.access.csv', - 'data/account_account_template.xml', 'views/l10n_fr_das2.xml', - 'views/account_account.xml', + 'views/partner.xml', 'views/account_config_settings.xml', ], - 'post_init_hook': 'setup_das2_accounts', 'installable': True, 'application': True, } diff --git a/l10n_fr_das2/data/account_account_template.xml b/l10n_fr_das2/data/account_account_template.xml deleted file mode 100644 index 30150aa02..000000000 --- a/l10n_fr_das2/data/account_account_template.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - commission - - - - fee - - - - commission - - - - attendance_fee - - - - copyright_royalties - - - - diff --git a/l10n_fr_das2/models/__init__.py b/l10n_fr_das2/models/__init__.py index 0d009227c..02a7fcc3e 100644 --- a/l10n_fr_das2/models/__init__.py +++ b/l10n_fr_das2/models/__init__.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- -from . import account_account from . import l10n_fr_das2 +from . import partner from . import company from . import account_config_settings diff --git a/l10n_fr_das2/models/account_account.py b/l10n_fr_das2/models/account_account.py deleted file mode 100644 index 0d7f6e64e..000000000 --- a/l10n_fr_das2/models/account_account.py +++ /dev/null @@ -1,49 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright 2020 Akretion France (http://www.akretion.com/) -# @author: Alexis de Lattre -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - -from odoo import api, fields, models, _ -from odoo.exceptions import ValidationError - - -class AccountAccount(models.Model): - _inherit = 'account.account' - - fr_das2 = fields.Selection('get_das2_types', string='DAS2') - - @api.model - def get_das2_types(self): - res = [ - ('fee', u'Honoraires et vacations'), - ('commission', u'Commissions'), - ('brokerage', u'Courtages'), - ('discount', u'Ristournes'), - ('attendance_fee', u'Jetons de présence'), - ('copyright_royalties', u"Droits d'auteur"), - ('licence_royalties', u"Droits d'inventeur"), - ('other_income', u'Autre rémunérations'), - ('allowance', u'Indemnités et remboursements'), - ('withholding_tax', u'Retenue à la source'), - ] - return res - - @api.constrains('fr_das2', 'user_type_id') - def das2_check(self): - exp_acc_type = self.env.ref('account.data_account_type_expenses') - for account in self: - if account.fr_das2 and account.user_type_id != exp_acc_type: - raise ValidationError(_( - "The DAS2 option cannot be activated on account '%s' " - "because it is not an expense account.") - % account.display_name) - - -class AccountAccountTemplate(models.Model): - _inherit = 'account.account.template' - - fr_das2 = fields.Selection('get_das2_types', string='DAS2') - - @api.model - def get_das2_types(self): - return self.env['account.account'].get_das2_types() diff --git a/l10n_fr_das2/models/l10n_fr_das2.py b/l10n_fr_das2/models/l10n_fr_das2.py index 4736ef4d3..a12ed3a02 100644 --- a/l10n_fr_das2/models/l10n_fr_das2.py +++ b/l10n_fr_das2/models/l10n_fr_das2.py @@ -5,8 +5,8 @@ from odoo import api, fields, models, _ from odoo.exceptions import UserError, ValidationError -from odoo.tools import float_compare, float_is_zero, float_round from dateutil.relativedelta import relativedelta +from datetime import datetime from odoo.addons.l10n_fr_siret.models.partner import _check_luhn from unidecode import unidecode import base64 @@ -14,7 +14,6 @@ logger = logging.getLogger(__name__) - try: from unidecode import unidecode except ImportError: @@ -22,7 +21,8 @@ logger.debug('Cannot import unidecode') -FRANCE_CODES = ('FR', 'GP', 'MQ', 'GF', 'RE', 'YT') +FRANCE_CODES = ('FR', 'GP', 'MQ', 'GF', 'RE', 'YT', 'NC', + 'PF', 'TF', 'MF', 'BL', 'PM', 'WF') AMOUNT_FIELDS = [ 'fee_amount', 'commission_amount', 'brokerage_amount', 'discount_amount', 'attendance_fee_amount', 'copyright_royalties_amount', @@ -33,18 +33,13 @@ class L10nFrDas2(models.Model): _name = 'l10n.fr.das2' _inherit = ['mail.thread'] - _order = 'date_start desc' + _order = 'year desc' _description = 'DAS2' - date_range_id = fields.Many2one( - 'date.range', string='Fiscal Year', ondelete='restrict', copy=False, - required=True, domain="[('type_id.fiscal_year', '=', True)]", - states={'done': [('readonly', True)]}, track_visibility='onchange', - default=lambda self: self._default_date_range()) - date_start = fields.Date( - related='date_range_id.date_start', store=True, readonly=True) - date_end = fields.Date( - related='date_range_id.date_end', store=True, readonly=True) + year = fields.Integer( + string='Year', required=True, states={'done': [('readonly', True)]}, + track_visibility='onchange', + default=lambda self: self._default_year()) state = fields.Selection([ ('draft', 'Draft'), ('done', 'Done'), @@ -57,10 +52,10 @@ class L10nFrDas2(models.Model): currency_id = fields.Many2one( related='company_id.currency_id', readonly=True, store=True, string='Company Currency') - source_journal_ids = fields.Many2many( + payment_journal_ids = fields.Many2many( 'account.journal', - string='Source Journals', required=True, - default=lambda self: self._default_source_journals(), + string='Payment Journals', required=True, + default=lambda self: self._default_payment_journals(), states={'done': [('readonly', True)]}) line_ids = fields.One2many( 'l10n.fr.das2.line', 'parent_id', string='Lines', @@ -77,33 +72,31 @@ class L10nFrDas2(models.Model): 'ir.attachment', string='File Export', readonly=True) _sql_constraints = [( - 'fiscal_year_company_uniq', - 'unique(company_id, date_range_id)', - 'A DAS2 already exists for this fiscal year!')] + 'year_company_uniq', + 'unique(company_id, year)', + 'A DAS2 already exists for that year!')] - def _default_source_journals(self): + @api.model + def _default_payment_journals(self): res = [] - src_journals = self.env['account.journal'].search([ - ('type', '=', 'purchase'), + pay_journals = self.env['account.journal'].search([ + ('type', 'in', ('bank', 'cash')), ('company_id', '=', self.env.user.company_id.id)]) - if src_journals: - res = src_journals.ids + if pay_journals: + res = pay_journals.ids return res @api.model - def _default_date_range(self): - date_range = self.env['date.range'].search([ - ('company_id', '=', self.env.user.company_id.id), - ('type_id.fiscal_year', '=', True), - ('date_end', '<', fields.Date.context_today(self)), - ], order='date_end desc', limit=1) - return date_range - - @api.depends('date_range_id') + def _default_year(self): + today = datetime.today() + prev_year = today - relativedelta(years=1) + return prev_year.year + + @api.depends('year') def name_get(self): res = [] for rec in self: - res.append((rec.id, 'DAS2 %s' % rec.date_range_id.name)) + res.append((rec.id, 'DAS2 %s' % rec.year)) return res def done(self): @@ -114,15 +107,19 @@ def back2draft(self): self.state = 'draft' return + def unlink(self): + for rec in self: + if rec.state == 'done': + raise UserError(_( + "Cannot delete declaration %s in done state.") + % rec.display_name) + return super(L10nFrDas2, self).unlink() + def generate_lines(self): self.ensure_one() - amo = self.env['account.move'] - ato = self.env['account.tax'] amlo = self.env['account.move.line'] - aao = self.env['account.account'] lfdlo = self.env['l10n.fr.das2.line'] company = self.company_id - ccur_prec = company.currency_id.rounding if not company.country_id: raise UserError(_( "Country not set on company '%s'.") % company.display_name) @@ -144,204 +141,95 @@ def generate_lines(self): "company '%s'.") % company.display_name) self.partner_declare_threshold =\ company.fr_das2_partner_declare_threshold - das2_accounts = self.env['account.account'].search([ - ('company_id', '=', company.id), - ('fr_das2', '!=', False), - ]) - if not das2_accounts: - raise UserError(_( - "There are no expense accounts for DAS2 in company '%s'.") - % company.display_name) - das2acc2type = {} - for das2_account in das2_accounts: - das2acc2type[das2_account] = das2_account.fr_das2 - # delete existing lines + das2_partners = self.env['res.partner'].search([ + ('parent_id', '=', False), + ('fr_das2_type', '!=', False)]) self.line_ids.unlink() - vat_deduc_accounts = aao.search([ - '|', '|', - ('code', '=like', '4452%'), # also include intracom due VAT - ('code', '=like', '44562%'), - ('code', '=like', '44566%'), - ('internal_type', '=', 'other'), + if not das2_partners: + raise UserError(_( + "There are no partners configured for DAS2.")) + base_domain = [ + ('company_id', '=', self.company_id.id), + ('date', '>=', '%d-01-01' % self.year), + ('date', '<=', '%d-12-31' % self.year), + ('journal_id', 'in', self.payment_journal_ids.ids), + ('balance', '!=', 0), + ] + for partner in das2_partners: + mlines = amlo.search(base_domain + [ + ('partner_id', '=', partner.id), + ('account_id', '=', partner.property_account_payable_id.id), + ]) + note = [] + amount = 0.0 + for mline in mlines: + amount += mline.balance + if mline.full_reconcile_id: + rec = _('reconciliation mark %s') % mline.full_reconcile_id.name + else: + rec = _('not reconciled') + note.append(_( + "Payment dated %s in journal '%s': %.2f € (%s, journal entry %s)") % ( + mline.date, + mline.journal_id.display_name, + mline.balance, + rec, + mline.move_id.name)) + if note: + field_name = '%s_amount' % partner.fr_das2_type + vals = { + field_name: int(round(amount, 2)), + 'parent_id': self.id, + 'partner_id': partner.id, + 'job': partner.fr_das2_job, + 'note': '\n'.join(note), + } + if partner.siren and partner.nic: + vals['partner_siret'] = partner.siret + lfdlo.create(vals) + self.add_warning_in_chatter(das2_partners) + + def add_warning_in_chatter(self, das2_partners): + amlo = self.env['account.move.line'] + aao = self.env['account.account'] + ajo = self.env['account.journal'] + rpo = self.env['res.partner'] + company = self.company_id + # The code below is just to write warnings in chatter + purchase_journals = ajo.search([ + ('type', '=', 'purchase'), ('company_id', '=', company.id)]) - if not vat_deduc_accounts: - raise UserError(_('No VAT deductible accounts found')) - logger.info( - 'VAT deductible accounts: %s', [a.code for a in vat_deduc_accounts]) - supplier_accounts = aao.search([ + das2_accounts = aao.search([ ('company_id', '=', company.id), - ('reconcile', '=', True), - ('internal_type', '=', 'payable'), + '|', '|', '|', '|', + ('code', '=like', '6222%'), + ('code', '=like', '6226%'), + ('code', '=like', '6228%'), + ('code', '=like', '653%'), + ('code', '=like', '6516%'), ]) - if not supplier_accounts: - raise UserError(_('No supplier accounts found.')) - expense_accounts = aao.search([ + rg_res = amlo.read_group([ ('company_id', '=', company.id), - ('internal_type', '=', 'other'), - '|', ('code', '=like', '6%'), ('code', '=like', '2%'), - ]) - # I include 2% accounts so that VAT prorata works - if not expense_accounts: - raise UserError(_("No expense accounts found.")) - speed_vattax2rate = {} - vattaxes = ato.search([ - ('company_id', '=', company.id), - ('type_tax_use', '=', 'purchase'), - ('amount_type', '=', 'percent'), - ('amount', '!=', False)]) - for vattax in vattaxes: - if not float_is_zero(vattax.amount, precision_digits=4): - speed_vattax2rate[vattax.id] = vattax.amount - - date_end_scan_dt = fields.Date.from_string(self.date_end)\ - + relativedelta(months=5) - date_start_scan_dt = fields.Date.from_string(self.date_start)\ - - relativedelta(years=1) - base_domain = [ - ('company_id', '=', self.company_id.id), - ('date', '>=', fields.Date.to_string(date_start_scan_dt)), - ('date', '<=', fields.Date.to_string(date_end_scan_dt)), - ('journal_id', 'in', self.source_journal_ids.ids), - ] - res = {} # key = partner, value = {field: amount, 'note': []} - - rg_res = amlo.read_group( - base_domain + [('account_id', 'in', das2_accounts.ids)], - ['move_id'], ['move_id']) - for rg_re in rg_res: - move_id = rg_re['move_id'][0] - move = amo.browse(move_id) - logger.info('Processing move %s ID %d', move.name, move.id) - # Inspired by account_vat_prorata - tmp = { - 'total_vat': 0.0, - 'total_with_vat': 0.0, - 'total_exp': 0.0, - 'exps_have_tax_ids': False, - 'partner': False, - 'exps': {}, # expenses - # key = id - # value = {'weight_tax_ids', 'weight_no_tax_ids', 'bal': account_id - 'payments': [], - 'total_payments': 0.0, - 'total_theoric_vat': 0.0, # for prorata - } - for line in move.line_ids: - if float_is_zero(line.balance, precision_rounding=ccur_prec): - continue - # VAT line - if line.account_id in vat_deduc_accounts: - tmp['total_vat'] += line.balance - # Payable line - elif line.account_id in supplier_accounts: - if not line.partner_id: - raise UserError(_( - "Move line '%s' with account '%s' " - "dated %s (ID %d) has no partner.") % ( - line.name, line.account_id.display_name, - line.date, line.id)) - tmp['total_with_vat'] -= line.balance - partner = line.partner_id - tmp['partner'] = partner - if line.full_reconcile_id: - for rec_line in line.full_reconcile_id.reconciled_line_ids: - if ( - rec_line != line and - rec_line.journal_id.type != 'purchase' - and - rec_line.date >= self.date_start and - rec_line.date <= self.date_end and - rec_line.partner_id == partner): - tmp['payments'].append({ - 'date': rec_line.date, - 'amount': rec_line.balance}) - tmp['total_payments'] += rec_line.balance - # Expense line - elif line.account_id in expense_accounts: - tmp['total_exp'] += line.balance - if line.tax_ids and len(line.tax_ids) == 1 and line.tax_ids[0].id in speed_vattax2rate: - - tmp['exps_have_tax_ids'] = True - theoric_vat_amount = speed_vattax2rate[line.tax_ids[0].id] * line.balance / 100.0 - tmp['exps'][line.id] = { - 'bal': line.balance, - 'theoric_vat_amount': theoric_vat_amount, - 'account': line.account_id, - 'das2type': das2acc2type.get(line.account_id), - 'date': line.date, - 'name': line.name, - } - tmp['total_theoric_vat'] += theoric_vat_amount - else: - tmp['exps'][line.id] = { - 'bal': line.balance, - 'account': line.account_id, - 'das2type': das2acc2type.get(line.account_id), - 'theoric_vat_amount': 0, - 'date': line.date, - 'name': line.name, - } - - # Check - if float_compare(tmp['total_with_vat'], tmp['total_exp'] + tmp['total_vat'], precision_rounding=ccur_prec): - raise UserError(_("Move %s (ID %d) has a total with VAT (%s) with is different from the sum of VAT amount (%s) plus total expense (%s). This should not happen in a purchase journal.") % (move.name, move.id, tmp['total_with_vat'], tmp['total_vat'], tmp['total_exp'])) - - # process - if not float_is_zero(tmp['total_with_vat'], precision_rounding=ccur_prec) and tmp['payments'] and not float_is_zero(tmp['total_exp'], precision_rounding=ccur_prec): - payment_ratio = 100 - if float_compare(tmp['total_payments'], tmp['total_with_vat'], precision_rounding=ccur_prec): - payment_ratio = round(100 * tmp['total_payments'] / tmp['total_with_vat'], 2) - logger.info('Move ID %s selected with payment_ratio %s', move.id, payment_ratio) - for exp_line_id, exp_line_val in tmp['exps'].items(): - if exp_line_val['das2type']: - if tmp['exps_have_tax_ids'] and not float_is_zero(tmp['total_theoric_vat'], precision_rounding=ccur_prec): - bal_with_vat = float_round(exp_line_val['bal'] + tmp['total_vat'] * exp_line_val['theoric_vat_amount'] / tmp['total_theoric_vat'], precision_rounding=ccur_prec) - else: - bal_with_vat = float_round(exp_line_val['bal'] + tmp['total_vat'] * exp_line_val['bal'] / tmp['total_exp'], precision_rounding=ccur_prec) - paid_amount = bal_with_vat * payment_ratio / 100.0 - note = _( - u"Expense Line ID %d '%s' supplier '%s' dated %s " - u"account %s DAS2 type '%s': " - u"amount without VAT: %s €, amount with VAT: %s €, " - u"payment ratio %s %%, paid amount with VAT: %s €. " - u"Payments of related move: %s.") % ( - exp_line_id, - exp_line_val['name'], - tmp['partner'].display_name, - exp_line_val['date'], - exp_line_val['account'].code, - exp_line_val['das2type'], - exp_line_val['bal'], - bal_with_vat, - payment_ratio, - paid_amount, - ','.join([ - _(u'%s € on %s') % (x['amount'], x['date']) - for x in tmp['payments']]), - ) - - field_name = '%s_amount' % exp_line_val['das2type'] - if partner in res: - res[partner]['note'].append(note) - if field_name in res[partner]: - res[partner][field_name] += paid_amount - else: - res[partner][field_name] = paid_amount - else: - res[partner] = {field_name: paid_amount, 'note': [note]} - - for partner, vals in res.items(): - for key, value in vals.items(): - if key.endswith('_amount'): - vals[key] = int(round(value)) - vals.update({ - 'parent_id': self.id, - 'partner_id': partner.id, - 'note': '\n'.join(vals['note']) - }) - if partner.siren and partner.nic: - vals['partner_siret'] = partner.siret - lfdlo.create(vals) + ('date', '>=', '%d-01-01' % self.year), + ('date', '<=', '%d-12-31' % self.year), + ('journal_id', 'in', purchase_journals.ids), + ('partner_id', '!=', False), + ('partner_id', 'not in', das2_partners.ids), + ('account_id', 'in', das2_accounts.ids), + ('balance', '!=', 0), + ], ['partner_id'], ['partner_id']) + if rg_res: + msg = _( + "

The following partners are not configured for DAS2 but " + "they have expenses in some accounts that indicate " + "that maybe they should be configured for DAS2:

    ") + for rg_re in rg_res: + partner = rpo.browse(rg_re['partner_id'][0]) + msg += '
  • %s' % ( + partner.id, + partner.display_name) + msg += '
' + self.message_post(msg) @api.model def _prepare_field(self, field_name, partner, value, size, required=False, numeric=False): @@ -381,11 +269,40 @@ def _prepare_field(self, field_name, partner, value, size, required=False, numer def _prepare_address(self, partner): cstreet2 = self._prepare_field('Street2', partner, partner.street2, 32) - cstreet = self._prepare_field('Street', partner, partner.street, 26) - ccity = self._prepare_field('City', partner, partner.city, 26, True) - czip = self._prepare_field('Zip', partner, partner.zip, 5, True) + cstreet = self._prepare_field('Street', partner, partner.street, 32) + # specs section 5.4 : only bureau distributeur and code postal are + # required. And they say it is tolerated to set city as + # bureau distributeur + # And we don't set the commune field because we put the same info + # in bureau distributeur (section 5.4.1.3) + # specs section 5.4 and 5.4.1.2.b: it is possible to set the field + # "Adresse voie" without structuration on 32 chars => that's what we do + if not partner.city: + raise UserError(_( + "Missing city on partner '%s'.") % partner.display_name) - caddress = cstreet2 + ' ' + '0' * 4 + ' ' + ' ' + cstreet + '0' * 5 + ' ' + ccity + czip + ' ' + ' ' * 26 + if partner.country_id and partner.country_id.code not in FRANCE_CODES: + if not partner.country_id.fr_cog: + raise UserError(_( + u"Missing Code Officiel Géographique on country '%s'.") + % partner.country_id.display_name) + cog = self._prepare_field( + 'COG', partner, partner.country_id.fr_cog, 5, True, numeric=True) + raw_country_name = partner.with_context(lang='fr_FR').country_id.name + country_name = self._prepare_field( + 'Nom du pays', partner, raw_country_name, 26, True) + raw_commune = partner.city + if partner.zip: + raw_commune = '%s %s' % (partner.zip, partner.city) + commune = self._prepare_field('Commune', partner, raw_commune, 26, True) + caddress = cstreet2 + ' ' + cstreet + cog + ' ' + commune + cog + ' ' + country_name + # According to the specs, we should have some code specific for DOM-TOM + # But it's not easy, because we are supposed to give the INSEE code + # of the city, not of the territory => we don't handle that for the moment + else: + ccity = self._prepare_field('City', partner, partner.city, 26, True) + czip = self._prepare_field('Zip', partner, partner.zip, 5, True) + caddress = cstreet2 + ' ' + cstreet + '0' * 5 + ' ' + ' ' * 26 + czip + ' ' + ccity assert len(caddress) == 129 return caddress @@ -425,7 +342,8 @@ def generate_file(self): cape = self._prepare_field('APE', cpartner, company.ape, 5, True) cname = self._prepare_field('Name', cpartner, company.name, 50, True) file_type = 'X' # tous déclarants honoraires seuls - year = str(fields.Date.from_string(self.date_start).year) + year = str(self.year) + assert len(year) == 4 caddress = self._prepare_address(cpartner) cprefix = csiret + '01' + year + '5' # line 010 Company header @@ -520,8 +438,7 @@ def generate_file(self): % (len(fline), fline)) file_content = '\n'.join(flines) file_content_encoded = file_content.encode('latin1') - filename = 'DAS2_%s.txt' % ( - unidecode(self.date_range_id.name.replace(' ', '_'))) + filename = 'DAS2_%s.txt' % self.year attach = self.env['ir.attachment'].create({ 'name': filename, 'res_id': self.id, diff --git a/l10n_fr_das2/models/partner.py b/l10n_fr_das2/models/partner.py new file mode 100644 index 000000000..720c920a0 --- /dev/null +++ b/l10n_fr_das2/models/partner.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Copyright 2020 Akretion France (http://www.akretion.com/) +# @author: Alexis de Lattre +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class ResPartner(models.Model): + _inherit = 'res.partner' + + fr_das2_type = fields.Selection([ + ('fee', u'Honoraires et vacations'), + ('commission', u'Commissions'), + ('brokerage', u'Courtages'), + ('discount', u'Ristournes'), + ('attendance_fee', u'Jetons de présence'), + ('copyright_royalties', u"Droits d'auteur"), + ('licence_royalties', u"Droits d'inventeur"), + ('other_income', u'Autre rémunérations'), + ('allowance', u'Indemnités et remboursements'), + ], string='DAS2 Type', track_visibility='onchange') + fr_das2_job = fields.Char( + string='DAS2 Job', size=30, + help="Used in the field 'Profession' of DAS2.") diff --git a/l10n_fr_das2/views/account_account.xml b/l10n_fr_das2/views/account_account.xml deleted file mode 100644 index 788608289..000000000 --- a/l10n_fr_das2/views/account_account.xml +++ /dev/null @@ -1,49 +0,0 @@ - - - - - - - - l10n.fr.das2.account.account.form - account.account - - - - - - - - - - l10n.fr.das2.account.account.search - account.account - - - - - - - - - - - - - - l10n.fr.das2.account.account.template.form - account.account.template - - - - - - - - - - diff --git a/l10n_fr_das2/views/l10n_fr_das2.xml b/l10n_fr_das2/views/l10n_fr_das2.xml index 39542b264..8e777fea7 100644 --- a/l10n_fr_das2/views/l10n_fr_das2.xml +++ b/l10n_fr_das2/views/l10n_fr_das2.xml @@ -34,10 +34,8 @@ - - - - + + @@ -65,7 +63,7 @@ l10n.fr.das2 - + @@ -84,18 +82,6 @@ - - DAS2 l10n.fr.das2 @@ -113,7 +99,7 @@
- + @@ -152,6 +138,7 @@ + diff --git a/l10n_fr_das2/views/partner.xml b/l10n_fr_das2/views/partner.xml new file mode 100644 index 000000000..948831c9f --- /dev/null +++ b/l10n_fr_das2/views/partner.xml @@ -0,0 +1,39 @@ + + + + + + + + l10n.fr.das2.res.partner.form + res.partner + + + + + + + + + + + + + l10n.fr.das2.res.partner.search + res.partner + + + + + + + + + + + + From 27c2c7090db80f54f1815a9d1868e1216c76c540 Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Thu, 6 Feb 2020 19:06:29 +0100 Subject: [PATCH 05/58] Add filter on to_declare for file generation --- l10n_fr_das2/models/l10n_fr_das2.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/l10n_fr_das2/models/l10n_fr_das2.py b/l10n_fr_das2/models/l10n_fr_das2.py index a12ed3a02..ac99dd865 100644 --- a/l10n_fr_das2/models/l10n_fr_das2.py +++ b/l10n_fr_das2/models/l10n_fr_das2.py @@ -358,7 +358,9 @@ def generate_file(self): cprefix + '020' + ' ' * 14 + cape + '0' * 14 + ' ' * 41 + cname + caddress + ' ' * 40 + ' ' * 53 + 'N' * 6 + ' ' * 296) - for line in self.line_ids: + i = 0 + for line in self.line_ids.filtered(lambda x: x.to_declare): + i += 1 partner = line.partner_id if ( partner.country_id and @@ -424,7 +426,7 @@ def generate_file(self): '0' * 12 * 5 + ' ' * 74 + contact_name + contact_phone + contact_email + ' ' * 76) - lines_number = self._prepare_field('Number of lines', cpartner, len(self.line_ids), 6, numeric=True) + lines_number = self._prepare_field('Number of lines', cpartner, i, 6, numeric=True) flines.append( csiren + '9' * 12 + '310' + '00001' + '0' * 6 + lines_number + '0' * 6 * 3 + ' ' * 18 + '9' * 12 * 9 + From 07dec060adfbf43e768eb49cb95bc1ebdbc951a0 Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Fri, 7 Feb 2020 17:58:34 +0100 Subject: [PATCH 06/58] DAS2: Fix file format Small improvements --- l10n_fr_das2/models/l10n_fr_das2.py | 28 +++++++++++++++++++++++----- l10n_fr_das2/views/l10n_fr_das2.xml | 1 + 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/l10n_fr_das2/models/l10n_fr_das2.py b/l10n_fr_das2/models/l10n_fr_das2.py index ac99dd865..af6651bac 100644 --- a/l10n_fr_das2/models/l10n_fr_das2.py +++ b/l10n_fr_das2/models/l10n_fr_das2.py @@ -62,6 +62,11 @@ class L10nFrDas2(models.Model): states={'done': [('readonly', True)]}) partner_declare_threshold = fields.Integer( string='Partner Declaration Threshold', readonly=True) + dads_type = fields.Selection([ + ('5', u"La société verse des salaires"), + ('6', u"La société ne verse pas de salaires"), + ], 'Type DADS', required=True, + default=lambda self: self._default_dads_type()) # option for draft moves ? contact_id = fields.Many2one( 'res.partner', string='Administrative Contact', @@ -76,6 +81,15 @@ class L10nFrDas2(models.Model): 'unique(company_id, year)', 'A DAS2 already exists for that year!')] + @api.model + def _default_dads_type(self): + previous_decl = self.search( + [('dads_type', '!=', False)], order='year desc', limit=1) + if previous_decl: + return previous_decl.dads_type + else: + return '5' + @api.model def _default_payment_journals(self): res = [] @@ -345,7 +359,7 @@ def generate_file(self): year = str(self.year) assert len(year) == 4 caddress = self._prepare_address(cpartner) - cprefix = csiret + '01' + year + '5' + cprefix = csiret + '01' + year + self.dads_type # line 010 Company header flines = [] flines.append( @@ -354,6 +368,9 @@ def generate_file(self): caddress + ' ' + ' ' * 288) # ligne 020 Etablissement header + # We don't add a field for profession on the company + # because it's not even a field on the paper form! + # We only set profession for suppliers flines.append( cprefix + '020' + ' ' * 14 + cape + '0' * 14 + ' ' * 41 + cname + caddress + ' ' * 40 + ' ' * 53 + 'N' * 6 + ' ' * 296) @@ -416,7 +433,7 @@ def generate_file(self): contact_name = self._prepare_field('Administrative contact name', contact, contact.name, 50) contact_email = self._prepare_field('Administrative contact email', contact, contact.email, 60) phone = contact.phone or contact.mobile - phone = phone.replace(' ', '').replace('.', '').replace('-', '') + phone = phone.replace(' ', '').replace('.', '').replace('-', '').replace(u'\u00A0', '') if phone.startswith('+33'): phone = '0%s' % phone[3:] contact_phone = self._prepare_field('Administrative contact phone', contact, phone, 10) @@ -429,7 +446,7 @@ def generate_file(self): lines_number = self._prepare_field('Number of lines', cpartner, i, 6, numeric=True) flines.append( csiren + '9' * 12 + '310' + '00001' + '0' * 6 + lines_number + - '0' * 6 * 3 + ' ' * 18 + '9' * 12 * 9 + + '0' * 6 * 3 + ' ' * 18 + '0' * 12 * 9 + ''.join(total_fields_list) + ' ' * 12 + '0' * 12 * 2 + '0' * 6 + '0' * 12 * 5 + ' ' * 253) for fline in flines: @@ -440,7 +457,8 @@ def generate_file(self): % (len(fline), fline)) file_content = '\n'.join(flines) file_content_encoded = file_content.encode('latin1') - filename = 'DAS2_%s.txt' % self.year + filename = 'DAS2_%s_%s.txt' % ( + self.year, company.name.replace(' ', '_')) attach = self.env['ir.attachment'].create({ 'name': filename, 'res_id': self.id, @@ -486,7 +504,7 @@ class L10nFrDas2Line(models.Model): domain=[('parent_id', '=', False)], states={'done': [('readonly', True)]}, required=True) partner_siret = fields.Char( - string='Partner SIRET', size=14, states={'done': [('readonly', True)]}) + string='SIRET', size=14, states={'done': [('readonly', True)]}) company_id = fields.Many2one( related='parent_id.company_id', store=True, readonly=True) currency_id = fields.Many2one( diff --git a/l10n_fr_das2/views/l10n_fr_das2.xml b/l10n_fr_das2/views/l10n_fr_das2.xml index 8e777fea7..fdd1e0dee 100644 --- a/l10n_fr_das2/views/l10n_fr_das2.xml +++ b/l10n_fr_das2/views/l10n_fr_das2.xml @@ -39,6 +39,7 @@ + From 2bd3964e93682723f28f4b607f974a1c4dde5b17 Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Sat, 8 Feb 2020 00:46:51 +0100 Subject: [PATCH 07/58] Add README for l10n_fr_cog and l10n_fr_das2 Add account 6221 in the warning method --- l10n_fr_cog/readme/CONTRIBUTORS.rst | 1 + l10n_fr_cog/readme/DESCRIPTION.rst | 3 +++ l10n_fr_das2/models/l10n_fr_das2.py | 3 ++- l10n_fr_das2/readme/CONFIGURE.rst | 10 ++++++++++ l10n_fr_das2/readme/CONTRIBUTORS.rst | 1 + l10n_fr_das2/readme/DESCRIPTION.rst | 3 +++ l10n_fr_das2/readme/USAGE.rst | 9 +++++++++ 7 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 l10n_fr_cog/readme/CONTRIBUTORS.rst create mode 100644 l10n_fr_cog/readme/DESCRIPTION.rst create mode 100644 l10n_fr_das2/readme/CONFIGURE.rst create mode 100644 l10n_fr_das2/readme/CONTRIBUTORS.rst create mode 100644 l10n_fr_das2/readme/DESCRIPTION.rst create mode 100644 l10n_fr_das2/readme/USAGE.rst diff --git a/l10n_fr_cog/readme/CONTRIBUTORS.rst b/l10n_fr_cog/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000..ff65d68ce --- /dev/null +++ b/l10n_fr_cog/readme/CONTRIBUTORS.rst @@ -0,0 +1 @@ +* Alexis de Lattre diff --git a/l10n_fr_cog/readme/DESCRIPTION.rst b/l10n_fr_cog/readme/DESCRIPTION.rst new file mode 100644 index 000000000..d8c839907 --- /dev/null +++ b/l10n_fr_cog/readme/DESCRIPTION.rst @@ -0,0 +1,3 @@ +This module adds the *Code Officiel Géographique* of `INSEE `_ on countries. All countries except France and DOM-TOMs (and some very particular territories) have this code. + +This module is used by other modules of the French localization such as the DAS2 module. diff --git a/l10n_fr_das2/models/l10n_fr_das2.py b/l10n_fr_das2/models/l10n_fr_das2.py index af6651bac..153fe3bda 100644 --- a/l10n_fr_das2/models/l10n_fr_das2.py +++ b/l10n_fr_das2/models/l10n_fr_das2.py @@ -215,7 +215,8 @@ def add_warning_in_chatter(self, das2_partners): ('company_id', '=', company.id)]) das2_accounts = aao.search([ ('company_id', '=', company.id), - '|', '|', '|', '|', + '|', '|', '|', '|', '|', + ('code', '=like', '6221%'), ('code', '=like', '6222%'), ('code', '=like', '6226%'), ('code', '=like', '6228%'), diff --git a/l10n_fr_das2/readme/CONFIGURE.rst b/l10n_fr_das2/readme/CONFIGURE.rst new file mode 100644 index 000000000..d9c8d38a7 --- /dev/null +++ b/l10n_fr_das2/readme/CONFIGURE.rst @@ -0,0 +1,10 @@ +On the supplier form view, in the *Accounting* tab, you will see a section *DAS2*. For the suppliers that must be declared in DAS2, you must set: + +* the DAS2 Type, +* the job for the DAS2 declaration, +* their SIRET number (for French suppliers only), +* their full address (street, zip code, city and country). + +On the company configuration form, the APE code, SIRET and address must be set. + +For the user responsible for the declaration, the phone number and email must be set on his related partner form (name, email and phone number are used in the DAS2 declaration file). diff --git a/l10n_fr_das2/readme/CONTRIBUTORS.rst b/l10n_fr_das2/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000..ff65d68ce --- /dev/null +++ b/l10n_fr_das2/readme/CONTRIBUTORS.rst @@ -0,0 +1 @@ +* Alexis de Lattre diff --git a/l10n_fr_das2/readme/DESCRIPTION.rst b/l10n_fr_das2/readme/DESCRIPTION.rst new file mode 100644 index 000000000..d8376603c --- /dev/null +++ b/l10n_fr_das2/readme/DESCRIPTION.rst @@ -0,0 +1,3 @@ +This module adds support for `DAS2 _`, which is an annual fiscal declaration also called *Déclaration d'honoraires*. It will allow you to auto-generate the lines of DAS2 from the accounting entries, check the result, manually update lines if needed and eventually generate a declaration file. This declaration file can be uploaded on the dedicated website `https://teletd.impots.gouv.fr/teletd/connexionEFI.do `_; that way, you won't have to manually type the declaration. + +The specifications of the file are available on `this page `_ (select *Salaires, honoraires et actionnariat salarié*). diff --git a/l10n_fr_das2/readme/USAGE.rst b/l10n_fr_das2/readme/USAGE.rst new file mode 100644 index 000000000..321b7fe22 --- /dev/null +++ b/l10n_fr_das2/readme/USAGE.rst @@ -0,0 +1,9 @@ +Go to the menu *Reports > French Statements > DAS2* and create a new DAS2 report. + +Then click on the button *Generate Lines*. Check and edit the generated lines. You can get the details of the computation performed by Odoo in the *Note* fields of each line. + +You should also have a look in the chatter: you may have a message that warn you about suppliers that have expenses recorded in accounts such as 622100 Commissions et courtages sur achats, 622200 Commissions et courtages sur ventes, 622600 Honoraires, 622800 Rémunérations d'intermédiaires divers, 653000 Jetons de présence, 651600 Droits d'auteur et de reproduction and are not configured for DAS2. + +Once your declaration is OK, click on the button *Generate File* and download the generated file. + +Connect to `https://teletd.impots.gouv.fr/teletd/connexionEFI.do `_, select *Transmission par internet des fichiers TD/bilatéral* and type your login and password (the credentials are specific to this website; their are not the same as on *impots.gouv.fr*). Then follow the online declaration process and upload the file generated by Odoo. From c52b727397a645eedbbf0a699d568a883c3b9292 Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Thu, 13 Feb 2020 11:23:20 +0100 Subject: [PATCH 08/58] Fix DAS2 file generation --- l10n_fr_das2/models/l10n_fr_das2.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/l10n_fr_das2/models/l10n_fr_das2.py b/l10n_fr_das2/models/l10n_fr_das2.py index 153fe3bda..17207b6d0 100644 --- a/l10n_fr_das2/models/l10n_fr_das2.py +++ b/l10n_fr_das2/models/l10n_fr_das2.py @@ -63,8 +63,8 @@ class L10nFrDas2(models.Model): partner_declare_threshold = fields.Integer( string='Partner Declaration Threshold', readonly=True) dads_type = fields.Selection([ - ('5', u"La société verse des salaires"), - ('6', u"La société ne verse pas de salaires"), + ('4', u"La société verse des salaires"), + ('1', u"La société ne verse pas de salaires"), ], 'Type DADS', required=True, default=lambda self: self._default_dads_type()) # option for draft moves ? @@ -88,7 +88,7 @@ def _default_dads_type(self): if previous_decl: return previous_decl.dads_type else: - return '5' + return '4' @api.model def _default_payment_journals(self): @@ -423,8 +423,8 @@ def generate_file(self): else: allow_letters = ' ' * 3 flines.append( - cprefix + '210' + partner_siret + partner_name + firstname + - lastname + job + address + ''.join(amount_fields_list) + + cprefix + '210' + partner_siret + lastname + firstname + + partner_name + job + address + ''.join(amount_fields_list) + bik_letters + allow_letters + ' ' * 2 + '0' * 10 + ' ' * 245) rg = self.env['l10n.fr.das2.line'].read_group([('parent_id', '=', self.id)], AMOUNT_FIELDS, [])[0] @@ -456,7 +456,7 @@ def generate_file(self): "One of the lines has a length of %d. " "All lines should have a length of 672. Line: %s.") % (len(fline), fline)) - file_content = '\n'.join(flines) + file_content = '\r\n'.join(flines) + '\r\n' file_content_encoded = file_content.encode('latin1') filename = 'DAS2_%s_%s.txt' % ( self.year, company.name.replace(' ', '_')) From 1b46e2d5b7014b468d2360bac0e1acde341e11a8 Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Fri, 14 Feb 2020 22:02:20 +0100 Subject: [PATCH 09/58] PEP8 fixes, code cleanup and small improvements --- l10n_fr_das2/__init__.py | 1 - l10n_fr_das2/__manifest__.py | 2 - l10n_fr_das2/models/l10n_fr_das2.py | 351 ++++++++++++------ l10n_fr_das2/post_install.py | 41 -- l10n_fr_das2/readme/USAGE.rst | 6 +- .../views/account_config_settings.xml | 2 +- 6 files changed, 235 insertions(+), 168 deletions(-) delete mode 100644 l10n_fr_das2/post_install.py diff --git a/l10n_fr_das2/__init__.py b/l10n_fr_das2/__init__.py index a223b2f37..cde864bae 100644 --- a/l10n_fr_das2/__init__.py +++ b/l10n_fr_das2/__init__.py @@ -1,4 +1,3 @@ # -*- coding: utf-8 -*- from . import models -from .post_install import setup_das2_accounts diff --git a/l10n_fr_das2/__manifest__.py b/l10n_fr_das2/__manifest__.py index 8a7da1a3c..c78636fac 100644 --- a/l10n_fr_das2/__manifest__.py +++ b/l10n_fr_das2/__manifest__.py @@ -12,8 +12,6 @@ 'author': 'Akretion,Odoo Community Association (OCA)', 'website': 'https://github.com/OCA/l10n-france', 'depends': [ - 'account_fiscal_year', - 'l10n_fr', 'l10n_fr_siret', 'l10n_fr_cog', ], diff --git a/l10n_fr_das2/models/l10n_fr_das2.py b/l10n_fr_das2/models/l10n_fr_das2.py index 17207b6d0..986a43914 100644 --- a/l10n_fr_das2/models/l10n_fr_das2.py +++ b/l10n_fr_das2/models/l10n_fr_das2.py @@ -43,7 +43,8 @@ class L10nFrDas2(models.Model): state = fields.Selection([ ('draft', 'Draft'), ('done', 'Done'), - ], default='draft', readonly=True, string='State') + ], default='draft', readonly=True, string='State', + track_visibility='onchange') company_id = fields.Many2one( 'res.company', string='Company', ondelete='cascade', required=True, @@ -65,14 +66,17 @@ class L10nFrDas2(models.Model): dads_type = fields.Selection([ ('4', u"La société verse des salaires"), ('1', u"La société ne verse pas de salaires"), - ], 'Type DADS', required=True, + ], 'DADS Type', required=True, + track_visibility='onchange', default=lambda self: self._default_dads_type()) # option for draft moves ? contact_id = fields.Many2one( 'res.partner', string='Administrative Contact', states={'done': [('readonly', True)]}, default=lambda self: self.env.user.partner_id.id, - help='Contact in the company for the fiscal administration: the name, email and phone number of this partner will be used in the file.') + track_visibility='onchange', + help='Contact in the company for the fiscal administration: the name, ' + 'email and phone number of this partner will be used in the file.') attachment_id = fields.Many2one( 'ir.attachment', string='File Export', readonly=True) @@ -131,7 +135,6 @@ def unlink(self): def generate_lines(self): self.ensure_one() - amlo = self.env['account.move.line'] lfdlo = self.env['l10n.fr.das2.line'] company = self.company_id if not company.country_id: @@ -158,10 +161,10 @@ def generate_lines(self): das2_partners = self.env['res.partner'].search([ ('parent_id', '=', False), ('fr_das2_type', '!=', False)]) - self.line_ids.unlink() if not das2_partners: raise UserError(_( "There are no partners configured for DAS2.")) + self.line_ids.unlink() base_domain = [ ('company_id', '=', self.company_id.id), ('date', '>=', '%d-01-01' % self.year), @@ -170,38 +173,47 @@ def generate_lines(self): ('balance', '!=', 0), ] for partner in das2_partners: - mlines = amlo.search(base_domain + [ - ('partner_id', '=', partner.id), - ('account_id', '=', partner.property_account_payable_id.id), - ]) - note = [] - amount = 0.0 - for mline in mlines: - amount += mline.balance - if mline.full_reconcile_id: - rec = _('reconciliation mark %s') % mline.full_reconcile_id.name - else: - rec = _('not reconciled') - note.append(_( - "Payment dated %s in journal '%s': %.2f € (%s, journal entry %s)") % ( + vals = self._prepare_line(partner, base_domain) + if vals: + lfdlo.create(vals) + self.add_warning_in_chatter(das2_partners) + + def _prepare_line(self, partner, base_domain): + amlo = self.env['account.move.line'] + mlines = amlo.search(base_domain + [ + ('partner_id', '=', partner.id), + ('account_id', '=', partner.property_account_payable_id.id), + ]) + note = [] + amount = 0.0 + for mline in mlines: + amount += mline.balance + if mline.full_reconcile_id: + rec = _( + 'reconciliation mark %s') % mline.full_reconcile_id.name + else: + rec = _('not reconciled') + note.append(_( + "Payment dated %s in journal '%s': " + "%.2f € (%s, journal entry %s)") % ( mline.date, mline.journal_id.display_name, mline.balance, rec, mline.move_id.name)) - if note: - field_name = '%s_amount' % partner.fr_das2_type - vals = { - field_name: int(round(amount, 2)), - 'parent_id': self.id, - 'partner_id': partner.id, - 'job': partner.fr_das2_job, - 'note': '\n'.join(note), - } - if partner.siren and partner.nic: - vals['partner_siret'] = partner.siret - lfdlo.create(vals) - self.add_warning_in_chatter(das2_partners) + res = False + if note: + field_name = '%s_amount' % partner.fr_das2_type + res = { + field_name: int(round(amount)), + 'parent_id': self.id, + 'partner_id': partner.id, + 'job': partner.fr_das2_job, + 'note': '\n'.join(note), + } + if partner.siren and partner.nic: + res['partner_siret'] = partner.siret + return res def add_warning_in_chatter(self, das2_partners): amlo = self.env['account.move.line'] @@ -209,7 +221,6 @@ def add_warning_in_chatter(self, das2_partners): ajo = self.env['account.journal'] rpo = self.env['res.partner'] company = self.company_id - # The code below is just to write warnings in chatter purchase_journals = ajo.search([ ('type', '=', 'purchase'), ('company_id', '=', company.id)]) @@ -240,14 +251,15 @@ def add_warning_in_chatter(self, das2_partners): "that maybe they should be configured for DAS2:

    ") for rg_re in rg_res: partner = rpo.browse(rg_re['partner_id'][0]) - msg += '
  • %s' % ( - partner.id, - partner.display_name) + msg += '
  • %s' % (partner.id, partner.display_name) msg += '
' self.message_post(msg) @api.model - def _prepare_field(self, field_name, partner, value, size, required=False, numeric=False): + def _prepare_field( + self, field_name, partner, value, size, + required=False, numeric=False): '''This function is designed to be inherited.''' if numeric: if not value: @@ -257,8 +269,8 @@ def _prepare_field(self, field_name, partner, value, size, required=False, numer value = int(value) except Exception: raise UserError(_( - "Failed to convert field '%s' (partner %s) to integer.") - % (field_name, partner.display_name)) + "Failed to convert field '%s' (partner %s) " + "to integer.") % (field_name, partner.display_name)) value = str(value) if len(value) > size: raise UserError(_( @@ -271,7 +283,9 @@ def _prepare_field(self, field_name, partner, value, size, required=False, numer return value if required and not value: raise UserError(_( - "The field '%s' (partner %s) is empty or 0. It should have a non-null " "value.") % (field_name, partner.display_name)) + "The field '%s' (partner %s) is empty or 0. " + "It should have a non-null value.") + % (field_name, partner.display_name)) if not value: value = ' ' * size # Cut if too long @@ -279,7 +293,6 @@ def _prepare_field(self, field_name, partner, value, size, required=False, numer # enlarge if too small if len(value) < size: value = value.ljust(size, ' ') - assert len(value) == size, 'The length of the field is wrong' return value def _prepare_address(self, partner): @@ -295,65 +308,46 @@ def _prepare_address(self, partner): if not partner.city: raise UserError(_( "Missing city on partner '%s'.") % partner.display_name) - if partner.country_id and partner.country_id.code not in FRANCE_CODES: if not partner.country_id.fr_cog: raise UserError(_( u"Missing Code Officiel Géographique on country '%s'.") % partner.country_id.display_name) cog = self._prepare_field( - 'COG', partner, partner.country_id.fr_cog, 5, True, numeric=True) - raw_country_name = partner.with_context(lang='fr_FR').country_id.name + 'COG', partner, partner.country_id.fr_cog, 5, True, + numeric=True) + raw_country_name = partner.with_context( + lang='fr_FR').country_id.name country_name = self._prepare_field( 'Nom du pays', partner, raw_country_name, 26, True) raw_commune = partner.city if partner.zip: raw_commune = '%s %s' % (partner.zip, partner.city) - commune = self._prepare_field('Commune', partner, raw_commune, 26, True) - caddress = cstreet2 + ' ' + cstreet + cog + ' ' + commune + cog + ' ' + country_name + commune = self._prepare_field( + 'Commune', partner, raw_commune, 26, True) + caddress = cstreet2 + ' ' + cstreet + cog + ' ' + commune\ + + cog + ' ' + country_name # According to the specs, we should have some code specific for DOM-TOM # But it's not easy, because we are supposed to give the INSEE code - # of the city, not of the territory => we don't handle that for the moment + # of the city, not of the territory => we don't handle that for the + # moment else: - ccity = self._prepare_field('City', partner, partner.city, 26, True) + ccity = self._prepare_field( + 'City', partner, partner.city, 26, True) czip = self._prepare_field('Zip', partner, partner.zip, 5, True) - caddress = cstreet2 + ' ' + cstreet + '0' * 5 + ' ' + ' ' * 26 + czip + ' ' + ccity + caddress = cstreet2 + ' ' + cstreet + '0' * 5 + ' ' + ' ' * 26 +\ + czip + ' ' + ccity assert len(caddress) == 129 return caddress - def generate_file(self): - self.ensure_one() + def _prepare_file(self): company = self.company_id cpartner = company.partner_id - if not self.line_ids: - raise UserError(_("The DAS2 has no lines.")) - if not company.siret: - raise UserError(_( - "Missing SIRET on company '%s'.") % company.display_name) - if not company.ape: - raise UserError(_( - "Missing APE on company '%s'.") % company.display_name) - if not company.street: - raise UserError(_( - "Missing Street on company '%s'") % company.display_name) contact = self.contact_id - if not contact: - raise UserError(_("Missing administrative contact.")) - if not contact.email: - raise UserError(_( - "The email is not set on the administrative contact " - "partner '%s'.") % contact.display_name) - if not contact.phone and not contact.mobile: - raise UserError(_( - "The phone number is not set on the administrative contact " - "partner '%s'.") % contact.display_name) - if self.attachment_id: - raise UserError(_( - "An export file already exists. First, delete it via the " - "attachments and then re-generate it.")) - - csiren = self._prepare_field('SIREN', cpartner, cpartner.siren, 9, True) - csiret = self._prepare_field('SIRET', cpartner, cpartner.siret, 14, True) + csiren = self._prepare_field( + 'SIREN', cpartner, cpartner.siren, 9, True) + csiret = self._prepare_field( + 'SIRET', cpartner, cpartner.siret, 14, True) cape = self._prepare_field('APE', cpartner, company.ape, 5, True) cname = self._prepare_field('Name', cpartner, company.name, 50, True) file_type = 'X' # tous déclarants honoraires seuls @@ -389,19 +383,24 @@ def generate_file(self): % partner.display_name) # ligne 210 honoraire if partner.is_company: - partner_name = self._prepare_field('Partner name', partner, partner.name, 50, True) + partner_name = self._prepare_field( + 'Partner name', partner, partner.name, 50, True) lastname = ' ' * 30 firstname = ' ' * 20 else: partner_name = ' ' * 50 if hasattr(partner, 'firstname') and partner.firstname: - lastname = self._prepare_field('Partner lastname', partner, partner.lastname, 30, True) - firstname = self._prepare_field('Partner firstname', partner, partner.firstname, 20, True) + lastname = self._prepare_field( + 'Lastname', partner, partner.lastname, 30, True) + firstname = self._prepare_field( + 'Firstname', partner, partner.firstname, 20, True) else: - lastname = self._prepare_field('Partner name', partner, partner.name, 30, True) + lastname = self._prepare_field( + 'Partner name', partner, partner.name, 30, True) firstname = ' ' * 20 address = self._prepare_address(partner) - partner_siret = self._prepare_field('SIRET', partner, line.partner_siret, 14) + partner_siret = self._prepare_field( + 'SIRET', partner, line.partner_siret, 14) job = self._prepare_field('Profession', partner, line.job, 30) amount_fields_list = [ self._prepare_field(x, partner, line[x], 10, numeric=True) @@ -409,7 +408,8 @@ def generate_file(self): if line.benefits_in_kind_amount: bik_letters = '' bik_letters += line.benefits_in_kind_food and 'N' or ' ' - bik_letters += line.benefits_in_kind_accomodation and 'L' or ' ' + bik_letters +=\ + line.benefits_in_kind_accomodation and 'L' or ' ' bik_letters += line.benefits_in_kind_car and 'V' or ' ' bik_letters += line.benefits_in_kind_other and 'A' or ' ' bik_letters += line.benefits_in_kind_nict and 'T' or ' ' @@ -427,24 +427,30 @@ def generate_file(self): partner_name + job + address + ''.join(amount_fields_list) + bik_letters + allow_letters + ' ' * 2 + '0' * 10 + ' ' * 245) - rg = self.env['l10n.fr.das2.line'].read_group([('parent_id', '=', self.id)], AMOUNT_FIELDS, [])[0] + rg = self.env['l10n.fr.das2.line'].read_group( + [('parent_id', '=', self.id)], AMOUNT_FIELDS, [])[0] total_fields_list = [ self._prepare_field(x, cpartner, rg[x], 12, numeric=True) for x in AMOUNT_FIELDS] - contact_name = self._prepare_field('Administrative contact name', contact, contact.name, 50) - contact_email = self._prepare_field('Administrative contact email', contact, contact.email, 60) + contact_name = self._prepare_field( + 'Administrative contact name', contact, contact.name, 50) + contact_email = self._prepare_field( + 'Administrative contact email', contact, contact.email, 60) phone = contact.phone or contact.mobile - phone = phone.replace(' ', '').replace('.', '').replace('-', '').replace(u'\u00A0', '') + phone = phone.replace(' ', '').replace('.', '').replace( + '-', '').replace(u'\u00A0', '') if phone.startswith('+33'): phone = '0%s' % phone[3:] - contact_phone = self._prepare_field('Administrative contact phone', contact, phone, 10) + contact_phone = self._prepare_field( + 'Administrative contact phone', contact, phone, 10) flines.append( cprefix + '300' + ' ' * 36 + '0' * 12 * 9 + ''.join(total_fields_list) + ' ' * 12 + '0' * 12 * 2 + '0' * 6 + '0' * 12 * 5 + ' ' * 74 + contact_name + contact_phone + contact_email + ' ' * 76) - lines_number = self._prepare_field('Number of lines', cpartner, i, 6, numeric=True) + lines_number = self._prepare_field( + 'Number of lines', cpartner, i, 6, numeric=True) flines.append( csiren + '9' * 12 + '310' + '00001' + '0' * 6 + lines_number + '0' * 6 * 3 + ' ' * 18 + '0' * 12 * 9 + @@ -457,6 +463,39 @@ def generate_file(self): "All lines should have a length of 672. Line: %s.") % (len(fline), fline)) file_content = '\r\n'.join(flines) + '\r\n' + return file_content + + def generate_file(self): + self.ensure_one() + company = self.company_id + if not self.line_ids: + raise UserError(_("The DAS2 has no lines.")) + if not company.siret: + raise UserError(_( + "Missing SIRET on company '%s'.") % company.display_name) + if not company.ape: + raise UserError(_( + "Missing APE on company '%s'.") % company.display_name) + if not company.street: + raise UserError(_( + "Missing Street on company '%s'") % company.display_name) + contact = self.contact_id + if not contact: + raise UserError(_("Missing administrative contact.")) + if not contact.email: + raise UserError(_( + "The email is not set on the administrative contact " + "partner '%s'.") % contact.display_name) + if not contact.phone and not contact.mobile: + raise UserError(_( + "The phone number is not set on the administrative contact " + "partner '%s'.") % contact.display_name) + if self.attachment_id: + raise UserError(_( + "An export file already exists. First, delete it via the " + "attachments and then re-generate it.")) + + file_content = self._prepare_file() file_content_encoded = file_content.encode('latin1') filename = 'DAS2_%s_%s.txt' % ( self.year, company.name.replace(' ', '_')) @@ -481,6 +520,18 @@ def generate_file(self): 'account_payment_order.view_attachment_simplified_form').id except Exception: pass + + # The code below works and triggers an immediate download, but + # the form view of DAS2 is frozen after that, we it's not usable in v10 + # action = { + # 'name': 'DAS2', + # 'type': 'ir.actions.act_url', + # 'url': "web/content/?model=ir.attachment&id=%d" + # "&filename_field=filename" + # "&field=datas&download=true&filename=%s" % ( + # attach.id, filename), + # 'target': 'self', + # } return action def button_lines_fullscreen(self): @@ -511,46 +562,102 @@ class L10nFrDas2Line(models.Model): currency_id = fields.Many2one( related='parent_id.company_id.currency_id', store=True, readonly=True, string='Company Currency') - fee_amount = fields.Integer(string=u'Honoraires et vacations', states={'done': [('readonly', True)]}) - commission_amount = fields.Integer(string=u'Commissions', states={'done': [('readonly', True)]}) - brokerage_amount = fields.Integer(string=u'Courtages', states={'done': [('readonly', True)]}) - discount_amount = fields.Integer(string=u'Ristournes', states={'done': [('readonly', True)]}) - attendance_fee_amount = fields.Integer(string=u'Jetons de présence', states={'done': [('readonly', True)]}) - copyright_royalties_amount = fields.Integer(string=u"Droits d'auteur", states={'done': [('readonly', True)]}) - licence_royalties_amount = fields.Integer(string=u"Droits d'inventeur", states={'done': [('readonly', True)]}) - other_income_amount = fields.Integer(string=u'Autre rémunérations', states={'done': [('readonly', True)]}) - allowance_amount = fields.Integer(string=u'Indemnités et remboursements', states={'done': [('readonly', True)]}) - benefits_in_kind_amount = fields.Integer(string='Avantages en nature', states={'done': [('readonly', True)]}) - withholding_tax_amount = fields.Integer(string=u'Retenue à la source', states={'done': [('readonly', True)]}) + fee_amount = fields.Integer( + string=u'Honoraires et vacations', + states={'done': [('readonly', True)]}) + commission_amount = fields.Integer( + string=u'Commissions', states={'done': [('readonly', True)]}) + brokerage_amount = fields.Integer( + string=u'Courtages', states={'done': [('readonly', True)]}) + discount_amount = fields.Integer( + string=u'Ristournes', states={'done': [('readonly', True)]}) + attendance_fee_amount = fields.Integer( + string=u'Jetons de présence', states={'done': [('readonly', True)]}) + copyright_royalties_amount = fields.Integer( + string=u"Droits d'auteur", states={'done': [('readonly', True)]}) + licence_royalties_amount = fields.Integer( + string=u"Droits d'inventeur", states={'done': [('readonly', True)]}) + other_income_amount = fields.Integer( + string=u'Autre rémunérations', states={'done': [('readonly', True)]}) + allowance_amount = fields.Integer( + string=u'Indemnités et remboursements', + states={'done': [('readonly', True)]}) + benefits_in_kind_amount = fields.Integer( + string='Avantages en nature', states={'done': [('readonly', True)]}) + withholding_tax_amount = fields.Integer( + string=u'Retenue à la source', states={'done': [('readonly', True)]}) total_amount = fields.Integer( - compute='_compute_total_amount', string='Total Amount', store=True, readonly=True) + compute='_compute_total_amount', string='Total Amount', + store=True, readonly=True) to_declare = fields.Boolean( compute='_compute_total_amount', string='To Declare', readonly=True) - allowance_fixed = fields.Boolean(u'Allocation forfaitaire', states={'done': [('readonly', True)]}) - allowance_real = fields.Boolean(u'Sur frais réels', states={'done': [('readonly', True)]}) - allowance_employer = fields.Boolean(u"Prise en charge directe par l'employeur", states={'done': [('readonly', True)]}) - benefits_in_kind_food = fields.Boolean(u'Nourriture', states={'done': [('readonly', True)]}) - benefits_in_kind_accomodation = fields.Boolean(u'Logement', states={'done': [('readonly', True)]}) - benefits_in_kind_car = fields.Boolean(u'Voiture', states={'done': [('readonly', True)]}) - benefits_in_kind_other = fields.Boolean(u'Autres', states={'done': [('readonly', True)]}) - benefits_in_kind_nict = fields.Boolean(u'Outils issus des NTIC', states={'done': [('readonly', True)]}) + allowance_fixed = fields.Boolean( + u'Allocation forfaitaire', states={'done': [('readonly', True)]}) + allowance_real = fields.Boolean( + u'Sur frais réels', states={'done': [('readonly', True)]}) + allowance_employer = fields.Boolean( + u"Prise en charge directe par l'employeur", + states={'done': [('readonly', True)]}) + benefits_in_kind_food = fields.Boolean( + u'Nourriture', states={'done': [('readonly', True)]}) + benefits_in_kind_accomodation = fields.Boolean( + u'Logement', states={'done': [('readonly', True)]}) + benefits_in_kind_car = fields.Boolean( + u'Voiture', states={'done': [('readonly', True)]}) + benefits_in_kind_other = fields.Boolean( + u'Autres', states={'done': [('readonly', True)]}) + benefits_in_kind_nict = fields.Boolean( + u'Outils issus des NTIC', states={'done': [('readonly', True)]}) state = fields.Selection( related='parent_id.state', store=True, readonly=True) note = fields.Text() job = fields.Char(string='Profession', size=30) _sql_constraints = [ - ('fee_amount_positive', 'CHECK(fee_amount >= 0)', 'Negative amounts not allowed!'), - ('commission_amount_positive', 'CHECK(commission_amount >= 0)', 'Negative amounts not allowed!'), - ('brokerage_amount_positive', 'CHECK(brokerage_amount >= 0)', 'Negative amounts not allowed!'), - ('discount_amount_positive', 'CHECK(discount_amount >= 0)', 'Negative amounts not allowed!'), - ('attendance_fee_amount_positive', 'CHECK(attendance_fee_amount >= 0)', 'Negative amounts not allowed!'), - ('copyright_royalties_amount_positive', 'CHECK(copyright_royalties_amount >= 0)', 'Negative amounts not allowed!'), - ('licence_royalties_amount_positive', 'CHECK(licence_royalties_amount >= 0)', 'Negative amounts not allowed!'), - ('other_income_amount_positive', 'CHECK(other_income_amount >= 0)', 'Negative amounts not allowed!'), - ('allowance_amount_positive', 'CHECK(allowance_amount >= 0)', 'Negative amounts not allowed!'), - ('benefits_in_kind_amount_positive', 'CHECK(benefits_in_kind_amount >= 0)', 'Negative amounts not allowed!'), - ('withholding_tax_amount_positive', 'CHECK(withholding_tax_amount >= 0)', 'Negative amounts not allowed!'), + ( + 'fee_amount_positive', + 'CHECK(fee_amount >= 0)', + 'Negative amounts not allowed!'), + ( + 'commission_amount_positive', + 'CHECK(commission_amount >= 0)', + 'Negative amounts not allowed!'), + ( + 'brokerage_amount_positive', + 'CHECK(brokerage_amount >= 0)', + 'Negative amounts not allowed!'), + ( + 'discount_amount_positive', + 'CHECK(discount_amount >= 0)', + 'Negative amounts not allowed!'), + ( + 'attendance_fee_amount_positive', + 'CHECK(attendance_fee_amount >= 0)', + 'Negative amounts not allowed!'), + ( + 'copyright_royalties_amount_positive', + 'CHECK(copyright_royalties_amount >= 0)', + 'Negative amounts not allowed!'), + ( + 'licence_royalties_amount_positive', + 'CHECK(licence_royalties_amount >= 0)', + 'Negative amounts not allowed!'), + ( + 'other_income_amount_positive', + 'CHECK(other_income_amount >= 0)', + 'Negative amounts not allowed!'), + ( + 'allowance_amount_positive', + 'CHECK(allowance_amount >= 0)', + 'Negative amounts not allowed!'), + ( + 'benefits_in_kind_amount_positive', + 'CHECK(benefits_in_kind_amount >= 0)', + 'Negative amounts not allowed!'), + ( + 'withholding_tax_amount_positive', + 'CHECK(withholding_tax_amount >= 0)', + 'Negative amounts not allowed!'), ] @api.depends( diff --git a/l10n_fr_das2/post_install.py b/l10n_fr_das2/post_install.py deleted file mode 100644 index 20bb8c193..000000000 --- a/l10n_fr_das2/post_install.py +++ /dev/null @@ -1,41 +0,0 @@ -# -*- coding: utf-8 -*- -# © 2016-2017 Akretion (Alexis de Lattre ) -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - -from odoo import api, SUPERUSER_ID -from odoo.tools import file_open -from lxml import etree -import logging -logger = logging.getLogger(__name__) - - -def setup_das2_accounts(cr, registry): - f = file_open( - 'l10n_fr_das2/data/account_account_template.xml', 'rb') - xml_root = etree.parse(f) - data = {} - for record in xml_root.xpath('//record'): - xmlid = record.attrib['id'] - account_code = xmlid.split('_')[-1] - for xfield in record.xpath('field'): - data[account_code] = xfield.text - logger.info('setup_das2_accounts data=%s', data) - with api.Environment.manage(): - env = api.Environment(cr, SUPERUSER_ID, {}) - companies = env['res.company'].search([]) - aao = env['account.account'] - for company in companies: - logger.debug( - 'setup_das2_accounts working on company %s ID %d', - company.display_name, company.id) - if company.country_id and company.country_id != env.ref('base.fr'): - continue - for account_code, fr_das2 in data.items(): - accounts = aao.search([ - ('company_id', '=', company.id), - ('code', '=ilike', account_code + '%')]) - accounts.write({'fr_das2': fr_das2}) - logger.info( - 'Company %s: set fr_das2=%s on account IDs %s', - company.display_name, fr_das2, accounts.ids) - return diff --git a/l10n_fr_das2/readme/USAGE.rst b/l10n_fr_das2/readme/USAGE.rst index 321b7fe22..c1cd60c1e 100644 --- a/l10n_fr_das2/readme/USAGE.rst +++ b/l10n_fr_das2/readme/USAGE.rst @@ -1,4 +1,4 @@ -Go to the menu *Reports > French Statements > DAS2* and create a new DAS2 report. +Go to the menu *Accounting > Reports > French Statements > DAS2* and create a new DAS2 report. Then click on the button *Generate Lines*. Check and edit the generated lines. You can get the details of the computation performed by Odoo in the *Note* fields of each line. @@ -7,3 +7,7 @@ You should also have a look in the chatter: you may have a message that warn you Once your declaration is OK, click on the button *Generate File* and download the generated file. Connect to `https://teletd.impots.gouv.fr/teletd/connexionEFI.do `_, select *Transmission par internet des fichiers TD/bilatéral* and type your login and password (the credentials are specific to this website; their are not the same as on *impots.gouv.fr*). Then follow the online declaration process and upload the file generated by Odoo. + +In the minutes following the upload of the file on the website of the administration, you will receive a first e-mail with a subject *ACCUSÉ DE DÉPÔT de 1er NIVEAU (Déclaration de salaires et/ou honoraires et/ou actionnariat salarié)*; it is just an acknowledgement, it doesn't mean that the file is valid. + +Then, on the next open day (in my experience), you will receive a second email with a subject *Déclaration annuelle DADS BILATERALE. Référence DGFIP: xxxx. Numéro d'envoi : xxx. VALIDE*, which means that the file was considered as valid. If the subject ends with *BLOQUANT*, then you should look for the detailed report in the attached PDF report. diff --git a/l10n_fr_das2/views/account_config_settings.xml b/l10n_fr_das2/views/account_config_settings.xml index db3f81f01..d366aabbe 100644 --- a/l10n_fr_das2/views/account_config_settings.xml +++ b/l10n_fr_das2/views/account_config_settings.xml @@ -1,6 +1,6 @@ From 25be707e35775838bcf661982359680e71b66cde Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Fri, 14 Feb 2020 22:24:54 +0100 Subject: [PATCH 10/58] Catch exception if can't encode to latin1 Improve views --- l10n_fr_das2/models/l10n_fr_das2.py | 9 +++- l10n_fr_das2/views/l10n_fr_das2.xml | 64 ++++++++++++++++------------- 2 files changed, 43 insertions(+), 30 deletions(-) diff --git a/l10n_fr_das2/models/l10n_fr_das2.py b/l10n_fr_das2/models/l10n_fr_das2.py index 986a43914..e9eae25d9 100644 --- a/l10n_fr_das2/models/l10n_fr_das2.py +++ b/l10n_fr_das2/models/l10n_fr_das2.py @@ -67,6 +67,7 @@ class L10nFrDas2(models.Model): ('4', u"La société verse des salaires"), ('1', u"La société ne verse pas de salaires"), ], 'DADS Type', required=True, + states={'done': [('readonly', True)]}, track_visibility='onchange', default=lambda self: self._default_dads_type()) # option for draft moves ? @@ -496,7 +497,13 @@ def generate_file(self): "attachments and then re-generate it.")) file_content = self._prepare_file() - file_content_encoded = file_content.encode('latin1') + try: + file_content_encoded = file_content.encode('latin1') + except UnicodeEncodeError: + raise UserError(_( + "A special character in the DAS2 file is not in the latin1 " + "table. Please locate this special character and replace " + "it by a standard character and try again.")) filename = 'DAS2_%s_%s.txt' % ( self.year, company.name.replace(' ', '_')) attach = self.env['ir.attachment'].create({ diff --git a/l10n_fr_das2/views/l10n_fr_das2.xml b/l10n_fr_das2/views/l10n_fr_das2.xml index fdd1e0dee..636e28fb7 100644 --- a/l10n_fr_das2/views/l10n_fr_das2.xml +++ b/l10n_fr_das2/views/l10n_fr_das2.xml @@ -35,13 +35,13 @@ + + - - @@ -100,33 +100,39 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 1a62df02c73d24b8f298f6535ff0bf4294a22b2f Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Fri, 21 Feb 2020 10:34:12 +0000 Subject: [PATCH 11/58] README.rst --- l10n_fr_cog/README.rst | 75 ++++ l10n_fr_cog/static/description/index.html | 420 +++++++++++++++++++ l10n_fr_das2/README.rst | 106 +++++ l10n_fr_das2/static/description/index.html | 444 +++++++++++++++++++++ 4 files changed, 1045 insertions(+) create mode 100644 l10n_fr_cog/README.rst create mode 100644 l10n_fr_cog/static/description/index.html create mode 100644 l10n_fr_das2/README.rst create mode 100644 l10n_fr_das2/static/description/index.html diff --git a/l10n_fr_cog/README.rst b/l10n_fr_cog/README.rst new file mode 100644 index 000000000..f98027813 --- /dev/null +++ b/l10n_fr_cog/README.rst @@ -0,0 +1,75 @@ +========================== +Code Officiel Géographique +========================== + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fl10n--france-lightgray.png?logo=github + :target: https://github.com/OCA/l10n-france/tree/10.0/l10n_fr_cog + :alt: OCA/l10n-france +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/l10n-france-10-0/l10n-france-10-0-l10n_fr_cog + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/121/10.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module adds the *Code Officiel Géographique* of `INSEE `_ on countries. All countries except France and DOM-TOMs (and some very particular territories) have this code. + +This module is used by other modules of the French localization such as the DAS2 module. + +**Table of contents** + +.. contents:: + :local: + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* Akretion + +Contributors +~~~~~~~~~~~~ + +* Alexis de Lattre + +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +This module is part of the `OCA/l10n-france `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/l10n_fr_cog/static/description/index.html b/l10n_fr_cog/static/description/index.html new file mode 100644 index 000000000..e615f3c7d --- /dev/null +++ b/l10n_fr_cog/static/description/index.html @@ -0,0 +1,420 @@ + + + + + + +Code Officiel Géographique + + + +
+

Code Officiel Géographique

+ + +

Beta License: AGPL-3 OCA/l10n-france Translate me on Weblate Try me on Runbot

+

This module adds the Code Officiel Géographique of INSEE on countries. All countries except France and DOM-TOMs (and some very particular territories) have this code.

+

This module is used by other modules of the French localization such as the DAS2 module.

+

Table of contents

+ +
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Akretion
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

This module is part of the OCA/l10n-france project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/l10n_fr_das2/README.rst b/l10n_fr_das2/README.rst new file mode 100644 index 000000000..84f139548 --- /dev/null +++ b/l10n_fr_das2/README.rst @@ -0,0 +1,106 @@ +==== +DAS2 +==== + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fl10n--france-lightgray.png?logo=github + :target: https://github.com/OCA/l10n-france/tree/10.0/l10n_fr_das2 + :alt: OCA/l10n-france +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/l10n-france-10-0/l10n-france-10-0-l10n_fr_das2 + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/121/10.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module adds support for `DAS2 _`, which is an annual fiscal declaration also called *Déclaration d'honoraires*. It will allow you to auto-generate the lines of DAS2 from the accounting entries, check the result, manually update lines if needed and eventually generate a declaration file. This declaration file can be uploaded on the dedicated website `https://teletd.impots.gouv.fr/teletd/connexionEFI.do `_; that way, you won't have to manually type the declaration. + +The specifications of the file are available on `this page `_ (select *Salaires, honoraires et actionnariat salarié*). + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +On the supplier form view, in the *Accounting* tab, you will see a section *DAS2*. For the suppliers that must be declared in DAS2, you must set: + +* the DAS2 Type, +* the job for the DAS2 declaration, +* their SIRET number (for French suppliers only), +* their full address (street, zip code, city and country). + +On the company configuration form, the APE code, SIRET and address must be set. + +For the user responsible for the declaration, the phone number and email must be set on his related partner form (name, email and phone number are used in the DAS2 declaration file). + +Usage +===== + +Go to the menu *Accounting > Reports > French Statements > DAS2* and create a new DAS2 report. + +Then click on the button *Generate Lines*. Check and edit the generated lines. You can get the details of the computation performed by Odoo in the *Note* fields of each line. + +You should also have a look in the chatter: you may have a message that warn you about suppliers that have expenses recorded in accounts such as 622100 Commissions et courtages sur achats, 622200 Commissions et courtages sur ventes, 622600 Honoraires, 622800 Rémunérations d'intermédiaires divers, 653000 Jetons de présence, 651600 Droits d'auteur et de reproduction and are not configured for DAS2. + +Once your declaration is OK, click on the button *Generate File* and download the generated file. + +Connect to `https://teletd.impots.gouv.fr/teletd/connexionEFI.do `_, select *Transmission par internet des fichiers TD/bilatéral* and type your login and password (the credentials are specific to this website; their are not the same as on *impots.gouv.fr*). Then follow the online declaration process and upload the file generated by Odoo. + +In the minutes following the upload of the file on the website of the administration, you will receive a first e-mail with a subject *ACCUSÉ DE DÉPÔT de 1er NIVEAU (Déclaration de salaires et/ou honoraires et/ou actionnariat salarié)*; it is just an acknowledgement, it doesn't mean that the file is valid. + +Then, on the next open day (in my experience), you will receive a second email with a subject *Déclaration annuelle DADS BILATERALE. Référence DGFIP: xxxx. Numéro d'envoi : xxx. VALIDE*, which means that the file was considered as valid. If the subject ends with *BLOQUANT*, then you should look for the detailed report in the attached PDF report. + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* Akretion + +Contributors +~~~~~~~~~~~~ + +* Alexis de Lattre + +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +This module is part of the `OCA/l10n-france `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/l10n_fr_das2/static/description/index.html b/l10n_fr_das2/static/description/index.html new file mode 100644 index 000000000..c62df0286 --- /dev/null +++ b/l10n_fr_das2/static/description/index.html @@ -0,0 +1,444 @@ + + + + + + +DAS2 + + + +
+

DAS2

+ + +

Beta License: AGPL-3 OCA/l10n-france Translate me on Weblate Try me on Runbot

+

This module adds support for DAS2 <https://www.impots.gouv.fr/portail/formulaire/das2/etat-des-honoraires-vacations-commissions-courtages-ristournes-et-jetons>_, which is an annual fiscal declaration also called Déclaration d’honoraires. It will allow you to auto-generate the lines of DAS2 from the accounting entries, check the result, manually update lines if needed and eventually generate a declaration file. This declaration file can be uploaded on the dedicated website https://teletd.impots.gouv.fr/teletd/connexionEFI.do; that way, you won’t have to manually type the declaration.

+

The specifications of the file are available on this page (select Salaires, honoraires et actionnariat salarié).

+

Table of contents

+ +
+

Configuration

+

On the supplier form view, in the Accounting tab, you will see a section DAS2. For the suppliers that must be declared in DAS2, you must set:

+
    +
  • the DAS2 Type,
  • +
  • the job for the DAS2 declaration,
  • +
  • their SIRET number (for French suppliers only),
  • +
  • their full address (street, zip code, city and country).
  • +
+

On the company configuration form, the APE code, SIRET and address must be set.

+

For the user responsible for the declaration, the phone number and email must be set on his related partner form (name, email and phone number are used in the DAS2 declaration file).

+
+
+

Usage

+

Go to the menu Accounting > Reports > French Statements > DAS2 and create a new DAS2 report.

+

Then click on the button Generate Lines. Check and edit the generated lines. You can get the details of the computation performed by Odoo in the Note fields of each line.

+

You should also have a look in the chatter: you may have a message that warn you about suppliers that have expenses recorded in accounts such as 622100 Commissions et courtages sur achats, 622200 Commissions et courtages sur ventes, 622600 Honoraires, 622800 Rémunérations d’intermédiaires divers, 653000 Jetons de présence, 651600 Droits d’auteur et de reproduction and are not configured for DAS2.

+

Once your declaration is OK, click on the button Generate File and download the generated file.

+

Connect to https://teletd.impots.gouv.fr/teletd/connexionEFI.do, select Transmission par internet des fichiers TD/bilatéral and type your login and password (the credentials are specific to this website; their are not the same as on impots.gouv.fr). Then follow the online declaration process and upload the file generated by Odoo.

+

In the minutes following the upload of the file on the website of the administration, you will receive a first e-mail with a subject ACCUSÉ DE DÉPÔT de 1er NIVEAU (Déclaration de salaires et/ou honoraires et/ou actionnariat salarié); it is just an acknowledgement, it doesn’t mean that the file is valid.

+

Then, on the next open day (in my experience), you will receive a second email with a subject Déclaration annuelle DADS BILATERALE. Référence DGFIP: xxxx. Numéro d’envoi : xxx. VALIDE, which means that the file was considered as valid. If the subject ends with BLOQUANT, then you should look for the detailed report in the attached PDF report.

+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Akretion
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

This module is part of the OCA/l10n-france project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + From 8c22eaba865e143ab295e7485405ec7d37252155 Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Fri, 21 Feb 2020 10:34:12 +0000 Subject: [PATCH 12/58] icon.png --- l10n_fr_cog/static/description/icon.png | Bin 0 -> 9455 bytes l10n_fr_das2/static/description/icon.png | Bin 0 -> 9455 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 l10n_fr_cog/static/description/icon.png create mode 100644 l10n_fr_das2/static/description/icon.png diff --git a/l10n_fr_cog/static/description/icon.png b/l10n_fr_cog/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..3a0328b516c4980e8e44cdb63fd945757ddd132d GIT binary patch literal 9455 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 z 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 Xviia!U7SGha1wx#SCgwmn*{w2TRX*I literal 0 HcmV?d00001 diff --git a/l10n_fr_das2/static/description/icon.png b/l10n_fr_das2/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..3a0328b516c4980e8e44cdb63fd945757ddd132d GIT binary patch literal 9455 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 z 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 Xviia!U7SGha1wx#SCgwmn*{w2TRX*I literal 0 HcmV?d00001 From 3e49f003d32ee51b4a53cd98e3e6539475b22aae Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Mon, 30 Mar 2020 19:46:56 +0200 Subject: [PATCH 13/58] l10n_fr_das2: improve date interval for chatter msg --- l10n_fr_das2/models/l10n_fr_das2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/l10n_fr_das2/models/l10n_fr_das2.py b/l10n_fr_das2/models/l10n_fr_das2.py index e9eae25d9..b6c171950 100644 --- a/l10n_fr_das2/models/l10n_fr_das2.py +++ b/l10n_fr_das2/models/l10n_fr_das2.py @@ -237,7 +237,7 @@ def add_warning_in_chatter(self, das2_partners): ]) rg_res = amlo.read_group([ ('company_id', '=', company.id), - ('date', '>=', '%d-01-01' % self.year), + ('date', '>=', '%d-10-01' % (self.year - 1)), ('date', '<=', '%d-12-31' % self.year), ('journal_id', 'in', purchase_journals.ids), ('partner_id', '!=', False), From 421b4e2ea3ba8ba052b06077fb2da56c69492eed Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Mon, 30 Mar 2020 23:57:38 +0200 Subject: [PATCH 14/58] [MIG] l10n_fr_cog and l10n_fr_das2 from v10 to v12 l10n_fr_cog: update country list l10n_fr_das2: - add demo data, - add constraint on lines, - improve chatter messages, - fix readonly status of 'job' field on lines, - handle 'job' field in partner change on lines. --- l10n_fr_cog/__init__.py | 2 - l10n_fr_cog/__manifest__.py | 7 +- l10n_fr_cog/data/country.xml | 8 +- l10n_fr_cog/models/country.py | 5 +- l10n_fr_cog/post_install.py | 1 - l10n_fr_das2/__init__.py | 2 - l10n_fr_das2/__manifest__.py | 8 +- l10n_fr_das2/demo/demo.xml | 69 +++++++++++++++++ l10n_fr_das2/models/__init__.py | 5 +- l10n_fr_das2/models/company.py | 1 - ..._config_settings.py => config_settings.py} | 5 +- l10n_fr_das2/models/l10n_fr_das2.py | 74 ++++++++++--------- l10n_fr_das2/models/partner.py | 19 +++-- .../views/account_config_settings.xml | 24 ------ l10n_fr_das2/views/config_settings.xml | 34 +++++++++ 15 files changed, 167 insertions(+), 97 deletions(-) create mode 100644 l10n_fr_das2/demo/demo.xml rename l10n_fr_das2/models/{account_config_settings.py => config_settings.py} (75%) delete mode 100644 l10n_fr_das2/views/account_config_settings.xml create mode 100644 l10n_fr_das2/views/config_settings.xml diff --git a/l10n_fr_cog/__init__.py b/l10n_fr_cog/__init__.py index 2e1c9a971..65d052762 100644 --- a/l10n_fr_cog/__init__.py +++ b/l10n_fr_cog/__init__.py @@ -1,4 +1,2 @@ -# -*- coding: utf-8 -*- - from .post_install import set_fr_cog from . import models diff --git a/l10n_fr_cog/__manifest__.py b/l10n_fr_cog/__manifest__.py index 63cc1c099..39e4fb6ef 100644 --- a/l10n_fr_cog/__manifest__.py +++ b/l10n_fr_cog/__manifest__.py @@ -1,12 +1,11 @@ -# -*- coding: utf-8 -*- # Copyright 2020 Akretion France (http://www.akretion.com/) # @author: Alexis de Lattre # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). { - 'name': u'Code Officiel Géographique', - 'summary': u'Add Code Officiel Géographique (COG) on countries', - 'version': '10.0.1.0.0', + 'name': 'Code Officiel Géographique', + 'summary': 'Add Code Officiel Géographique (COG) on countries', + 'version': '12.0.1.0.0', 'category': 'French Localization', 'author': "Akretion,Odoo Community Association (OCA)", 'website': 'https://github.com/OCA/l10n-france', diff --git a/l10n_fr_cog/data/country.xml b/l10n_fr_cog/data/country.xml index 074559aa0..d0087682c 100644 --- a/l10n_fr_cog/data/country.xml +++ b/l10n_fr_cog/data/country.xml @@ -36,10 +36,6 @@ 99252
- - 99431 - - 99395 @@ -836,6 +832,10 @@ 99502 + + 99262 + + 99260 diff --git a/l10n_fr_cog/models/country.py b/l10n_fr_cog/models/country.py index 0b2231828..0dbd77de6 100644 --- a/l10n_fr_cog/models/country.py +++ b/l10n_fr_cog/models/country.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2020 Akretion France (http://www.akretion.com/) # @author: Alexis de Lattre # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). @@ -10,5 +9,5 @@ class ResCountry(models.Model): _inherit = 'res.country' fr_cog = fields.Integer( - string=u'Code Officiel Géographique', - help=u"Code Officiel Géographique, by INSEE") + string='Code Officiel Géographique', + help="Code Officiel Géographique, by INSEE") diff --git a/l10n_fr_cog/post_install.py b/l10n_fr_cog/post_install.py index 76757e98d..08526218b 100644 --- a/l10n_fr_cog/post_install.py +++ b/l10n_fr_cog/post_install.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2020 Akretion France (http://www.akretion.com/) # @author: Alexis de Lattre # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). diff --git a/l10n_fr_das2/__init__.py b/l10n_fr_das2/__init__.py index cde864bae..0650744f6 100644 --- a/l10n_fr_das2/__init__.py +++ b/l10n_fr_das2/__init__.py @@ -1,3 +1 @@ -# -*- coding: utf-8 -*- - from . import models diff --git a/l10n_fr_das2/__manifest__.py b/l10n_fr_das2/__manifest__.py index c78636fac..bd7ab3356 100644 --- a/l10n_fr_das2/__manifest__.py +++ b/l10n_fr_das2/__manifest__.py @@ -1,12 +1,11 @@ -# -*- coding: utf-8 -*- # Copyright 2020 Akretion France (http://www.akretion.com/) # @author: Alexis de Lattre # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). { 'name': 'DAS2', - 'version': '10.0.1.0.0', - 'category': 'Accounting', + 'version': '12.0.1.0.0', + 'category': 'Invoicing Management', 'license': 'AGPL-3', 'summary': 'DAS2 (France)', 'author': 'Akretion,Odoo Community Association (OCA)', @@ -23,8 +22,9 @@ 'security/ir.model.access.csv', 'views/l10n_fr_das2.xml', 'views/partner.xml', - 'views/account_config_settings.xml', + 'views/config_settings.xml', ], + 'demo': ['demo/demo.xml'], 'installable': True, 'application': True, } diff --git a/l10n_fr_das2/demo/demo.xml b/l10n_fr_das2/demo/demo.xml new file mode 100644 index 000000000..f30535e6d --- /dev/null +++ b/l10n_fr_das2/demo/demo.xml @@ -0,0 +1,69 @@ + + + + + + + + + 6201Z + + + + 999999998 + 00019 + + + + + + Experts comptables du Rhône + + + 12 rue du chiffre + 69100 + Villeurbanne + + experts@comptables.example.com + fee + Expert comptable + 111111118 + 00019 + + + + Cabinet d'avocats Juridon + + + 42 rue du crime + 69100 + Villeurbanne + + avocats@example.com + fee + Avocat + 222222226 + 00011 + + + + Cabinet CACtus + + + 42 rue de l'audit + 69100 + Villeurbanne + + cac@example.com + fee + Commissaire aux comptes + 333333334 + 00014 + + + + diff --git a/l10n_fr_das2/models/__init__.py b/l10n_fr_das2/models/__init__.py index 02a7fcc3e..36aac8dba 100644 --- a/l10n_fr_das2/models/__init__.py +++ b/l10n_fr_das2/models/__init__.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- - from . import l10n_fr_das2 from . import partner from . import company -from . import account_config_settings - +from . import config_settings diff --git a/l10n_fr_das2/models/company.py b/l10n_fr_das2/models/company.py index 20f0432bd..7378d8384 100644 --- a/l10n_fr_das2/models/company.py +++ b/l10n_fr_das2/models/company.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2020 Akretion France (http://www.akretion.com/) # @author: Alexis de Lattre # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). diff --git a/l10n_fr_das2/models/account_config_settings.py b/l10n_fr_das2/models/config_settings.py similarity index 75% rename from l10n_fr_das2/models/account_config_settings.py rename to l10n_fr_das2/models/config_settings.py index f9e3e6a0a..d3d62abef 100644 --- a/l10n_fr_das2/models/account_config_settings.py +++ b/l10n_fr_das2/models/config_settings.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2020 Akretion France (http://www.akretion.com/) # @author: Alexis de Lattre # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). @@ -6,8 +5,8 @@ from odoo import fields, models -class AccountConfigSettings(models.TransientModel): - _inherit = 'account.config.settings' +class ResConfigSettings(models.TransientModel): + _inherit = 'res.config.settings' fr_das2_partner_declare_threshold = fields.Integer( related='company_id.fr_das2_partner_declare_threshold', readonly=False) diff --git a/l10n_fr_das2/models/l10n_fr_das2.py b/l10n_fr_das2/models/l10n_fr_das2.py index b6c171950..bad909f5f 100644 --- a/l10n_fr_das2/models/l10n_fr_das2.py +++ b/l10n_fr_das2/models/l10n_fr_das2.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2020 Akretion France (http://www.akretion.com/) # @author: Alexis de Lattre # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). @@ -64,8 +63,8 @@ class L10nFrDas2(models.Model): partner_declare_threshold = fields.Integer( string='Partner Declaration Threshold', readonly=True) dads_type = fields.Selection([ - ('4', u"La société verse des salaires"), - ('1', u"La société ne verse pas de salaires"), + ('4', "La société verse des salaires"), + ('1', "La société ne verse pas de salaires"), ], 'DADS Type', required=True, states={'done': [('readonly', True)]}, track_visibility='onchange', @@ -177,6 +176,7 @@ def generate_lines(self): vals = self._prepare_line(partner, base_domain) if vals: lfdlo.create(vals) + self.message_post(body="DAS2 lines generated.") self.add_warning_in_chatter(das2_partners) def _prepare_line(self, partner, base_domain): @@ -189,18 +189,12 @@ def _prepare_line(self, partner, base_domain): amount = 0.0 for mline in mlines: amount += mline.balance - if mline.full_reconcile_id: - rec = _( - 'reconciliation mark %s') % mline.full_reconcile_id.name - else: - rec = _('not reconciled') note.append(_( "Payment dated %s in journal '%s': " - "%.2f € (%s, journal entry %s)") % ( + "%.2f € (journal entry %s)") % ( mline.date, mline.journal_id.display_name, mline.balance, - rec, mline.move_id.name)) res = False if note: @@ -255,7 +249,7 @@ def add_warning_in_chatter(self, das2_partners): msg += '
  • %s' % (partner.id, partner.display_name) msg += '' - self.message_post(msg) + self.message_post(body=msg) @api.model def _prepare_field( @@ -312,7 +306,7 @@ def _prepare_address(self, partner): if partner.country_id and partner.country_id.code not in FRANCE_CODES: if not partner.country_id.fr_cog: raise UserError(_( - u"Missing Code Officiel Géographique on country '%s'.") + "Missing Code Officiel Géographique on country '%s'.") % partner.country_id.display_name) cog = self._prepare_field( 'COG', partner, partner.country_id.fr_cog, 5, True, @@ -439,7 +433,7 @@ def _prepare_file(self): 'Administrative contact email', contact, contact.email, 60) phone = contact.phone or contact.mobile phone = phone.replace(' ', '').replace('.', '').replace( - '-', '').replace(u'\u00A0', '') + '-', '').replace('\u00A0', '') if phone.startswith('+33'): phone = '0%s' % phone[3:] contact_phone = self._prepare_field( @@ -514,6 +508,7 @@ def generate_file(self): 'datas_fname': filename, }) self.attachment_id = attach.id + self.message_post(body="DAS2 file generated.") action = { 'type': 'ir.actions.act_window', 'name': _('DAS2 Export File'), @@ -529,7 +524,8 @@ def generate_file(self): pass # The code below works and triggers an immediate download, but - # the form view of DAS2 is frozen after that, we it's not usable in v10 + # the form view of DAS2 is frozen after that, so it's not usable + # in v10/v12... maybe it's only designed to be used in a wizard # action = { # 'name': 'DAS2', # 'type': 'ir.actions.act_url', @@ -570,57 +566,62 @@ class L10nFrDas2Line(models.Model): related='parent_id.company_id.currency_id', store=True, readonly=True, string='Company Currency') fee_amount = fields.Integer( - string=u'Honoraires et vacations', + string='Honoraires et vacations', states={'done': [('readonly', True)]}) commission_amount = fields.Integer( - string=u'Commissions', states={'done': [('readonly', True)]}) + string='Commissions', states={'done': [('readonly', True)]}) brokerage_amount = fields.Integer( - string=u'Courtages', states={'done': [('readonly', True)]}) + string='Courtages', states={'done': [('readonly', True)]}) discount_amount = fields.Integer( - string=u'Ristournes', states={'done': [('readonly', True)]}) + string='Ristournes', states={'done': [('readonly', True)]}) attendance_fee_amount = fields.Integer( - string=u'Jetons de présence', states={'done': [('readonly', True)]}) + string='Jetons de présence', states={'done': [('readonly', True)]}) copyright_royalties_amount = fields.Integer( - string=u"Droits d'auteur", states={'done': [('readonly', True)]}) + string="Droits d'auteur", states={'done': [('readonly', True)]}) licence_royalties_amount = fields.Integer( - string=u"Droits d'inventeur", states={'done': [('readonly', True)]}) + string="Droits d'inventeur", states={'done': [('readonly', True)]}) other_income_amount = fields.Integer( - string=u'Autre rémunérations', states={'done': [('readonly', True)]}) + string='Autre rémunérations', states={'done': [('readonly', True)]}) allowance_amount = fields.Integer( - string=u'Indemnités et remboursements', + string='Indemnités et remboursements', states={'done': [('readonly', True)]}) benefits_in_kind_amount = fields.Integer( string='Avantages en nature', states={'done': [('readonly', True)]}) withholding_tax_amount = fields.Integer( - string=u'Retenue à la source', states={'done': [('readonly', True)]}) + string='Retenue à la source', states={'done': [('readonly', True)]}) total_amount = fields.Integer( compute='_compute_total_amount', string='Total Amount', store=True, readonly=True) to_declare = fields.Boolean( compute='_compute_total_amount', string='To Declare', readonly=True) allowance_fixed = fields.Boolean( - u'Allocation forfaitaire', states={'done': [('readonly', True)]}) + 'Allocation forfaitaire', states={'done': [('readonly', True)]}) allowance_real = fields.Boolean( - u'Sur frais réels', states={'done': [('readonly', True)]}) + 'Sur frais réels', states={'done': [('readonly', True)]}) allowance_employer = fields.Boolean( - u"Prise en charge directe par l'employeur", + "Prise en charge directe par l'employeur", states={'done': [('readonly', True)]}) benefits_in_kind_food = fields.Boolean( - u'Nourriture', states={'done': [('readonly', True)]}) + 'Nourriture', states={'done': [('readonly', True)]}) benefits_in_kind_accomodation = fields.Boolean( - u'Logement', states={'done': [('readonly', True)]}) + 'Logement', states={'done': [('readonly', True)]}) benefits_in_kind_car = fields.Boolean( - u'Voiture', states={'done': [('readonly', True)]}) + 'Voiture', states={'done': [('readonly', True)]}) benefits_in_kind_other = fields.Boolean( - u'Autres', states={'done': [('readonly', True)]}) + 'Autres', states={'done': [('readonly', True)]}) benefits_in_kind_nict = fields.Boolean( - u'Outils issus des NTIC', states={'done': [('readonly', True)]}) + 'Outils issus des NTIC', states={'done': [('readonly', True)]}) state = fields.Selection( related='parent_id.state', store=True, readonly=True) note = fields.Text() - job = fields.Char(string='Profession', size=30) + job = fields.Char( + string='Profession', size=30, states={'done': [('readonly', True)]}) _sql_constraints = [ + ( + 'partner_parent_unique', + 'unique(partner_id, parent_id)', + 'Same partner used on several lines!'), ( 'fee_amount_positive', 'CHECK(fee_amount >= 0)', @@ -702,5 +703,8 @@ def check_siret(self): @api.onchange('partner_id') def partner_id_change(self): - if self.partner_id and self.partner_id.siren and self.partner_id.nic: - self.partner_siret = self.partner_id.siret + if self.partner_id: + if self.partner_id.siren and self.partner_id.nic: + self.partner_siret = self.partner_id.siret + if self.partner_id.fr_das2_job: + self.job = self.partner_id.fr_das2_job diff --git a/l10n_fr_das2/models/partner.py b/l10n_fr_das2/models/partner.py index 720c920a0..47cd0efb0 100644 --- a/l10n_fr_das2/models/partner.py +++ b/l10n_fr_das2/models/partner.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2020 Akretion France (http://www.akretion.com/) # @author: Alexis de Lattre # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). @@ -10,15 +9,15 @@ class ResPartner(models.Model): _inherit = 'res.partner' fr_das2_type = fields.Selection([ - ('fee', u'Honoraires et vacations'), - ('commission', u'Commissions'), - ('brokerage', u'Courtages'), - ('discount', u'Ristournes'), - ('attendance_fee', u'Jetons de présence'), - ('copyright_royalties', u"Droits d'auteur"), - ('licence_royalties', u"Droits d'inventeur"), - ('other_income', u'Autre rémunérations'), - ('allowance', u'Indemnités et remboursements'), + ('fee', 'Honoraires et vacations'), + ('commission', 'Commissions'), + ('brokerage', 'Courtages'), + ('discount', 'Ristournes'), + ('attendance_fee', 'Jetons de présence'), + ('copyright_royalties', "Droits d'auteur"), + ('licence_royalties', "Droits d'inventeur"), + ('other_income', 'Autre rémunérations'), + ('allowance', 'Indemnités et remboursements'), ], string='DAS2 Type', track_visibility='onchange') fr_das2_job = fields.Char( string='DAS2 Job', size=30, diff --git a/l10n_fr_das2/views/account_config_settings.xml b/l10n_fr_das2/views/account_config_settings.xml deleted file mode 100644 index d366aabbe..000000000 --- a/l10n_fr_das2/views/account_config_settings.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - chorus.account.config.settings.form - account.config.settings - - - - - - - - - - - - diff --git a/l10n_fr_das2/views/config_settings.xml b/l10n_fr_das2/views/config_settings.xml new file mode 100644 index 000000000..9f14c4f3e --- /dev/null +++ b/l10n_fr_das2/views/config_settings.xml @@ -0,0 +1,34 @@ + + + + + + + + das2.res.config.settings.form + res.config.settings + + + +

    Intrastat

    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + + + + + From 7b14557390cdeb6ebaa8ede32bc70e99642e3dd4 Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Fri, 3 Apr 2020 11:18:53 +0200 Subject: [PATCH 15/58] Add missing _ on message string --- l10n_fr_das2/models/l10n_fr_das2.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/l10n_fr_das2/models/l10n_fr_das2.py b/l10n_fr_das2/models/l10n_fr_das2.py index bad909f5f..0ae682186 100644 --- a/l10n_fr_das2/models/l10n_fr_das2.py +++ b/l10n_fr_das2/models/l10n_fr_das2.py @@ -176,7 +176,7 @@ def generate_lines(self): vals = self._prepare_line(partner, base_domain) if vals: lfdlo.create(vals) - self.message_post(body="DAS2 lines generated.") + self.message_post(body=_("DAS2 lines generated.")) self.add_warning_in_chatter(das2_partners) def _prepare_line(self, partner, base_domain): @@ -508,7 +508,7 @@ def generate_file(self): 'datas_fname': filename, }) self.attachment_id = attach.id - self.message_post(body="DAS2 file generated.") + self.message_post(body=_("DAS2 file generated.")) action = { 'type': 'ir.actions.act_window', 'name': _('DAS2 Export File'), From bf92d07900e39af3d96c6932bfc378323daad999 Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Fri, 3 Apr 2020 11:27:28 +0200 Subject: [PATCH 16/58] Fix double import of unidecode --- l10n_fr_das2/models/l10n_fr_das2.py | 1 - 1 file changed, 1 deletion(-) diff --git a/l10n_fr_das2/models/l10n_fr_das2.py b/l10n_fr_das2/models/l10n_fr_das2.py index 0ae682186..4c1fbf810 100644 --- a/l10n_fr_das2/models/l10n_fr_das2.py +++ b/l10n_fr_das2/models/l10n_fr_das2.py @@ -7,7 +7,6 @@ from dateutil.relativedelta import relativedelta from datetime import datetime from odoo.addons.l10n_fr_siret.models.partner import _check_luhn -from unidecode import unidecode import base64 import logging From 368be3bc7fda8917c69a1e8c10a6220f8cf4ad01 Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Fri, 3 Apr 2020 13:06:52 +0200 Subject: [PATCH 17/58] Remove utf-8 coding in python file --- l10n_fr_cog/models/__init__.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/l10n_fr_cog/models/__init__.py b/l10n_fr_cog/models/__init__.py index f2d611a89..30b933141 100644 --- a/l10n_fr_cog/models/__init__.py +++ b/l10n_fr_cog/models/__init__.py @@ -1,3 +1 @@ -# -*- coding: utf-8 -*- - from . import country From 35e179f55b735c8ac904dfa4fe88d567115b0d27 Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Fri, 3 Apr 2020 12:52:17 +0000 Subject: [PATCH 18/58] [UPD] README.rst --- l10n_fr_cog/README.rst | 10 +++++----- l10n_fr_cog/static/description/index.html | 6 +++--- l10n_fr_das2/README.rst | 10 +++++----- l10n_fr_das2/static/description/index.html | 6 +++--- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/l10n_fr_cog/README.rst b/l10n_fr_cog/README.rst index f98027813..4666060b4 100644 --- a/l10n_fr_cog/README.rst +++ b/l10n_fr_cog/README.rst @@ -14,13 +14,13 @@ Code Officiel Géographique :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html :alt: License: AGPL-3 .. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fl10n--france-lightgray.png?logo=github - :target: https://github.com/OCA/l10n-france/tree/10.0/l10n_fr_cog + :target: https://github.com/OCA/l10n-france/tree/12.0/l10n_fr_cog :alt: OCA/l10n-france .. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png - :target: https://translation.odoo-community.org/projects/l10n-france-10-0/l10n-france-10-0-l10n_fr_cog + :target: https://translation.odoo-community.org/projects/l10n-france-12-0/l10n-france-12-0-l10n_fr_cog :alt: Translate me on Weblate .. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png - :target: https://runbot.odoo-community.org/runbot/121/10.0 + :target: https://runbot.odoo-community.org/runbot/121/12.0 :alt: Try me on Runbot |badge1| |badge2| |badge3| |badge4| |badge5| @@ -40,7 +40,7 @@ Bug Tracker Bugs are tracked on `GitHub Issues `_. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us smashing it by providing a detailed and welcomed -`feedback `_. +`feedback `_. Do not contact contributors directly about support or help with technical issues. @@ -70,6 +70,6 @@ OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use. -This module is part of the `OCA/l10n-france `_ project on GitHub. +This module is part of the `OCA/l10n-france `_ project on GitHub. You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/l10n_fr_cog/static/description/index.html b/l10n_fr_cog/static/description/index.html index e615f3c7d..4c6a68dcf 100644 --- a/l10n_fr_cog/static/description/index.html +++ b/l10n_fr_cog/static/description/index.html @@ -367,7 +367,7 @@

    Code Officiel Géographique

    !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> -

    Beta License: AGPL-3 OCA/l10n-france Translate me on Weblate Try me on Runbot

    +

    Beta License: AGPL-3 OCA/l10n-france Translate me on Weblate Try me on Runbot

    This module adds the Code Officiel Géographique of INSEE on countries. All countries except France and DOM-TOMs (and some very particular territories) have this code.

    This module is used by other modules of the French localization such as the DAS2 module.

    Table of contents

    @@ -387,7 +387,7 @@

    Bug Tracker

    Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us smashing it by providing a detailed and welcomed -feedback.

    +feedback.

    Do not contact contributors directly about support or help with technical issues.

    @@ -411,7 +411,7 @@

    Maintainers

    OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use.

    -

    This module is part of the OCA/l10n-france project on GitHub.

    +

    This module is part of the OCA/l10n-france project on GitHub.

    You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

    diff --git a/l10n_fr_das2/README.rst b/l10n_fr_das2/README.rst index 84f139548..f497668eb 100644 --- a/l10n_fr_das2/README.rst +++ b/l10n_fr_das2/README.rst @@ -14,13 +14,13 @@ DAS2 :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html :alt: License: AGPL-3 .. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fl10n--france-lightgray.png?logo=github - :target: https://github.com/OCA/l10n-france/tree/10.0/l10n_fr_das2 + :target: https://github.com/OCA/l10n-france/tree/12.0/l10n_fr_das2 :alt: OCA/l10n-france .. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png - :target: https://translation.odoo-community.org/projects/l10n-france-10-0/l10n-france-10-0-l10n_fr_das2 + :target: https://translation.odoo-community.org/projects/l10n-france-12-0/l10n-france-12-0-l10n_fr_das2 :alt: Translate me on Weblate .. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png - :target: https://runbot.odoo-community.org/runbot/121/10.0 + :target: https://runbot.odoo-community.org/runbot/121/12.0 :alt: Try me on Runbot |badge1| |badge2| |badge3| |badge4| |badge5| @@ -71,7 +71,7 @@ Bug Tracker Bugs are tracked on `GitHub Issues `_. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us smashing it by providing a detailed and welcomed -`feedback `_. +`feedback `_. Do not contact contributors directly about support or help with technical issues. @@ -101,6 +101,6 @@ OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use. -This module is part of the `OCA/l10n-france `_ project on GitHub. +This module is part of the `OCA/l10n-france `_ project on GitHub. You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/l10n_fr_das2/static/description/index.html b/l10n_fr_das2/static/description/index.html index c62df0286..4f6def775 100644 --- a/l10n_fr_das2/static/description/index.html +++ b/l10n_fr_das2/static/description/index.html @@ -367,7 +367,7 @@

    DAS2

    !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> -

    Beta License: AGPL-3 OCA/l10n-france Translate me on Weblate Try me on Runbot

    +

    Beta License: AGPL-3 OCA/l10n-france Translate me on Weblate Try me on Runbot

    This module adds support for DAS2 <https://www.impots.gouv.fr/portail/formulaire/das2/etat-des-honoraires-vacations-commissions-courtages-ristournes-et-jetons>_, which is an annual fiscal declaration also called Déclaration d’honoraires. It will allow you to auto-generate the lines of DAS2 from the accounting entries, check the result, manually update lines if needed and eventually generate a declaration file. This declaration file can be uploaded on the dedicated website https://teletd.impots.gouv.fr/teletd/connexionEFI.do; that way, you won’t have to manually type the declaration.

    The specifications of the file are available on this page (select Salaires, honoraires et actionnariat salarié).

    Table of contents

    @@ -411,7 +411,7 @@

    Bug Tracker

    Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us smashing it by providing a detailed and welcomed -feedback.

    +feedback.

    Do not contact contributors directly about support or help with technical issues.

    @@ -435,7 +435,7 @@

    Maintainers

    OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use.

    -

    This module is part of the OCA/l10n-france project on GitHub.

    +

    This module is part of the OCA/l10n-france project on GitHub.

    You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

    From 1f3c2c51a6dee6c08a9d1fb9be4dc0bd3ab811ef Mon Sep 17 00:00:00 2001 From: oca-travis Date: Fri, 3 Apr 2020 13:01:48 +0000 Subject: [PATCH 19/58] [UPD] Update l10n_fr_cog.pot --- l10n_fr_cog/i18n/l10n_fr_cog.pot | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 l10n_fr_cog/i18n/l10n_fr_cog.pot diff --git a/l10n_fr_cog/i18n/l10n_fr_cog.pot b/l10n_fr_cog/i18n/l10n_fr_cog.pot new file mode 100644 index 000000000..6940e38e4 --- /dev/null +++ b/l10n_fr_cog/i18n/l10n_fr_cog.pot @@ -0,0 +1,30 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * l10n_fr_cog +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 12.0\n" +"Report-Msgid-Bugs-To: \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_fr_cog +#: model:ir.model.fields,field_description:l10n_fr_cog.field_res_country__fr_cog +msgid "Code Officiel Géographique" +msgstr "" + +#. module: l10n_fr_cog +#: model:ir.model.fields,help:l10n_fr_cog.field_res_country__fr_cog +msgid "Code Officiel Géographique, by INSEE" +msgstr "" + +#. module: l10n_fr_cog +#: model:ir.model,name:l10n_fr_cog.model_res_country +msgid "Country" +msgstr "" + From e654f4d65172f2878ba9c75c846433298dd23705 Mon Sep 17 00:00:00 2001 From: oca-travis Date: Fri, 3 Apr 2020 13:01:51 +0000 Subject: [PATCH 20/58] [UPD] Update l10n_fr_das2.pot --- l10n_fr_das2/i18n/l10n_fr_das2.pot | 698 +++++++++++++++++++++++++++++ 1 file changed, 698 insertions(+) create mode 100644 l10n_fr_das2/i18n/l10n_fr_das2.pot diff --git a/l10n_fr_das2/i18n/l10n_fr_das2.pot b/l10n_fr_das2/i18n/l10n_fr_das2.pot new file mode 100644 index 000000000..ec2c3ca6a --- /dev/null +++ b/l10n_fr_das2/i18n/l10n_fr_das2.pot @@ -0,0 +1,698 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * l10n_fr_das2 +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 12.0\n" +"Report-Msgid-Bugs-To: \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_fr_das2 +#: code:addons/l10n_fr_das2/models/l10n_fr_das2.py:242 +#, python-format +msgid "

    The following partners are not configured for DAS2 but they have expenses in some accounts that indicate that maybe they should be configured for DAS2:

      " +msgstr "" + +#. module: l10n_fr_das2 +#: model_terms:ir.ui.view,arch_db:l10n_fr_das2.l10n_fr_das2_form +msgid "Lines Fullscreen" +msgstr "" + +#. module: l10n_fr_das2 +#: sql_constraint:l10n.fr.das2:0 +msgid "A DAS2 already exists for that year!" +msgstr "" + +#. module: l10n_fr_das2 +#: code:addons/l10n_fr_das2/models/l10n_fr_das2.py:496 +#, python-format +msgid "A special character in the DAS2 file is not in the latin1 table. Please locate this special character and replace it by a standard character and try again." +msgstr "" + +#. module: l10n_fr_das2 +#: model:ir.model.fields,field_description:l10n_fr_das2.field_l10n_fr_das2__message_needaction +msgid "Action Needed" +msgstr "" + +#. module: l10n_fr_das2 +#: model:ir.model.fields,field_description:l10n_fr_das2.field_l10n_fr_das2__contact_id +msgid "Administrative Contact" +msgstr "" + +#. module: l10n_fr_das2 +#: model:ir.model.fields,field_description:l10n_fr_das2.field_l10n_fr_das2_line__allowance_fixed +msgid "Allocation forfaitaire" +msgstr "" + +#. module: l10n_fr_das2 +#: code:addons/l10n_fr_das2/models/l10n_fr_das2.py:488 +#, python-format +msgid "An export file already exists. First, delete it via the attachments and then re-generate it." +msgstr "" + +#. module: l10n_fr_das2 +#: model:ir.model.fields,field_description:l10n_fr_das2.field_l10n_fr_das2__message_attachment_count +msgid "Attachment Count" +msgstr "" + +#. module: l10n_fr_das2 +#: model:ir.model.fields,field_description:l10n_fr_das2.field_l10n_fr_das2_line__other_income_amount +#: selection:res.partner,fr_das2_type:0 +msgid "Autre rémunérations" +msgstr "" + +#. module: l10n_fr_das2 +#: model:ir.model.fields,field_description:l10n_fr_das2.field_l10n_fr_das2_line__benefits_in_kind_other +msgid "Autres" +msgstr "" + +#. module: l10n_fr_das2 +#: model:ir.model.fields,field_description:l10n_fr_das2.field_l10n_fr_das2_line__benefits_in_kind_amount +msgid "Avantages en nature" +msgstr "" + +#. module: l10n_fr_das2 +#: model_terms:ir.ui.view,arch_db:l10n_fr_das2.l10n_fr_das2_form +msgid "Back to Draft" +msgstr "" + +#. module: l10n_fr_das2 +#: code:addons/l10n_fr_das2/models/l10n_fr_das2.py:130 +#, python-format +msgid "Cannot delete declaration %s in done state." +msgstr "" + +#. module: l10n_fr_das2 +#: model:ir.model.fields,field_description:l10n_fr_das2.field_l10n_fr_das2_line__commission_amount +#: selection:res.partner,fr_das2_type:0 +msgid "Commissions" +msgstr "" + +#. module: l10n_fr_das2 +#: model:ir.model,name:l10n_fr_das2.model_res_company +msgid "Companies" +msgstr "" + +#. module: l10n_fr_das2 +#: model:ir.model.fields,field_description:l10n_fr_das2.field_l10n_fr_das2__company_id +#: model:ir.model.fields,field_description:l10n_fr_das2.field_l10n_fr_das2_line__company_id +msgid "Company" +msgstr "" + +#. module: l10n_fr_das2 +#: code:addons/l10n_fr_das2/models/l10n_fr_das2.py:143 +#, python-format +msgid "Company '%s' is configured in country '%s'. The DAS2 is only for France and it's oversea territories." +msgstr "" + +#. module: l10n_fr_das2 +#: code:addons/l10n_fr_das2/models/l10n_fr_das2.py:149 +#, python-format +msgid "Company '%s' is configured with currency '%s'. It should be EUR." +msgstr "" + +#. module: l10n_fr_das2 +#: model:ir.model.fields,field_description:l10n_fr_das2.field_l10n_fr_das2__currency_id +#: model:ir.model.fields,field_description:l10n_fr_das2.field_l10n_fr_das2_line__currency_id +msgid "Company Currency" +msgstr "" + +#. module: l10n_fr_das2 +#: model:ir.model,name:l10n_fr_das2.model_res_config_settings +msgid "Config Settings" +msgstr "" + +#. module: l10n_fr_das2 +#: model:ir.model,name:l10n_fr_das2.model_res_partner +msgid "Contact" +msgstr "" + +#. module: l10n_fr_das2 +#: model:ir.model.fields,help:l10n_fr_das2.field_l10n_fr_das2__contact_id +msgid "Contact in the company for the fiscal administration: the name, email and phone number of this partner will be used in the file." +msgstr "" + +#. module: l10n_fr_das2 +#: code:addons/l10n_fr_das2/models/l10n_fr_das2.py:140 +#, python-format +msgid "Country not set on company '%s'." +msgstr "" + +#. module: l10n_fr_das2 +#: model:ir.model.fields,field_description:l10n_fr_das2.field_l10n_fr_das2_line__brokerage_amount +#: selection:res.partner,fr_das2_type:0 +msgid "Courtages" +msgstr "" + +#. module: l10n_fr_das2 +#: model:ir.model.fields,field_description:l10n_fr_das2.field_l10n_fr_das2__create_uid +#: model:ir.model.fields,field_description:l10n_fr_das2.field_l10n_fr_das2_line__create_uid +msgid "Created by" +msgstr "" + +#. module: l10n_fr_das2 +#: model:ir.model.fields,field_description:l10n_fr_das2.field_l10n_fr_das2__create_date +#: model:ir.model.fields,field_description:l10n_fr_das2.field_l10n_fr_das2_line__create_date +msgid "Created on" +msgstr "" + +#. module: l10n_fr_das2 +#: model:ir.model.fields,field_description:l10n_fr_das2.field_l10n_fr_das2__dads_type +msgid "DADS Type" +msgstr "" + +#. module: l10n_fr_das2 +#: model:ir.actions.act_window,name:l10n_fr_das2.l10n_fr_das2_action +#: model:ir.model,name:l10n_fr_das2.model_l10n_fr_das2 +#: model:ir.ui.menu,name:l10n_fr_das2.l10n_fr_das2_menu +#: model_terms:ir.ui.view,arch_db:l10n_fr_das2.l10n_fr_das2_form +#: model_terms:ir.ui.view,arch_db:l10n_fr_das2.l10n_fr_das2_tree +#: model_terms:ir.ui.view,arch_db:l10n_fr_das2.view_partner_property_form +#: model_terms:ir.ui.view,arch_db:l10n_fr_das2.view_res_partner_filter +msgid "DAS2" +msgstr "" + +#. module: l10n_fr_das2 +#: code:addons/l10n_fr_das2/models/l10n_fr_das2.py:513 +#, python-format +msgid "DAS2 Export File" +msgstr "" + +#. module: l10n_fr_das2 +#: model:ir.model.fields,field_description:l10n_fr_das2.field_res_partner__fr_das2_job +#: model:ir.model.fields,field_description:l10n_fr_das2.field_res_users__fr_das2_job +msgid "DAS2 Job" +msgstr "" + +#. module: l10n_fr_das2 +#: model_terms:ir.ui.view,arch_db:l10n_fr_das2.l10n_fr_das2_line_form +msgid "DAS2 Line" +msgstr "" + +#. module: l10n_fr_das2 +#: model:ir.actions.act_window,name:l10n_fr_das2.l10n_fr_das2_line_action +#: model_terms:ir.ui.view,arch_db:l10n_fr_das2.l10n_fr_das2_line_tree +msgid "DAS2 Lines" +msgstr "" + +#. module: l10n_fr_das2 +#: model:ir.model.fields,field_description:l10n_fr_das2.field_res_company__fr_das2_partner_declare_threshold +#: model:ir.model.fields,field_description:l10n_fr_das2.field_res_config_settings__fr_das2_partner_declare_threshold +msgid "DAS2 Partner Declaration Threshold" +msgstr "" + +#. module: l10n_fr_das2 +#: model:ir.model.fields,field_description:l10n_fr_das2.field_l10n_fr_das2_line__parent_id +msgid "DAS2 Report" +msgstr "" + +#. module: l10n_fr_das2 +#: model:ir.model.fields,field_description:l10n_fr_das2.field_res_partner__fr_das2_type +#: model:ir.model.fields,field_description:l10n_fr_das2.field_res_users__fr_das2_type +msgid "DAS2 Type" +msgstr "" + +#. module: l10n_fr_das2 +#: code:addons/l10n_fr_das2/models/l10n_fr_das2.py:510 +#, python-format +msgid "DAS2 file generated." +msgstr "" + +#. module: l10n_fr_das2 +#: model:ir.model,name:l10n_fr_das2.model_l10n_fr_das2_line +msgid "DAS2 line" +msgstr "" + +#. module: l10n_fr_das2 +#: code:addons/l10n_fr_das2/models/l10n_fr_das2.py:178 +#, python-format +msgid "DAS2 lines generated." +msgstr "" + +#. module: l10n_fr_das2 +#: model:ir.model.fields,field_description:l10n_fr_das2.field_l10n_fr_das2__display_name +#: model:ir.model.fields,field_description:l10n_fr_das2.field_l10n_fr_das2_line__display_name +msgid "Display Name" +msgstr "" + +#. module: l10n_fr_das2 +#: model_terms:ir.ui.view,arch_db:l10n_fr_das2.l10n_fr_das2_form +#: model_terms:ir.ui.view,arch_db:l10n_fr_das2.l10n_fr_das2_search +#: selection:l10n.fr.das2,state:0 +msgid "Done" +msgstr "" + +#. module: l10n_fr_das2 +#: model_terms:ir.ui.view,arch_db:l10n_fr_das2.l10n_fr_das2_search +#: selection:l10n.fr.das2,state:0 +msgid "Draft" +msgstr "" + +#. module: l10n_fr_das2 +#: model:ir.model.fields,field_description:l10n_fr_das2.field_l10n_fr_das2_line__copyright_royalties_amount +#: selection:res.partner,fr_das2_type:0 +msgid "Droits d'auteur" +msgstr "" + +#. module: l10n_fr_das2 +#: model:ir.model.fields,field_description:l10n_fr_das2.field_l10n_fr_das2_line__licence_royalties_amount +#: selection:res.partner,fr_das2_type:0 +msgid "Droits d'inventeur" +msgstr "" + +#. module: l10n_fr_das2 +#: code:addons/l10n_fr_das2/models/l10n_fr_das2.py:265 +#, python-format +msgid "Failed to convert field '%s' (partner %s) to integer." +msgstr "" + +#. module: l10n_fr_das2 +#: code:addons/l10n_fr_das2/models/l10n_fr_das2.py:270 +#, python-format +msgid "Field %s (partner %s) has value %s: it is bigger than the maximum size (%d characters)" +msgstr "" + +#. module: l10n_fr_das2 +#: model:ir.model.fields,field_description:l10n_fr_das2.field_l10n_fr_das2__attachment_id +msgid "File Export" +msgstr "" + +#. module: l10n_fr_das2 +#: model:ir.model.fields,field_description:l10n_fr_das2.field_l10n_fr_das2__message_follower_ids +msgid "Followers" +msgstr "" + +#. module: l10n_fr_das2 +#: model:ir.model.fields,field_description:l10n_fr_das2.field_l10n_fr_das2__message_channel_ids +msgid "Followers (Channels)" +msgstr "" + +#. module: l10n_fr_das2 +#: model:ir.model.fields,field_description:l10n_fr_das2.field_l10n_fr_das2__message_partner_ids +msgid "Followers (Partners)" +msgstr "" + +#. module: l10n_fr_das2 +#: model_terms:ir.ui.view,arch_db:l10n_fr_das2.l10n_fr_das2_form +msgid "Generate File" +msgstr "" + +#. module: l10n_fr_das2 +#: model_terms:ir.ui.view,arch_db:l10n_fr_das2.l10n_fr_das2_form +msgid "Generate Lines" +msgstr "" + +#. module: l10n_fr_das2 +#: model:ir.model.fields,field_description:l10n_fr_das2.field_l10n_fr_das2_line__fee_amount +#: selection:res.partner,fr_das2_type:0 +msgid "Honoraires et vacations" +msgstr "" + +#. module: l10n_fr_das2 +#: model:ir.model.fields,field_description:l10n_fr_das2.field_l10n_fr_das2__id +#: model:ir.model.fields,field_description:l10n_fr_das2.field_l10n_fr_das2_line__id +msgid "ID" +msgstr "" + +#. module: l10n_fr_das2 +#: model:ir.model.fields,help:l10n_fr_das2.field_l10n_fr_das2__message_unread +msgid "If checked new messages require your attention." +msgstr "" + +#. module: l10n_fr_das2 +#: model:ir.model.fields,help:l10n_fr_das2.field_l10n_fr_das2__message_needaction +msgid "If checked, new messages require your attention." +msgstr "" + +#. module: l10n_fr_das2 +#: model:ir.model.fields,help:l10n_fr_das2.field_l10n_fr_das2__message_has_error +msgid "If checked, some messages have a delivery error." +msgstr "" + +#. module: l10n_fr_das2 +#: model:ir.model.fields,field_description:l10n_fr_das2.field_l10n_fr_das2_line__allowance_amount +#: selection:res.partner,fr_das2_type:0 +msgid "Indemnités et remboursements" +msgstr "" + +#. module: l10n_fr_das2 +#: model_terms:ir.ui.view,arch_db:l10n_fr_das2.view_account_config_settings +msgid "Intrastat" +msgstr "" + +#. module: l10n_fr_das2 +#: model:ir.model.fields,field_description:l10n_fr_das2.field_l10n_fr_das2__message_is_follower +msgid "Is Follower" +msgstr "" + +#. module: l10n_fr_das2 +#: model:ir.model.fields,field_description:l10n_fr_das2.field_l10n_fr_das2_line__attendance_fee_amount +#: selection:res.partner,fr_das2_type:0 +msgid "Jetons de présence" +msgstr "" + +#. module: l10n_fr_das2 +#: model_terms:ir.ui.view,arch_db:l10n_fr_das2.view_partner_property_form +msgid "Job" +msgstr "" + +#. module: l10n_fr_das2 +#: selection:l10n.fr.das2,dads_type:0 +msgid "La société ne verse pas de salaires" +msgstr "" + +#. module: l10n_fr_das2 +#: selection:l10n.fr.das2,dads_type:0 +msgid "La société verse des salaires" +msgstr "" + +#. module: l10n_fr_das2 +#: model:ir.model.fields,field_description:l10n_fr_das2.field_l10n_fr_das2____last_update +#: model:ir.model.fields,field_description:l10n_fr_das2.field_l10n_fr_das2_line____last_update +msgid "Last Modified on" +msgstr "" + +#. module: l10n_fr_das2 +#: model:ir.model.fields,field_description:l10n_fr_das2.field_l10n_fr_das2__write_uid +#: model:ir.model.fields,field_description:l10n_fr_das2.field_l10n_fr_das2_line__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: l10n_fr_das2 +#: model:ir.model.fields,field_description:l10n_fr_das2.field_l10n_fr_das2__write_date +#: model:ir.model.fields,field_description:l10n_fr_das2.field_l10n_fr_das2_line__write_date +msgid "Last Updated on" +msgstr "" + +#. module: l10n_fr_das2 +#: model:ir.model.fields,field_description:l10n_fr_das2.field_l10n_fr_das2__line_ids +msgid "Lines" +msgstr "" + +#. module: l10n_fr_das2 +#: model_terms:ir.ui.view,arch_db:l10n_fr_das2.l10n_fr_das2_form +msgid "List view of lines" +msgstr "" + +#. module: l10n_fr_das2 +#: model:ir.model.fields,field_description:l10n_fr_das2.field_l10n_fr_das2_line__benefits_in_kind_accomodation +msgid "Logement" +msgstr "" + +#. module: l10n_fr_das2 +#: model:ir.model.fields,field_description:l10n_fr_das2.field_l10n_fr_das2__message_main_attachment_id +msgid "Main Attachment" +msgstr "" + +#. module: l10n_fr_das2 +#: model:ir.model.fields,field_description:l10n_fr_das2.field_l10n_fr_das2__message_has_error +msgid "Message Delivery error" +msgstr "" + +#. module: l10n_fr_das2 +#: model:ir.model.fields,field_description:l10n_fr_das2.field_l10n_fr_das2__message_ids +msgid "Messages" +msgstr "" + +#. module: l10n_fr_das2 +#: code:addons/l10n_fr_das2/models/l10n_fr_das2.py:471 +#, python-format +msgid "Missing APE on company '%s'." +msgstr "" + +#. module: l10n_fr_das2 +#: code:addons/l10n_fr_das2/models/l10n_fr_das2.py:307 +#, python-format +msgid "Missing Code Officiel Géographique on country '%s'." +msgstr "" + +#. module: l10n_fr_das2 +#: code:addons/l10n_fr_das2/models/l10n_fr_das2.py:375 +#, python-format +msgid "Missing SIRET for french partner %s." +msgstr "" + +#. module: l10n_fr_das2 +#: code:addons/l10n_fr_das2/models/l10n_fr_das2.py:468 +#, python-format +msgid "Missing SIRET on company '%s'." +msgstr "" + +#. module: l10n_fr_das2 +#: code:addons/l10n_fr_das2/models/l10n_fr_das2.py:474 +#, python-format +msgid "Missing Street on company '%s'" +msgstr "" + +#. module: l10n_fr_das2 +#: code:addons/l10n_fr_das2/models/l10n_fr_das2.py:478 +#, python-format +msgid "Missing administrative contact." +msgstr "" + +#. module: l10n_fr_das2 +#: code:addons/l10n_fr_das2/models/l10n_fr_das2.py:303 +#, python-format +msgid "Missing city on partner '%s'." +msgstr "" + +#. module: l10n_fr_das2 +#: sql_constraint:l10n.fr.das2.line:0 +msgid "Negative amounts not allowed!" +msgstr "" + +#. module: l10n_fr_das2 +#: model:ir.model.fields,field_description:l10n_fr_das2.field_l10n_fr_das2_line__note +msgid "Note" +msgstr "" + +#. module: l10n_fr_das2 +#: model_terms:ir.ui.view,arch_db:l10n_fr_das2.l10n_fr_das2_line_form +msgid "Notes" +msgstr "" + +#. module: l10n_fr_das2 +#: model:ir.model.fields,field_description:l10n_fr_das2.field_l10n_fr_das2_line__benefits_in_kind_food +msgid "Nourriture" +msgstr "" + +#. module: l10n_fr_das2 +#: model:ir.model.fields,field_description:l10n_fr_das2.field_l10n_fr_das2__message_needaction_counter +msgid "Number of Actions" +msgstr "" + +#. module: l10n_fr_das2 +#: model:ir.model.fields,field_description:l10n_fr_das2.field_l10n_fr_das2__message_has_error_counter +msgid "Number of error" +msgstr "" + +#. module: l10n_fr_das2 +#: model:ir.model.fields,help:l10n_fr_das2.field_l10n_fr_das2__message_needaction_counter +msgid "Number of messages which requires an action" +msgstr "" + +#. module: l10n_fr_das2 +#: model:ir.model.fields,help:l10n_fr_das2.field_l10n_fr_das2__message_has_error_counter +msgid "Number of messages with delivery error" +msgstr "" + +#. module: l10n_fr_das2 +#: model:ir.model.fields,help:l10n_fr_das2.field_l10n_fr_das2__message_unread_counter +msgid "Number of unread messages" +msgstr "" + +#. module: l10n_fr_das2 +#: code:addons/l10n_fr_das2/models/l10n_fr_das2.py:455 +#, python-format +msgid "One of the lines has a length of %d. All lines should have a length of 672. Line: %s." +msgstr "" + +#. module: l10n_fr_das2 +#: model:ir.model.fields,field_description:l10n_fr_das2.field_l10n_fr_das2_line__benefits_in_kind_nict +msgid "Outils issus des NTIC" +msgstr "" + +#. module: l10n_fr_das2 +#: model:ir.model.fields,field_description:l10n_fr_das2.field_l10n_fr_das2__partner_declare_threshold +msgid "Partner Declaration Threshold" +msgstr "" + +#. module: l10n_fr_das2 +#: model:ir.model.fields,field_description:l10n_fr_das2.field_l10n_fr_das2__payment_journal_ids +msgid "Payment Journals" +msgstr "" + +#. module: l10n_fr_das2 +#: code:addons/l10n_fr_das2/models/l10n_fr_das2.py:191 +#, python-format +msgid "Payment dated %s in journal '%s': %.2f € (journal entry %s)" +msgstr "" + +#. module: l10n_fr_das2 +#: model:ir.model.fields,field_description:l10n_fr_das2.field_l10n_fr_das2_line__allowance_employer +msgid "Prise en charge directe par l'employeur" +msgstr "" + +#. module: l10n_fr_das2 +#: model:ir.model.fields,field_description:l10n_fr_das2.field_l10n_fr_das2_line__job +msgid "Profession" +msgstr "" + +#. module: l10n_fr_das2 +#: model:ir.model.fields,field_description:l10n_fr_das2.field_l10n_fr_das2_line__withholding_tax_amount +msgid "Retenue à la source" +msgstr "" + +#. module: l10n_fr_das2 +#: model:ir.model.fields,field_description:l10n_fr_das2.field_l10n_fr_das2_line__discount_amount +#: selection:res.partner,fr_das2_type:0 +msgid "Ristournes" +msgstr "" + +#. module: l10n_fr_das2 +#: model:ir.model.fields,field_description:l10n_fr_das2.field_l10n_fr_das2_line__partner_siret +msgid "SIRET" +msgstr "" + +#. module: l10n_fr_das2 +#: code:addons/l10n_fr_das2/models/l10n_fr_das2.py:695 +#, python-format +msgid "SIRET %s is invalid: it must have 14 digits." +msgstr "" + +#. module: l10n_fr_das2 +#: code:addons/l10n_fr_das2/models/l10n_fr_das2.py:699 +#, python-format +msgid "SIRET %s is invalid: the checksum is wrong." +msgstr "" + +#. module: l10n_fr_das2 +#: sql_constraint:l10n.fr.das2.line:0 +msgid "Same partner used on several lines!" +msgstr "" + +#. module: l10n_fr_das2 +#: model_terms:ir.ui.view,arch_db:l10n_fr_das2.l10n_fr_das2_search +msgid "Search DAS2" +msgstr "" + +#. module: l10n_fr_das2 +#: model_terms:ir.ui.view,arch_db:l10n_fr_das2.l10n_fr_das2_line_search +msgid "Search DAS2 Lines" +msgstr "" + +#. module: l10n_fr_das2 +#: model:ir.model.fields,field_description:l10n_fr_das2.field_l10n_fr_das2__state +#: model:ir.model.fields,field_description:l10n_fr_das2.field_l10n_fr_das2_line__state +msgid "State" +msgstr "" + +#. module: l10n_fr_das2 +#: model:ir.model.fields,field_description:l10n_fr_das2.field_l10n_fr_das2_line__partner_id +msgid "Supplier" +msgstr "" + +#. module: l10n_fr_das2 +#: model:ir.model.fields,field_description:l10n_fr_das2.field_l10n_fr_das2_line__allowance_real +msgid "Sur frais réels" +msgstr "" + +#. module: l10n_fr_das2 +#: code:addons/l10n_fr_das2/models/l10n_fr_das2.py:466 +#, python-format +msgid "The DAS2 has no lines." +msgstr "" + +#. module: l10n_fr_das2 +#: code:addons/l10n_fr_das2/models/l10n_fr_das2.py:155 +#, python-format +msgid "The DAS2 partner declaration threshold is not set on company '%s'." +msgstr "" + +#. module: l10n_fr_das2 +#: sql_constraint:res.company:0 +msgid "The DAS2 partner declaration threshold must be positive!" +msgstr "" + +#. module: l10n_fr_das2 +#: code:addons/l10n_fr_das2/models/l10n_fr_das2.py:480 +#, python-format +msgid "The email is not set on the administrative contact partner '%s'." +msgstr "" + +#. module: l10n_fr_das2 +#: code:addons/l10n_fr_das2/models/l10n_fr_das2.py:279 +#, python-format +msgid "The field '%s' (partner %s) is empty or 0. It should have a non-null value." +msgstr "" + +#. module: l10n_fr_das2 +#: code:addons/l10n_fr_das2/models/l10n_fr_das2.py:484 +#, python-format +msgid "The phone number is not set on the administrative contact partner '%s'." +msgstr "" + +#. module: l10n_fr_das2 +#: code:addons/l10n_fr_das2/models/l10n_fr_das2.py:164 +#, python-format +msgid "There are no partners configured for DAS2." +msgstr "" + +#. module: l10n_fr_das2 +#: model:ir.model.fields,field_description:l10n_fr_das2.field_l10n_fr_das2_line__to_declare +#: model_terms:ir.ui.view,arch_db:l10n_fr_das2.l10n_fr_das2_line_search +msgid "To Declare" +msgstr "" + +#. module: l10n_fr_das2 +#: model:ir.model.fields,field_description:l10n_fr_das2.field_l10n_fr_das2_line__total_amount +msgid "Total Amount" +msgstr "" + +#. module: l10n_fr_das2 +#: model_terms:ir.ui.view,arch_db:l10n_fr_das2.view_partner_property_form +msgid "Type" +msgstr "" + +#. module: l10n_fr_das2 +#: model:ir.model.fields,field_description:l10n_fr_das2.field_l10n_fr_das2__message_unread +msgid "Unread Messages" +msgstr "" + +#. module: l10n_fr_das2 +#: model:ir.model.fields,field_description:l10n_fr_das2.field_l10n_fr_das2__message_unread_counter +msgid "Unread Messages Counter" +msgstr "" + +#. module: l10n_fr_das2 +#: model:ir.model.fields,help:l10n_fr_das2.field_res_partner__fr_das2_job +#: model:ir.model.fields,help:l10n_fr_das2.field_res_users__fr_das2_job +msgid "Used in the field 'Profession' of DAS2." +msgstr "" + +#. module: l10n_fr_das2 +#: model:ir.model.fields,field_description:l10n_fr_das2.field_l10n_fr_das2_line__benefits_in_kind_car +msgid "Voiture" +msgstr "" + +#. module: l10n_fr_das2 +#: model:ir.model.fields,field_description:l10n_fr_das2.field_l10n_fr_das2__website_message_ids +msgid "Website Messages" +msgstr "" + +#. module: l10n_fr_das2 +#: model:ir.model.fields,help:l10n_fr_das2.field_l10n_fr_das2__website_message_ids +msgid "Website communication history" +msgstr "" + +#. module: l10n_fr_das2 +#: model:ir.model.fields,field_description:l10n_fr_das2.field_l10n_fr_das2__year +msgid "Year" +msgstr "" + From d00ab098474a7115b449763b1fcb389c769af29b Mon Sep 17 00:00:00 2001 From: David Beal Date: Tue, 21 Apr 2020 15:13:32 +0200 Subject: [PATCH 21/58] FIX l10n_fr_das2 E7901 rst error 'Duplicate explicit target name --- l10n_fr_das2/README.rst | 5 ++++- l10n_fr_das2/readme/USAGE.rst | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/l10n_fr_das2/README.rst b/l10n_fr_das2/README.rst index f497668eb..f72cfd0fe 100644 --- a/l10n_fr_das2/README.rst +++ b/l10n_fr_das2/README.rst @@ -51,6 +51,9 @@ For the user responsible for the declaration, the phone number and email must be Usage ===== +.. _DAS2_declaration: https://teletd.impots.gouv.fr/teletd/connexionEFI.do + + Go to the menu *Accounting > Reports > French Statements > DAS2* and create a new DAS2 report. Then click on the button *Generate Lines*. Check and edit the generated lines. You can get the details of the computation performed by Odoo in the *Note* fields of each line. @@ -59,7 +62,7 @@ You should also have a look in the chatter: you may have a message that warn you Once your declaration is OK, click on the button *Generate File* and download the generated file. -Connect to `https://teletd.impots.gouv.fr/teletd/connexionEFI.do `_, select *Transmission par internet des fichiers TD/bilatéral* and type your login and password (the credentials are specific to this website; their are not the same as on *impots.gouv.fr*). Then follow the online declaration process and upload the file generated by Odoo. +Connect to DAS2_declaration_, select *Transmission par internet des fichiers TD/bilatéral* and type your login and password (the credentials are specific to this website; their are not the same as on *impots.gouv.fr*). Then follow the online declaration process and upload the file generated by Odoo. In the minutes following the upload of the file on the website of the administration, you will receive a first e-mail with a subject *ACCUSÉ DE DÉPÔT de 1er NIVEAU (Déclaration de salaires et/ou honoraires et/ou actionnariat salarié)*; it is just an acknowledgement, it doesn't mean that the file is valid. diff --git a/l10n_fr_das2/readme/USAGE.rst b/l10n_fr_das2/readme/USAGE.rst index c1cd60c1e..5eb3d5429 100644 --- a/l10n_fr_das2/readme/USAGE.rst +++ b/l10n_fr_das2/readme/USAGE.rst @@ -1,3 +1,6 @@ +.. _DAS2_declaration: https://teletd.impots.gouv.fr/teletd/connexionEFI.do + + Go to the menu *Accounting > Reports > French Statements > DAS2* and create a new DAS2 report. Then click on the button *Generate Lines*. Check and edit the generated lines. You can get the details of the computation performed by Odoo in the *Note* fields of each line. @@ -6,7 +9,7 @@ You should also have a look in the chatter: you may have a message that warn you Once your declaration is OK, click on the button *Generate File* and download the generated file. -Connect to `https://teletd.impots.gouv.fr/teletd/connexionEFI.do `_, select *Transmission par internet des fichiers TD/bilatéral* and type your login and password (the credentials are specific to this website; their are not the same as on *impots.gouv.fr*). Then follow the online declaration process and upload the file generated by Odoo. +Connect to DAS2_declaration_, select *Transmission par internet des fichiers TD/bilatéral* and type your login and password (the credentials are specific to this website; their are not the same as on *impots.gouv.fr*). Then follow the online declaration process and upload the file generated by Odoo. In the minutes following the upload of the file on the website of the administration, you will receive a first e-mail with a subject *ACCUSÉ DE DÉPÔT de 1er NIVEAU (Déclaration de salaires et/ou honoraires et/ou actionnariat salarié)*; it is just an acknowledgement, it doesn't mean that the file is valid. From 58700798b403b1343c5b39a5335aa5efbebe6b8b Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Wed, 22 Apr 2020 11:19:10 +0000 Subject: [PATCH 22/58] [UPD] README.rst --- l10n_fr_das2/static/description/index.html | 30 +++++++++++----------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/l10n_fr_das2/static/description/index.html b/l10n_fr_das2/static/description/index.html index 4f6def775..46a13a868 100644 --- a/l10n_fr_das2/static/description/index.html +++ b/l10n_fr_das2/static/description/index.html @@ -373,19 +373,19 @@

      DAS2

      Table of contents

      -

      Configuration

      +

      Configuration

      On the supplier form view, in the Accounting tab, you will see a section DAS2. For the suppliers that must be declared in DAS2, you must set:

      • the DAS2 Type,
      • @@ -397,17 +397,17 @@

        Configuration

        For the user responsible for the declaration, the phone number and email must be set on his related partner form (name, email and phone number are used in the DAS2 declaration file).

      -

      Usage

      +

      Usage

      Go to the menu Accounting > Reports > French Statements > DAS2 and create a new DAS2 report.

      Then click on the button Generate Lines. Check and edit the generated lines. You can get the details of the computation performed by Odoo in the Note fields of each line.

      You should also have a look in the chatter: you may have a message that warn you about suppliers that have expenses recorded in accounts such as 622100 Commissions et courtages sur achats, 622200 Commissions et courtages sur ventes, 622600 Honoraires, 622800 Rémunérations d’intermédiaires divers, 653000 Jetons de présence, 651600 Droits d’auteur et de reproduction and are not configured for DAS2.

      Once your declaration is OK, click on the button Generate File and download the generated file.

      -

      Connect to https://teletd.impots.gouv.fr/teletd/connexionEFI.do, select Transmission par internet des fichiers TD/bilatéral and type your login and password (the credentials are specific to this website; their are not the same as on impots.gouv.fr). Then follow the online declaration process and upload the file generated by Odoo.

      +

      Connect to DAS2_declaration, select Transmission par internet des fichiers TD/bilatéral and type your login and password (the credentials are specific to this website; their are not the same as on impots.gouv.fr). Then follow the online declaration process and upload the file generated by Odoo.

      In the minutes following the upload of the file on the website of the administration, you will receive a first e-mail with a subject ACCUSÉ DE DÉPÔT de 1er NIVEAU (Déclaration de salaires et/ou honoraires et/ou actionnariat salarié); it is just an acknowledgement, it doesn’t mean that the file is valid.

      Then, on the next open day (in my experience), you will receive a second email with a subject Déclaration annuelle DADS BILATERALE. Référence DGFIP: xxxx. Numéro d’envoi : xxx. VALIDE, which means that the file was considered as valid. If the subject ends with BLOQUANT, then you should look for the detailed report in the attached PDF report.

      -

      Bug Tracker

      +

      Bug Tracker

      Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us smashing it by providing a detailed and welcomed @@ -415,21 +415,21 @@

      Bug Tracker

      Do not contact contributors directly about support or help with technical issues.

      -

      Credits

      +

      Credits

      -

      Authors

      +

      Authors

      • Akretion
      -

      Maintainers

      +

      Maintainers

      This module is maintained by the OCA.

      Odoo Community Association

      OCA, or the Odoo Community Association, is a nonprofit organization whose From d98f169a1deef5a79797920c4d5e22b8e65f8a8b Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Wed, 22 Apr 2020 11:19:11 +0000 Subject: [PATCH 23/58] l10n_fr_das2 12.0.1.0.1 --- l10n_fr_das2/__manifest__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/l10n_fr_das2/__manifest__.py b/l10n_fr_das2/__manifest__.py index bd7ab3356..15b800ad5 100644 --- a/l10n_fr_das2/__manifest__.py +++ b/l10n_fr_das2/__manifest__.py @@ -4,7 +4,7 @@ { 'name': 'DAS2', - 'version': '12.0.1.0.0', + 'version': '12.0.1.0.1', 'category': 'Invoicing Management', 'license': 'AGPL-3', 'summary': 'DAS2 (France)', From 2378ace19413c22e242bc4f92afa5bfbc7f3630b Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Wed, 13 May 2020 22:46:02 +0200 Subject: [PATCH 24/58] l10n_fr_das2: FIX computation of total_amount on lines --- l10n_fr_das2/models/l10n_fr_das2.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/l10n_fr_das2/models/l10n_fr_das2.py b/l10n_fr_das2/models/l10n_fr_das2.py index 4c1fbf810..79262702b 100644 --- a/l10n_fr_das2/models/l10n_fr_das2.py +++ b/l10n_fr_das2/models/l10n_fr_das2.py @@ -677,15 +677,15 @@ class L10nFrDas2Line(models.Model): 'withholding_tax_amount') def _compute_total_amount(self): for line in self: - amount_total = 0 + total_amount = 0 for field_name in AMOUNT_FIELDS: - amount_total += line[field_name] + total_amount += line[field_name] to_declare = False if line.parent_id: - if amount_total >= line.parent_id.partner_declare_threshold: + if total_amount >= line.parent_id.partner_declare_threshold: to_declare = True line.to_declare = to_declare - line.amount_total = amount_total + line.total_amount = total_amount @api.constrains('partner_siret') def check_siret(self): From 18e7f532c9e7b5df2b8eabd3b47d73c074edf417 Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Thu, 18 Feb 2021 22:23:34 +0100 Subject: [PATCH 25/58] [MIG] l10n_fr_das2 and l10n_fr_cog to v14 Add warning message at the top of DAS2 form (and not only in chatter) Use python-stdnum to validate SIRET --- l10n_fr_cog/__manifest__.py | 4 +- l10n_fr_cog/data/country.xml | 2 +- l10n_fr_cog/models/__init__.py | 2 +- .../models/{country.py => res_country.py} | 2 +- l10n_fr_cog/views/country.xml | 2 +- l10n_fr_das2/__init__.py | 1 + l10n_fr_das2/__manifest__.py | 10 +- l10n_fr_das2/demo/demo.xml | 14 +-- l10n_fr_das2/models/__init__.py | 5 +- l10n_fr_das2/models/l10n_fr_das2.py | 110 +++++++----------- .../models/{company.py => res_company.py} | 2 +- .../models/{partner.py => res_partner.py} | 4 +- l10n_fr_das2/security/das2_security.xml | 4 +- l10n_fr_das2/views/l10n_fr_das2.xml | 15 ++- ...g_settings.xml => res_config_settings.xml} | 8 +- .../views/{partner.xml => res_partner.xml} | 5 +- l10n_fr_das2/wizards/__init__.py | 1 + .../res_config_settings.py} | 2 +- 18 files changed, 90 insertions(+), 103 deletions(-) rename l10n_fr_cog/models/{country.py => res_country.py} (84%) rename l10n_fr_das2/models/{company.py => res_company.py} (89%) rename l10n_fr_das2/models/{partner.py => res_partner.py} (86%) rename l10n_fr_das2/views/{config_settings.xml => res_config_settings.xml} (83%) rename l10n_fr_das2/views/{partner.xml => res_partner.xml} (89%) create mode 100644 l10n_fr_das2/wizards/__init__.py rename l10n_fr_das2/{models/config_settings.py => wizards/res_config_settings.py} (85%) diff --git a/l10n_fr_cog/__manifest__.py b/l10n_fr_cog/__manifest__.py index 39e4fb6ef..18a8eac9c 100644 --- a/l10n_fr_cog/__manifest__.py +++ b/l10n_fr_cog/__manifest__.py @@ -1,11 +1,11 @@ -# Copyright 2020 Akretion France (http://www.akretion.com/) +# Copyright 2020-2021 Akretion France (http://www.akretion.com/) # @author: Alexis de Lattre # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). { 'name': 'Code Officiel Géographique', 'summary': 'Add Code Officiel Géographique (COG) on countries', - 'version': '12.0.1.0.0', + 'version': '14.0.1.0.0', 'category': 'French Localization', 'author': "Akretion,Odoo Community Association (OCA)", 'website': 'https://github.com/OCA/l10n-france', diff --git a/l10n_fr_cog/data/country.xml b/l10n_fr_cog/data/country.xml index d0087682c..7afb82fc7 100644 --- a/l10n_fr_cog/data/country.xml +++ b/l10n_fr_cog/data/country.xml @@ -1,6 +1,6 @@ diff --git a/l10n_fr_cog/models/__init__.py b/l10n_fr_cog/models/__init__.py index 30b933141..11573766f 100644 --- a/l10n_fr_cog/models/__init__.py +++ b/l10n_fr_cog/models/__init__.py @@ -1 +1 @@ -from . import country +from . import res_country diff --git a/l10n_fr_cog/models/country.py b/l10n_fr_cog/models/res_country.py similarity index 84% rename from l10n_fr_cog/models/country.py rename to l10n_fr_cog/models/res_country.py index 0dbd77de6..d0eb7ac4c 100644 --- a/l10n_fr_cog/models/country.py +++ b/l10n_fr_cog/models/res_country.py @@ -1,4 +1,4 @@ -# Copyright 2020 Akretion France (http://www.akretion.com/) +# Copyright 2020-2021 Akretion France (http://www.akretion.com/) # @author: Alexis de Lattre # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). diff --git a/l10n_fr_cog/views/country.xml b/l10n_fr_cog/views/country.xml index 0373c4728..aad4efd4f 100644 --- a/l10n_fr_cog/views/country.xml +++ b/l10n_fr_cog/views/country.xml @@ -1,6 +1,6 @@ diff --git a/l10n_fr_das2/__init__.py b/l10n_fr_das2/__init__.py index 0650744f6..aee8895e7 100644 --- a/l10n_fr_das2/__init__.py +++ b/l10n_fr_das2/__init__.py @@ -1 +1,2 @@ from . import models +from . import wizards diff --git a/l10n_fr_das2/__manifest__.py b/l10n_fr_das2/__manifest__.py index 15b800ad5..1dee664a7 100644 --- a/l10n_fr_das2/__manifest__.py +++ b/l10n_fr_das2/__manifest__.py @@ -1,10 +1,10 @@ -# Copyright 2020 Akretion France (http://www.akretion.com/) +# Copyright 2020-2021 Akretion France (http://www.akretion.com/) # @author: Alexis de Lattre # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). { 'name': 'DAS2', - 'version': '12.0.1.0.1', + 'version': '14.0.1.0.0', 'category': 'Invoicing Management', 'license': 'AGPL-3', 'summary': 'DAS2 (France)', @@ -15,14 +15,14 @@ 'l10n_fr_cog', ], 'external_dependencies': { - 'python': ['unidecode'], + 'python': ['unidecode', 'python-stdnum'], }, 'data': [ 'security/das2_security.xml', 'security/ir.model.access.csv', 'views/l10n_fr_das2.xml', - 'views/partner.xml', - 'views/config_settings.xml', + 'views/res_partner.xml', + 'views/res_config_settings.xml', ], 'demo': ['demo/demo.xml'], 'installable': True, diff --git a/l10n_fr_das2/demo/demo.xml b/l10n_fr_das2/demo/demo.xml index f30535e6d..01c28dbd0 100644 --- a/l10n_fr_das2/demo/demo.xml +++ b/l10n_fr_das2/demo/demo.xml @@ -1,6 +1,6 @@ @@ -22,8 +22,8 @@ Experts comptables du Rhône - - + + 12 rue du chiffre 69100 Villeurbanne @@ -37,8 +37,8 @@ Cabinet d'avocats Juridon - - + + 42 rue du crime 69100 Villeurbanne @@ -52,8 +52,8 @@ Cabinet CACtus - - + + 42 rue de l'audit 69100 Villeurbanne diff --git a/l10n_fr_das2/models/__init__.py b/l10n_fr_das2/models/__init__.py index 36aac8dba..cb3d3203f 100644 --- a/l10n_fr_das2/models/__init__.py +++ b/l10n_fr_das2/models/__init__.py @@ -1,4 +1,3 @@ from . import l10n_fr_das2 -from . import partner -from . import company -from . import config_settings +from . import res_partner +from . import res_company diff --git a/l10n_fr_das2/models/l10n_fr_das2.py b/l10n_fr_das2/models/l10n_fr_das2.py index 79262702b..83a0a1e40 100644 --- a/l10n_fr_das2/models/l10n_fr_das2.py +++ b/l10n_fr_das2/models/l10n_fr_das2.py @@ -1,12 +1,11 @@ -# Copyright 2020 Akretion France (http://www.akretion.com/) +# Copyright 2020-2021 Akretion France (http://www.akretion.com/) # @author: Alexis de Lattre # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from odoo import api, fields, models, _ from odoo.exceptions import UserError, ValidationError -from dateutil.relativedelta import relativedelta from datetime import datetime -from odoo.addons.l10n_fr_siret.models.partner import _check_luhn +from stdnum.fr.siret import is_valid import base64 import logging @@ -30,24 +29,24 @@ class L10nFrDas2(models.Model): _name = 'l10n.fr.das2' - _inherit = ['mail.thread'] + _inherit = ['mail.thread', 'mail.activity.mixin'] _order = 'year desc' _description = 'DAS2' year = fields.Integer( string='Year', required=True, states={'done': [('readonly', True)]}, - track_visibility='onchange', + tracking=True, default=lambda self: self._default_year()) state = fields.Selection([ ('draft', 'Draft'), ('done', 'Done'), ], default='draft', readonly=True, string='State', - track_visibility='onchange') + tracking=True) company_id = fields.Many2one( 'res.company', string='Company', ondelete='cascade', required=True, states={'done': [('readonly', True)]}, - default=lambda self: self.env['res.company']._company_default_get()) + default=lambda self: self.env.company) currency_id = fields.Many2one( related='company_id.currency_id', readonly=True, store=True, string='Company Currency') @@ -55,6 +54,7 @@ class L10nFrDas2(models.Model): 'account.journal', string='Payment Journals', required=True, default=lambda self: self._default_payment_journals(), + domain="[('company_id', '=', company_id)]", states={'done': [('readonly', True)]}) line_ids = fields.One2many( 'l10n.fr.das2.line', 'parent_id', string='Lines', @@ -66,18 +66,25 @@ class L10nFrDas2(models.Model): ('1', "La société ne verse pas de salaires"), ], 'DADS Type', required=True, states={'done': [('readonly', True)]}, - track_visibility='onchange', + tracking=True, default=lambda self: self._default_dads_type()) # option for draft moves ? contact_id = fields.Many2one( 'res.partner', string='Administrative Contact', states={'done': [('readonly', True)]}, default=lambda self: self.env.user.partner_id.id, - track_visibility='onchange', + tracking=True, help='Contact in the company for the fiscal administration: the name, ' 'email and phone number of this partner will be used in the file.') attachment_id = fields.Many2one( - 'ir.attachment', string='File Export', readonly=True) + 'ir.attachment', string='Attachment', readonly=True) + attachment_datas = fields.Binary( + related="attachment_id.datas", string="File Export") + attachment_name = fields.Char( + related="attachment_id.name", string="Filename") + # The only drawback of the warning_msg solution is that I didn't find a way + # to put a link to partners inside it + warning_msg = fields.Html(readonly=True) _sql_constraints = [( 'year_company_uniq', @@ -98,16 +105,15 @@ def _default_payment_journals(self): res = [] pay_journals = self.env['account.journal'].search([ ('type', 'in', ('bank', 'cash')), - ('company_id', '=', self.env.user.company_id.id)]) + ('company_id', '=', self.env.company.id)]) if pay_journals: res = pay_journals.ids return res @api.model def _default_year(self): - today = datetime.today() - prev_year = today - relativedelta(years=1) - return prev_year.year + last_year = datetime.today().year - 1 + return last_year @api.depends('year') def name_get(self): @@ -117,11 +123,11 @@ def name_get(self): return res def done(self): - self.state = 'done' + self.write({'state': 'done'}) return def back2draft(self): - self.state = 'draft' + self.write({'state': 'draft'}) return def unlink(self): @@ -130,7 +136,7 @@ def unlink(self): raise UserError(_( "Cannot delete declaration %s in done state.") % rec.display_name) - return super(L10nFrDas2, self).unlink() + return super().unlink() def generate_lines(self): self.ensure_one() @@ -175,8 +181,7 @@ def generate_lines(self): vals = self._prepare_line(partner, base_domain) if vals: lfdlo.create(vals) - self.message_post(body=_("DAS2 lines generated.")) - self.add_warning_in_chatter(das2_partners) + self.generate_warning_msg(das2_partners) def _prepare_line(self, partner, base_domain): amlo = self.env['account.move.line'] @@ -209,7 +214,7 @@ def _prepare_line(self, partner, base_domain): res['partner_siret'] = partner.siret return res - def add_warning_in_chatter(self, das2_partners): + def generate_warning_msg(self, das2_partners): amlo = self.env['account.move.line'] aao = self.env['account.account'] ajo = self.env['account.journal'] @@ -238,17 +243,23 @@ def add_warning_in_chatter(self, das2_partners): ('account_id', 'in', das2_accounts.ids), ('balance', '!=', 0), ], ['partner_id'], ['partner_id']) + msg = False + msg_post = _("DAS2 lines generated. ") if rg_res: msg = _( - "

      The following partners are not configured for DAS2 but " + "The following partners are not configured for DAS2 but " "they have expenses in some accounts that indicate " - "that maybe they should be configured for DAS2:

        ") + "they should probably be configured for DAS2:
          ") + msg_post += msg for rg_re in rg_res: partner = rpo.browse(rg_re['partner_id'][0]) - msg += '
        • %s' % (partner.id, partner.display_name) + msg_post += '
        • %s
        • ' % (partner.id, partner.display_name) + msg += "
        • %s
        • " % partner.display_name + msg_post += '
        ' msg += '
      ' - self.message_post(body=msg) + self.message_post(body=msg_post) + self.write({'warning_msg': msg}) @api.model def _prepare_field( @@ -503,43 +514,15 @@ def generate_file(self): 'name': filename, 'res_id': self.id, 'res_model': self._name, - 'datas': base64.encodestring(file_content_encoded), - 'datas_fname': filename, + 'datas': base64.encodebytes(file_content_encoded), }) self.attachment_id = attach.id self.message_post(body=_("DAS2 file generated.")) - action = { - 'type': 'ir.actions.act_window', - 'name': _('DAS2 Export File'), - 'view_mode': 'form', - 'res_model': 'ir.attachment', - 'target': 'current', - 'res_id': attach.id, - } - try: - action['view_id'] = self.env.ref( - 'account_payment_order.view_attachment_simplified_form').id - except Exception: - pass - - # The code below works and triggers an immediate download, but - # the form view of DAS2 is frozen after that, so it's not usable - # in v10/v12... maybe it's only designed to be used in a wizard - # action = { - # 'name': 'DAS2', - # 'type': 'ir.actions.act_url', - # 'url': "web/content/?model=ir.attachment&id=%d" - # "&filename_field=filename" - # "&field=datas&download=true&filename=%s" % ( - # attach.id, filename), - # 'target': 'self', - # } - return action def button_lines_fullscreen(self): self.ensure_one() - action = self.env['ir.actions.act_window'].for_xml_id( - 'l10n_fr_das2', 'l10n_fr_das2_line_action') + action = self.env.ref( + 'l10n_fr_das2.l10n_fr_das2_line_action').sudo().read()[0] action.update({ 'domain': [('parent_id', '=', self.id)], 'views': False, @@ -592,7 +575,8 @@ class L10nFrDas2Line(models.Model): compute='_compute_total_amount', string='Total Amount', store=True, readonly=True) to_declare = fields.Boolean( - compute='_compute_total_amount', string='To Declare', readonly=True) + compute='_compute_total_amount', string='To Declare', readonly=True, + store=True) allowance_fixed = fields.Boolean( 'Allocation forfaitaire', states={'done': [('readonly', True)]}) allowance_real = fields.Boolean( @@ -690,15 +674,9 @@ def _compute_total_amount(self): @api.constrains('partner_siret') def check_siret(self): for line in self: - if line.partner_siret: - if len(line.partner_siret) != 14: - raise ValidationError(_( - "SIRET %s is invalid: it must have 14 digits.") - % line.partner_siret) - if not _check_luhn(line.partner_siret): - raise ValidationError(_( - "SIRET %s is invalid: the checksum is wrong.") - % line.partner_siret) + if line.partner_siret and not is_valid(line.partner_siret): + raise ValidationError(_( + "SIRET '%s' is invalid.") % line.partner_siret) @api.onchange('partner_id') def partner_id_change(self): diff --git a/l10n_fr_das2/models/company.py b/l10n_fr_das2/models/res_company.py similarity index 89% rename from l10n_fr_das2/models/company.py rename to l10n_fr_das2/models/res_company.py index 7378d8384..e18007a01 100644 --- a/l10n_fr_das2/models/company.py +++ b/l10n_fr_das2/models/res_company.py @@ -1,4 +1,4 @@ -# Copyright 2020 Akretion France (http://www.akretion.com/) +# Copyright 2020-2021 Akretion France (http://www.akretion.com/) # @author: Alexis de Lattre # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). diff --git a/l10n_fr_das2/models/partner.py b/l10n_fr_das2/models/res_partner.py similarity index 86% rename from l10n_fr_das2/models/partner.py rename to l10n_fr_das2/models/res_partner.py index 47cd0efb0..bf5882cec 100644 --- a/l10n_fr_das2/models/partner.py +++ b/l10n_fr_das2/models/res_partner.py @@ -1,4 +1,4 @@ -# Copyright 2020 Akretion France (http://www.akretion.com/) +# Copyright 2020-2021 Akretion France (http://www.akretion.com/) # @author: Alexis de Lattre # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). @@ -18,7 +18,7 @@ class ResPartner(models.Model): ('licence_royalties', "Droits d'inventeur"), ('other_income', 'Autre rémunérations'), ('allowance', 'Indemnités et remboursements'), - ], string='DAS2 Type', track_visibility='onchange') + ], string='DAS2 Type', tracking=100) fr_das2_job = fields.Char( string='DAS2 Job', size=30, help="Used in the field 'Profession' of DAS2.") diff --git a/l10n_fr_das2/security/das2_security.xml b/l10n_fr_das2/security/das2_security.xml index d6e9b4a9a..fca416f08 100644 --- a/l10n_fr_das2/security/das2_security.xml +++ b/l10n_fr_das2/security/das2_security.xml @@ -5,14 +5,14 @@ DAS2 multi-company - ['|', ('company_id', '=', False), ('company_id', 'child_of', [user.company_id.id])] + [('company_id', 'in', company_ids)] DAS2 Line multi-company - ['|', ('company_id', '=', False), ('company_id', 'child_of', [user.company_id.id])] + [('company_id', 'in', company_ids)] diff --git a/l10n_fr_das2/views/l10n_fr_das2.xml b/l10n_fr_das2/views/l10n_fr_das2.xml index 636e28fb7..8fd06a9e4 100644 --- a/l10n_fr_das2/views/l10n_fr_das2.xml +++ b/l10n_fr_das2/views/l10n_fr_das2.xml @@ -1,6 +1,6 @@ @@ -20,6 +20,9 @@
    " + msg += "" self.message_post(body=msg_post) - self.write({'warning_msg': msg}) + self.write({"warning_msg": msg}) @api.model def _prepare_field( - self, field_name, partner, value, size, - required=False, numeric=False): - '''This function is designed to be inherited.''' + self, field_name, partner, value, size, required=False, numeric=False + ): + """This function is designed to be inherited.""" if numeric: if not value: value = 0 @@ -273,36 +347,43 @@ def _prepare_field( try: value = int(value) except Exception: - raise UserError(_( - "Failed to convert field '%s' (partner %s) " - "to integer.") % (field_name, partner.display_name)) + raise UserError( + _("Failed to convert field '%s' (partner %s) " "to integer.") + % (field_name, partner.display_name) + ) value = str(value) if len(value) > size: - raise UserError(_( - "Field %s (partner %s) has value %s: " - "it is bigger than the maximum size " - "(%d characters)") - % (field_name, partner.display_name, value, size)) + raise UserError( + _( + "Field %s (partner %s) has value %s: " + "it is bigger than the maximum size " + "(%d characters)" + ) + % (field_name, partner.display_name, value, size) + ) if len(value) < size: - value = value.rjust(size, '0') + value = value.rjust(size, "0") return value if required and not value: - raise UserError(_( - "The field '%s' (partner %s) is empty or 0. " - "It should have a non-null value.") - % (field_name, partner.display_name)) + raise UserError( + _( + "The field '%s' (partner %s) is empty or 0. " + "It should have a non-null value." + ) + % (field_name, partner.display_name) + ) if not value: - value = ' ' * size + value = " " * size # Cut if too long value = value[0:size] # enlarge if too small if len(value) < size: - value = value.ljust(size, ' ') + value = value.ljust(size, " ") return value def _prepare_address(self, partner): - cstreet2 = self._prepare_field('Street2', partner, partner.street2, 32) - cstreet = self._prepare_field('Street', partner, partner.street, 32) + cstreet2 = self._prepare_field("Street2", partner, partner.street2, 32) + cstreet = self._prepare_field("Street", partner, partner.street, 32) # specs section 5.4 : only bureau distributeur and code postal are # required. And they say it is tolerated to set city as # bureau distributeur @@ -311,37 +392,45 @@ def _prepare_address(self, partner): # specs section 5.4 and 5.4.1.2.b: it is possible to set the field # "Adresse voie" without structuration on 32 chars => that's what we do if not partner.city: - raise UserError(_( - "Missing city on partner '%s'.") % partner.display_name) + raise UserError(_("Missing city on partner '%s'.") % partner.display_name) if partner.country_id and partner.country_id.code not in FRANCE_CODES: if not partner.country_id.fr_cog: - raise UserError(_( - "Missing Code Officiel Géographique on country '%s'.") - % partner.country_id.display_name) + raise UserError( + _("Missing Code Officiel Géographique on country '%s'.") + % partner.country_id.display_name + ) cog = self._prepare_field( - 'COG', partner, partner.country_id.fr_cog, 5, True, - numeric=True) - raw_country_name = partner.with_context( - lang='fr_FR').country_id.name + "COG", partner, partner.country_id.fr_cog, 5, True, numeric=True + ) + raw_country_name = partner.with_context(lang="fr_FR").country_id.name country_name = self._prepare_field( - 'Nom du pays', partner, raw_country_name, 26, True) + "Nom du pays", partner, raw_country_name, 26, True + ) raw_commune = partner.city if partner.zip: - raw_commune = '%s %s' % (partner.zip, partner.city) - commune = self._prepare_field( - 'Commune', partner, raw_commune, 26, True) - caddress = cstreet2 + ' ' + cstreet + cog + ' ' + commune\ - + cog + ' ' + country_name + raw_commune = "{} {}".format(partner.zip, partner.city) + commune = self._prepare_field("Commune", partner, raw_commune, 26, True) + caddress = ( + cstreet2 + + " " + + cstreet + + cog + + " " + + commune + + cog + + " " + + country_name + ) # According to the specs, we should have some code specific for DOM-TOM # But it's not easy, because we are supposed to give the INSEE code # of the city, not of the territory => we don't handle that for the # moment else: - ccity = self._prepare_field( - 'City', partner, partner.city, 26, True) - czip = self._prepare_field('Zip', partner, partner.zip, 5, True) - caddress = cstreet2 + ' ' + cstreet + '0' * 5 + ' ' + ' ' * 26 +\ - czip + ' ' + ccity + ccity = self._prepare_field("City", partner, partner.city, 26, True) + czip = self._prepare_field("Zip", partner, partner.zip, 5, True) + caddress = ( + cstreet2 + " " + cstreet + "0" * 5 + " " + " " * 26 + czip + " " + ccity + ) assert len(caddress) == 129 return caddress @@ -349,125 +438,200 @@ def _prepare_file(self): company = self.company_id cpartner = company.partner_id contact = self.contact_id - csiren = self._prepare_field( - 'SIREN', cpartner, cpartner.siren, 9, True) - csiret = self._prepare_field( - 'SIRET', cpartner, cpartner.siret, 14, True) - cape = self._prepare_field('APE', cpartner, company.ape, 5, True) - cname = self._prepare_field('Name', cpartner, company.name, 50, True) - file_type = 'X' # tous déclarants honoraires seuls + csiren = self._prepare_field("SIREN", cpartner, cpartner.siren, 9, True) + csiret = self._prepare_field("SIRET", cpartner, cpartner.siret, 14, True) + cape = self._prepare_field("APE", cpartner, company.ape, 5, True) + cname = self._prepare_field("Name", cpartner, company.name, 50, True) + file_type = "X" # tous déclarants honoraires seuls year = str(self.year) assert len(year) == 4 caddress = self._prepare_address(cpartner) - cprefix = csiret + '01' + year + self.dads_type + cprefix = csiret + "01" + year + self.dads_type # line 010 Company header flines = [] flines.append( - csiren + '0' * 12 + '010' + ' ' * 14 + cape + ' ' * 4 + - cname + caddress + ' ' * 8 + file_type + csiret + ' ' * 5 + - caddress + ' ' + ' ' * 288) + csiren + + "0" * 12 + + "010" + + " " * 14 + + cape + + " " * 4 + + cname + + caddress + + " " * 8 + + file_type + + csiret + + " " * 5 + + caddress + + " " + + " " * 288 + ) # ligne 020 Etablissement header # We don't add a field for profession on the company # because it's not even a field on the paper form! # We only set profession for suppliers flines.append( - cprefix + '020' + ' ' * 14 + cape + '0' * 14 + ' ' * 41 + - cname + caddress + ' ' * 40 + ' ' * 53 + 'N' * 6 + ' ' * 296) + cprefix + + "020" + + " " * 14 + + cape + + "0" * 14 + + " " * 41 + + cname + + caddress + + " " * 40 + + " " * 53 + + "N" * 6 + + " " * 296 + ) i = 0 for line in self.line_ids.filtered(lambda x: x.to_declare): i += 1 partner = line.partner_id if ( - partner.country_id and - partner.country_id.code in FRANCE_CODES and - not line.partner_siret): - raise UserError(_( - "Missing SIRET for french partner %s.") - % partner.display_name) + partner.country_id + and partner.country_id.code in FRANCE_CODES + and not line.partner_siret + ): + raise UserError( + _("Missing SIRET for french partner %s.") % partner.display_name + ) # ligne 210 honoraire if partner.is_company: partner_name = self._prepare_field( - 'Partner name', partner, partner.name, 50, True) - lastname = ' ' * 30 - firstname = ' ' * 20 + "Partner name", partner, partner.name, 50, True + ) + lastname = " " * 30 + firstname = " " * 20 else: - partner_name = ' ' * 50 - if hasattr(partner, 'firstname') and partner.firstname: + partner_name = " " * 50 + if hasattr(partner, "firstname") and partner.firstname: lastname = self._prepare_field( - 'Lastname', partner, partner.lastname, 30, True) + "Lastname", partner, partner.lastname, 30, True + ) firstname = self._prepare_field( - 'Firstname', partner, partner.firstname, 20, True) + "Firstname", partner, partner.firstname, 20, True + ) else: lastname = self._prepare_field( - 'Partner name', partner, partner.name, 30, True) - firstname = ' ' * 20 + "Partner name", partner, partner.name, 30, True + ) + firstname = " " * 20 address = self._prepare_address(partner) partner_siret = self._prepare_field( - 'SIRET', partner, line.partner_siret, 14) - job = self._prepare_field('Profession', partner, line.job, 30) + "SIRET", partner, line.partner_siret, 14 + ) + job = self._prepare_field("Profession", partner, line.job, 30) amount_fields_list = [ self._prepare_field(x, partner, line[x], 10, numeric=True) - for x in AMOUNT_FIELDS] + for x in AMOUNT_FIELDS + ] if line.benefits_in_kind_amount: - bik_letters = '' - bik_letters += line.benefits_in_kind_food and 'N' or ' ' - bik_letters +=\ - line.benefits_in_kind_accomodation and 'L' or ' ' - bik_letters += line.benefits_in_kind_car and 'V' or ' ' - bik_letters += line.benefits_in_kind_other and 'A' or ' ' - bik_letters += line.benefits_in_kind_nict and 'T' or ' ' + bik_letters = "" + bik_letters += line.benefits_in_kind_food and "N" or " " + bik_letters += line.benefits_in_kind_accomodation and "L" or " " + bik_letters += line.benefits_in_kind_car and "V" or " " + bik_letters += line.benefits_in_kind_other and "A" or " " + bik_letters += line.benefits_in_kind_nict and "T" or " " else: - bik_letters = ' ' * 5 + bik_letters = " " * 5 if line.allowance_amount: - allow_letters = '' - allow_letters += line.allowance_fixed and 'F' or ' ' - allow_letters += line.allowance_real and 'R' or ' ' - allow_letters += line.allowance_employer and 'P' or ' ' + allow_letters = "" + allow_letters += line.allowance_fixed and "F" or " " + allow_letters += line.allowance_real and "R" or " " + allow_letters += line.allowance_employer and "P" or " " else: - allow_letters = ' ' * 3 + allow_letters = " " * 3 flines.append( - cprefix + '210' + partner_siret + lastname + firstname + - partner_name + job + address + ''.join(amount_fields_list) + - bik_letters + allow_letters + - ' ' * 2 + '0' * 10 + ' ' * 245) - rg = self.env['l10n.fr.das2.line'].read_group( - [('parent_id', '=', self.id)], AMOUNT_FIELDS, [])[0] + cprefix + + "210" + + partner_siret + + lastname + + firstname + + partner_name + + job + + address + + "".join(amount_fields_list) + + bik_letters + + allow_letters + + " " * 2 + + "0" * 10 + + " " * 245 + ) + rg = self.env["l10n.fr.das2.line"].read_group( + [("parent_id", "=", self.id)], AMOUNT_FIELDS, [] + )[0] total_fields_list = [ self._prepare_field(x, cpartner, rg[x], 12, numeric=True) - for x in AMOUNT_FIELDS] + for x in AMOUNT_FIELDS + ] contact_name = self._prepare_field( - 'Administrative contact name', contact, contact.name, 50) + "Administrative contact name", contact, contact.name, 50 + ) contact_email = self._prepare_field( - 'Administrative contact email', contact, contact.email, 60) + "Administrative contact email", contact, contact.email, 60 + ) phone = contact.phone or contact.mobile - phone = phone.replace(' ', '').replace('.', '').replace( - '-', '').replace('\u00A0', '') - if phone.startswith('+33'): - phone = '0%s' % phone[3:] + phone = ( + phone.replace(" ", "") + .replace(".", "") + .replace("-", "") + .replace("\u00A0", "") + ) + if phone.startswith("+33"): + phone = "0%s" % phone[3:] contact_phone = self._prepare_field( - 'Administrative contact phone', contact, phone, 10) + "Administrative contact phone", contact, phone, 10 + ) flines.append( - cprefix + '300' + ' ' * 36 + '0' * 12 * 9 + - ''.join(total_fields_list) + ' ' * 12 + '0' * 12 * 2 + '0' * 6 + - '0' * 12 * 5 + ' ' * 74 + - contact_name + contact_phone + contact_email + ' ' * 76) + cprefix + + "300" + + " " * 36 + + "0" * 12 * 9 + + "".join(total_fields_list) + + " " * 12 + + "0" * 12 * 2 + + "0" * 6 + + "0" * 12 * 5 + + " " * 74 + + contact_name + + contact_phone + + contact_email + + " " * 76 + ) lines_number = self._prepare_field( - 'Number of lines', cpartner, i, 6, numeric=True) + "Number of lines", cpartner, i, 6, numeric=True + ) flines.append( - csiren + '9' * 12 + '310' + '00001' + '0' * 6 + lines_number + - '0' * 6 * 3 + ' ' * 18 + '0' * 12 * 9 + - ''.join(total_fields_list) + ' ' * 12 + '0' * 12 * 2 + '0' * 6 + - '0' * 12 * 5 + ' ' * 253) + csiren + + "9" * 12 + + "310" + + "00001" + + "0" * 6 + + lines_number + + "0" * 6 * 3 + + " " * 18 + + "0" * 12 * 9 + + "".join(total_fields_list) + + " " * 12 + + "0" * 12 * 2 + + "0" * 6 + + "0" * 12 * 5 + + " " * 253 + ) for fline in flines: if len(fline) != 672: - raise UserError(_( - "One of the lines has a length of %d. " - "All lines should have a length of 672. Line: %s.") - % (len(fline), fline)) - file_content = '\r\n'.join(flines) + '\r\n' + raise UserError( + _( + "One of the lines has a length of %d. " + "All lines should have a length of 672. Line: %s." + ) + % (len(fline), fline) + ) + file_content = "\r\n".join(flines) + "\r\n" return file_content def generate_file(self): @@ -476,189 +640,246 @@ def generate_file(self): if not self.line_ids: raise UserError(_("The DAS2 has no lines.")) if not company.siret: - raise UserError(_( - "Missing SIRET on company '%s'.") % company.display_name) + raise UserError(_("Missing SIRET on company '%s'.") % company.display_name) if not company.ape: - raise UserError(_( - "Missing APE on company '%s'.") % company.display_name) + raise UserError(_("Missing APE on company '%s'.") % company.display_name) if not company.street: - raise UserError(_( - "Missing Street on company '%s'") % company.display_name) + raise UserError(_("Missing Street on company '%s'") % company.display_name) contact = self.contact_id if not contact: raise UserError(_("Missing administrative contact.")) if not contact.email: - raise UserError(_( - "The email is not set on the administrative contact " - "partner '%s'.") % contact.display_name) + raise UserError( + _("The email is not set on the administrative contact " "partner '%s'.") + % contact.display_name + ) if not contact.phone and not contact.mobile: - raise UserError(_( - "The phone number is not set on the administrative contact " - "partner '%s'.") % contact.display_name) + raise UserError( + _( + "The phone number is not set on the administrative contact " + "partner '%s'." + ) + % contact.display_name + ) if self.attachment_id: - raise UserError(_( - "An export file already exists. First, delete it via the " - "attachments and then re-generate it.")) + raise UserError( + _( + "An export file already exists. First, delete it via the " + "attachments and then re-generate it." + ) + ) file_content = self._prepare_file() try: - file_content_encoded = file_content.encode('latin1') + file_content_encoded = file_content.encode("latin1") except UnicodeEncodeError: - raise UserError(_( - "A special character in the DAS2 file is not in the latin1 " - "table. Please locate this special character and replace " - "it by a standard character and try again.")) - filename = 'DAS2_%s_%s.txt' % ( - self.year, company.name.replace(' ', '_')) - attach = self.env['ir.attachment'].create({ - 'name': filename, - 'res_id': self.id, - 'res_model': self._name, - 'datas': base64.encodebytes(file_content_encoded), - }) + raise UserError( + _( + "A special character in the DAS2 file is not in the latin1 " + "table. Please locate this special character and replace " + "it by a standard character and try again." + ) + ) + filename = "DAS2_{}_{}.txt".format(self.year, company.name.replace(" ", "_")) + attach = self.env["ir.attachment"].create( + { + "name": filename, + "res_id": self.id, + "res_model": self._name, + "datas": base64.encodebytes(file_content_encoded), + } + ) self.attachment_id = attach.id self.message_post(body=_("DAS2 file generated.")) def button_lines_fullscreen(self): self.ensure_one() - action = self.env.ref( - 'l10n_fr_das2.l10n_fr_das2_line_action').sudo().read()[0] - action.update({ - 'domain': [('parent_id', '=', self.id)], - 'views': False, - }) + action = self.env.ref("l10n_fr_das2.l10n_fr_das2_line_action").sudo().read()[0] + action.update( + { + "domain": [("parent_id", "=", self.id)], + "views": False, + } + ) return action class L10nFrDas2Line(models.Model): - _name = 'l10n.fr.das2.line' - _description = 'DAS2 line' + _name = "l10n.fr.das2.line" + _description = "DAS2 line" parent_id = fields.Many2one( - 'l10n.fr.das2', string='DAS2 Report', ondelete='cascade') + "l10n.fr.das2", string="DAS2 Report", ondelete="cascade" + ) partner_id = fields.Many2one( - 'res.partner', string='Supplier', ondelete='restrict', - domain=[('parent_id', '=', False)], - states={'done': [('readonly', True)]}, required=True) + "res.partner", + string="Supplier", + ondelete="restrict", + domain=[("parent_id", "=", False)], + states={"done": [("readonly", True)]}, + required=True, + ) partner_siret = fields.Char( - string='SIRET', size=14, states={'done': [('readonly', True)]}) + string="SIRET", size=14, states={"done": [("readonly", True)]} + ) company_id = fields.Many2one( - related='parent_id.company_id', store=True, readonly=True) + related="parent_id.company_id", store=True, readonly=True + ) currency_id = fields.Many2one( - related='parent_id.company_id.currency_id', store=True, readonly=True, - string='Company Currency') + related="parent_id.company_id.currency_id", + store=True, + readonly=True, + string="Company Currency", + ) fee_amount = fields.Integer( - string='Honoraires et vacations', - states={'done': [('readonly', True)]}) + string="Honoraires et vacations", states={"done": [("readonly", True)]} + ) commission_amount = fields.Integer( - string='Commissions', states={'done': [('readonly', True)]}) + string="Commissions", states={"done": [("readonly", True)]} + ) brokerage_amount = fields.Integer( - string='Courtages', states={'done': [('readonly', True)]}) + string="Courtages", states={"done": [("readonly", True)]} + ) discount_amount = fields.Integer( - string='Ristournes', states={'done': [('readonly', True)]}) + string="Ristournes", states={"done": [("readonly", True)]} + ) attendance_fee_amount = fields.Integer( - string='Jetons de présence', states={'done': [('readonly', True)]}) + string="Jetons de présence", states={"done": [("readonly", True)]} + ) copyright_royalties_amount = fields.Integer( - string="Droits d'auteur", states={'done': [('readonly', True)]}) + string="Droits d'auteur", states={"done": [("readonly", True)]} + ) licence_royalties_amount = fields.Integer( - string="Droits d'inventeur", states={'done': [('readonly', True)]}) + string="Droits d'inventeur", states={"done": [("readonly", True)]} + ) other_income_amount = fields.Integer( - string='Autre rémunérations', states={'done': [('readonly', True)]}) + string="Autre rémunérations", states={"done": [("readonly", True)]} + ) allowance_amount = fields.Integer( - string='Indemnités et remboursements', - states={'done': [('readonly', True)]}) + string="Indemnités et remboursements", states={"done": [("readonly", True)]} + ) benefits_in_kind_amount = fields.Integer( - string='Avantages en nature', states={'done': [('readonly', True)]}) + string="Avantages en nature", states={"done": [("readonly", True)]} + ) withholding_tax_amount = fields.Integer( - string='Retenue à la source', states={'done': [('readonly', True)]}) + string="Retenue à la source", states={"done": [("readonly", True)]} + ) total_amount = fields.Integer( - compute='_compute_total_amount', string='Total Amount', - store=True, readonly=True) + compute="_compute_total_amount", + string="Total Amount", + store=True, + readonly=True, + ) to_declare = fields.Boolean( - compute='_compute_total_amount', string='To Declare', readonly=True, - store=True) + compute="_compute_total_amount", string="To Declare", readonly=True, store=True + ) allowance_fixed = fields.Boolean( - 'Allocation forfaitaire', states={'done': [('readonly', True)]}) + "Allocation forfaitaire", states={"done": [("readonly", True)]} + ) allowance_real = fields.Boolean( - 'Sur frais réels', states={'done': [('readonly', True)]}) + "Sur frais réels", states={"done": [("readonly", True)]} + ) allowance_employer = fields.Boolean( - "Prise en charge directe par l'employeur", - states={'done': [('readonly', True)]}) + "Prise en charge directe par l'employeur", states={"done": [("readonly", True)]} + ) benefits_in_kind_food = fields.Boolean( - 'Nourriture', states={'done': [('readonly', True)]}) + "Nourriture", states={"done": [("readonly", True)]} + ) benefits_in_kind_accomodation = fields.Boolean( - 'Logement', states={'done': [('readonly', True)]}) + "Logement", states={"done": [("readonly", True)]} + ) benefits_in_kind_car = fields.Boolean( - 'Voiture', states={'done': [('readonly', True)]}) + "Voiture", states={"done": [("readonly", True)]} + ) benefits_in_kind_other = fields.Boolean( - 'Autres', states={'done': [('readonly', True)]}) + "Autres", states={"done": [("readonly", True)]} + ) benefits_in_kind_nict = fields.Boolean( - 'Outils issus des NTIC', states={'done': [('readonly', True)]}) - state = fields.Selection( - related='parent_id.state', store=True, readonly=True) + "Outils issus des NTIC", states={"done": [("readonly", True)]} + ) + state = fields.Selection(related="parent_id.state", store=True, readonly=True) note = fields.Text() job = fields.Char( - string='Profession', size=30, states={'done': [('readonly', True)]}) + string="Profession", size=30, states={"done": [("readonly", True)]} + ) _sql_constraints = [ ( - 'partner_parent_unique', - 'unique(partner_id, parent_id)', - 'Same partner used on several lines!'), + "partner_parent_unique", + "unique(partner_id, parent_id)", + "Same partner used on several lines!", + ), ( - 'fee_amount_positive', - 'CHECK(fee_amount >= 0)', - 'Negative amounts not allowed!'), + "fee_amount_positive", + "CHECK(fee_amount >= 0)", + "Negative amounts not allowed!", + ), ( - 'commission_amount_positive', - 'CHECK(commission_amount >= 0)', - 'Negative amounts not allowed!'), + "commission_amount_positive", + "CHECK(commission_amount >= 0)", + "Negative amounts not allowed!", + ), ( - 'brokerage_amount_positive', - 'CHECK(brokerage_amount >= 0)', - 'Negative amounts not allowed!'), + "brokerage_amount_positive", + "CHECK(brokerage_amount >= 0)", + "Negative amounts not allowed!", + ), ( - 'discount_amount_positive', - 'CHECK(discount_amount >= 0)', - 'Negative amounts not allowed!'), + "discount_amount_positive", + "CHECK(discount_amount >= 0)", + "Negative amounts not allowed!", + ), ( - 'attendance_fee_amount_positive', - 'CHECK(attendance_fee_amount >= 0)', - 'Negative amounts not allowed!'), + "attendance_fee_amount_positive", + "CHECK(attendance_fee_amount >= 0)", + "Negative amounts not allowed!", + ), ( - 'copyright_royalties_amount_positive', - 'CHECK(copyright_royalties_amount >= 0)', - 'Negative amounts not allowed!'), + "copyright_royalties_amount_positive", + "CHECK(copyright_royalties_amount >= 0)", + "Negative amounts not allowed!", + ), ( - 'licence_royalties_amount_positive', - 'CHECK(licence_royalties_amount >= 0)', - 'Negative amounts not allowed!'), + "licence_royalties_amount_positive", + "CHECK(licence_royalties_amount >= 0)", + "Negative amounts not allowed!", + ), ( - 'other_income_amount_positive', - 'CHECK(other_income_amount >= 0)', - 'Negative amounts not allowed!'), + "other_income_amount_positive", + "CHECK(other_income_amount >= 0)", + "Negative amounts not allowed!", + ), ( - 'allowance_amount_positive', - 'CHECK(allowance_amount >= 0)', - 'Negative amounts not allowed!'), + "allowance_amount_positive", + "CHECK(allowance_amount >= 0)", + "Negative amounts not allowed!", + ), ( - 'benefits_in_kind_amount_positive', - 'CHECK(benefits_in_kind_amount >= 0)', - 'Negative amounts not allowed!'), + "benefits_in_kind_amount_positive", + "CHECK(benefits_in_kind_amount >= 0)", + "Negative amounts not allowed!", + ), ( - 'withholding_tax_amount_positive', - 'CHECK(withholding_tax_amount >= 0)', - 'Negative amounts not allowed!'), - ] + "withholding_tax_amount_positive", + "CHECK(withholding_tax_amount >= 0)", + "Negative amounts not allowed!", + ), + ] @api.depends( - 'parent_id.partner_declare_threshold', - 'fee_amount', 'commission_amount', - 'brokerage_amount', 'discount_amount', - 'attendance_fee_amount', 'copyright_royalties_amount', - 'licence_royalties_amount', 'other_income_amount', - 'allowance_amount', 'benefits_in_kind_amount', - 'withholding_tax_amount') + "parent_id.partner_declare_threshold", + "fee_amount", + "commission_amount", + "brokerage_amount", + "discount_amount", + "attendance_fee_amount", + "copyright_royalties_amount", + "licence_royalties_amount", + "other_income_amount", + "allowance_amount", + "benefits_in_kind_amount", + "withholding_tax_amount", + ) def _compute_total_amount(self): for line in self: total_amount = 0 @@ -671,14 +892,13 @@ def _compute_total_amount(self): line.to_declare = to_declare line.total_amount = total_amount - @api.constrains('partner_siret') + @api.constrains("partner_siret") def check_siret(self): for line in self: if line.partner_siret and not is_valid(line.partner_siret): - raise ValidationError(_( - "SIRET '%s' is invalid.") % line.partner_siret) + raise ValidationError(_("SIRET '%s' is invalid.") % line.partner_siret) - @api.onchange('partner_id') + @api.onchange("partner_id") def partner_id_change(self): if self.partner_id: if self.partner_id.siren and self.partner_id.nic: diff --git a/l10n_fr_das2/models/res_company.py b/l10n_fr_das2/models/res_company.py index e18007a01..dc55313aa 100644 --- a/l10n_fr_das2/models/res_company.py +++ b/l10n_fr_das2/models/res_company.py @@ -6,13 +6,16 @@ class ResCompany(models.Model): - _inherit = 'res.company' + _inherit = "res.company" fr_das2_partner_declare_threshold = fields.Integer( - string='DAS2 Partner Declaration Threshold', - default=1200) + string="DAS2 Partner Declaration Threshold", default=1200 + ) - _sql_constraints = [( - 'fr_das2_partner_declare_threshold_positive', - 'CHECK(fr_das2_partner_declare_threshold >= 0)', - 'The DAS2 partner declaration threshold must be positive!')] + _sql_constraints = [ + ( + "fr_das2_partner_declare_threshold_positive", + "CHECK(fr_das2_partner_declare_threshold >= 0)", + "The DAS2 partner declaration threshold must be positive!", + ) + ] diff --git a/l10n_fr_das2/models/res_partner.py b/l10n_fr_das2/models/res_partner.py index bf5882cec..3679c93f9 100644 --- a/l10n_fr_das2/models/res_partner.py +++ b/l10n_fr_das2/models/res_partner.py @@ -6,19 +6,23 @@ class ResPartner(models.Model): - _inherit = 'res.partner' + _inherit = "res.partner" - fr_das2_type = fields.Selection([ - ('fee', 'Honoraires et vacations'), - ('commission', 'Commissions'), - ('brokerage', 'Courtages'), - ('discount', 'Ristournes'), - ('attendance_fee', 'Jetons de présence'), - ('copyright_royalties', "Droits d'auteur"), - ('licence_royalties', "Droits d'inventeur"), - ('other_income', 'Autre rémunérations'), - ('allowance', 'Indemnités et remboursements'), - ], string='DAS2 Type', tracking=100) + fr_das2_type = fields.Selection( + [ + ("fee", "Honoraires et vacations"), + ("commission", "Commissions"), + ("brokerage", "Courtages"), + ("discount", "Ristournes"), + ("attendance_fee", "Jetons de présence"), + ("copyright_royalties", "Droits d'auteur"), + ("licence_royalties", "Droits d'inventeur"), + ("other_income", "Autre rémunérations"), + ("allowance", "Indemnités et remboursements"), + ], + string="DAS2 Type", + tracking=100, + ) fr_das2_job = fields.Char( - string='DAS2 Job', size=30, - help="Used in the field 'Profession' of DAS2.") + string="DAS2 Job", size=30, help="Used in the field 'Profession' of DAS2." + ) diff --git a/l10n_fr_das2/security/das2_security.xml b/l10n_fr_das2/security/das2_security.xml index fca416f08..88c12ddf2 100644 --- a/l10n_fr_das2/security/das2_security.xml +++ b/l10n_fr_das2/security/das2_security.xml @@ -1,17 +1,17 @@ - + DAS2 multi-company - + [('company_id', 'in', company_ids)] DAS2 Line multi-company - + [('company_id', 'in', company_ids)] diff --git a/l10n_fr_das2/views/l10n_fr_das2.xml b/l10n_fr_das2/views/l10n_fr_das2.xml index 8fd06a9e4..965f7c641 100644 --- a/l10n_fr_das2/views/l10n_fr_das2.xml +++ b/l10n_fr_das2/views/l10n_fr_das2.xml @@ -1,10 +1,9 @@ - + - @@ -14,22 +13,45 @@
    -
    -