diff --git a/sale_product_origin_report/README.rst b/sale_product_origin_report/README.rst new file mode 100644 index 00000000..a9baf705 --- /dev/null +++ b/sale_product_origin_report/README.rst @@ -0,0 +1,54 @@ +.. image:: https://img.shields.io/badge/license-AGPL--3-blue.svg + :target: https://opensource.org/licenses/AGPL-3.0 + :alt: License: AGPL-3 + +========================== +Sale Product Origin Report +========================== + +This module extends sale quotation/order reports and adds product extra information +to each regular sale order line: + +* Product name as line title (first line only) +* Product origin from sales description (``description_sale``) +* Optional external product URL (if available and valid) + +Behavior +======== + +* The product name is shown in the Description column. +* If a valid product URL exists, product name is shown as a clickable link. +* If product sales description exists, it is shown below as ``Origin: ...``. +* Invalid URL placeholders such as ``0.0`` are ignored. +* If URL/origin do not exist, report remains unchanged for that line. + +Compatibility +============= + +URL lookup is compatible with these optional modules: + +* ``product_variant_url`` (``external_url``) +* ``website_sale_product_external_link`` (``website_link``) + +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 smash it by providing detailed and welcomed feedback. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Contributors +------------ + +* AvanzOSC + +License +======= + +This project is licensed under the AGPL-3 License. For more details, refer to the LICENSE file or visit . diff --git a/sale_product_origin_report/__init__.py b/sale_product_origin_report/__init__.py new file mode 100644 index 00000000..0650744f --- /dev/null +++ b/sale_product_origin_report/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/sale_product_origin_report/__manifest__.py b/sale_product_origin_report/__manifest__.py new file mode 100644 index 00000000..817c6f6b --- /dev/null +++ b/sale_product_origin_report/__manifest__.py @@ -0,0 +1,15 @@ +# Copyright 2026 AvanzOSC +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +{ + "name": "Sale Product Origin Report", + "version": "16.0.1.1.0", + "category": "Sales", + "summary": "Show product origin and valid external link in sale reports", + "depends": ["sale"], + "data": ["views/sale_report_templates.xml"], + "installable": True, + "auto_install": False, + "author": "AvanzOSC", + "license": "AGPL-3", + "website": "https://github.com/avanzosc/sale-addons", +} diff --git a/sale_product_origin_report/i18n/es.po b/sale_product_origin_report/i18n/es.po new file mode 100644 index 00000000..26b26504 --- /dev/null +++ b/sale_product_origin_report/i18n/es.po @@ -0,0 +1,22 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * sale_product_origin_report +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2026-02-25 10:10+0000\n" +"PO-Revision-Date: 2026-02-25 10:10+0000\n" +"Last-Translator: \n" +"Language-Team: Spanish\n" +"Language: es\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: sale_product_origin_report +#: model_terms:ir.ui.view,arch_db:sale_product_origin_report.report_saleorder_document +msgid "Origin:" +msgstr "Origen:" diff --git a/sale_product_origin_report/i18n/sale_product_origin_report.pot b/sale_product_origin_report/i18n/sale_product_origin_report.pot new file mode 100644 index 00000000..b3b17447 --- /dev/null +++ b/sale_product_origin_report/i18n/sale_product_origin_report.pot @@ -0,0 +1,21 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * sale_product_origin_report +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2026-02-25 10:10+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: sale_product_origin_report +#: model_terms:ir.ui.view,arch_db:sale_product_origin_report.report_saleorder_document +msgid "Origin:" +msgstr "" diff --git a/sale_product_origin_report/models/__init__.py b/sale_product_origin_report/models/__init__.py new file mode 100644 index 00000000..8eb9d1d4 --- /dev/null +++ b/sale_product_origin_report/models/__init__.py @@ -0,0 +1 @@ +from . import sale_order_line diff --git a/sale_product_origin_report/models/sale_order_line.py b/sale_product_origin_report/models/sale_order_line.py new file mode 100644 index 00000000..135ccc3e --- /dev/null +++ b/sale_product_origin_report/models/sale_order_line.py @@ -0,0 +1,74 @@ +# Copyright 2026 AvanzOSC +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from odoo import models + + +class SaleOrderLine(models.Model): + _inherit = "sale.order.line" + + def _normalize_report_url(self, raw_url): + if not raw_url: + return False + if not isinstance(raw_url, str): + raw_url = str(raw_url) + url = raw_url.strip() + if not url: + return False + + if url.lower() in {"0", "0.0", "false", "none", "null"}: + return False + + if url.startswith(("http://", "https://")): + return url + + if "://" not in url and "." in url and " " not in url: + return f"https://{url}" + + return False + + def _get_product_origin_for_report(self): + self.ensure_one() + product = self.product_id + if not product: + return False + origin = product.description_sale or product.product_tmpl_id.description_sale + if not origin: + return False + origin = origin.strip() + line_title = (self.name or "").split("\n")[0].strip() + if line_title and origin.lower() == line_title.lower(): + return False + return origin + + def _get_product_external_url_for_report(self): + self.ensure_one() + product = self.product_id + if not product: + return False + + if "external_url" in product._fields and product.external_url: + url = self._normalize_report_url(product.external_url) + if url: + return url + if ( + "external_url" in product.product_tmpl_id._fields + and product.product_tmpl_id.external_url + ): + url = self._normalize_report_url(product.product_tmpl_id.external_url) + if url: + return url + + if "website_link" in product._fields and product.website_link: + url = self._normalize_report_url(product.website_link) + if url: + return url + if ( + "website_link" in product.product_tmpl_id._fields + and product.product_tmpl_id.website_link + ): + url = self._normalize_report_url(product.product_tmpl_id.website_link) + if url: + return url + + return False diff --git a/sale_product_origin_report/views/sale_report_templates.xml b/sale_product_origin_report/views/sale_report_templates.xml new file mode 100644 index 00000000..020fb772 --- /dev/null +++ b/sale_product_origin_report/views/sale_report_templates.xml @@ -0,0 +1,41 @@ + + + + diff --git a/setup/sale_product_origin_report/odoo/addons/sale_product_origin_report b/setup/sale_product_origin_report/odoo/addons/sale_product_origin_report new file mode 120000 index 00000000..0cdcdb63 --- /dev/null +++ b/setup/sale_product_origin_report/odoo/addons/sale_product_origin_report @@ -0,0 +1 @@ +../../../../sale_product_origin_report \ No newline at end of file diff --git a/setup/sale_product_origin_report/setup.py b/setup/sale_product_origin_report/setup.py new file mode 100644 index 00000000..28c57bb6 --- /dev/null +++ b/setup/sale_product_origin_report/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +)