diff --git a/product_harmonized_system_per_country/README.rst b/product_harmonized_system_per_country/README.rst new file mode 100644 index 000000000..5441b2c44 --- /dev/null +++ b/product_harmonized_system_per_country/README.rst @@ -0,0 +1,98 @@ +============================================= +Product Harmonized System Codes per countries +============================================= + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:afd00899a20aac3c96a9b21fbcfc0c4a66f1617290ff92124a6b6987e5d9c975 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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%2Fintrastat--extrastat-lightgray.png?logo=github + :target: https://github.com/OCA/intrastat-extrastat/tree/16.0/product_harmonized_system_per_country + :alt: OCA/intrastat-extrastat +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/intrastat-extrastat-16-0/intrastat-extrastat-16-0-product_harmonized_system_per_country + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/intrastat-extrastat&target_branch=16.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module contains the objects for Harmonised System Codes (H.S. codes) with applicable country. + + +**Table of contents** + +.. contents:: + :local: + +Installation +============ + +This module is NOT compatible with the *account_intrastat* module from Odoo Enterprise. + +Usage +===== + +This module only depends on the *intrastat_product* module, whiwh depends on *product_harmonized_systems* module. + +This module adds the applicable country field to HS Codes, and provides a list of HS Codes by product and/or category, in order to dynamically find the code corresponding to the destination country. + +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 +~~~~~~~ + +* Emilie SOUTIRAS +* Groupe Voltaire + +Contributors +~~~~~~~~~~~~ + +* Emilie SOUTIRAS, Groupe Voltaire + +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. + +.. |maintainer-emiliesoutiras| image:: https://github.com/emiliesoutiras.png?size=40px + :target: https://github.com/emiliesoutiras + :alt: emiliesoutiras + +Current `maintainer `__: + +|maintainer-emiliesoutiras| + +This module is part of the `OCA/intrastat-extrastat `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/product_harmonized_system_per_country/__init__.py b/product_harmonized_system_per_country/__init__.py new file mode 100644 index 000000000..0650744f6 --- /dev/null +++ b/product_harmonized_system_per_country/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/product_harmonized_system_per_country/__manifest__.py b/product_harmonized_system_per_country/__manifest__.py new file mode 100644 index 000000000..4dd8e7aee --- /dev/null +++ b/product_harmonized_system_per_country/__manifest__.py @@ -0,0 +1,20 @@ +# Copyright (c) 2024 Groupe Voltaire +# @author Emilie SOUTIRAS +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +{ + "name": "Product Harmonized System Codes per countries", + "version": "16.0.1.1.0", + "category": "Reporting", + "license": "AGPL-3", + "summary": "Module for to custom H.S. Codes par country", + "author": "Emilie SOUTIRAS, Groupe Voltaire, Odoo Community Association (OCA)", + "maintainers": ["emiliesoutiras"], + "website": "https://github.com/OCA/intrastat-extrastat", + "depends": ["intrastat_product"], + "data": [ + "views/product_category.xml", + "views/product_template.xml", + "views/hs_code.xml", + ], + "installable": True, +} diff --git a/product_harmonized_system_per_country/models/__init__.py b/product_harmonized_system_per_country/models/__init__.py new file mode 100644 index 000000000..7b5e031c7 --- /dev/null +++ b/product_harmonized_system_per_country/models/__init__.py @@ -0,0 +1,8 @@ +# Copyright (c) 2024 Groupe Voltaire +# @author Emilie SOUTIRAS +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from . import account_move +from . import hs_code +from . import product_category +from . import product_product diff --git a/product_harmonized_system_per_country/models/account_move.py b/product_harmonized_system_per_country/models/account_move.py new file mode 100644 index 000000000..e810a19dd --- /dev/null +++ b/product_harmonized_system_per_country/models/account_move.py @@ -0,0 +1,50 @@ +# Copyright (c) 2024 Groupe Voltaire +# @author Emilie SOUTIRAS +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import models + + +class AccountMove(models.Model): + _inherit = "account.move" + + def _get_intrastat_lines_info(self): + destination_country = self.partner_id.country_id or False + self.env.context = dict(self.env.context) + self.env.context.update({"hs_code_for_country": destination_country.id}) + res = {} + for line in ( + self.invoice_line_ids.filtered( + lambda x: x.product_id.get_hs_code_recursively() + and x.product_id.origin_country_id + ) + if not self.intrastat_line_ids + else self.intrastat_line_ids + ): + res.setdefault(line.product_id.id, {"weight": 0}) + vals = self._prepare_intrastat_line_info(line) + if vals.get("hs_code_id"): + weight = vals.pop("weight") + res[line.product_id.id].update(vals) + res[line.product_id.id]["weight"] += weight + else: + res.pop(line.product_id.id) + # sort res : + if res: + res = dict( + sorted( + res.items(), + key=lambda val: val[1]['product_id'].display_name or "" + ) + ) + return res.values() + + def _prepare_intrastat_line_info(self, line): + res = super()._prepare_intrastat_line_info(line) + if "hs_code_id" in res: + res.update( + { + "hs_code_id": line.product_id.get_hs_code_recursively() + } + ) + return res \ No newline at end of file diff --git a/product_harmonized_system_per_country/models/hs_code.py b/product_harmonized_system_per_country/models/hs_code.py new file mode 100644 index 000000000..35a01e788 --- /dev/null +++ b/product_harmonized_system_per_country/models/hs_code.py @@ -0,0 +1,65 @@ +# Copyright (c) 2024 Groupe Voltaire +# @author Emilie SOUTIRAS +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import api, fields, models + + +class HSCode(models.Model): + _inherit = "hs.code" + + country_id = fields.Many2one( + comodel_name="res.country", string="Applicable country" + ) + pdt_categ_ids = fields.Many2many( + comodel_name="product.category", + relation="hs_code_pdt_category_rel", + column1="pdt_categ_id", + column2="hs_code_id", + string="Product Categories on H.S. Codes list", + readonly=True, + ) + pdt_tmpl_ids = fields.Many2many( + comodel_name="product.template", + relation="hs_code_pdt_tmpl_rel", + column1="pdt_tmpl_id", + column2="hs_code_id", + string="Products on H.S. Codes list", + readonly=True, + ) + + @api.depends("product_categ_ids", "pdt_categ_ids") + def _compute_product_categ_count(self): + for code in self: + code.product_categ_count = len( + set(code.product_categ_ids.ids).union(set(code.pdt_categ_ids.ids)) + ) + + @api.depends("product_tmpl_ids", "pdt_tmpl_ids") + def _compute_product_tmpl_count(self): + for code in self: + code.product_tmpl_count = len( + set(code.product_tmpl_ids.ids).union(set(code.pdt_tmpl_ids.ids)) + ) + + def filter_per_country(self): + country_id = self.env.context.get("hs_code_for_country", False) + active_companies = self.env.context.get("allowed_company_ids") + company_ids = [active_companies[0]] if active_companies else [] + company_ids += [False] + if country_id: + res = self.filtered( + lambda hs: (not hs.country_id or hs.country_id.id == country_id) + and (hs.company_id.id in company_ids) + ) + res = ( + res.sorted(key="company_id", reverse=True) + .sorted(key="country_id", reverse=True) + .sorted() + ) + else: + res = self.filtered(lambda hs: (hs.company_id.id in company_ids)) + res = res.sorted(key="company_id", reverse=True) + if res: + res = res[0] + return res diff --git a/product_harmonized_system_per_country/models/product_category.py b/product_harmonized_system_per_country/models/product_category.py new file mode 100644 index 000000000..81870c8da --- /dev/null +++ b/product_harmonized_system_per_country/models/product_category.py @@ -0,0 +1,31 @@ +# Copyright (c) 2024 Groupe Voltaire +# @author Emilie SOUTIRAS +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +import logging + +from odoo import fields, models + +_logger = logging.getLogger(__name__) + + +class ProductCategory(models.Model): + _inherit = "product.category" + + hs_code_ids = fields.Many2many( + comodel_name="hs.code", + relation="hs_code_pdt_category_rel", + column1="hs_code_id", + column2="pdt_categ_id", + string="H.S. Codes", + help="Harmonised System Codes. This list is used to filter by " + "destination country. If no code is set, the simple H.S. Code " + "is used.", + ) + + def get_hs_code_recursively(self): + self.ensure_one() + if self.hs_code_ids: + res = self.hs_code_ids.filter_per_country() + else: + res = super().get_hs_code_recursively() + return res diff --git a/product_harmonized_system_per_country/models/product_product.py b/product_harmonized_system_per_country/models/product_product.py new file mode 100644 index 000000000..48a4d0bb1 --- /dev/null +++ b/product_harmonized_system_per_country/models/product_product.py @@ -0,0 +1,34 @@ +# Copyright (c) 2024 Groupe Voltaire +# @author Emilie SOUTIRAS +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class ProductTemplate(models.Model): + _inherit = "product.template" + + hs_code_ids = fields.Many2many( + comodel_name="hs.code", + relation="hs_code_pdt_tmpl_rel", + column1="hs_code_id", + column2="pdt_tmpl_id", + string="H.S. Codes", + help="Harmonised System Codes. This list is used to filter by " + "destination country. If no code is set, the simple H.S. Code " + "is used, otherwise those of the related product category.", + ) + + +class ProductProduct(models.Model): + _inherit = "product.product" + + def get_hs_code_recursively(self): + res = self.env["hs.code"] + if self: + self.ensure_one() + if self.hs_code_ids: + res = self.hs_code_ids.filter_per_country() + else: + res = super().get_hs_code_recursively() + return res diff --git a/product_harmonized_system_per_country/readme/CONTRIBUTORS.rst b/product_harmonized_system_per_country/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000..26d89b1ef --- /dev/null +++ b/product_harmonized_system_per_country/readme/CONTRIBUTORS.rst @@ -0,0 +1 @@ +* Emilie SOUTIRAS, Groupe Voltaire diff --git a/product_harmonized_system_per_country/readme/DESCRIPTION.rst b/product_harmonized_system_per_country/readme/DESCRIPTION.rst new file mode 100644 index 000000000..de1331fc4 --- /dev/null +++ b/product_harmonized_system_per_country/readme/DESCRIPTION.rst @@ -0,0 +1,2 @@ +This module contains the objects for Harmonised System Codes (H.S. codes) with applicable country. + diff --git a/product_harmonized_system_per_country/readme/INSTALL.rst b/product_harmonized_system_per_country/readme/INSTALL.rst new file mode 100644 index 000000000..5b11a6ab9 --- /dev/null +++ b/product_harmonized_system_per_country/readme/INSTALL.rst @@ -0,0 +1 @@ +This module is NOT compatible with the *account_intrastat* module from Odoo Enterprise. diff --git a/product_harmonized_system_per_country/readme/USAGE.rst b/product_harmonized_system_per_country/readme/USAGE.rst new file mode 100644 index 000000000..f8aa81240 --- /dev/null +++ b/product_harmonized_system_per_country/readme/USAGE.rst @@ -0,0 +1,3 @@ +This module only depends on the *intrastat_product* module, whiwh depends on *product_harmonized_systems* module. + +This module adds the applicable country field to HS Codes, and provides a list of HS Codes by product and/or category, in order to dynamically find the code corresponding to the destination country. diff --git a/product_harmonized_system_per_country/static/description/icon.png b/product_harmonized_system_per_country/static/description/icon.png new file mode 100644 index 000000000..3a0328b51 Binary files /dev/null and b/product_harmonized_system_per_country/static/description/icon.png differ diff --git a/product_harmonized_system_per_country/static/description/index.html b/product_harmonized_system_per_country/static/description/index.html new file mode 100644 index 000000000..3f13be2a8 --- /dev/null +++ b/product_harmonized_system_per_country/static/description/index.html @@ -0,0 +1,437 @@ + + + + + +Product Harmonized System Codes per countries + + + +
+

Product Harmonized System Codes per countries

+ + +

Beta License: AGPL-3 OCA/intrastat-extrastat Translate me on Weblate Try me on Runboat

+

This module contains the objects for Harmonised System Codes (H.S. codes) with applicable country.

+

Table of contents

+ +
+

Installation

+

This module is NOT compatible with the account_intrastat module from Odoo Enterprise.

+
+
+

Usage

+

This module only depends on the intrastat_product module, whiwh depends on product_harmonized_systems module.

+

This module adds the applicable country field to HS Codes, and provides a list of HS Codes by product and/or category, in order to dynamically find the code corresponding to the destination country.

+
+
+

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

+
    +
  • Emilie SOUTIRAS
  • +
  • Groupe Voltaire
  • +
+
+
+

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.

+

Current maintainer:

+

emiliesoutiras

+

This module is part of the OCA/intrastat-extrastat project on GitHub.

+

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

+
+
+
+ + diff --git a/product_harmonized_system_per_country/tests/__init__.py b/product_harmonized_system_per_country/tests/__init__.py new file mode 100644 index 000000000..6e8891c77 --- /dev/null +++ b/product_harmonized_system_per_country/tests/__init__.py @@ -0,0 +1,4 @@ +# Copyright (c) 2024 Groupe Voltaire +# @author Emilie SOUTIRAS +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from . import test_hs_code diff --git a/product_harmonized_system_per_country/tests/test_hs_code.py b/product_harmonized_system_per_country/tests/test_hs_code.py new file mode 100644 index 000000000..6cf107f57 --- /dev/null +++ b/product_harmonized_system_per_country/tests/test_hs_code.py @@ -0,0 +1,64 @@ +# Copyright (c) 2024 Groupe Voltaire +# @author Emilie SOUTIRAS +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from odoo.tests import TransactionCase + +from odoo.addons.intrastat_product.tests.common import IntrastatProductCommon + + +class TestHSCodes(IntrastatProductCommon, TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.pdt_c3po = cls.product_c3po.product_variant_ids[0] + + def test_hs_code_ids_product(self): + hs_code_7050 = self.env.ref("product_harmonized_system.84717050") + self.categ_robots.hs_code_ids = [(4, hs_code_7050.id)] + self.assertEqual( + self.pdt_c3po.get_hs_code_recursively(), + self.hs_code_computer, + ) + country_fr = self.env.ref("base.fr") + hs_code_7050.country_id = country_fr + self.pdt_c3po.hs_code_ids = [ + (4, self.hs_code_computer.id, 0), + (4, hs_code_7050.id, 0), + ] + self.assertEqual( + self.pdt_c3po.get_hs_code_recursively(), + self.hs_code_computer, + ) + self.assertEqual( + self.pdt_c3po.with_context( + hs_code_for_country=country_fr.id + ).get_hs_code_recursively(), + hs_code_7050, + ) + + def test_hs_code_ids_category(self): + self.pdt_c3po.hs_code_id = False + country_fr = self.env.ref("base.fr") + hs_code_7050 = self.env.ref("product_harmonized_system.84717050") + hs_code_7050.country_id = country_fr + hs_code_7049 = hs_code_7050.copy({"local_code": "84717049"}) + self.assertFalse(self.hs_code_computer.country_id) + self.categ_robots.hs_code_ids = [ + (4, self.hs_code_computer.id, 0), + (4, hs_code_7050.id, 0), + (4, hs_code_7049.id, 0), + ] + self.assertEqual( + self.pdt_c3po.categ_id, + self.categ_robots, + ) + self.assertEqual( + self.pdt_c3po.get_hs_code_recursively(), + self.hs_code_computer, + ) + self.assertEqual( + self.pdt_c3po.with_context( + hs_code_for_country=country_fr.id + ).get_hs_code_recursively(), + hs_code_7049, + ) diff --git a/product_harmonized_system_per_country/views/hs_code.xml b/product_harmonized_system_per_country/views/hs_code.xml new file mode 100644 index 000000000..99895d690 --- /dev/null +++ b/product_harmonized_system_per_country/views/hs_code.xml @@ -0,0 +1,46 @@ + + + + + + hs.code.tree + hs.code + + + + + + + + + + hs.code.form + hs.code + + + + + + + + + + ['|', ('hs_code_id', '=', active_id),('hs_code_ids', '=', active_id)] + + + + + ['|', ('hs_code_id', '=', active_id),('hs_code_ids', '=', active_id)] + + + diff --git a/product_harmonized_system_per_country/views/product_category.xml b/product_harmonized_system_per_country/views/product_category.xml new file mode 100644 index 000000000..5b211c971 --- /dev/null +++ b/product_harmonized_system_per_country/views/product_category.xml @@ -0,0 +1,19 @@ + + + + + + hs_code.product.category.form + product.category + + + + + + + + diff --git a/product_harmonized_system_per_country/views/product_template.xml b/product_harmonized_system_per_country/views/product_template.xml new file mode 100644 index 000000000..2631ba33b --- /dev/null +++ b/product_harmonized_system_per_country/views/product_template.xml @@ -0,0 +1,38 @@ + + + + + + hs_code.product.template.form + product.template + + + + + + + + + product.template + + + + + + + + diff --git a/setup/product_harmonized_system_per_country/odoo/addons/product_harmonized_system_per_country b/setup/product_harmonized_system_per_country/odoo/addons/product_harmonized_system_per_country new file mode 120000 index 000000000..75b70ecc5 --- /dev/null +++ b/setup/product_harmonized_system_per_country/odoo/addons/product_harmonized_system_per_country @@ -0,0 +1 @@ +../../../../product_harmonized_system_per_country \ No newline at end of file diff --git a/setup/product_harmonized_system_per_country/setup.py b/setup/product_harmonized_system_per_country/setup.py new file mode 100644 index 000000000..28c57bb64 --- /dev/null +++ b/setup/product_harmonized_system_per_country/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +)