diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a85e5923ae..bfd44e4ab0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -53,11 +53,9 @@ exclude: | ^shopinvader_product_new/| ^shopinvader_product_order/| ^shopinvader_product_price_tax/| - ^shopinvader_product_stock/| ^shopinvader_product_stock_assortment/| ^shopinvader_product_stock_forecast/| ^shopinvader_product_stock_forecast_expiry/| - ^shopinvader_product_stock_state/| ^shopinvader_product_template_multi_link/| ^shopinvader_product_template_multi_link_date_span/| ^shopinvader_product_template_tags/| diff --git a/requirements.txt b/requirements.txt index b5ce4cb763..bad8d6dadf 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,4 +7,5 @@ fastapi openupgradelib pydantic>=2.0.0 python-slugify +slugify unidecode diff --git a/setup/shopinvader_search_engine_product_stock/odoo/addons/shopinvader_search_engine_product_stock b/setup/shopinvader_search_engine_product_stock/odoo/addons/shopinvader_search_engine_product_stock new file mode 120000 index 0000000000..206dacfdd9 --- /dev/null +++ b/setup/shopinvader_search_engine_product_stock/odoo/addons/shopinvader_search_engine_product_stock @@ -0,0 +1 @@ +../../../../shopinvader_search_engine_product_stock \ No newline at end of file diff --git a/setup/shopinvader_search_engine_product_stock/setup.py b/setup/shopinvader_search_engine_product_stock/setup.py new file mode 100644 index 0000000000..28c57bb640 --- /dev/null +++ b/setup/shopinvader_search_engine_product_stock/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/setup/shopinvader_search_engine_product_stock_state/odoo/addons/shopinvader_search_engine_product_stock_state b/setup/shopinvader_search_engine_product_stock_state/odoo/addons/shopinvader_search_engine_product_stock_state new file mode 120000 index 0000000000..8af2c707cd --- /dev/null +++ b/setup/shopinvader_search_engine_product_stock_state/odoo/addons/shopinvader_search_engine_product_stock_state @@ -0,0 +1 @@ +../../../../shopinvader_search_engine_product_stock_state \ No newline at end of file diff --git a/setup/shopinvader_search_engine_product_stock_state/setup.py b/setup/shopinvader_search_engine_product_stock_state/setup.py new file mode 100644 index 0000000000..28c57bb640 --- /dev/null +++ b/setup/shopinvader_search_engine_product_stock_state/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/shopinvader_product_stock/data/ir_export_product.xml b/shopinvader_product_stock/data/ir_export_product.xml deleted file mode 100644 index 229206dd19..0000000000 --- a/shopinvader_product_stock/data/ir_export_product.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - stock_data - stock_data:stock - - - - diff --git a/shopinvader_product_stock/models/__init__.py b/shopinvader_product_stock/models/__init__.py deleted file mode 100644 index 6707e2e9f3..0000000000 --- a/shopinvader_product_stock/models/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -# Copyright 2018 Akretion (http://www.akretion.com) -# Copyright 2018 ACSONE SA/NV -# Sébastien BEAU -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -from . import shopinvader_backend -from . import shopinvader_variant -from . import stock_move -from . import product_product diff --git a/shopinvader_product_stock/models/product_product.py b/shopinvader_product_stock/models/product_product.py deleted file mode 100644 index 928ced37f4..0000000000 --- a/shopinvader_product_stock/models/product_product.py +++ /dev/null @@ -1,62 +0,0 @@ -# Copyright 2018 Akretion (http://www.akretion.com) -# Copyright 2018 ACSONE SA/NV -# Sébastien BEAU -# Copyright 2020 Camptocamp SA (http://www.camptocamp.com) -# Simone Orsi -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). - -from odoo import models - - -class ProductProduct(models.Model): - _inherit = "product.product" - - def synchronize_all_binding_stock_level(self, company_id=None): - """Compute new stock information and update index data. - - If data changed and binding is in done state it forces it to 'to_update'. - :return: - """ - # Use `sudo` because this action might be triggered - # from a low access level user (eg: external user on portal/website). - # In any case, the real operation is done w/ the backend user below. - - # ensure user company propagation if the method has bee delayed... - # this feature should be provide by queue job ... - # see https://github.com/OCA/queue/issues/363 - products = self - if company_id: - products = self.with_company(company_id) - - all_bindinds = products.mapped("shopinvader_bind_ids") - backends = all_bindinds.mapped("backend_id") - for backend in backends: - bindings = all_bindinds.filtered( - lambda r, b=backend: r.backend_id == b and r.active - ) - # To avoid access rights issues, execute the job with sudo - bindings = bindings.sudo() - for binding in bindings: - if binding.sync_state == "new": - # this binding has been not yet computed - # so we do not care to update it as it's not yet - # on the site. The right stock qty will be exported - # at its first export. - continue - # I do not recommend to rename the stock export key, but if - # you have a good reason to do it, do not worry we will use - # this key here - stock_export_key = binding._get_stock_export_key() - if not stock_export_key: - continue - data = binding.data - if data[stock_export_key] != binding.stock_data: - data[stock_export_key] = binding.stock_data - vals = {"data": data} - if binding.backend_id.synchronize_stock == "immediatly": - binding.write(vals) - binding.synchronize() - elif binding.backend_id.synchronize_stock == "in_batch": - if binding.sync_state == "done": - vals["sync_state"] = "to_update" - binding.write(vals) diff --git a/shopinvader_product_stock/models/shopinvader_variant.py b/shopinvader_product_stock/models/shopinvader_variant.py deleted file mode 100644 index f01e8000b9..0000000000 --- a/shopinvader_product_stock/models/shopinvader_variant.py +++ /dev/null @@ -1,46 +0,0 @@ -# Copyright 2018 Akretion (http://www.akretion.com) -# Copyright 2018 ACSONE SA/NV -# Sébastien BEAU -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). - -from collections import defaultdict - -from odoo import models - -from odoo.addons.base_sparse_field.models.fields import Serialized - - -class ShopinvaderVariant(models.Model): - _inherit = "shopinvader.variant" - - stock_data = Serialized(compute="_compute_stock_data") - - def _get_stock_export_key(self): - self.ensure_one() - line = self.env["ir.exports.line"].search( - [ - ("export_id", "=", self.index_id.exporter_id.id), - ("name", "=", "stock_data"), - ] - ) - if line.target: - return line.target.split(":")[1] - else: - return line.name - - def _prepare_stock_data(self): - stock_field = self.backend_id.product_stock_field_id.name - return {"qty": self[stock_field]} - - def _compute_stock_data(self): - result = defaultdict(dict) - for backend in self.mapped("backend_id"): - loc_records = self.filtered(lambda s: s.backend_id == backend) - for ( - wh_key, - wh_ids, - ) in backend._get_warehouse_list_for_export().items(): - for loc_record in loc_records.with_context(warehouse=wh_ids): - result[loc_record.id][wh_key] = loc_record._prepare_stock_data() - for record in self: - record.stock_data = result[record.id] diff --git a/shopinvader_product_stock/readme/HISTORY.rst b/shopinvader_product_stock/readme/HISTORY.rst deleted file mode 100644 index 1c70785801..0000000000 --- a/shopinvader_product_stock/readme/HISTORY.rst +++ /dev/null @@ -1,9 +0,0 @@ -10.0.1.0.0 (2018-06-15) -~~~~~~~~~~~~~~~~~~~~~~~ - -* Rename modules - -12.0.1.0.0 (2019-05-23) -~~~~~~~~~~~~~~~~~~~~~~~ - -* [12.0][MIG] shopinvader_product_stock diff --git a/shopinvader_product_stock/tests/test_product.py b/shopinvader_product_stock/tests/test_product.py deleted file mode 100644 index 745d2c80e9..0000000000 --- a/shopinvader_product_stock/tests/test_product.py +++ /dev/null @@ -1,131 +0,0 @@ -# Copyright 2018 Akretion (http://www.akretion.com) -# Copyright 2018 ACSONE SA/NV -# Sébastien BEAU -# Copyright 2020 Camptocamp SA (http://www.camptocamp.com) -# Simone Orsi -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -from odoo.addons.shopinvader.tests.common import UtilsMixin - -from .common import StockCommonCase - - -class TestProductProduct(StockCommonCase, UtilsMixin): - """Tests for product stock info.""" - - def _expectect_qty_by_wh(self, warehouse_recs, prod): - res = { - "global": { - "qty": prod.with_context( - warehouse=list(warehouse_recs.ids) - ).qty_available - }, - } - for wh in warehouse_recs: - key = self.shopinvader_backend._make_warehouse_key(wh) - res[key] = {"qty": prod.with_context(warehouse=wh.id).qty_available} - return res - - def test_update_qty_from_wizard(self): - """Updating the quantity through an inventory create a job.""" - job = self.job_counter() - self._add_stock_to_product(self.product, self.loc_1, 100) - self.assertEqual(job.count_created(), 1) - - def test_update_stock_on_new_product(self): - """Recompute binding not exported yet does nothing.""" - self.assertEqual(self.product.shopinvader_bind_ids.sync_state, "new") - self.product.synchronize_all_binding_stock_level() - self.assertEqual(self.product.shopinvader_bind_ids.data, {}) - - def _test_update_stock_with_key(self, key_stock, sync_immediatly=True): - shopinvader_product = self.product.shopinvader_bind_ids - self._refresh_json_data(shopinvader_product, backend=self.shopinvader_backend) - shopinvader_product.sync_state = "to_update" - self.assertEqual(shopinvader_product.data[key_stock], {"global": {"qty": 0.0}}) - - jobs = self.job_counter() - self._add_stock_to_product(self.product, self.loc_1, 100) - self.assertEqual(jobs.count_created(), 1) - - shopinvader_product.invalidate_cache(["stock_data"]) - - with self.se_adapter_fake.mocked_calls() as calls: - self.perform_jobs(jobs) - - self.assertEqual( - shopinvader_product.data[key_stock], {"global": {"qty": 100.0}} - ) - if sync_immediatly: - self.assertEqual(len(calls), 1) - call = calls[0] - self.assertEqual(call["method"], "index") - self.assertEqual(len(call["args"]), 1) - self.assertEqual(call["args"][0][key_stock], {"global": {"qty": 100.0}}) - self.assertEqual(shopinvader_product.sync_state, "done") - else: - self.assertEqual(len(calls), 0) - self.assertEqual(shopinvader_product.sync_state, "to_update") - - def test_update_stock(self): - """Recompute product should update binding and export it.""" - self._test_update_stock_with_key("stock") - - def test_update_stock_differed(self): - """Recompute product should update binding and not export it.""" - self.shopinvader_backend.synchronize_stock = "in_batch" - self._test_update_stock_with_key("stock", sync_immediatly=False) - - def test_update_stock_with_special_key(self): - """Recompute product should update binding using custom key by user.""" - export_line = self.env.ref( - "shopinvader_product_stock." "ir_exp_shopinvader_variant_stock_data" - ) - export_line.target = "stock_data:custom_stock" - self._test_update_stock_with_key("custom_stock") - - def test_update_stock_without_target(self): - """Recompute product should update binding using the name as key.""" - export_line = self.env.ref( - "shopinvader_product_stock." "ir_exp_shopinvader_variant_stock_data" - ) - export_line.target = None - self._test_update_stock_with_key("stock_data") - - def test_update_stock_without_key(self): - """Recompute product should update binding without export line.""" - export_line = self.env.ref( - "shopinvader_product_stock." "ir_exp_shopinvader_variant_stock_data" - ) - export_line.unlink() - - shopinvader_product = self.product.shopinvader_bind_ids - self._refresh_json_data(shopinvader_product, backend=self.shopinvader_backend) - shopinvader_product.sync_state = "to_update" - self.assertNotIn("stock", shopinvader_product.data) - - jobs = self.job_counter() - self._add_stock_to_product(self.product, self.loc_1, 100) - self.assertEqual(jobs.count_created(), 1) - self.perform_jobs(jobs) - self.assertNotIn("stock", shopinvader_product.data) - - def test_multi_warehouse(self): - warehouses = self.warehouse_1 + self.warehouse_2 - self.shopinvader_backend.write({"warehouse_ids": [(6, 0, warehouses.ids)]}) - - shopinvader_product = self.product.shopinvader_bind_ids - self._refresh_json_data(shopinvader_product, backend=self.shopinvader_backend) - shopinvader_product.sync_state = "to_update" - expected = self._expectect_qty_by_wh(warehouses, self.product) - self.assertEqual(shopinvader_product.data["stock"], expected) - - jobs = self.job_counter() - self._add_stock_to_product(self.product, self.loc_1, 100) - self._add_stock_to_product(self.product, self.loc_2, 200) - - shopinvader_product.invalidate_cache(["stock_data"]) - self.assertEqual(jobs.count_created(), 1) - with self.se_adapter_fake.mocked_calls(): - self.perform_jobs(jobs) - expected = self._expectect_qty_by_wh(warehouses, self.product) - self.assertEqual(shopinvader_product.data["stock"], expected) diff --git a/shopinvader_product_stock/views/shopinvader_backend.xml b/shopinvader_product_stock/views/shopinvader_backend.xml deleted file mode 100644 index 6b39021c1d..0000000000 --- a/shopinvader_product_stock/views/shopinvader_backend.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - shopinvader.backend - - - - - - - - - - - - - - diff --git a/shopinvader_product_stock_state/README.rst b/shopinvader_product_stock_state/README.rst deleted file mode 100644 index b62bdfd313..0000000000 --- a/shopinvader_product_stock_state/README.rst +++ /dev/null @@ -1,64 +0,0 @@ -.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg - :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html - :alt: License: AGPL-3 - -================================ -Shopinvader Product Stock State -================================ - -This is shopinvader product stock state module. -This module is used to export the stock state of product -only if the value has been updated. - -Configuration -============= - -Go into shopinvader > backend, and on the backend you can choose the mode -- State and Low Quantity (only add the qty if it behind a defined value) -- State and Quantity -- Only State -- Only Quantity - - -Usage -===== -Instead of having only the qty in the json sent to the search engine -You will have (depending of the configuration, the qty and the state) - - -.. code-block:: python - - example : - {'global': { - 'state': 'in_limited_stock', - 'qty': 5.0, - }} - - -Known issues / Roadmap -====================== - - -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. - -Credits -======= - -Contributors ------------- - -* Sebastien BEAU -* Simone Orsi - -Funders -------- - -The development of this module has been financially supported by: - -* Akretion R&D diff --git a/shopinvader_product_stock_state/__init__.py b/shopinvader_product_stock_state/__init__.py deleted file mode 100644 index 0650744f6b..0000000000 --- a/shopinvader_product_stock_state/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from . import models diff --git a/shopinvader_product_stock_state/models/shopinvader_variant.py b/shopinvader_product_stock_state/models/shopinvader_variant.py deleted file mode 100644 index 10866393c6..0000000000 --- a/shopinvader_product_stock_state/models/shopinvader_variant.py +++ /dev/null @@ -1,25 +0,0 @@ -# Copyright 2018 Akretion (http://www.akretion.com) -# Sébastien BEAU -# Copyright 2020 Camptocamp SA (http://www.camptocamp.com) -# Simone Orsi -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). - -from odoo import models - - -class ShopinvaderVariant(models.Model): - _inherit = "shopinvader.variant" - - def _prepare_stock_data(self): - res = super(ShopinvaderVariant, self)._prepare_stock_data() - if "state" in self.backend_id.stock_level_config: - res["state"] = self.stock_state - if self._skip_stock_qty_update(): - res.pop("qty", None) - return res - - def _skip_stock_qty_update(self): - config = self.backend_id.stock_level_config - return config == "only_state" or ( - config == "state_and_low_qty" and self.stock_state != "in_limited_stock" - ) diff --git a/shopinvader_product_stock/README.rst b/shopinvader_search_engine_product_stock/README.rst similarity index 100% rename from shopinvader_product_stock/README.rst rename to shopinvader_search_engine_product_stock/README.rst diff --git a/shopinvader_search_engine_product_stock/__init__.py b/shopinvader_search_engine_product_stock/__init__.py new file mode 100644 index 0000000000..106fc57265 --- /dev/null +++ b/shopinvader_search_engine_product_stock/__init__.py @@ -0,0 +1,2 @@ +from . import models +from . import schemas diff --git a/shopinvader_product_stock/__manifest__.py b/shopinvader_search_engine_product_stock/__manifest__.py similarity index 52% rename from shopinvader_product_stock/__manifest__.py rename to shopinvader_search_engine_product_stock/__manifest__.py index 065e23d579..cba47e8b5e 100644 --- a/shopinvader_product_stock/__manifest__.py +++ b/shopinvader_search_engine_product_stock/__manifest__.py @@ -3,26 +3,19 @@ # Sébastien BEAU # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). { - "name": "Shopinvader Product Stock", - "summary": "This module is used to choose a stock field during the" - "export (by backend)", - "version": "14.0.1.0.3", - "development_status": "Production/Stable", + "name": "Shopinvader Search Engine Product Stock", + "summary": "This module is used to export stock data to search engine", + "version": "16.0.1.0.0", + "development_status": "Alpha", "category": "e-commerce", "website": "https://github.com/shopinvader/odoo-shopinvader", "author": "Akretion,ACSONE SA/NV,Camptocamp", "license": "AGPL-3", - "installable": False, - "depends": [ - "stock", - "shopinvader", - "queue_job", - "connector_search_engine", - "shopinvader_search_engine", - ], + "installable": True, + "depends": ["stock", "shopinvader_search_engine"], "data": [ - "views/shopinvader_backend.xml", - "data/ir_export_product.xml", + "views/se_backend.xml", + "views/se_index.xml", "data/queue_job_channel_data.xml", "data/queue_job_function_data.xml", ], diff --git a/shopinvader_product_stock/data/queue_job_channel_data.xml b/shopinvader_search_engine_product_stock/data/queue_job_channel_data.xml similarity index 100% rename from shopinvader_product_stock/data/queue_job_channel_data.xml rename to shopinvader_search_engine_product_stock/data/queue_job_channel_data.xml diff --git a/shopinvader_product_stock/data/queue_job_function_data.xml b/shopinvader_search_engine_product_stock/data/queue_job_function_data.xml similarity index 100% rename from shopinvader_product_stock/data/queue_job_function_data.xml rename to shopinvader_search_engine_product_stock/data/queue_job_function_data.xml diff --git a/shopinvader_product_stock/i18n/shopinvader_product_stock.pot b/shopinvader_search_engine_product_stock/i18n/shopinvader_product_stock.pot similarity index 100% rename from shopinvader_product_stock/i18n/shopinvader_product_stock.pot rename to shopinvader_search_engine_product_stock/i18n/shopinvader_product_stock.pot diff --git a/shopinvader_search_engine_product_stock/models/__init__.py b/shopinvader_search_engine_product_stock/models/__init__.py new file mode 100644 index 0000000000..e25561b60e --- /dev/null +++ b/shopinvader_search_engine_product_stock/models/__init__.py @@ -0,0 +1,4 @@ +from . import product_product +from . import se_backend +from . import se_index +from . import stock_move diff --git a/shopinvader_search_engine_product_stock/models/product_product.py b/shopinvader_search_engine_product_stock/models/product_product.py new file mode 100644 index 0000000000..37a071667e --- /dev/null +++ b/shopinvader_search_engine_product_stock/models/product_product.py @@ -0,0 +1,79 @@ +# Copyright 2018 Akretion (http://www.akretion.com) +# Copyright 2018 ACSONE SA/NV +# Sébastien BEAU +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from collections import defaultdict + +from odoo import api, fields, models + + +class ProductProduct(models.Model): + _inherit = "product.product" + + stock_data = fields.Json(compute="_compute_stock_data") + + def _prepare_stock_data(self): + self.ensure_one() + index_id = self._context.get("index_id", False) + if index_id: + index = self.env["se.index"].browse(index_id) + stock_field = index.product_stock_field_id.name + return {"qty": self[stock_field]} + return {"qty": None} + + @api.depends_context("index_id") + def _compute_stock_data(self): + result = defaultdict(dict) + index_id = self.env.context.get("index_id", False) + if index_id: + index = self.env["se.index"].browse(index_id) + loc_records = self._filter_by_index() + for (wh_key, wh_ids) in index._get_warehouse_list_for_export().items(): + for loc_record in loc_records.with_context(warehouse=wh_ids): + result[loc_record.id][wh_key] = loc_record._prepare_stock_data() + for record in self: + record.stock_data = result[record.id] + + def synchronize_all_binding_stock_level(self, company_id=None): + """Compute new stock information and update index data. + + If data changed and binding is in done state it forces it to 'to_export'. + :return: + """ + # Use `sudo` because this action might be triggered + # from a low access level user (eg: external user on portal/website). + # In any case, the real operation is done w/ the backend user below. + + # ensure user company propagation if the method has bee delayed... + # this feature should be provide by queue job ... + # see https://github.com/OCA/queue/issues/363 + products = self + if company_id: + products = self.with_company(company_id) + + all_bindinds = products.mapped("se_binding_ids") + indexes = all_bindinds.mapped("index_id") + for index in indexes: + for product in products.with_context(index_id=index.id)._filter_by_index(): + binding = product.sudo().se_binding_ids.filtered( + lambda b, i=index: b.index_id == i + ) + if not binding.data: + # this binding has been not yet computed + # so we do not care to update it as it's not yet + # on the site. The right stock qty will be exported + # at its first export. + continue + data = binding.data + product_stock_data = product.stock_data + if data["stock"] != product_stock_data: + data["stock"] = product_stock_data + vals = {"data": data} + if index.synchronize_stock == "immediatly": + binding.write(vals) + binding.export_record() + elif index.synchronize_stock == "in_batch": + if binding.state == "done": + vals["state"] = "to_export" + binding.write(vals) diff --git a/shopinvader_search_engine_product_stock/models/se_backend.py b/shopinvader_search_engine_product_stock/models/se_backend.py new file mode 100644 index 0000000000..6634917cb6 --- /dev/null +++ b/shopinvader_search_engine_product_stock/models/se_backend.py @@ -0,0 +1,36 @@ +# Copyright 2018 Akretion (http://www.akretion.com) +# Copyright 2018 ACSONE SA/NV +# Sébastien BEAU +# Copyright 2020 Camptocamp SA (http://www.camptocamp.com) +# Simone Orsi +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import api, fields, models + + +class SeBackend(models.Model): + _inherit = "se.backend" + + # NOTE: this field right now has no effect on qty sync. + # It's here to allow extending modules to define their own policies. + # See example in `shopinvader_product_stock_state`. + stock_level_config = fields.Selection( + selection="_selection_stock_level_config", + default="only_qty", + required=True, + help="Define stock level export policy", + ) + show_stock_level_config = fields.Boolean(compute="_compute_show_stock_level_config") + + def _selection_stock_level_config(self): + return [("only_qty", "Only Quantity")] + + @api.depends("index_ids", "index_ids.model_id") + def _compute_show_stock_level_config(self): + product_model_id = ( + self.env["ir.model"].search([("model", "=", "product.product")]).id + ) + for backend in self: + backend.show_stock_level_config = ( + product_model_id in backend.index_ids.mapped("model_id").ids + ) diff --git a/shopinvader_product_stock/models/shopinvader_backend.py b/shopinvader_search_engine_product_stock/models/se_index.py similarity index 86% rename from shopinvader_product_stock/models/shopinvader_backend.py rename to shopinvader_search_engine_product_stock/models/se_index.py index a736f76759..179313eb5b 100644 --- a/shopinvader_product_stock/models/shopinvader_backend.py +++ b/shopinvader_search_engine_product_stock/models/se_index.py @@ -16,8 +16,9 @@ _logger.debug(err) -class ShopinvaderBackend(models.Model): - _inherit = "shopinvader.backend" +class SeIndex(models.Model): + + _inherit = "se.index" warehouse_ids = fields.Many2many( "stock.warehouse", @@ -47,10 +48,9 @@ class ShopinvaderBackend(models.Model): # It's here to allow extending modules to define their own policies. # See example in `shopinvader_product_stock_state`. stock_level_config = fields.Selection( - selection="_selection_stock_level_config", - default="only_qty", - required=True, - help="Define stock level export policy", + selection=lambda self: self.backend_id._selection_stock_level_config(), + help="Define stock level export policy, " + "keep empty to take configuration from the backend", ) def _default_stock_field_id(self): @@ -59,10 +59,9 @@ def _default_stock_field_id(self): def _default_warehouse_ids(self): return self.env["stock.warehouse"].search([], limit=1) - def _selection_stock_level_config(self): - return [ - ("only_qty", "Only Quantity"), - ] + def _get_stock_level_config(self): + self.ensure_one() + return self.stock_level_config or self.backend_id.stock_level_config def _get_warehouse_list_for_export(self): """Get list of warehouse to be used for exporting stock level. diff --git a/shopinvader_product_stock/models/stock_move.py b/shopinvader_search_engine_product_stock/models/stock_move.py similarity index 95% rename from shopinvader_product_stock/models/stock_move.py rename to shopinvader_search_engine_product_stock/models/stock_move.py index bcf9415e31..08e7e4d5ca 100644 --- a/shopinvader_product_stock/models/stock_move.py +++ b/shopinvader_search_engine_product_stock/models/stock_move.py @@ -15,7 +15,7 @@ def _get_product_to_update(self): # Maybe we can be more restrictive # depending of the move location and destination # For now we take all moves and bound products - return self.mapped("product_id").filtered(lambda p: p.is_shopinvader_binded) + return self.mapped("product_id").filtered(lambda p: p.se_binding_ids) def _jobify_product_stock_update(self): """ diff --git a/shopinvader_product_stock/readme/CONTRIBUTORS.rst b/shopinvader_search_engine_product_stock/readme/CONTRIBUTORS.rst similarity index 81% rename from shopinvader_product_stock/readme/CONTRIBUTORS.rst rename to shopinvader_search_engine_product_stock/readme/CONTRIBUTORS.rst index b1919366b6..f116300263 100644 --- a/shopinvader_product_stock/readme/CONTRIBUTORS.rst +++ b/shopinvader_search_engine_product_stock/readme/CONTRIBUTORS.rst @@ -3,3 +3,4 @@ * Laurent Mignon * Denis Roussel * Simone Orsi +* Quentin Groulard diff --git a/shopinvader_product_stock/readme/CREDITS.rst b/shopinvader_search_engine_product_stock/readme/CREDITS.rst similarity index 100% rename from shopinvader_product_stock/readme/CREDITS.rst rename to shopinvader_search_engine_product_stock/readme/CREDITS.rst diff --git a/shopinvader_product_stock/readme/DESCRIPTION.rst b/shopinvader_search_engine_product_stock/readme/DESCRIPTION.rst similarity index 100% rename from shopinvader_product_stock/readme/DESCRIPTION.rst rename to shopinvader_search_engine_product_stock/readme/DESCRIPTION.rst diff --git a/shopinvader_search_engine_product_stock/readme/HISTORY.rst b/shopinvader_search_engine_product_stock/readme/HISTORY.rst new file mode 100644 index 0000000000..ea43e4a41c --- /dev/null +++ b/shopinvader_search_engine_product_stock/readme/HISTORY.rst @@ -0,0 +1,19 @@ +16.0.1.0.0 (2023-10-19) +~~~~~~~~~~~~~~~~~~~~~~~ + +- Rename module shopinvader_product_stock -> shopinvader_search_engine_product_stock +- Refactoring to fit the refactoring of shopinvader_search_engine + +For now exclusively made to work with shopinvader_search_engine. If you need to use +another way of exporting bindings, you'll need a place to configure warehouse, product +stock field, etc. and you'll need to extract part of the code of this module. + +10.0.1.0.0 (2018-06-15) +~~~~~~~~~~~~~~~~~~~~~~~ + +* Rename modules + +12.0.1.0.0 (2019-05-23) +~~~~~~~~~~~~~~~~~~~~~~~ + +* [12.0][MIG] shopinvader_product_stock diff --git a/shopinvader_search_engine_product_stock/schemas/__init__.py b/shopinvader_search_engine_product_stock/schemas/__init__.py new file mode 100644 index 0000000000..e7c5b7099e --- /dev/null +++ b/shopinvader_search_engine_product_stock/schemas/__init__.py @@ -0,0 +1 @@ +from .product import ProductProduct diff --git a/shopinvader_search_engine_product_stock/schemas/product.py b/shopinvader_search_engine_product_stock/schemas/product.py new file mode 100644 index 0000000000..3b02802cde --- /dev/null +++ b/shopinvader_search_engine_product_stock/schemas/product.py @@ -0,0 +1,16 @@ +# Copyright 2023 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from typing import Any + +from odoo.addons.shopinvader_product.schemas import ProductProduct as BaseProduct + + +class ProductProduct(BaseProduct, extends=True): + stock: dict[str, Any] = {} + + @classmethod + def from_product_product(cls, odoo_rec): + obj = super().from_product_product(odoo_rec) + obj.stock = odoo_rec.stock_data + return obj diff --git a/shopinvader_product_stock/static/description/index.html b/shopinvader_search_engine_product_stock/static/description/index.html similarity index 100% rename from shopinvader_product_stock/static/description/index.html rename to shopinvader_search_engine_product_stock/static/description/index.html diff --git a/shopinvader_product_stock/tests/__init__.py b/shopinvader_search_engine_product_stock/tests/__init__.py similarity index 100% rename from shopinvader_product_stock/tests/__init__.py rename to shopinvader_search_engine_product_stock/tests/__init__.py diff --git a/shopinvader_product_stock/tests/common.py b/shopinvader_search_engine_product_stock/tests/common.py similarity index 78% rename from shopinvader_product_stock/tests/common.py rename to shopinvader_search_engine_product_stock/tests/common.py index 65e6009616..6f4611f71c 100644 --- a/shopinvader_product_stock/tests/common.py +++ b/shopinvader_search_engine_product_stock/tests/common.py @@ -4,14 +4,15 @@ # Simone Orsi # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo.addons.connector_search_engine.tests.test_all import TestBindingIndexBaseFake from odoo.addons.queue_job.tests.common import JobMixin +from odoo.addons.shopinvader_search_engine.tests.common import TestBindingIndexBase -class StockCommonCase(TestBindingIndexBaseFake, JobMixin): +class StockCommonCase(TestBindingIndexBase, JobMixin): @classmethod def setUpClass(cls): - super(StockCommonCase, cls).setUpClass() + super().setUpClass() + cls.env = cls.env( context=dict( cls.env.context, @@ -20,7 +21,6 @@ def setUpClass(cls): ) ) ref = cls.env.ref - cls.shopinvader_backend = ref("shopinvader.backend_1") cls.warehouse_1 = ref("stock.warehouse0") cls.loc_1 = cls.warehouse_1.lot_stock_id cls.warehouse_2 = ref("stock.stock_warehouse_shop0") @@ -28,25 +28,19 @@ def setUpClass(cls): cls.product = cls.env["product.product"].create( {"name": "Stock prod 1", "type": "product"} ) - cls.shopinvader_backend.bind_all_product() cls.index = cls.env["se.index"].create( { - "name": "test-product-index", - "backend_id": cls.backend_specific.se_backend_id.id, - "exporter_id": ref("shopinvader.ir_exp_shopinvader_variant").id, - "lang_id": ref("base.lang_en").id, - "model_id": ref("shopinvader.model_shopinvader_variant").id, - } - ) - cls.shopinvader_backend.write( - { - "se_backend_id": cls.backend_specific.se_backend_id.id, + "name": "product", + "backend_id": cls.backend.id, + "model_id": cls.env.ref("product.model_product_product").id, + "serializer_type": "shopinvader_product_exports", "warehouse_ids": [(6, 0, cls.warehouse_1.ids)], "product_stock_field_id": ref( "stock.field_product_product__qty_available" ).id, } ) + cls.product_binding = cls.product._add_to_index(cls.index) cls.loc_supplier = cls.env.ref("stock.stock_location_suppliers") cls.picking_type_in = cls.env.ref("stock.picking_type_in") @@ -60,7 +54,7 @@ def _add_stock_to_product(self, product, location, qty): { "product_id": product.id, "location_id": location.id, - "inventory_quantity": qty, + "inventory_quantity_auto_apply": qty, } ) diff --git a/shopinvader_search_engine_product_stock/tests/test_product.py b/shopinvader_search_engine_product_stock/tests/test_product.py new file mode 100644 index 0000000000..eefe37ebdd --- /dev/null +++ b/shopinvader_search_engine_product_stock/tests/test_product.py @@ -0,0 +1,84 @@ +# Copyright 2018 Akretion (http://www.akretion.com) +# Copyright 2018 ACSONE SA/NV +# Sébastien BEAU +# Copyright 2020 Camptocamp SA (http://www.camptocamp.com) +# Simone Orsi +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from .common import StockCommonCase + + +class TestProductProduct(StockCommonCase): + """Tests for product stock info.""" + + def _expectect_qty_by_wh(self, warehouse_recs, prod): + res = { + "global": { + "qty": prod.with_context( + warehouse=list(warehouse_recs.ids) + ).qty_available + }, + } + for wh in warehouse_recs: + key = self.index._make_warehouse_key(wh) + res[key] = {"qty": prod.with_context(warehouse=wh.id).qty_available} + return res + + def test_update_qty_from_wizard(self): + """Updating the quantity through an inventory create a job.""" + job = self.job_counter() + self._add_stock_to_product(self.product, self.loc_1, 100) + self.assertEqual(job.count_created(), 1) + + def _test_update_stock(self, sync_immediatly=True): + self.product_binding.recompute_json() + self.assertEqual(self.product_binding.data["stock"], {"global": {"qty": 0.0}}) + + jobs = self.job_counter() + self._add_stock_to_product(self.product, self.loc_1, 100) + self.assertEqual(jobs.count_created(), 1) + + self.product.invalidate_recordset(["stock_data"]) + + with self.se_adapter.mocked_calls() as calls: + self.perform_jobs(jobs) + + self.assertEqual(self.product_binding.data["stock"], {"global": {"qty": 100.0}}) + if sync_immediatly: + self.assertEqual(len(calls), 1) + call = calls[0] + self.assertEqual(call["method"], "index") + self.assertEqual(len(call["args"]), 1) + self.assertEqual(call["args"][0]["stock"], {"global": {"qty": 100.0}}) + self.assertEqual(self.product_binding.state, "done") + else: + self.assertEqual(len(calls), 0) + self.assertEqual(self.product_binding.state, "to_export") + + def test_update_stock(self): + """Recompute product should update binding and export it.""" + self._test_update_stock() + + def test_update_stock_differed(self): + """Recompute product should update binding and not export it.""" + self.index.synchronize_stock = "in_batch" + self._test_update_stock(sync_immediatly=False) + + def test_multi_warehouse(self): + warehouses = self.warehouse_1 + self.warehouse_2 + self.index.write({"warehouse_ids": [(6, 0, warehouses.ids)]}) + + self.product_binding.recompute_json() + expected = self._expectect_qty_by_wh(warehouses, self.product) + self.assertEqual(self.product_binding.data["stock"], expected) + + jobs = self.job_counter() + self._add_stock_to_product(self.product, self.loc_1, 100) + self._add_stock_to_product(self.product, self.loc_2, 200) + + self.product.invalidate_recordset(["stock_data"]) + self.assertEqual(jobs.count_created(), 1) + with self.se_adapter.mocked_calls(): + self.perform_jobs(jobs) + expected = self._expectect_qty_by_wh(warehouses, self.product) + self.assertEqual(self.product_binding.data["stock"], expected) diff --git a/shopinvader_product_stock/tests/test_stock_move.py b/shopinvader_search_engine_product_stock/tests/test_stock_move.py similarity index 97% rename from shopinvader_product_stock/tests/test_stock_move.py rename to shopinvader_search_engine_product_stock/tests/test_stock_move.py index 2a7bed672c..fa38134df3 100644 --- a/shopinvader_product_stock/tests/test_stock_move.py +++ b/shopinvader_search_engine_product_stock/tests/test_stock_move.py @@ -41,7 +41,7 @@ def test_action_done(self): def test_action_confirm_not_bound(self): """action_confirm for non bound products should not create a job.""" job = self.job_counter() - self.product.shopinvader_bind_ids.unlink() + self.product_binding.unlink() move = self._create_incoming_move() move._action_confirm() self.assertEqual(job.count_created(), 0) diff --git a/shopinvader_search_engine_product_stock/views/se_backend.xml b/shopinvader_search_engine_product_stock/views/se_backend.xml new file mode 100644 index 0000000000..41245361e3 --- /dev/null +++ b/shopinvader_search_engine_product_stock/views/se_backend.xml @@ -0,0 +1,23 @@ + + + + + + se.backend.form (in shopinvader_search_engine_product_stock) + se.backend + + + + + + + + + + diff --git a/shopinvader_search_engine_product_stock/views/se_index.xml b/shopinvader_search_engine_product_stock/views/se_index.xml new file mode 100644 index 0000000000..247653c349 --- /dev/null +++ b/shopinvader_search_engine_product_stock/views/se_index.xml @@ -0,0 +1,24 @@ + + + + + + se.index.form (in shopinvader_search_engine_product_stock) + se.index + + + + + + + + + + + + + + diff --git a/shopinvader_search_engine_product_stock_state/README.rst b/shopinvader_search_engine_product_stock_state/README.rst new file mode 100644 index 0000000000..badd1df345 --- /dev/null +++ b/shopinvader_search_engine_product_stock_state/README.rst @@ -0,0 +1,108 @@ +============================================= +Shopinvader Search Engine Product Stock State +============================================= + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Alpha-red.png + :target: https://odoo-community.org/page/development-status + :alt: Alpha +.. |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%2Fodoo--shopinvader-lightgray.png?logo=github + :target: https://github.com/OCA/odoo-shopinvader/tree/16.0/shopinvader_search_engine_product_stock_state + :alt: OCA/odoo-shopinvader +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/odoo-shopinvader-16-0/odoo-shopinvader-16-0-shopinvader_search_engine_product_stock_state + :alt: Translate me on Weblate + +|badge1| |badge2| |badge3| |badge4| + +This is shopinvader product stock state module. +This module is used to export the stock state of product +only if the value has been updated. + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +Go into shopinvader > backend, and on the backend you can choose the mode +- State and Low Quantity (only add the qty if it behind a defined value) +- State and Quantity +- Only State +- Only Quantity + +Usage +===== + +Instead of having only the qty in the json sent to the search engine +You will have (depending of the configuration, the qty and the state) + + +.. code-block:: python + + example : + {'global': { + 'state': 'in_limited_stock', + 'qty': 5.0, + }} + +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 +~~~~~~~~~~~~ + +* Sebastien BEAU +* Simone Orsi +* Quentin Groulard + +Other credits +~~~~~~~~~~~~~ + +Funders +------- + +The development of this module has been financially supported by: + +* Akretion R&D + +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/odoo-shopinvader `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/shopinvader_product_stock/__init__.py b/shopinvader_search_engine_product_stock_state/__init__.py similarity index 100% rename from shopinvader_product_stock/__init__.py rename to shopinvader_search_engine_product_stock_state/__init__.py diff --git a/shopinvader_product_stock_state/__manifest__.py b/shopinvader_search_engine_product_stock_state/__manifest__.py similarity index 59% rename from shopinvader_product_stock_state/__manifest__.py rename to shopinvader_search_engine_product_stock_state/__manifest__.py index 7d63854959..8182477102 100644 --- a/shopinvader_product_stock_state/__manifest__.py +++ b/shopinvader_search_engine_product_stock_state/__manifest__.py @@ -2,14 +2,15 @@ # Sébastien BEAU # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). { - "name": "Shopinvader Product Stock State", + "name": "Shopinvader Search Engine Product Stock State", "summary": "This module is used to choose a stock state during the" - "export (by backend)", - "version": "14.0.1.0.0", + "export t search engine", + "version": "16.0.1.0.0", + "development_status": "Alpha", "category": "e-commerce", "website": "https://github.com/shopinvader/odoo-shopinvader", "author": "Akretion", "license": "AGPL-3", - "installable": False, - "depends": ["product_stock_state", "shopinvader_product_stock"], + "installable": True, + "depends": ["product_stock_state", "shopinvader_search_engine_product_stock"], } diff --git a/shopinvader_product_stock_state/i18n/shopinvader_product_stock_state.pot b/shopinvader_search_engine_product_stock_state/i18n/shopinvader_product_stock_state.pot similarity index 100% rename from shopinvader_product_stock_state/i18n/shopinvader_product_stock_state.pot rename to shopinvader_search_engine_product_stock_state/i18n/shopinvader_product_stock_state.pot diff --git a/shopinvader_product_stock_state/models/__init__.py b/shopinvader_search_engine_product_stock_state/models/__init__.py similarity index 71% rename from shopinvader_product_stock_state/models/__init__.py rename to shopinvader_search_engine_product_stock_state/models/__init__.py index cb58000f4b..ea848cb553 100644 --- a/shopinvader_product_stock_state/models/__init__.py +++ b/shopinvader_search_engine_product_stock_state/models/__init__.py @@ -2,5 +2,5 @@ # Sébastien BEAU # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -from . import shopinvader_variant -from . import shopinvader_backend +from . import product_product +from . import se_backend diff --git a/shopinvader_search_engine_product_stock_state/models/product_product.py b/shopinvader_search_engine_product_stock_state/models/product_product.py new file mode 100644 index 0000000000..f60884e3f2 --- /dev/null +++ b/shopinvader_search_engine_product_stock_state/models/product_product.py @@ -0,0 +1,32 @@ +# Copyright 2018 Akretion (http://www.akretion.com) +# Sébastien BEAU +# Copyright 2020 Camptocamp SA (http://www.camptocamp.com) +# Simone Orsi +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import models + + +class ProductProduct(models.Model): + _inherit = "product.product" + + def _prepare_stock_data(self): + self.ensure_one() + res = super()._prepare_stock_data() + index_id = self.env.context.get("index_id", False) + if index_id: + index = self.env["se.index"].browse(index_id) + if "state" in index._get_stock_level_config(): + res["state"] = self.stock_state + if self._skip_stock_qty_update(index): + res.pop("qty", None) + return res + + def _skip_stock_qty_update(self, index=None): + self.ensure_one() + if index: + config = index._get_stock_level_config() + return config == "only_state" or ( + config == "state_and_low_qty" and self.stock_state != "in_limited_stock" + ) + return False diff --git a/shopinvader_product_stock_state/models/shopinvader_backend.py b/shopinvader_search_engine_product_stock_state/models/se_backend.py similarity index 87% rename from shopinvader_product_stock_state/models/shopinvader_backend.py rename to shopinvader_search_engine_product_stock_state/models/se_backend.py index 9e1e9c000b..c9d162ed05 100644 --- a/shopinvader_product_stock_state/models/shopinvader_backend.py +++ b/shopinvader_search_engine_product_stock_state/models/se_backend.py @@ -6,8 +6,8 @@ from odoo import models -class ShopinvaderBackend(models.Model): - _inherit = "shopinvader.backend" +class SeBackend(models.Model): + _inherit = "se.backend" def _selection_stock_level_config(self): return super()._selection_stock_level_config() + [ diff --git a/shopinvader_search_engine_product_stock_state/readme/CONFIGURE.rst b/shopinvader_search_engine_product_stock_state/readme/CONFIGURE.rst new file mode 100644 index 0000000000..7997341c3e --- /dev/null +++ b/shopinvader_search_engine_product_stock_state/readme/CONFIGURE.rst @@ -0,0 +1,5 @@ +Go into shopinvader > backend, and on the backend you can choose the mode +- State and Low Quantity (only add the qty if it behind a defined value) +- State and Quantity +- Only State +- Only Quantity diff --git a/shopinvader_search_engine_product_stock_state/readme/CONTRIBUTORS.rst b/shopinvader_search_engine_product_stock_state/readme/CONTRIBUTORS.rst new file mode 100644 index 0000000000..cee674fd87 --- /dev/null +++ b/shopinvader_search_engine_product_stock_state/readme/CONTRIBUTORS.rst @@ -0,0 +1,3 @@ +* Sebastien BEAU +* Simone Orsi +* Quentin Groulard diff --git a/shopinvader_search_engine_product_stock_state/readme/CREDITS.rst b/shopinvader_search_engine_product_stock_state/readme/CREDITS.rst new file mode 100644 index 0000000000..b8c7a2936a --- /dev/null +++ b/shopinvader_search_engine_product_stock_state/readme/CREDITS.rst @@ -0,0 +1,6 @@ +Funders +------- + +The development of this module has been financially supported by: + +* Akretion R&D diff --git a/shopinvader_search_engine_product_stock_state/readme/DESCRIPTION.rst b/shopinvader_search_engine_product_stock_state/readme/DESCRIPTION.rst new file mode 100644 index 0000000000..4f7b2ce17c --- /dev/null +++ b/shopinvader_search_engine_product_stock_state/readme/DESCRIPTION.rst @@ -0,0 +1,3 @@ +This is shopinvader product stock state module. +This module is used to export the stock state of product +only if the value has been updated. diff --git a/shopinvader_search_engine_product_stock_state/readme/USAGE.rst b/shopinvader_search_engine_product_stock_state/readme/USAGE.rst new file mode 100644 index 0000000000..cff3f28a2c --- /dev/null +++ b/shopinvader_search_engine_product_stock_state/readme/USAGE.rst @@ -0,0 +1,11 @@ +Instead of having only the qty in the json sent to the search engine +You will have (depending of the configuration, the qty and the state) + + +.. code-block:: python + + example : + {'global': { + 'state': 'in_limited_stock', + 'qty': 5.0, + }} diff --git a/shopinvader_search_engine_product_stock_state/static/description/index.html b/shopinvader_search_engine_product_stock_state/static/description/index.html new file mode 100644 index 0000000000..ea17bb07bf --- /dev/null +++ b/shopinvader_search_engine_product_stock_state/static/description/index.html @@ -0,0 +1,459 @@ + + + + + + +Shopinvader Search Engine Product Stock State + + + +
+

Shopinvader Search Engine Product Stock State

+ + +

Alpha License: AGPL-3 OCA/odoo-shopinvader Translate me on Weblate

+

This is shopinvader product stock state module. +This module is used to export the stock state of product +only if the value has been updated.

+

Table of contents

+ +
+

Configuration

+

Go into shopinvader > backend, and on the backend you can choose the mode +- State and Low Quantity (only add the qty if it behind a defined value) +- State and Quantity +- Only State +- Only Quantity

+
+
+

Usage

+

Instead of having only the qty in the json sent to the search engine +You will have (depending of the configuration, the qty and the state)

+
+example :
+    {'global': {
+        'state': 'in_limited_stock',
+        'qty': 5.0,
+    }}
+
+
+
+

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

+ +
+
+

Other credits

+
+

Funders

+

The development of this module has been financially supported by:

+
    +
  • Akretion R&D
  • +
+
+
+
+

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/odoo-shopinvader project on GitHub.

+

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

+
+
+
+ + diff --git a/shopinvader_product_stock_state/tests/__init__.py b/shopinvader_search_engine_product_stock_state/tests/__init__.py similarity index 100% rename from shopinvader_product_stock_state/tests/__init__.py rename to shopinvader_search_engine_product_stock_state/tests/__init__.py diff --git a/shopinvader_product_stock_state/tests/test_product.py b/shopinvader_search_engine_product_stock_state/tests/test_product.py similarity index 66% rename from shopinvader_product_stock_state/tests/test_product.py rename to shopinvader_search_engine_product_stock_state/tests/test_product.py index 16203efbd6..320a9dd72c 100644 --- a/shopinvader_product_stock_state/tests/test_product.py +++ b/shopinvader_search_engine_product_stock_state/tests/test_product.py @@ -4,7 +4,9 @@ # Simone Orsi # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -from odoo.addons.shopinvader_product_stock.tests.common import StockCommonCase +from odoo.addons.shopinvader_search_engine_product_stock.tests.common import ( + StockCommonCase, +) class TestProductProduct(StockCommonCase): @@ -13,20 +15,20 @@ class TestProductProduct(StockCommonCase): @classmethod def setUpClass(cls): super(TestProductProduct, cls).setUpClass() - cls.shopinvader_product = cls.product.shopinvader_bind_ids cls.company = cls.env.ref("base.main_company") - cls.shopinvader_backend.stock_level_config = "state_and_low_qty" + cls.index.stock_level_config = "state_and_low_qty" + cls.product = cls.product.with_context(index_id=cls.index.id) def test_out_of_stock(self): self.assertEqual( - self.shopinvader_product.stock_data, + self.product.stock_data, {"global": {"state": "out_of_stock"}}, ) def test_in_limited_stock(self): self._add_stock_to_product(self.product, self.loc_1, 5) self.assertEqual( - self.shopinvader_product.stock_data, + self.product.stock_data, {"global": {"state": "in_limited_stock", "qty": 5.0}}, ) @@ -34,7 +36,7 @@ def test_in_stock(self): self.company.stock_state_threshold = 0 self._add_stock_to_product(self.product, self.loc_1, 20) self.assertEqual( - self.shopinvader_product.stock_data, + self.product.stock_data, {"global": {"state": "in_stock"}}, ) @@ -42,25 +44,25 @@ def test_resupplying(self): move = self._create_incoming_move() move._action_confirm() self.assertEqual( - self.shopinvader_product.stock_data, + self.product.stock_data, {"global": {"state": "resupplying"}}, ) def test_config_only_qty(self): - self.shopinvader_backend.stock_level_config = "only_qty" - self.assertEqual(self.shopinvader_product.stock_data, {"global": {"qty": 0.00}}) + self.index.stock_level_config = "only_qty" + self.assertEqual(self.product.stock_data, {"global": {"qty": 0.00}}) def test_config_only_state(self): - self.shopinvader_backend.stock_level_config = "only_state" + self.index.stock_level_config = "only_state" self._add_stock_to_product(self.product, self.loc_1, 5) self.assertEqual( - self.shopinvader_product.stock_data, + self.product.stock_data, {"global": {"state": "in_limited_stock"}}, ) def test_config_state_and_qty(self): - self.shopinvader_backend.stock_level_config = "state_and_qty" + self.index.stock_level_config = "state_and_qty" self.assertEqual( - self.shopinvader_product.stock_data, + self.product.stock_data, {"global": {"qty": 0.00, "state": "out_of_stock"}}, ) diff --git a/test-requirements.txt b/test-requirements.txt index 268eb05ad2..4cecfa7d32 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -8,3 +8,4 @@ vcrpy-unittest unittest2 # For shopinvader test_controller, which inherits component odoo-test-helper httpx # For FastAPI tests +odoo-addon-product-stock-state @ git+https://github.com/OCA/product-attribute@refs/pull/1339/head#subdirectory=setup/product_stock_state