diff --git a/connector_extension_woocommerce/README.rst b/connector_extension_woocommerce/README.rst new file mode 100644 index 000000000..0d5d30100 --- /dev/null +++ b/connector_extension_woocommerce/README.rst @@ -0,0 +1,65 @@ +=============================== +Connector Extension Woocommerce +=============================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:f381547277a60fe3768f6861b4c213c845b21507225ce930457b473de3c08289 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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-LGPL--3-blue.png + :target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html + :alt: License: LGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-NuoBiT%2Fodoo--addons-lightgray.png?logo=github + :target: https://github.com/NuoBiT/odoo-addons/tree/18.0/connector_extension_woocommerce + :alt: NuoBiT/odoo-addons + +|badge1| |badge2| |badge3| + +This module extends the connector extension module to add support for +Woocommerce + +**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 +------- + +* NuoBiT Solutions SL + +Contributors +------------ + +- `NuoBiT `__: + + - Kilian Niubo kniubo@nuobit.com + - Eric Antones eantones@nuobit.com + - Deniz Gallo dgallo@nuobit.com + +Maintainers +----------- + +This module is part of the `NuoBiT/odoo-addons `_ project on GitHub. + +You are welcome to contribute. diff --git a/connector_extension_woocommerce/__init__.py b/connector_extension_woocommerce/__init__.py new file mode 100644 index 000000000..1377f57f5 --- /dev/null +++ b/connector_extension_woocommerce/__init__.py @@ -0,0 +1 @@ +from . import components diff --git a/connector_extension_woocommerce/__manifest__.py b/connector_extension_woocommerce/__manifest__.py new file mode 100644 index 000000000..dc80943b1 --- /dev/null +++ b/connector_extension_woocommerce/__manifest__.py @@ -0,0 +1,16 @@ +# Copyright NuoBiT Solutions - Kilian Niubo +# Copyright NuoBiT Solutions - Eric Antones +# Copyright 2026 NuoBiT Solutions SL - Deniz Gallo +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html) + +{ + "name": "Connector Extension Woocommerce", + "summary": "This module extends the connector extension module " + "to add support for Woocommerce", + "version": "18.0.1.0.0", + "author": "NuoBiT Solutions SL", + "license": "LGPL-3", + "category": "Connector", + "website": "https://github.com/NuoBiT/odoo-addons", + "depends": ["connector_extension"], +} diff --git a/connector_extension_woocommerce/components/__init__.py b/connector_extension_woocommerce/components/__init__.py new file mode 100644 index 000000000..f502287fe --- /dev/null +++ b/connector_extension_woocommerce/components/__init__.py @@ -0,0 +1 @@ +from . import adapter diff --git a/connector_extension_woocommerce/components/adapter.py b/connector_extension_woocommerce/components/adapter.py new file mode 100644 index 000000000..02c841e59 --- /dev/null +++ b/connector_extension_woocommerce/components/adapter.py @@ -0,0 +1,515 @@ +# Copyright 2025 NuoBiT Solutions - Eric Antones +# Copyright NuoBiT Solutions - Kilian Niubo +# Copyright 2026 NuoBiT Solutions SL - Deniz Gallo +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html) + +import json +import logging + +from requests.exceptions import ConnectionError as RequestConnectionError + +from odoo import _ +from odoo.exceptions import ValidationError + +from odoo.addons.component.core import AbstractComponent +from odoo.addons.connector.exception import RetryableJobError + +from ...connector_extension.common.tools import trim_domain + +_logger = logging.getLogger(__name__) + + +class ConnectorExtensionWooCommerceAdapterCRUD(AbstractComponent): + _name = "connector.extension.woocommerce.adapter.crud" + _inherit = "connector.extension.adapter.crud" + + def _exec(self, op, resource, *args, **kwargs): + if kwargs.get("domain"): + kwargs["domain"] = trim_domain(kwargs["domain"]) + func = getattr(self, f"_exec_{op}") + return func(resource, *args, **kwargs) + + def _manage_error_codes( + self, op, res_data, res, resource, *args, raise_on_error=True, **kwargs + ): + if not res.ok: + error_message = None + if res.status_code == 404: + if res_data.get("code") == "rest_no_route": + error_message = _( + "Error: '%(message)s'. Probably the %(resource)s has been" + " removed from Woocommerce. " + "If it's the case, try to remove the binding of the %(model)s." + ) % { + "message": res_data.get("message"), + "resource": resource, + "model": self.model._name, + } + # elif res_data.get("code") == "woocommerce_rest_term_invalid": + # error_message = _( + # "Error: '%s'. Probably the %s has been " + # "removed from Woocommerce. " + # "If it's the case, try to remove the binding of the %s." + # % (res_data.get("message"), resource, self.model._name) + # ) + # elif ( + # res_data.get("code") + # == "woocommerce_rest_product_variation_invalid_parent" + # ): + # error_message = _( + # "Error: '%s'. Probably the product in %s " + # "has been removed from Woocommerce. " + # "If it's the case, try to remove the binding of the " + # "woocommerce.product.template" + # % ( + # res_data.get("message"), + # resource, + # ) + # ) + elif res.status_code == 400: + if res_data.get("code") == "term_exists": + error_message = _( + "Error: '%(message)s'. Probably repeated record already " + "exists in Woocommerce. " + "If you don't see it from the interface probably the database" + " is dirty and you should check the database directly. It means" + " that there's terms (value attributes) in table " + "wp_term_taxonomy with a non-existent taxonomy (attribute).\n" + "Please, review the data in %(resource)s/%(resource_id)s " + "and compare it with %(data)s" + ) % { + "message": res_data["message"], + "resource": resource, + "resource_id": res_data["data"]["resource_id"], + "data": kwargs["data"], + } + # elif res_data.get("code") in [ + # "woocommerce_rest_product_variation_invalid_id", + # "woocommerce_rest_product_invalid_id", + # ]: + # error_message = _( + # "Error: '%s'. Probably the %s has been + # removed from Woocommerce. " + # "If it's the case, try to remove the binding of the %s." + # % (res_data.get("message"), resource, self.model._name) + # ) + if not error_message: + error_message = _( + "Error: %(data)s -> Op: %(op)s, Resource: %(resource)s, " + "Args: %(args)s, KWArgs: %(kwargs)s" + ) % { + "data": res_data, + "op": op, + "resource": resource, + "args": args, + "kwargs": kwargs, + } + + if raise_on_error: + raise ValidationError(error_message) + return error_message + return res_data + + # TODO: Remove *args and *kwargs and put params=None + # Check other methods than get to see if it'll work for them too + def _exec_wcapi_call(self, op, resource, *args, **kwargs): # noqa: C901 + if self.backend_record.enable_call_logging: + _logger.info( + "WooCommerce API Call (_exec_wcapi_call) - OP: %s, " + "Resource: %s, Args: %s, KWArgs: %s", + op, + resource, + args, + kwargs, + ) + + # WooCommerce has the parameter next on the response headers + # to get the next page but we can't use it because if we use + # the offset, the next page will have the same items as the first page. + # It looks like a bug in WooCommerce API. + # So the 'page' parameter is incompatible with 'offset' parameter. + # If 'offset' and 'page' are used at the same time, only 'offset' + # will be taken into account and the 'page' will be ignored. + params = kwargs.get("params", {}) + if {"page", "offset"}.issubset(params): + raise ValidationError( + _( + "The 'offset' and 'page' parameters are incompatible " + "in WooCommerce API calls. Please, use only one of them." + ) + ) + + func = getattr(self.wcapi, op) + try: + response = func(resource, *args, **kwargs) + res_data = response.json() + result = { + "data": None, + "returned_items": 0, + "total_items": 0, + "total_pages": 0, + # "next": None, + # "prev": None, + } + if not response.ok: + # These are the cases where a not found should be an empty result + # instead of an error, we need to bypass the standard REST behaviour + # and make it look like more like an SQL whre if the parameters are + # wrong it just returns no value + if op == "get": + if response.status_code != 404 or res_data["code"] not in ( + "woocommerce_rest_product_invalid_id", + "woocommerce_rest_product_variation_invalid_id", + "woocommerce_rest_product_variation_invalid_parent", + "woocommerce_rest_term_invalid", + ): + self._manage_error_codes( + op, res_data, response, resource, *args, **kwargs + ) + result["data"] = [] + else: + self._manage_error_codes( + op, res_data, response, resource, *args, **kwargs + ) + result["data"] = {} + else: + # check if the response is a singleton or a list + if isinstance(res_data, dict): + singleton = True + elif isinstance(res_data, list): + singleton = False + else: + raise ValidationError( + _("Unexpected response from WooCommerce: %s") % res_data + ) + # check consistency between headers and response type + multi_headers = ["X-WP-Total", "X-WP-TotalPages"] + if singleton: + for header in multi_headers: + if header in response.headers: + raise ValidationError( + _( + "The '%(header)s' header should not be present in " + "singleton responses: %(response)s" + ) + % { + "header": header, + "response": res_data, + } + ) + # for link in response.links.keys(): + # if link in ["next", "prev", "first", "last"]: + # raise ValidationError( + # _( + # "The '%s' link should not be + # present in singleton " + # "responses: %s" + # ) + # % (link, res_data) + # ) + else: + for header in multi_headers: + if header not in response.headers: + raise ValidationError( + _( + "The '%(header)s' header is missing" + " in multi responses: %(response)s" + ) + % { + "header": header, + "response": res_data, + } + ) + if singleton: + result["data"] = [res_data] + result["total_items"] = 1 + result["total_pages"] = 1 + else: + if op != "get": + raise ValidationError( + _( + "Unexpected multi-response for " + "operation '%(op)s': %(response)s" + ) + % { + "op": op, + "response": res_data, + } + ) + result["data"] = res_data + result["total_items"] = int(response.headers["X-WP-Total"]) + result["total_pages"] = int(response.headers["X-WP-TotalPages"]) + # if "next" in response.links: + # result["next"] = response.links["next"]["url"] + # if "prev" in response.links: + # result["prev"] = response.links["prev"]["url"] + result["returned_items"] = len(result["data"]) + except RequestConnectionError as e: + raise RetryableJobError(_("Error connecting to WooCommerce: %s") % e) from e + except json.decoder.JSONDecodeError as e: + raise ValidationError( + _( + "Error decoding json WooCommerce response: " + "%(error)s\nArgs:%(args)s\nKwargs:%(kwargs)s\n" + "URL:%(url)s\nHeaders:%(headers)s\nMethod:%(method)s\nBody:%(body)s" + ) + % { + "error": e, + "args": args, + "kwargs": kwargs, + "url": response.url, + "headers": response.request.headers, + "method": response.request.method, + "body": response.text and response.text[:100] + " ...", + } + ) from e + + if self.backend_record.enable_call_logging: + result_debug = dict(result) + result_debug["data"] = "..." + _logger.info( + "WooCommerce API Response (_exec_wcapi_call) - Result: %s", result_debug + ) + return result + + def get_total_items(self, resource, domain=None): + """ + Get the total count of items matching the given domain. + + This method leverages the optimized _exec_get count mode which: + - Makes only 1 API call when no local filtering is needed + - Extracts count from X-WP-Total header + - Minimizes data transfer (per_page=1, _fields=id) + + :param resource: WooCommerce API endpoint (e.g., "products", "orders") + :param domain: Filter conditions (Odoo-style domain) + :return: Total count of matching items (integer) + """ + return self._exec_get(resource, domain=domain, count=True) + + def _get_search_fields(self): + return ["modified_after"] # , "offset", "per_page", "page"] + + # TODO: User API params search and search_fields to find for some fields like + # name, etc. This is a ilike contain %name% search so we need _filter + # as well but on a lot less records + def _exec_get( + self, + resource, + domain=None, + offset=0, + limit=None, + count=False, + ): + """ + Execute GET operation with pagination, filtering, and optional counting. + + WooCommerce API Bug: 'offset' and 'page' parameters are incompatible. + Large offsets require multiple API calls (offset=1000 → ~100 calls). + Prefer date filters like ('modified_after', '>=', '2026-01-01'). + + Domain filtering: API-supported fields (api_domain) are sent to WooCommerce, + unsupported fields (local_domain) are filtered locally after fetch. + + Count optimization: When count=True and no local filtering needed, + makes 1 API call with per_page=1 and extracts count from headers. + + :param resource: WooCommerce endpoint (e.g., "products", "orders") + :param domain: Odoo-style domain filters + :param offset: Records to skip (simulated via pagination) + :param limit: Max records to return + :param count: If True, return count; if False, return data + :return: list[dict] or int + """ + # flake8: noqa: C901 + if resource == "system_status": + return self._exec_wcapi_call("get", resource) # , *args, **kwargs) + # prepare parameters + if domain is None: + domain = [] + if limit is not None: + if limit < 0: + limit = 0 + if offset < 0: + offset = 0 + + # get domains + # Separate domain into: + # - api_domain: Domain clauses supported natively by WooCommerce API + # - local_domain: Domain clauses that must be applied locally in + # Odoo after fetching + search_fields = self._get_search_fields() + api_domain, local_domain = self._extract_domain_clauses(domain, search_fields) + + # get the api call parameters + params = self._domain_to_normalized_dict(api_domain) + # per_page (records per page) + if "per_page" in params: + raise ValidationError( + _( + "The 'per_page' parameter is managed automatically " + "in WooCommerce API calls. Do not use it " + "in the domain." + ) + ) + page_size = self.backend_record.page_size + if page_size > 0: + params["per_page"] = page_size + if count: + # Optimization: If no local filtering, only fetch 1 record + # to minimize data transfer + # We only need the total count from headers (X-WP-Total) + if not local_domain: + params["per_page"] = 1 + params["_fields"] = "id" # only need the ids to count + + # Deduplication protection: WooCommerce API may return duplicate IDs + # in some edge cases (e.g., when items are modified during pagination) + seen_ids = set() + all_data = [] + counter = 0 + page = 1 + api_calls = 0 + while True: + res = self._exec_wcapi_call( + "get", + resource, + params={ + **params, + "page": page, + }, + ) + api_calls += 1 + + # Optimization: If counting with no local filtering, + # use total from API headers + if count and not local_domain: + counter = res["total_items"] + break + + if res["returned_items"] == 0: + break + data = [d for d in res["data"] if d["id"] not in seen_ids] + data = self._filter(data, local_domain) + seen_ids |= {d["id"] for d in data} + + # Compute offset (simulate offset by discarding records from early pages) + # This is necessary because WooCommerce's offset + # parameter is incompatible with page parameter + if data and offset > 0: + data_count = len(data) + if offset < data_count: + # We've reached the page containing the offset position + # Keep only records after the offset + data = data[offset:] + offset = 0 # Reset offset since we've consumed it + else: + # This entire page is before the offset position + # Discard all records and reduce offset by page size + data = [] + offset -= data_count + + data_count = len(data) + + # Compute limit (stop when we've collected enough records) + if limit is not None and (counter + data_count) >= limit: + # We have more records than needed, take only what's required + remaining_items = limit - counter + all_data += data[:remaining_items] + counter += remaining_items + break + counter += data_count + if not count: + all_data += data + if page >= res["total_pages"]: + break + page += 1 + + if self.backend_record.enable_call_logging: + _logger.info( + "WooCommerce API Response (_exec_get) " + "- OP: %s, Resource: %s, Domain: %s, " + "Offset: %s, Limit: %s, Count: %s, Params: %s, API Calls: %s", + "GET", + resource, + domain, + offset, + limit, + count, + params, + api_calls, + ) + if count: + return counter + else: + return all_data + + def _exec_post(self, resource, *args, **kwargs): + res = self._exec_wcapi_call( + "post", + resource, + *args, + **kwargs, + ) + if res["total_items"] != 1: + raise ValidationError( + _( + "Unexpected response from WooCommerce " + "POST %(resource)s: (response))s" + ) + % { + "resource": resource, + "response": res, + } + ) + return res["data"][0] + + def _exec_put(self, resource, *args, **kwargs): + res = self._exec_wcapi_call("put", resource, *args, **kwargs) + if res["total_items"] != 1: + raise ValidationError( + _( + "Unexpected response from WooCommerce " + "PUT %(resource)s: %(response)s" + ) + % { + "resource": resource, + "response": res, + } + ) + return res["data"][0] + + def _exec_delete(self, resource, *args, **kwargs): + res = self._exec_wcapi_call( + "delete", + resource, + *args, + **kwargs, + ) + if res["total_items"] != 1: + raise ValidationError( + _( + "Unexpected response from WooCommerce " + "DELETE %(resource)s: %(response)s" + ) + % { + "resource": resource, + "response": res, + } + ) + return res["data"][0] + + def _exec_options(self, resource, *args, **kwargs): + raise NotImplementedError() + + def get_version(self): + system_status = self._exec("get", "system_status") + version = False + if system_status: + if system_status.get("returned_items", 0) != 1: + raise ValidationError( + _("Unexpected response from WooCommerce system_status: %s") + % system_status + ) + version = system_status["data"][0].get("environment", {}).get("version") + return version diff --git a/connector_extension_woocommerce/i18n/ca.po b/connector_extension_woocommerce/i18n/ca.po new file mode 100644 index 000000000..9a5aa5098 --- /dev/null +++ b/connector_extension_woocommerce/i18n/ca.po @@ -0,0 +1,82 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * connector_extension_woocommerce +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 14.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-10-22 12:51+0000\n" +"PO-Revision-Date: 2024-10-22 12:51+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: connector_extension_woocommerce +#: code:addons/connector_extension_woocommerce/components/adapter.py:0 +#, python-format +msgid "Error connecting to WooCommerce: %s" +msgstr "Error al connectar amb WooCommerce: %s" + +#. module: connector_extension_woocommerce +#: code:addons/connector_extension_woocommerce/components/adapter.py:0 +#, python-format +msgid "" +"Error decoding json WooCommerce response: %s\n" +"Args:%s\n" +"Kwargs:%s\n" +"URL:%s\n" +"Headers:%s\n" +"Method:%s\n" +"Body:%s" +msgstr "" +"Error decodificant la resposta json de WooCommerce: %s\n" +"Args:%s\n" +"Kwargs:%s\n" +"URL:%s\n" +"Headers:%s\n" +"Method:%s\n" +"Body:%s" + +#. module: connector_extension_woocommerce +#: code:addons/connector_extension_woocommerce/components/adapter.py:0 +#, python-format +msgid "Error: %s, Resource: %s" +msgstr "Error: %s, Recurs: %s" + +#. module: connector_extension_woocommerce +#: code:addons/connector_extension_woocommerce/components/adapter.py:0 +#, python-format +msgid "" +"Error: '%s'. Probably repeated record already exists in Woocommerce.\n" +"Please, review the data in %s/%s and compare it with %s" +msgstr "" +"Error: '%s'. Probablement el registre repetit ja existeix a Woocommerce.\n" +"Si us plau, revisa les dades a %s/%s i compara-les amb %s" + +#. module: connector_extension_woocommerce +#: code:addons/connector_extension_woocommerce/components/adapter.py:0 +#: code:addons/connector_extension_woocommerce/components/adapter.py:0 +#: code:addons/connector_extension_woocommerce/components/adapter.py:0 +#, python-format +msgid "" +"Error: '%s'. Probably the %s has been removed from Woocommerce. If it's the " +"case, try to remove the binding of the %s." +msgstr "" +"Error: '%s'. Probablement el %s ha estat eliminat de Woocommerce. Si és el " +"cas, intenta eliminar la vinculació del %s." + +#. module: connector_extension_woocommerce +#: code:addons/connector_extension_woocommerce/components/adapter.py:0 +#, python-format +msgid "" +"Error: '%s'. Probably the product in %s has been removed from Woocommerce. " +"If it's the case, try to remove the binding of the " +"woocommerce.product.template" +msgstr "" +"Error: '%s'. Probablement el producte a %s ha estat eliminat de Woocommerce. " +"Si és el cas, intenta eliminar la vinculació del " +"woocommerce.product.template" diff --git a/connector_extension_woocommerce/i18n/es.po b/connector_extension_woocommerce/i18n/es.po new file mode 100644 index 000000000..2fe5efed6 --- /dev/null +++ b/connector_extension_woocommerce/i18n/es.po @@ -0,0 +1,82 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * connector_extension_woocommerce +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 14.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-10-22 12:50+0000\n" +"PO-Revision-Date: 2024-10-22 12:50+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: connector_extension_woocommerce +#: code:addons/connector_extension_woocommerce/components/adapter.py:0 +#, python-format +msgid "Error connecting to WooCommerce: %s" +msgstr "Error de conexión a WooCommerce: %s" + +#. module: connector_extension_woocommerce +#: code:addons/connector_extension_woocommerce/components/adapter.py:0 +#, python-format +msgid "" +"Error decoding json WooCommerce response: %s\n" +"Args:%s\n" +"Kwargs:%s\n" +"URL:%s\n" +"Headers:%s\n" +"Method:%s\n" +"Body:%s" +msgstr "" +"Error al decodificar la respuesta json de WooCommerce: %s\n" +"Args:%s\n" +"Kwargs:%s\n" +"URL:%s\n" +"Headers:%s\n" +"Método:%s\n" +"Cuerpo:%s" + +#. module: connector_extension_woocommerce +#: code:addons/connector_extension_woocommerce/components/adapter.py:0 +#, python-format +msgid "Error: %s, Resource: %s" +msgstr "Error: %s, Recurso: %s" + +#. module: connector_extension_woocommerce +#: code:addons/connector_extension_woocommerce/components/adapter.py:0 +#, python-format +msgid "" +"Error: '%s'. Probably repeated record already exists in Woocommerce.\n" +"Please, review the data in %s/%s and compare it with %s" +msgstr "" +"Error: '%s'. Probablemente el registro repetido ya existe en Woocommerce.\n" +"Por favor, revise los datos en %s/%s y compárelos con %s" + +#. module: connector_extension_woocommerce +#: code:addons/connector_extension_woocommerce/components/adapter.py:0 +#: code:addons/connector_extension_woocommerce/components/adapter.py:0 +#: code:addons/connector_extension_woocommerce/components/adapter.py:0 +#, python-format +msgid "" +"Error: '%s'. Probably the %s has been removed from Woocommerce. If it's the " +"case, try to remove the binding of the %s." +msgstr "" +"Error: '%s'. Probablemente el %s ha sido eliminado de Woocommerce. Si es el " +"caso, intente eliminar el enlace del %s." + +#. module: connector_extension_woocommerce +#: code:addons/connector_extension_woocommerce/components/adapter.py:0 +#, python-format +msgid "" +"Error: '%s'. Probably the product in %s has been removed from Woocommerce. " +"If it's the case, try to remove the binding of the " +"woocommerce.product.template" +msgstr "" +"Error: '%s'. Probablemente el producto en %s ha sido eliminado de " +"Woocommerce. Si es el caso, intente eliminar el enlace de la " +"woocommerce.product.template" diff --git a/connector_extension_woocommerce/pyproject.toml b/connector_extension_woocommerce/pyproject.toml new file mode 100644 index 000000000..f759fc0b0 --- /dev/null +++ b/connector_extension_woocommerce/pyproject.toml @@ -0,0 +1,4 @@ + +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/connector_extension_woocommerce/readme/CONTRIBUTORS.md b/connector_extension_woocommerce/readme/CONTRIBUTORS.md new file mode 100644 index 000000000..0355cecbc --- /dev/null +++ b/connector_extension_woocommerce/readme/CONTRIBUTORS.md @@ -0,0 +1,4 @@ +- [NuoBiT](https://www.nuobit.com): + - Kilian Niubo + - Eric Antones + - Deniz Gallo diff --git a/connector_extension_woocommerce/readme/DESCRIPTION.md b/connector_extension_woocommerce/readme/DESCRIPTION.md new file mode 100644 index 000000000..cf1445fe3 --- /dev/null +++ b/connector_extension_woocommerce/readme/DESCRIPTION.md @@ -0,0 +1 @@ +This module extends the connector extension module to add support for Woocommerce diff --git a/connector_extension_woocommerce/static/description/icon.png b/connector_extension_woocommerce/static/description/icon.png new file mode 100644 index 000000000..1cd641e79 Binary files /dev/null and b/connector_extension_woocommerce/static/description/icon.png differ diff --git a/connector_extension_woocommerce/static/description/index.html b/connector_extension_woocommerce/static/description/index.html new file mode 100644 index 000000000..17a7a7b18 --- /dev/null +++ b/connector_extension_woocommerce/static/description/index.html @@ -0,0 +1,422 @@ + + + + + +Connector Extension Woocommerce + + + +
+

Connector Extension Woocommerce

+ + +

Beta License: LGPL-3 NuoBiT/odoo-addons

+

This module extends the connector extension module to add support for +Woocommerce

+

Table of contents

+ +
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • NuoBiT Solutions SL
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is part of the NuoBiT/odoo-addons project on GitHub.

+

You are welcome to contribute.

+
+
+
+ + diff --git a/setup/connector_extension_woocommerce/odoo/addons/connector_extension_woocommerce b/setup/connector_extension_woocommerce/odoo/addons/connector_extension_woocommerce new file mode 120000 index 000000000..257fd24c0 --- /dev/null +++ b/setup/connector_extension_woocommerce/odoo/addons/connector_extension_woocommerce @@ -0,0 +1 @@ +../../../../connector_extension_woocommerce \ No newline at end of file diff --git a/setup/connector_extension_woocommerce/setup.py b/setup/connector_extension_woocommerce/setup.py new file mode 100644 index 000000000..28c57bb64 --- /dev/null +++ b/setup/connector_extension_woocommerce/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +)