diff --git a/account_subsequence_fiscal_year/README.rst b/account_subsequence_fiscal_year/README.rst new file mode 100644 index 000000000..e83d36db2 --- /dev/null +++ b/account_subsequence_fiscal_year/README.rst @@ -0,0 +1,8 @@ +======================================== +Accounting Subsequences per Fiscal Years +======================================== + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! diff --git a/account_subsequence_fiscal_year/__init__.py b/account_subsequence_fiscal_year/__init__.py new file mode 100644 index 000000000..0650744f6 --- /dev/null +++ b/account_subsequence_fiscal_year/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/account_subsequence_fiscal_year/__manifest__.py b/account_subsequence_fiscal_year/__manifest__.py new file mode 100644 index 000000000..b1b13ae51 --- /dev/null +++ b/account_subsequence_fiscal_year/__manifest__.py @@ -0,0 +1,27 @@ +# Copyright (C) 2020 - Today: GRAP (http://www.grap.coop) +# @author: Sylvain LE GAL (https://twitter.com/legalsylvain) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +{ + "name": "Accounting Subsequences per Fiscal Years", + "summary": "Allow to create sub sequences for account moves number, based" + " on the fiscal years settings", + "version": "12.0.1.0.0", + "category": "Accounting", + "author": "GRAP,Odoo Community Association (OCA)", + "maintainers": ["legalsylvain"], + "website": "http://www.github.com/OCA/account-financial-tools", + "license": "AGPL-3", + "depends": [ + "account", + ], + "data": [ + "views/view_res_config_settings.xml", + ], + "demo": [ + "demo/res_groups.xml", + ], + "images": [ + "static/description/res_config_setting.png", + ], +} diff --git a/account_subsequence_fiscal_year/demo/res_groups.xml b/account_subsequence_fiscal_year/demo/res_groups.xml new file mode 100644 index 000000000..12fb85d0a --- /dev/null +++ b/account_subsequence_fiscal_year/demo/res_groups.xml @@ -0,0 +1,14 @@ + + + + + + + + + + diff --git a/account_subsequence_fiscal_year/i18n/fr.po b/account_subsequence_fiscal_year/i18n/fr.po new file mode 100644 index 000000000..0dcc21557 --- /dev/null +++ b/account_subsequence_fiscal_year/i18n/fr.po @@ -0,0 +1,79 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * account_subsequence_fiscal_year +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 12.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-05-30 17:11+0000\n" +"PO-Revision-Date: 2020-05-30 17:11+0000\n" +"Last-Translator: <>\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: account_subsequence_fiscal_year +#: model_terms:ir.ui.view,arch_db:account_subsequence_fiscal_year.view_res_config_settings_form +msgid "Accounting Subsequences Method" +msgstr "Méthode pour les sous-séquences comptables" + +#. module: account_subsequence_fiscal_year +#: model:ir.model.fields,field_description:account_subsequence_fiscal_year.field_res_company__account_subsequence_method +#: model:ir.model.fields,field_description:account_subsequence_fiscal_year.field_res_config_settings__account_subsequence_method +msgid "Accounting Subsequences Method" +msgstr "Méthode pour les sous-séquences comptables" + +#. module: account_subsequence_fiscal_year +#: model:ir.model,name:account_subsequence_fiscal_year.model_account_bank_statement +msgid "Bank Statement" +msgstr "Relevé bancaire" + +#. module: account_subsequence_fiscal_year +#: selection:res.company,account_subsequence_method:0 +msgid "Based on Company Settings" +msgstr "Basé sur les paramètres de la société" + +#. module: account_subsequence_fiscal_year +#: selection:res.company,account_subsequence_method:0 +msgid "Based on Fiscal Years Settings" +msgstr "Basés sur les exercices comptables" + +#. module: account_subsequence_fiscal_year +#: model:ir.model,name:account_subsequence_fiscal_year.model_res_company +msgid "Companies" +msgstr "Sociétés" + +#. module: account_subsequence_fiscal_year +#: model:ir.model,name:account_subsequence_fiscal_year.model_res_config_settings +msgid "Config Settings" +msgstr "Paramètres de config" + +#. module: account_subsequence_fiscal_year +#: model_terms:ir.ui.view,arch_db:account_subsequence_fiscal_year.view_res_config_settings_form +msgid "Define how accounting subsequences are generated" +msgstr "Définit comment les sous-séquences comptables sont générées" + +#. module: account_subsequence_fiscal_year +#: model_terms:ir.ui.view,arch_db:account_subsequence_fiscal_year.view_res_config_settings_form +msgid "Generation Method" +msgstr "Méthode de génération" + +#. module: account_subsequence_fiscal_year +#: model:ir.model,name:account_subsequence_fiscal_year.model_account_move +msgid "Journal Entries" +msgstr "Pièces comptables" + +#. module: account_subsequence_fiscal_year +#: model:ir.model,name:account_subsequence_fiscal_year.model_ir_sequence +msgid "Sequence" +msgstr "Séquence" + +#. module: account_subsequence_fiscal_year +#: code:addons/account_subsequence_fiscal_year/models/ir_sequence.py:45 +#, python-format +msgid "You can not post an accounting entry for the date %s because there is no fiscal year defined at this date." +msgstr "Vous ne pouvez pas valider une pièce comptable pour la date du %s car aucun exercice comptable n'est défini à cette date." + diff --git a/account_subsequence_fiscal_year/models/__init__.py b/account_subsequence_fiscal_year/models/__init__.py new file mode 100644 index 000000000..5a544b6e6 --- /dev/null +++ b/account_subsequence_fiscal_year/models/__init__.py @@ -0,0 +1,5 @@ +from . import account_bank_statement +from . import account_move +from . import ir_sequence +from . import res_company +from . import res_config_settings diff --git a/account_subsequence_fiscal_year/models/account_bank_statement.py b/account_subsequence_fiscal_year/models/account_bank_statement.py new file mode 100644 index 000000000..8ad2e9034 --- /dev/null +++ b/account_subsequence_fiscal_year/models/account_bank_statement.py @@ -0,0 +1,14 @@ +# Copyright (C) 2020 - Today: GRAP (http://www.grap.coop) +# @author: Sylvain LE GAL (https://twitter.com/legalsylvain) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from odoo import api, models + + +class AccountBankStatement(models.Model): + _inherit = "account.bank.statement" + + @api.multi + def button_open(self): + return super( + AccountBankStatement, self.with_context(account_sequence=True) + ).button_open() diff --git a/account_subsequence_fiscal_year/models/account_move.py b/account_subsequence_fiscal_year/models/account_move.py new file mode 100644 index 000000000..2f224a7a6 --- /dev/null +++ b/account_subsequence_fiscal_year/models/account_move.py @@ -0,0 +1,14 @@ +# Copyright (C) 2020 - Today: GRAP (http://www.grap.coop) +# @author: Sylvain LE GAL (https://twitter.com/legalsylvain) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from odoo import api, models + + +class AccountMove(models.Model): + _inherit = "account.move" + + @api.multi + def post(self, invoice=False): + return super( + AccountMove, self.with_context(account_sequence=True) + ).post(invoice=invoice) diff --git a/account_subsequence_fiscal_year/models/ir_sequence.py b/account_subsequence_fiscal_year/models/ir_sequence.py new file mode 100644 index 000000000..3f7c21911 --- /dev/null +++ b/account_subsequence_fiscal_year/models/ir_sequence.py @@ -0,0 +1,59 @@ +# Copyright (C) 2020 - Today: GRAP (http://www.grap.coop) +# @author: Sylvain LE GAL (https://twitter.com/legalsylvain) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +import datetime +from dateutil.relativedelta import relativedelta + +from odoo import _, fields, models +from odoo.exceptions import ValidationError + + +class IrSequence(models.Model): + _inherit = "ir.sequence" + + def _create_date_range_seq(self, date): + AccountFiscalYear = self.env["account.fiscal.year"] + IrSequenceDateRange = self.env["ir.sequence.date_range"] + if self._context.get("account_sequence", False)\ + and self.company_id\ + and self.company_id.account_subsequence_method: + + method = self.company_id.account_subsequence_method + object_date = fields.Date.from_string(date) + + if method == "company_setting": + base_date = datetime.date( + object_date.year, + int(self.company_id.fiscalyear_last_month), + int(self.company_id.fiscalyear_last_day), + ) + if object_date <= base_date: + date_from = base_date + relativedelta(years=-1, days=1) + date_to = base_date + else: + date_from = base_date + relativedelta(days=1) + date_to = base_date + relativedelta(years=1) + + elif method == "fiscal_year_setting": + fiscal_years = AccountFiscalYear.search( + [("date_to", ">=", object_date.strftime("%Y-%m-%d"))], + order="date_from desc", + limit=1 + ) + if not fiscal_years or fiscal_years[0].date_from > object_date: + raise ValidationError(_( + "You can not post an accounting entry for the" + " date %s because there is no fiscal year defined at" + " this date.") % (date)) + date_from = fiscal_years[0].date_from + date_to = fiscal_years[0].date_to + + # Create and return new sequence + return IrSequenceDateRange.sudo().create({ + 'sequence_id': self.id, + 'date_from': date_from, + 'date_to': date_to, + }) + + return super()._create_date_range_seq(date) diff --git a/account_subsequence_fiscal_year/models/res_company.py b/account_subsequence_fiscal_year/models/res_company.py new file mode 100644 index 000000000..9d45c1b4a --- /dev/null +++ b/account_subsequence_fiscal_year/models/res_company.py @@ -0,0 +1,16 @@ +# Copyright (C) 2020 - Today: GRAP (http://www.grap.coop) +# @author: Sylvain LE GAL (https://twitter.com/legalsylvain) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import fields, models + + +class ResCompany(models.Model): + _inherit = "res.company" + + account_subsequence_method = fields.Selection( + string="Accounting Subsequences Method", + selection=[ + ("company_setting", "Based on Company Settings"), + ("fiscal_year_setting", "Based on Fiscal Years Settings"), + ]) diff --git a/account_subsequence_fiscal_year/models/res_config_settings.py b/account_subsequence_fiscal_year/models/res_config_settings.py new file mode 100644 index 000000000..1ff9e9ba4 --- /dev/null +++ b/account_subsequence_fiscal_year/models/res_config_settings.py @@ -0,0 +1,13 @@ +# Copyright (C) 2020 - Today: GRAP (http://www.grap.coop) +# @author: Sylvain LE GAL (https://twitter.com/legalsylvain) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from odoo import fields, models + + +class ResConfigSettings(models.TransientModel): + _inherit = "res.config.settings" + + account_subsequence_method = fields.Selection( + related="company_id.account_subsequence_method", + readonly=False, + ) diff --git a/account_subsequence_fiscal_year/readme/CONFIGURE.rst b/account_subsequence_fiscal_year/readme/CONFIGURE.rst new file mode 100644 index 000000000..990acc24a --- /dev/null +++ b/account_subsequence_fiscal_year/readme/CONFIGURE.rst @@ -0,0 +1,27 @@ +To configure this module, you need to: + +* Add your users to the group 'Technical Settings / Show Full Accounting Features' + +* Go to Invoicing > Configuration / Settings + +* configure your accounting settings + +.. figure:: ../static/description/res_config_setting.png + +Depending on what you want, 3 options are available. + +Given the following example: + +- an account move with a date set to 2030-06-01 +- a company set with ``fiscalyear_last_day = 1`` and ``fiscalyear_last_month = 10`` + +* 'empty' (default value): + the subsequence will be created "normaly", with ``date_from = 2030-01-01`` and ``date_to = 2030-12-31`` + +* 'Based on Company Settings': + the subsequence will be created depending on the values of ``fiscalyear_last_day`` + and ``fiscalyear_last_month``, so ``date_from = 2029-10-01`` and ``date_to = 2030-09-30`` + +* 'Based on Fiscal Years Settings': + the subsequence will be created with the values defined in the according fiscal year. + Note that if no fiscal year is found, the generation will fail. diff --git a/account_subsequence_fiscal_year/readme/CONTRIBUTORS.rst b/account_subsequence_fiscal_year/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000..9f76a75bc --- /dev/null +++ b/account_subsequence_fiscal_year/readme/CONTRIBUTORS.rst @@ -0,0 +1 @@ +* Sylvain LE GAL diff --git a/account_subsequence_fiscal_year/readme/DESCRIPTION.rst b/account_subsequence_fiscal_year/readme/DESCRIPTION.rst new file mode 100644 index 000000000..73340d02d --- /dev/null +++ b/account_subsequence_fiscal_year/readme/DESCRIPTION.rst @@ -0,0 +1,9 @@ +This module extends the functionality of Accounting Odoo module, to +generate custom sub sequences for accounting entries, if fiscal years are not "classic". +(ie: last day = 31 / last month = 12) + +without this module, in such cases (for example, for fiscal year from 01 April 2020 to 31 May 2021) +accounting moves of the same fiscal year will not have the same numbering: Some entries will +have ``BILL/2020/`` prefix and other will have ``BILL/2021/``, that is not allowed in many countries. + +That modules fixes this problem. diff --git a/account_subsequence_fiscal_year/static/description/res_config_setting.png b/account_subsequence_fiscal_year/static/description/res_config_setting.png new file mode 100644 index 000000000..23ac1df7c Binary files /dev/null and b/account_subsequence_fiscal_year/static/description/res_config_setting.png differ diff --git a/account_subsequence_fiscal_year/tests/__init__.py b/account_subsequence_fiscal_year/tests/__init__.py new file mode 100644 index 000000000..d9b96c4fa --- /dev/null +++ b/account_subsequence_fiscal_year/tests/__init__.py @@ -0,0 +1 @@ +from . import test_module diff --git a/account_subsequence_fiscal_year/tests/test_module.py b/account_subsequence_fiscal_year/tests/test_module.py new file mode 100644 index 000000000..125366c76 --- /dev/null +++ b/account_subsequence_fiscal_year/tests/test_module.py @@ -0,0 +1,142 @@ +# Copyright 2020 ACSONE SA/NV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo.tests.common import TransactionCase +from odoo.exceptions import ValidationError + +from datetime import date + + +class TestModule(TransactionCase): + + def setUp(self): + super().setUp() + self.AccountFiscalYear = self.env["account.fiscal.year"] + self.company = self.env.ref("base.main_company") + self.account_type_receivable = self.env["account.account.type"].create( + {"name": "Test Receivable", "type": "receivable"} + ) + self.account_receivable = self.env["account.account"].create( + { + "name": "Test Receivable", + "code": "TEST_AR", + "user_type_id": self.account_type_receivable.id, + "reconcile": True, + } + ) + self.sale_journal = self.env["account.journal"].search( + [("type", "=", "sale"), ("company_id", "=", self.company.id)] + )[0] + self.sequence = self.sale_journal.sequence_id + self.sequence.use_date_range = True + + self.account_move = self.env["account.move"].create({ + "journal_id": self.sale_journal.id, + "company_id": self.company.id, + "line_ids": [(0, 0, { + "account_id": self.account_receivable.id, + "debit": 100, + }), (0, 0, { + "account_id": self.account_receivable.id, + "credit": 100, + })] + }) + + self.existing_subsequence_ids =\ + self.env["ir.sequence.date_range"].search([ + ("sequence_id", "=", self.sequence.id)]).ids + + def _get_new_subsequence(self): + return self.env["ir.sequence.date_range"].search([ + ("sequence_id", "=", self.sequence.id), + ("id", "not in", self.existing_subsequence_ids), + ]) + + def test_method_normal(self): + """Non Regression Test""" + self.company.account_subsequence_method = False + self.account_move.date = "2100-06-01" + self.account_move.post() + new_subsequences = self._get_new_subsequence() + self.assertEqual(len(new_subsequences), 1) + self.assertEqual( + new_subsequences[0].date_from, + date(2100, 1, 1) + ) + self.assertEqual( + new_subsequences[0].date_to, + date(2100, 12, 31) + ) + + def test_method_company_setting_before(self): + self.company.account_subsequence_method = 'company_setting' + self.company.fiscalyear_last_day = 31 + self.company.fiscalyear_last_month = 3 + self.account_move.date = "2100-03-15" + self.account_move.post() + + new_subsequences = self._get_new_subsequence() + self.assertEqual(len(new_subsequences), 1) + self.assertEqual( + new_subsequences[0].date_from, + date(2099, 4, 1) + ) + self.assertEqual( + new_subsequences[0].date_to, + date(2100, 3, 31) + ) + + def test_method_company_setting_after(self): + self.company.account_subsequence_method = 'company_setting' + self.company.fiscalyear_last_day = 31 + self.company.fiscalyear_last_month = 3 + self.account_move.date = "2100-05-15" + self.account_move.post() + + new_subsequences = self._get_new_subsequence() + self.assertEqual(len(new_subsequences), 1) + self.assertEqual( + new_subsequences[0].date_from, + date(2100, 4, 1) + ) + self.assertEqual( + new_subsequences[0].date_to, + date(2101, 3, 31) + ) + + def test_method_fiscal_year_setting(self): + self.company.account_subsequence_method = 'fiscal_year_setting' + with self.assertRaises(ValidationError): + self.account_move.date = "2100-06-01" + self.account_move.post() + + # create a Fiscal Year + self.AccountFiscalYear.create({ + "name": "2100 FY April to March", + "company_id": self.company.id, + "date_from": "2100-04-01", + "date_to": "2101-03-31", + }) + + # Try to post out the date range + with self.assertRaises(ValidationError): + self.account_move.date = "2100-03-31" + self.account_move.post() + + with self.assertRaises(ValidationError): + self.account_move.date = "2101-04-01" + self.account_move.post() + + # Post in the range should succeed + self.account_move.date = "2100-06-01" + self.account_move.post() + new_subsequences = self._get_new_subsequence() + self.assertEqual(len(new_subsequences), 1) + self.assertEqual( + new_subsequences[0].date_from, + date(2100, 4, 1) + ) + self.assertEqual( + new_subsequences[0].date_to, + date(2101, 3, 31) + ) diff --git a/account_subsequence_fiscal_year/views/view_res_config_settings.xml b/account_subsequence_fiscal_year/views/view_res_config_settings.xml new file mode 100644 index 000000000..b0ab90a5c --- /dev/null +++ b/account_subsequence_fiscal_year/views/view_res_config_settings.xml @@ -0,0 +1,33 @@ + + + + + + res.config.settings + + + + + + + Accounting Subsequences Method + + Define how accounting subsequences are generated + + + + + + + + + + + + + +