diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ae928d9..52b3bfb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -97,7 +97,7 @@ repos: rev: v0.1.3 hooks: - id: ruff - args: [--fix, --exit-non-zero-on-fix] + args: [--fix, --exit-non-zero-on-fix, --unsafe-fixes] - id: ruff-format - repo: https://github.com/OCA/pylint-odoo rev: v8.0.19 diff --git a/fiscal_company_base/README.rst b/fiscal_company_base/README.rst new file mode 100644 index 0000000..af36d6a --- /dev/null +++ b/fiscal_company_base/README.rst @@ -0,0 +1,111 @@ +========== +CAE - Base +========== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:6e3a501ed3b5d79a9292fe440d5247e9d2be0fa43389eeb4cae6314324eec2a6 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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-grap%2Fodoo--addons--cae-lightgray.png?logo=github + :target: https://github.com/grap/odoo-addons-cae/tree/12.0/fiscal_company_base + :alt: grap/odoo-addons-cae + +|badge1| |badge2| |badge3| + +This module extend Odoo functionnalities, regarding companies features to +manage CAE (Coopearatives of Activities and Employment) that is a special +status for french companies. + +(see above, links that describes what is CAE). + +Basically, in a CAE, there is a 'parent' company that hosts many 'child' +companies. People in a child company should have access only to their activity. +(account moves, customers, suppliers, products, etc...) + +In a fiscal and legal point of view, there is only one company (the parent one) +so there is only on chart of accounts. Accounting moves of the child +companies are written in the child company, but associated to the account of +the parent company. + +**Companies feature** + +* Add a new field on company `fiscal_type`: + * `normal` : classical company + * `fiscal_mother`: CAE company, that can host many child companies + * `fiscal_child`: child company, hosted by the CAE + + +**Users Feature** + +* If a user has access rights to a 'fiscal_mother' so he has access + rights to all 'fiscal_child' companies; + +**Groups Feature** + +* this module add a new group 'Disabled Features for Fiscal Company' + that should be affected to all the features bad designed by odoo, + specially when odoo introduced views based on datas computed on SQL hard + coded requests that can not work with the Odoo CAE design. + See 'account_fiscal_company' module for exemples. + +**More information about CAE [FR]** + +* https://fr.wikipedia.org/wiki/Coopérative_d'activités_et_d'emploi +* http://www.cooperer.coop/ +* http://www.copea.fr/ + +**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 to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* GRAP + +Contributors +~~~~~~~~~~~~ + +* Julien WESTE +* Sylvain LE GAL + +Other credits +~~~~~~~~~~~~~ + +The development of this module has been financially supported by: + +* GRAP, Groupement Régional Alimentaire de Proximité (http://www.grap.coop) + +Porting from odoo V8 to odoo V10 has been funded by : + * BABEL.COOP, leverage cooperation through the digital age () + +Maintainers +~~~~~~~~~~~ + +This module is part of the `grap/odoo-addons-cae `_ project on GitHub. + +You are welcome to contribute. diff --git a/fiscal_company_base/__init__.py b/fiscal_company_base/__init__.py new file mode 100644 index 0000000..0650744 --- /dev/null +++ b/fiscal_company_base/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/fiscal_company_base/__manifest__.py b/fiscal_company_base/__manifest__.py new file mode 100644 index 0000000..e4c9cdc --- /dev/null +++ b/fiscal_company_base/__manifest__.py @@ -0,0 +1,31 @@ +# Copyright (C) 2013-Today: GRAP (http://www.grap.coop) +# @author: Julien WESTE +# @author: Sylvain LE GAL (https://twitter.com/legalsylvain) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +{ + "name": "CAE - Base", + "version": "16.0.1.0.0", + "category": "CAE", + "summary": "Manage CAE (Cooperatives of Activities and Employment)", + "author": "GRAP", + "website": "https://github.com/grap/odoo-addons-cae", + "license": "AGPL-3", + "depends": [ + "base", + # Dependency added to have the possibility to create demo user, + # without "notification_type" error + "mail", + ], + "data": [ + # "security/ir_rule.xml", + "views/view_res_company.xml", + ], + "demo": [ + "demo/res_partner_company.xml", + "demo/res_partner_users.xml", + "demo/res_partner.xml", + "demo/res_groups.xml", + ], + "installable": True, +} diff --git a/fiscal_company_base/demo/res_groups.xml b/fiscal_company_base/demo/res_groups.xml new file mode 100644 index 0000000..5584f29 --- /dev/null +++ b/fiscal_company_base/demo/res_groups.xml @@ -0,0 +1,16 @@ + + + + + + + + + diff --git a/fiscal_company_base/demo/res_partner.xml b/fiscal_company_base/demo/res_partner.xml new file mode 100644 index 0000000..a70a3e5 --- /dev/null +++ b/fiscal_company_base/demo/res_partner.xml @@ -0,0 +1,15 @@ + + + + + + + CAE - Mother Partner + + + + diff --git a/fiscal_company_base/demo/res_partner_company.xml b/fiscal_company_base/demo/res_partner_company.xml new file mode 100644 index 0000000..0e1dd0c --- /dev/null +++ b/fiscal_company_base/demo/res_partner_company.xml @@ -0,0 +1,71 @@ + + + + + + + + + Group Company + + + + + + Group Company + + group + + + + + + + + + + CAE Company + + + + + + CAE Company + + fiscal_mother + + + + + + Integrated Company 1 (Service) + + + + + + Integrated Company 1 (Service) + + + fiscal_child + + + + + Integrated Company 2 (Production) + + + + + + Integrated Company 2 (Production) + + + fiscal_child + + + diff --git a/fiscal_company_base/demo/res_partner_users.xml b/fiscal_company_base/demo/res_partner_users.xml new file mode 100644 index 0000000..0aee9ff --- /dev/null +++ b/fiscal_company_base/demo/res_partner_users.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + Accountant User + accountant@cae.coop + + + + + accountant + demo + + + + + + + Worker User + worker@cae.coop + + + + + worker + demo + + + + + diff --git a/fiscal_company_base/i18n/fr.po b/fiscal_company_base/i18n/fr.po new file mode 100644 index 0000000..e9a29ab --- /dev/null +++ b/fiscal_company_base/i18n/fr.po @@ -0,0 +1,133 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * fiscal_company_base +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-08-05 10:52+0000\n" +"PO-Revision-Date: 2024-08-05 10:52+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: fiscal_company_base +#: model:ir.model.fields.selection,name:fiscal_company_base.selection__res_company__fiscal_type__fiscal_mother +msgid "CAE" +msgstr "" + +#. module: fiscal_company_base +#: model:ir.model,name:fiscal_company_base.model_res_company +msgid "Companies" +msgstr "Sociétés" + +#. module: fiscal_company_base +#: model:ir.model.fields,field_description:fiscal_company_base.field_res_company__fiscal_company_id +msgid "Fiscal Company" +msgstr "Société Fiscale" + +#. module: fiscal_company_base +#: model:ir.model,name:fiscal_company_base.model_fiscal_company_change_search_domain_mixin +msgid "Fiscal Company : Change Search Domain Mixin" +msgstr "Société Fiscale: Mixin changement de domaine de recherche" + +#. module: fiscal_company_base +#: model:ir.model,name:fiscal_company_base.model_fiscal_company_check_company_mixin +msgid "Fiscal Company : Check Company Mixin" +msgstr "Société Fiscale: Mixin de vérification de société" + +#. module: fiscal_company_base +#: model:ir.model.fields,field_description:fiscal_company_base.field_res_company__fiscal_type +msgid "Fiscal Type" +msgstr "Type fiscal" + +#. module: fiscal_company_base +#: model:ir.model.fields.selection,name:fiscal_company_base.selection__res_company__fiscal_type__group +msgid "Group" +msgstr "Groupe" + +#. module: fiscal_company_base +#: model:ir.model.fields.selection,name:fiscal_company_base.selection__res_company__fiscal_type__fiscal_child +msgid "Integrated Company" +msgstr "Activité intégrée" + +#. module: fiscal_company_base +#: model:ir.model.fields.selection,name:fiscal_company_base.selection__res_company__fiscal_type__normal +msgid "Normal" +msgstr "" + +#. module: fiscal_company_base +#: model:ir.model.fields,field_description:fiscal_company_base.field_res_company__fiscal_child_ids +msgid "Technical Integrated Companies" +msgstr "Sociétés fiscales filles (champ techniques)" + +#. module: fiscal_company_base +#. odoo-python +#: code:addons/fiscal_company_base/models/res_company.py:0 +#, python-format +msgid "" +"The company '%(company_name)s' (type %(fiscal_type_name)s) can only have a " +"parent company with a type 'Group'." +msgstr "" +"La société '%(company_name)s' (type %(fiscal_type_name)s) peut seulement " +"avoir une société parent de type 'Groupe'." + +#. module: fiscal_company_base +#. odoo-python +#: code:addons/fiscal_company_base/models/res_company.py:0 +#, python-format +msgid "" +"The company '%(company_name)s' (type 'Fiscal Child') can only have a parent " +"company with a type 'Fiscal Mother'." +msgstr "" +"La société '%(company_name)s' (type 'Activité Intégrée') peut seulement " +"avoir une société parent de type 'CAE'." + +#. module: fiscal_company_base +#. odoo-python +#: code:addons/fiscal_company_base/models/res_company.py:0 +#, python-format +msgid "" +"The company '%(company_name)s' can not be '%(fiscal_type)s' because it " +"contains companies." +msgstr "" +"La société '%(company_name)s' ne peut pas être de type '%(fiscal_type)s' car" +" elle contient des sociétés filles." + +#. module: fiscal_company_base +#. odoo-python +#: code:addons/fiscal_company_base/models/res_company.py:0 +#, python-format +msgid "" +"The company '%(company_name)s' can not be 'Fiscal Mother' because it " +"contains companies type of '%(error_types)s'" +msgstr "" +"La société '%(company_name)s' ne peut pas être de type 'CAE' car elle " +"contient des sociétés de type '%(error_types)s'" + +#. module: fiscal_company_base +#. odoo-python +#: code:addons/fiscal_company_base/models/res_company.py:0 +#, python-format +msgid "" +"The company '%(company_name)s' can not be 'Group' because it contains " +"companies type of '%(error_types)s'" +msgstr "" +"La société '%(company_name)s' ne peut pas être de type 'Groupe' car elle " +"contient des sociétés de type '%(error_types)s'" + +#. module: fiscal_company_base +#. odoo-python +#: code:addons/fiscal_company_base/models/fiscal_company_check_company_mixin.py:0 +#, python-format +msgid "" +"You can't affect the %(items_qty)s item(s) to company with such fiscal type.\n" +"\n" +" (model '%(model_name)s')" +msgstr "Vous ne pouvez pas affecter le(s) %(items_qty)s élément(s) à cette société avec ce type de société fiscale.\n" +"\n" +" (Modèle '%(model_name)s')" diff --git a/fiscal_company_base/models/__init__.py b/fiscal_company_base/models/__init__.py new file mode 100644 index 0000000..b455d87 --- /dev/null +++ b/fiscal_company_base/models/__init__.py @@ -0,0 +1,3 @@ +from . import res_company +from . import fiscal_company_change_search_domain_mixin +from . import fiscal_company_check_company_mixin diff --git a/fiscal_company_base/models/fiscal_company_change_search_domain_mixin.py b/fiscal_company_base/models/fiscal_company_change_search_domain_mixin.py new file mode 100644 index 0000000..2e80da4 --- /dev/null +++ b/fiscal_company_base/models/fiscal_company_change_search_domain_mixin.py @@ -0,0 +1,81 @@ +# Copyright (C) 2018 - 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 FiscalCompanyChangeSearchDomainMixin(models.AbstractModel): + """This abstract change the _search features for models. + + if a domain contain a ('company_id', '=', company_id) + it will be replace by ('company_id', 'in', [company_id, fiscal_company_id]) + """ + + _name = "fiscal.company.change.search.domain.mixin" + _description = "Fiscal Company : Change Search Domain Mixin" + + @api.model + def _fiscal_company_change_domain(self, domain): + new_domain = [] + ResCompany = self.env["res.company"] + + for item in domain: + if (isinstance(item, list) or isinstance(item, tuple)) and item[ + 0 + ] == "company_id": + if isinstance(item[2], list): + old_company_ids = item[2] + else: + old_company_ids = [item[2]] + + has_false = False in old_company_ids + if has_false: + if len(old_company_ids) == 1: + # if the domain is ('company_id', 'OP', False) + # We have nothing to change + new_domain.append(item) + continue + else: + old_company_ids.pop(False) + + old_companies = ResCompany.browse(old_company_ids) + new_company_ids = ( + old_companies | old_companies.mapped("fiscal_company_id") + ).ids + + if item[1] in ["=", "in"]: + new_operator = "in" + elif item[1] in ["!=", "not in"]: + new_operator = "not in" + else: + raise NotImplementedError("Not implemented operator") + + if has_false: + new_company_ids.append(False) + + new_domain.append(("company_id", new_operator, new_company_ids)) + else: + new_domain.append(item) + return new_domain + + @api.model + def _search( + self, + args, + offset=0, + limit=None, + order=None, + count=False, + access_rights_uid=None, + ): + new_args = self._fiscal_company_change_domain(args) + return super()._search( + new_args, + offset=offset, + limit=limit, + order=order, + count=count, + access_rights_uid=access_rights_uid, + ) diff --git a/fiscal_company_base/models/fiscal_company_check_company_mixin.py b/fiscal_company_base/models/fiscal_company_check_company_mixin.py new file mode 100644 index 0000000..e9a5153 --- /dev/null +++ b/fiscal_company_base/models/fiscal_company_check_company_mixin.py @@ -0,0 +1,37 @@ +# Copyright (C) 2021 - 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 +from odoo.exceptions import ValidationError + + +class FiscalCompanyCheckCompanyMixin(models.AbstractModel): + """This abstract block the possibility for a model to have + items linked to a company for some fiscal types. + Note that you can only inherit from this abstract, if the current model + has `company_id` fields defined. + """ + + _name = "fiscal.company.check.company.mixin" + _description = "Fiscal Company : Check Company Mixin" + + _fiscal_company_forbid_fiscal_type = [] + + @api.constrains("company_id") + def _fiscal_company_check_company_id(self): + bad_items = self.with_context(dont_change_filter=True).filtered( + lambda x: x.company_id.fiscal_type + in self._fiscal_company_forbid_fiscal_type + ) + if bad_items: + raise ValidationError( + _( + "You can't affect the %(items_qty)s item(s) to company" + " with such fiscal type.\n\n" + " (model '%(model_name)s')", + items_qty=len(bad_items), + model_name=self._name, + ) + ) diff --git a/fiscal_company_base/models/res_company.py b/fiscal_company_base/models/res_company.py new file mode 100644 index 0000000..03195bb --- /dev/null +++ b/fiscal_company_base/models/res_company.py @@ -0,0 +1,118 @@ +# Copyright (C) 2013-Today: GRAP (http://www.grap.coop) +# @author: Julien WESTE +# @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, fields, models +from odoo.exceptions import ValidationError + +_RES_COMPANY_FISCAL_TYPE = [ + ("group", "Group"), + ("normal", "Normal"), + ("fiscal_mother", "CAE"), + ("fiscal_child", "Integrated Company"), +] + + +class ResCompany(models.Model): + _inherit = "res.company" + + fiscal_type = fields.Selection( + selection=_RES_COMPANY_FISCAL_TYPE, + required=True, + default="normal", + ) + + fiscal_company_id = fields.Many2one( + comodel_name="res.company", + string="Fiscal Company", + compute="_compute_fiscal_company_id", + store=True, + ) + + fiscal_child_ids = fields.One2many( + comodel_name="res.company", + inverse_name="fiscal_company_id", + string="Technical Integrated Companies", + readonly=True, + ) + + @api.depends("fiscal_type", "parent_id") + def _compute_fiscal_company_id(self): + for company in self: + if company.fiscal_type in ["normal", "fiscal_mother"]: + company.fiscal_company_id = company + elif company.fiscal_type == "fiscal_child": + company.fiscal_company_id = company.parent_id + elif company.fiscal_type == "group": + company.fiscal_company_id = False + + # Constrains Section + @api.constrains("parent_id", "fiscal_type") + def _check_fiscal_type_with_parent_type(self): + for company in self.filtered( + lambda x: x.fiscal_type in ["group", "normal", "fiscal_mother"] + ): + if company.parent_id.fiscal_type not in ["group", False]: + raise ValidationError( + _( + "The company '%(company_name)s' (type %(fiscal_type_name)s)" + " can only have a parent company with a type 'Group'.", + company_name=company.name, + fiscal_type_name=company.fiscal_type, + ) + ) + for company in self.filtered(lambda x: x.fiscal_type in ["fiscal_child"]): + if company.parent_id.fiscal_type != "fiscal_mother": + raise ValidationError( + _( + "The company '%(company_name)s' (type 'Fiscal Child')" + " can only have a parent company with a type 'Fiscal Mother'.", + company_name=company.name, + ) + ) + + @api.constrains("child_ids", "fiscal_type") + def _check_fiscal_type_with_child_type(self): + for company in self.filtered( + lambda x: x.fiscal_type in ["normal", "fiscal_child"] + ): + if company.child_ids: + raise ValidationError( + _( + "The company '%(company_name)s' can not be '%(fiscal_type)s'" + " because it contains companies.", + company_name=company.name, + fiscal_type=company.fiscal_type, + ) + ) + + for company in self.filtered(lambda x: x.fiscal_type in ["fiscal_mother"]): + error_types = set(company.mapped("child_ids.fiscal_type")) - set( + {"fiscal_child"} + ) + + if error_types: + raise ValidationError( + _( + "The company '%(company_name)s' can not be 'Fiscal Mother'" + " because it contains companies type of '%(error_types)s'", + company_name=company.name, + error_types=error_types, + ) + ) + + for company in self.filtered(lambda x: x.fiscal_type in ["group"]): + error_types = set(company.mapped("child_ids.fiscal_type")) - set( + {"normal", "group", "fiscal_mother"} + ) + + if error_types: + raise ValidationError( + _( + "The company '%(company_name)s' can not be 'Group'" + " because it contains companies type of '%(error_types)s'", + company_name=company.name, + error_types=error_types, + ) + ) diff --git a/fiscal_company_base/readme/CONTRIBUTORS.rst b/fiscal_company_base/readme/CONTRIBUTORS.rst new file mode 100644 index 0000000..de2b331 --- /dev/null +++ b/fiscal_company_base/readme/CONTRIBUTORS.rst @@ -0,0 +1,2 @@ +* Julien WESTE +* Sylvain LE GAL diff --git a/fiscal_company_base/readme/DESCRIPTION.rst b/fiscal_company_base/readme/DESCRIPTION.rst new file mode 100644 index 0000000..e385322 --- /dev/null +++ b/fiscal_company_base/readme/DESCRIPTION.rst @@ -0,0 +1,31 @@ +This module extend Odoo functionnalities, regarding companies features to +manage CAE (Coopearatives of Activities and Employment) that is a special +status for french companies. + +(see above, links that describes what is CAE). + +Basically, in a CAE, there is a 'parent' company that hosts many 'child' +companies. People in a child company should have access only to their activity. +(account moves, customers, suppliers, products, etc...) + +In a fiscal and legal point of view, there is only one company (the parent one) +so there is only on chart of accounts. Accounting moves of the child +companies are written in the child company, but associated to the account of +the parent company. + +**Companies feature** + +* Add a new field on company `fiscal_type`: + * `group` : Container company: can only contains 'Normal' + or 'CAE' Companies. + * `normal` : Classical company, (by default) + * `fiscal_mother`: CAE company, that can host many child companies + * `fiscal_child`: child company, hosted by the CAE + +.. figure:: ../static/description/res_company_form.png + +**More information about CAE [FR]** + +* https://fr.wikipedia.org/wiki/Coopérative_d'activités_et_d'emploi +* http://www.cooperer.coop/ +* http://www.copea.fr/ diff --git a/fiscal_company_base/readme/DEVELOP.rst b/fiscal_company_base/readme/DEVELOP.rst new file mode 100644 index 0000000..b130f06 --- /dev/null +++ b/fiscal_company_base/readme/DEVELOP.rst @@ -0,0 +1,34 @@ +This module introduces 2 mixin: + +``fiscal.company.change.search.domain.mixin`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +the model that inherits this abstract will change the domain +in the search feature. If a domain contains ('company_id', '=', X) +it will be changed into ('company_id', 'in', [X, A, B]) +if X is a CAE and A and B are the integrated related companies. + +**Usage** + +.. code-block:: python + + class MyModel(models.Model): + _name = "my.model" + _inherit = ["fiscal.company.change.search.domain.mixin"] + +``fiscal.company.check.company.mixin`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The model that inherits this abstract will prevent to +create items with companies, depending on the +fiscal_type of the company. + +**Usage** + +.. code-block:: python + + class MyModel(models.Model): + _name = "my.model" + _inherit = ["fiscal.company.check.company.mixin"] + + _fiscal_company_forbid_fiscal_type = ["fiscal_mother"] diff --git a/fiscal_company_base/security/ir_rule.xml b/fiscal_company_base/security/ir_rule.xml new file mode 100644 index 0000000..a8fa53b --- /dev/null +++ b/fiscal_company_base/security/ir_rule.xml @@ -0,0 +1,30 @@ + + + + + + + + res.partner CAE company + + [ + '|', '|', '|', '|', '|', + ('company_id', '=', user.company_id.id), + ('company_id', '=', user.company_id.fiscal_company_id.id), + ('company_id', 'in', [c.id for c in user.company_id.fiscal_child_ids]), + ('company_id', '=', False), + ('is_odoo_user', '=', True), + ('is_odoo_company', '=', True), + ] + + + + diff --git a/fiscal_company_base/static/description/icon.png b/fiscal_company_base/static/description/icon.png new file mode 100644 index 0000000..129870f Binary files /dev/null and b/fiscal_company_base/static/description/icon.png differ diff --git a/fiscal_company_base/static/description/res_company_form.png b/fiscal_company_base/static/description/res_company_form.png new file mode 100644 index 0000000..fef864e Binary files /dev/null and b/fiscal_company_base/static/description/res_company_form.png differ diff --git a/fiscal_company_base/tests/__init__.py b/fiscal_company_base/tests/__init__.py new file mode 100644 index 0000000..f434a12 --- /dev/null +++ b/fiscal_company_base/tests/__init__.py @@ -0,0 +1,3 @@ +from . import test_fiscal_type_constrains +from . import test_fiscal_company_change_search_domain_mixin +from . import test_fiscal_company_check_company_mixin diff --git a/fiscal_company_base/tests/models.py b/fiscal_company_base/tests/models.py new file mode 100644 index 0000000..b69d827 --- /dev/null +++ b/fiscal_company_base/tests/models.py @@ -0,0 +1,21 @@ +from odoo import fields, models + + +class ModelFiscalCompanyChangeSearchDomainMixin(models.Model): + _name = "model.fiscal.company.change.search.domain.mixin" + _inherit = ["fiscal.company.change.search.domain.mixin"] + + _description = "model.fiscal.company.change.search.domain.mixin" + + company_id = fields.Many2one(comodel_name="res.company") + + +class FiscalCompanyCheckCompanyMixinFiscalMother(models.Model): + _name = "model.fiscal.company.check.company.mixin.fiscal.mother" + _inherit = ["fiscal.company.check.company.mixin"] + + _description = "model.fiscal.company.change.search.domain.mixin" + + _fiscal_company_forbid_fiscal_type = ["fiscal_mother"] + + company_id = fields.Many2one(comodel_name="res.company") diff --git a/fiscal_company_base/tests/test_abstract.py b/fiscal_company_base/tests/test_abstract.py new file mode 100644 index 0000000..48e4d9d --- /dev/null +++ b/fiscal_company_base/tests/test_abstract.py @@ -0,0 +1,17 @@ +# Copyright (C) 2024 - 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.tests.common import TransactionCase + + +class TestAbstract(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + + cls.ResCompany = cls.env["res.company"] + cls.group_company = cls.env.ref("fiscal_company_base.company_group") + cls.mother_company = cls.env.ref("fiscal_company_base.company_fiscal_mother") + cls.child_company = cls.env.ref("fiscal_company_base.company_fiscal_child_1") + cls.normal_company = cls.env.ref("base.main_company") diff --git a/fiscal_company_base/tests/test_fiscal_company_change_search_domain_mixin.py b/fiscal_company_base/tests/test_fiscal_company_change_search_domain_mixin.py new file mode 100644 index 0000000..f8c8c13 --- /dev/null +++ b/fiscal_company_base/tests/test_fiscal_company_change_search_domain_mixin.py @@ -0,0 +1,56 @@ +# Copyright (C) 2024 - 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_test_helper import FakeModelLoader + +from .test_abstract import TestAbstract + + +class TestFiscalCompanyChangeSearchDomainMixin(TestAbstract): + @classmethod + def setUpClass(cls): + super().setUpClass() + # Load a test model using odoo_test_helper + cls.loader = FakeModelLoader(cls.env, cls.__module__) + cls.loader.backup_registry() + from .models import ModelFiscalCompanyChangeSearchDomainMixin + + cls.loader.update_registry((ModelFiscalCompanyChangeSearchDomainMixin,)) + + cls.model = cls.env["model.fiscal.company.change.search.domain.mixin"] + cls.model.create( + [ + {"company_id": False}, + {"company_id": cls.group_company.id}, + {"company_id": cls.normal_company.id}, + {"company_id": cls.mother_company.id}, + {"company_id": cls.child_company.id}, + ] + ) + + @classmethod + def tearDownClass(cls): + cls.loader.restore_registry() + return super().tearDownClass() + + def test_search(self): + self.assertEqual( + len(self.model.search([("company_id", "=", False)])), + 1, + ) + self.assertEqual( + len(self.model.search([("company_id", "=", self.group_company.id)])), + 1, + ) + self.assertEqual( + len(self.model.search([("company_id", "=", self.normal_company.id)])), + 1, + ) + self.assertEqual( + len(self.model.search([("company_id", "=", self.mother_company.id)])), + 1, + ) + self.assertEqual( + len(self.model.search([("company_id", "=", self.child_company.id)])), + 2, + ) diff --git a/fiscal_company_base/tests/test_fiscal_company_check_company_mixin.py b/fiscal_company_base/tests/test_fiscal_company_check_company_mixin.py new file mode 100644 index 0000000..dcf957d --- /dev/null +++ b/fiscal_company_base/tests/test_fiscal_company_check_company_mixin.py @@ -0,0 +1,41 @@ +# Copyright (C) 2024 - 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_test_helper import FakeModelLoader + +from odoo.exceptions import ValidationError + +from .test_abstract import TestAbstract + + +class TestFiscalCompanyCheckCompanyMixin(TestAbstract): + @classmethod + def setUpClass(cls): + super().setUpClass() + # Load a test model using odoo_test_helper + cls.loader = FakeModelLoader(cls.env, cls.__module__) + cls.loader.backup_registry() + from .models import FiscalCompanyCheckCompanyMixinFiscalMother + + cls.loader.update_registry((FiscalCompanyCheckCompanyMixinFiscalMother,)) + + cls.model_fiscal_mother = cls.env[ + "model.fiscal.company.check.company.mixin.fiscal.mother" + ] + + @classmethod + def tearDownClass(cls): + cls.loader.restore_registry() + return super().tearDownClass() + + def test_check_fiscal_mother(self): + self.model_fiscal_mother.create({"company_id": False}) + self.model_fiscal_mother.create({"company_id": self.group_company.id}) + self.model_fiscal_mother.create({"company_id": self.normal_company.id}) + with self.assertRaises( + ValidationError, + msg="You can not create an item with fiscal_mother" + " company, due to mixin.", + ): + self.model_fiscal_mother.create({"company_id": self.mother_company.id}) + self.model_fiscal_mother.create({"company_id": self.child_company.id}) diff --git a/fiscal_company_base/tests/test_fiscal_type_constrains.py b/fiscal_company_base/tests/test_fiscal_type_constrains.py new file mode 100644 index 0000000..3d03639 --- /dev/null +++ b/fiscal_company_base/tests/test_fiscal_type_constrains.py @@ -0,0 +1,301 @@ +# Copyright (C) 2013 - Today: GRAP (http://www.grap.coop) +# @author Julien WESTE +# @author Sylvain LE GAL (https://twitter.com/legalsylvain) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo.exceptions import ValidationError +from odoo.tests import tagged + +from .test_abstract import TestAbstract + + +@tagged("post_install", "-at_install") +class TestFiscalTypeConstrains(TestAbstract): + @classmethod + def setUpClass(cls): + super().setUpClass() + + cls.ResCompany = cls.env["res.company"] + cls.group_company = cls.env.ref("fiscal_company_base.company_group") + cls.mother_company = cls.env.ref("fiscal_company_base.company_fiscal_mother") + cls.child_company = cls.env.ref("fiscal_company_base.company_fiscal_child_1") + cls.normal_company = cls.env.ref("base.main_company") + + # Test Section + def test_01_res_company_check_contraint_child_company_normal(self): + """A 'normal' company can only have parent type == 'group' + (or no parent)""" + self.ResCompany.create( + { + "name": "new_company_01_A", + "fiscal_type": "normal", + } + ) + + self.ResCompany.create( + { + "name": "new_company_01_B", + "fiscal_type": "normal", + "parent_id": self.group_company.id, + } + ) + + with self.assertRaises( + ValidationError, + msg="You can not create a child company ('normal')" + " and parent company ('normal')", + ): + self.ResCompany.create( + { + "name": "new_company_01_C", + "fiscal_type": "normal", + "parent_id": self.normal_company.id, + } + ) + + with self.assertRaises( + ValidationError, + msg="You can not create a child company ('normal')" + " and parent company ('fiscal_mother')", + ): + self.ResCompany.create( + { + "name": "new_company_01_D", + "fiscal_type": "normal", + "parent_id": self.mother_company.id, + } + ) + + with self.assertRaises( + ValidationError, + msg="You can not create a child company ('normal')" + " and parent company ('fiscal_child')", + ): + self.ResCompany.create( + { + "name": "new_company_01_E", + "fiscal_type": "normal", + "parent_id": self.child_company.id, + } + ) + + def test_02_res_company_check_contraint_child_company_group(self): + """A 'group' company can only have parent type == 'group' + (or no parent)""" + self.ResCompany.create( + { + "name": "new_company_02_A", + "fiscal_type": "group", + } + ) + + self.ResCompany.create( + { + "name": "new_company_02_B", + "fiscal_type": "group", + "parent_id": self.group_company.id, + } + ) + + with self.assertRaises( + ValidationError, + msg="You can not create a child company ('group')" + " and parent company ('normal')", + ): + self.ResCompany.create( + { + "name": "new_company_02_C", + "fiscal_type": "group", + "parent_id": self.normal_company.id, + } + ) + + with self.assertRaises( + ValidationError, + msg="You can not create a child company ('group')" + " and parent company ('fiscal_mother')", + ): + self.ResCompany.create( + { + "name": "new_company_02_D", + "fiscal_type": "group", + "parent_id": self.mother_company.id, + } + ) + + with self.assertRaises( + ValidationError, + msg="You can not create a child company ('group')" + " and parent company ('fiscal_child')", + ): + self.ResCompany.create( + { + "name": "new_company_02_E", + "fiscal_type": "group", + "parent_id": self.child_company.id, + } + ) + + def test_03_res_company_check_contraint_child_company_fiscal_mother(self): + """A 'fiscal_mother' company can only have parent type == 'group' + (or no parent)""" + self.ResCompany.create( + { + "name": "new_company_03_A", + "fiscal_type": "fiscal_mother", + } + ) + + self.ResCompany.create( + { + "name": "new_company_03_B", + "fiscal_type": "fiscal_mother", + "parent_id": self.group_company.id, + } + ) + + with self.assertRaises( + ValidationError, + msg="You can not create a child company ('fiscal_mother')" + " and parent company ('normal')", + ): + self.ResCompany.create( + { + "name": "new_company_03_C", + "fiscal_type": "fiscal_mother", + "parent_id": self.normal_company.id, + } + ) + + with self.assertRaises( + ValidationError, + msg="You can not create a child company ('fiscal_mother')" + " and parent company ('fiscal_mother')", + ): + self.ResCompany.create( + { + "name": "new_company_03_D", + "fiscal_type": "fiscal_mother", + "parent_id": self.mother_company.id, + } + ) + + with self.assertRaises( + ValidationError, + msg="You can not create a child company ('fiscal_mother')" + " and parent company ('fiscal_child')", + ): + self.ResCompany.create( + { + "name": "new_company_03_E", + "fiscal_type": "fiscal_mother", + "parent_id": self.child_company.id, + } + ) + + def test_04_res_company_check_contraint_child_company_fiscal_child(self): + """A 'fiscal_child' company can only have parent type == 'fiscal_mother'""" + with self.assertRaises( + ValidationError, + msg="You can not create a child company ('fiscal_child')" + " without parent company", + ): + self.ResCompany.create( + { + "name": "new_company_04_A", + "fiscal_type": "fiscal_child", + } + ) + + with self.assertRaises( + ValidationError, + msg="You can not create a child company ('fiscal_child')" + " and parent company ('group')", + ): + self.ResCompany.create( + { + "name": "new_company_04_B", + "fiscal_type": "fiscal_child", + "parent_id": self.group_company.id, + } + ) + + with self.assertRaises( + ValidationError, + msg="You can not create a child company ('fiscal_child')" + " and parent company ('normal')", + ): + self.ResCompany.create( + { + "name": "new_company_04_C", + "fiscal_type": "fiscal_child", + "parent_id": self.normal_company.id, + } + ) + + self.ResCompany.create( + { + "name": "new_company_04_D", + "fiscal_type": "fiscal_child", + "parent_id": self.mother_company.id, + } + ) + + with self.assertRaises( + ValidationError, + msg="You can not create a child company ('fiscal_child')" + " and parent company ('fiscal_child')", + ): + self.ResCompany.create( + { + "name": "new_company_04_E", + "fiscal_type": "fiscal_child", + "parent_id": self.child_company.id, + } + ) + + def test_10_res_company_check_contraint_parent_company_set_normal(self): + with self.assertRaises( + ValidationError, + msg="You can not set as 'normal' company" + " a company that contains other companies.", + ): + self.group_company.fiscal_type = "normal" + + with self.assertRaises( + ValidationError, + msg="You can not set as 'normal' company" + " a company that contains other companies.", + ): + self.mother_company.fiscal_type = "normal" + + def test_11_res_company_check_contraint_parent_company_set_fiscal_mother(self): + with self.assertRaises( + ValidationError, + msg="You can not set as 'fiscal_mother' company" + " a company that doesn't contains only fiscal_child companies.", + ): + self.group_company.fiscal_type = "fiscal_mother" + + def test_12_res_company_check_contraint_parent_company_set_fiscal_child(self): + with self.assertRaises( + ValidationError, + msg="You can not set as 'fiscal_child' company" + " a company that contains other companies.", + ): + self.group_company.fiscal_type = "fiscal_child" + + with self.assertRaises( + ValidationError, + msg="You can not set as 'fiscal_child' company" + " a company that contains other companies.", + ): + self.mother_company.fiscal_type = "fiscal_child" + + def test_13_res_company_check_contraint_parent_company_set_group(self): + with self.assertRaises( + ValidationError, + msg="You can not set as 'group' company" + " a company that contains fiscal_child companies.", + ): + self.mother_company.fiscal_type = "group" diff --git a/fiscal_company_base/views/view_res_company.xml b/fiscal_company_base/views/view_res_company.xml new file mode 100644 index 0000000..a3d687c --- /dev/null +++ b/fiscal_company_base/views/view_res_company.xml @@ -0,0 +1,50 @@ + + + + + + + res.company + + + + + + + + + + + res.company + + + + fiscal_type=='fiscal_child' + fiscal_type=='fiscal_mother' + fiscal_type=='group' + + + 1 + + + + + + + + + res.company + + + + + + + + + diff --git a/setup/fiscal_company_base/odoo/addons/fiscal_company_base b/setup/fiscal_company_base/odoo/addons/fiscal_company_base new file mode 120000 index 0000000..16a1e3a --- /dev/null +++ b/setup/fiscal_company_base/odoo/addons/fiscal_company_base @@ -0,0 +1 @@ +../../../../fiscal_company_base \ No newline at end of file diff --git a/setup/fiscal_company_base/setup.py b/setup/fiscal_company_base/setup.py new file mode 100644 index 0000000..28c57bb --- /dev/null +++ b/setup/fiscal_company_base/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/test-requirements.txt b/test-requirements.txt new file mode 100644 index 0000000..4ad8e0e --- /dev/null +++ b/test-requirements.txt @@ -0,0 +1 @@ +odoo-test-helper