From 447ff67f54979a34e411f5f6647d6df1a289398d Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Fri, 24 Jan 2020 14:34:17 +0100 Subject: [PATCH 01/57] 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 0000000000..a223b2f37b --- /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 0000000000..19220a7427 --- /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 0000000000..45254b8feb --- /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 0000000000..0d009227ce --- /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 0000000000..673802e0db --- /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 0000000000..a1dc976f4e --- /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 0000000000..e2edacab8a --- /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 0000000000..a321c0a10f --- /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 0000000000..a775f10827 --- /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 0000000000..d6e9b4a9a4 --- /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 0000000000..6601efbb36 --- /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 0000000000..5504e201d5 --- /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 0000000000..f2e028576c --- /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 0000000000..e81c7fb4cd --- /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 d279d1a3af22bc46df17da544b5c27a60f627046 Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Fri, 24 Jan 2020 15:26:51 +0100 Subject: [PATCH 02/57] 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 19220a7427..99f183cb87 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 a321c0a10f..c08f44c9f5 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 f2e028576c..db3f81f018 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 1cc28e56c061e1188621899b2719692968aab25f Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Fri, 31 Jan 2020 00:31:46 +0100 Subject: [PATCH 03/57] 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 99f183cb87..3f3c079c46 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 45254b8feb..30150aa02a 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 673802e0db..0d7f6e64ed 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 a1dc976f4e..f9e3e6a0ac 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 e2edacab8a..20f0432bd3 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 c08f44c9f5..4736ef4d36 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 a775f10827..20bb8c193c 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 5504e201d5..7886082893 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 e81c7fb4cd..39542b2649 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 c4f769923f4d803dda1363357a9b06b17cc7aebd Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Thu, 6 Feb 2020 19:02:13 +0100 Subject: [PATCH 04/57] 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 0000000000..2e1c9a971f --- /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 0000000000..63cc1c0995 --- /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 0000000000..074559aa0e --- /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 0000000000..f2d611a89c --- /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 0000000000..0b22318283 --- /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 0000000000..76757e98d8 --- /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 0000000000..0373c47281 --- /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 3f3c079c46..8a7da1a3c8 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 30150aa02a..0000000000 --- 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 0d009227ce..02a7fcc3e7 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 0d7f6e64ed..0000000000 --- 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 4736ef4d36..a12ed3a024 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 0000000000..720c920a0a --- /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 7886082893..0000000000 --- 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 39542b2649..8e777fea79 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 0000000000..948831c9fe --- /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 29d2c14e590ed7d931b4e14529dd96bbe51f6542 Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Thu, 6 Feb 2020 19:06:29 +0100 Subject: [PATCH 05/57] 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 a12ed3a024..ac99dd8658 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 3c509e7243a2db4278a10025df8add9b24e6fcd4 Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Fri, 7 Feb 2020 17:58:34 +0100 Subject: [PATCH 06/57] 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 ac99dd8658..af6651bacc 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 8e777fea79..fdd1e0dee4 100644 --- a/l10n_fr_das2/views/l10n_fr_das2.xml +++ b/l10n_fr_das2/views/l10n_fr_das2.xml @@ -39,6 +39,7 @@ + From 6695a497bfe939d1604693f6fbdf6b8119d7a666 Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Sat, 8 Feb 2020 00:46:51 +0100 Subject: [PATCH 07/57] 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 0000000000..ff65d68ce6 --- /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 0000000000..d8c8399070 --- /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 af6651bacc..153fe3bdab 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 0000000000..d9c8d38a71 --- /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 0000000000..ff65d68ce6 --- /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 0000000000..d8376603ce --- /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 0000000000..321b7fe22f --- /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 7477013cdb9dad08d2182931b3f28f717a460ece Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Thu, 13 Feb 2020 11:23:20 +0100 Subject: [PATCH 08/57] 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 153fe3bdab..17207b6d03 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 309fd548bad97234161459f2d79f8dc61ba6b298 Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Fri, 14 Feb 2020 22:02:20 +0100 Subject: [PATCH 09/57] 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 a223b2f37b..cde864bae2 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 8a7da1a3c8..c78636facb 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 17207b6d03..986a43914f 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 20bb8c193c..0000000000 --- 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 321b7fe22f..c1cd60c1e0 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 db3f81f018..d366aabbed 100644 --- a/l10n_fr_das2/views/account_config_settings.xml +++ b/l10n_fr_das2/views/account_config_settings.xml @@ -1,6 +1,6 @@ From 324fd39eae4f360ec98b0ceaf6c7c7040e279773 Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Fri, 14 Feb 2020 22:24:54 +0100 Subject: [PATCH 10/57] 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 986a43914f..e9eae25d9d 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 fdd1e0dee4..636e28fb75 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 4674e73bf834439697f6f58f3141bf0dcbc3538f Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Fri, 21 Feb 2020 10:34:12 +0000 Subject: [PATCH 11/57] 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 0000000000..f980278133 --- /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 0000000000..e615f3c7d9 --- /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 0000000000..84f1395489 --- /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 0000000000..c62df0286b --- /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 5ce133b7a5223eafce09876bb8495ea5ed8e26b4 Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Fri, 21 Feb 2020 10:34:12 +0000 Subject: [PATCH 12/57] 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 6d08e6ea4b40fbac148cf94855a63260b607575b Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Mon, 30 Mar 2020 19:46:56 +0200 Subject: [PATCH 13/57] 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 e9eae25d9d..b6c171950d 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 74d285ec74d7d40bd656907a93686970ed53d1cf Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Mon, 30 Mar 2020 23:57:38 +0200 Subject: [PATCH 14/57] [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 2e1c9a971f..65d052762b 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 63cc1c0995..39e4fb6ef1 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 074559aa0e..d0087682cd 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 0b22318283..0dbd77de6f 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 76757e98d8..08526218b5 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 cde864bae2..0650744f6b 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 c78636facb..bd7ab3356e 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 0000000000..f30535e6d9 --- /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 02a7fcc3e7..36aac8dba7 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 20f0432bd3..7378d83843 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 f9e3e6a0ac..d3d62abef5 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 b6c171950d..bad909f5f4 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 720c920a0a..47cd0efb0c 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 d366aabbed..0000000000 --- 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 0000000000..9f14c4f3ea --- /dev/null +++ b/l10n_fr_das2/views/config_settings.xml @@ -0,0 +1,34 @@ + + + + + + + + das2.res.config.settings.form + res.config.settings + + + +

    Intrastat

    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + + + + + From 31287e682724c2a6d9be882739d38cba79d22e2d Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Fri, 3 Apr 2020 11:18:53 +0200 Subject: [PATCH 15/57] 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 bad909f5f4..0ae682186a 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 db1ccb18889de84d972a59e9609dcc481fc81bf9 Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Fri, 3 Apr 2020 11:27:28 +0200 Subject: [PATCH 16/57] 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 0ae682186a..4c1fbf8104 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 921e1f3e2aa722d7d1d5fbc5631f4aa61f662fee Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Fri, 3 Apr 2020 13:06:52 +0200 Subject: [PATCH 17/57] 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 f2d611a89c..30b933141f 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 d55ff71ecc7edf74fe66afa39ab6071b03eed564 Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Fri, 3 Apr 2020 12:52:17 +0000 Subject: [PATCH 18/57] [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 f980278133..4666060b41 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 e615f3c7d9..4c6a68dcf7 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 84f1395489..f497668eb4 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 c62df0286b..4f6def775e 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 99c4bb5cbcd7e4c6d7e2d99491f782fbfa1e50f3 Mon Sep 17 00:00:00 2001 From: oca-travis Date: Fri, 3 Apr 2020 13:01:48 +0000 Subject: [PATCH 19/57] [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 0000000000..6940e38e47 --- /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 973a2f5fef54412110a84aeafa2b22edd57a24d3 Mon Sep 17 00:00:00 2001 From: oca-travis Date: Fri, 3 Apr 2020 13:01:51 +0000 Subject: [PATCH 20/57] [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 0000000000..ec2c3ca6a3 --- /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 3a5453e9bdf1e33f82ea897132d9bed27a921843 Mon Sep 17 00:00:00 2001 From: David Beal Date: Tue, 21 Apr 2020 15:13:32 +0200 Subject: [PATCH 21/57] 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 f497668eb4..f72cfd0fe2 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 c1cd60c1e0..5eb3d5429d 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 9c4ee1dead1e23ad2d92a3b56a02be0aed593e02 Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Wed, 22 Apr 2020 11:19:10 +0000 Subject: [PATCH 22/57] [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 4f6def775e..46a13a868f 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 36c1308d70b1165daf877fce990520c228a73eb5 Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Wed, 22 Apr 2020 11:19:11 +0000 Subject: [PATCH 23/57] 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 bd7ab3356e..15b800ad58 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 af889ec130d260d591c501d41bbcc98de2bea404 Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Wed, 13 May 2020 22:46:02 +0200 Subject: [PATCH 24/57] 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 4c1fbf8104..79262702bb 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 4dc3678d76253c69f03689b5ac79815f966179b3 Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Thu, 18 Feb 2021 22:23:34 +0100 Subject: [PATCH 25/57] [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 39e4fb6ef1..18a8eac9c4 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 d0087682cd..7afb82fc7a 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 30b933141f..11573766f2 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 0dbd77de6f..d0eb7ac4c7 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 0373c47281..aad4efd4f5 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 0650744f6b..aee8895e7a 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 15b800ad58..1dee664a7a 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 f30535e6d9..01c28dbd0b 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 36aac8dba7..cb3d3203f6 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 79262702bb..83a0a1e405 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 7378d83843..e18007a012 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 47cd0efb0c..bf5882cec2 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 d6e9b4a9a4..fca416f089 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 636e28fb75..8fd06a9e43 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 e18007a012..dc55313aaa 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 bf5882cec2..3679c93f9d 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 fca416f089..88c12ddf2b 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 8fd06a9e43..965f7c641c 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 @@
    -
    -