diff --git a/web_widget_product_label_section_and_note/README.rst b/web_widget_product_label_section_and_note/README.rst new file mode 100644 index 000000000..4d03bc616 --- /dev/null +++ b/web_widget_product_label_section_and_note/README.rst @@ -0,0 +1,106 @@ +========================================= +Web widget product label section and note +========================================= + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:b34f3ca01ada6453f702e988c896537cf322d1f7dae827582f4e07313735dc66 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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%2Fweb-lightgray.png?logo=github + :target: https://github.com/OCA/web/tree/16.0/web_widget_product_label_section_and_note + :alt: OCA/web +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/web-16-0/web-16-0-web_widget_product_label_section_and_note + :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/web&target_branch=16.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module unifies the product and name into a single column, making it more user-friendly and space-saving. + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +To extend this functionality to other modules (for example, Purchase), +in a new module, it is necessary to create an inherited view to change the widget for these fields: + +- `one2many` field with `widget="product_label_section_and_note_field_o2m"`. + +- `product_id` or product_tmpl_id field with `widget="product_label_section_and_note_field"`. + +- `name` field with `widget="section_and_note_text"`. + +Usage +===== + +- Go to the **Invoicing > Customers > Invoices** and add a new line. +- The product label must be displayed just after the product name. +- If the product does not have a description, a button must be displayed to add one. + +Known issues / Roadmap +====================== + +- Add compatibility with `sale` module +- Add compatibility with `purchase_product_matrix` module +- When this module is installed, the PDF report will always display the column `name` as a combination of the `product code`, `product name`, and `description`. This behavior may differ from the expected behavior, where only the column `name` is displayed and can be customized independently. + +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 +~~~~~~~ + +* Tecnativa +* Odoo S.A. + +Contributors +~~~~~~~~~~~~ + +* `Tecnativa `_: + + * Pedro M. Baeza + * Carlos Lopez + +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/web `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/web_widget_product_label_section_and_note/__init__.py b/web_widget_product_label_section_and_note/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/web_widget_product_label_section_and_note/__manifest__.py b/web_widget_product_label_section_and_note/__manifest__.py new file mode 100644 index 000000000..05eae540e --- /dev/null +++ b/web_widget_product_label_section_and_note/__manifest__.py @@ -0,0 +1,22 @@ +{ + "name": "Web widget product label section and note", + "version": "16.0.1.0.0", + "summary": "unify the product and name into a single column", + "author": "Tecnativa, Odoo Community Association (OCA), Odoo S.A.", + "website": "https://github.com/OCA/web", + "depends": [ + "web", + "account", + ], + "data": [ + "views/account_move_views.xml", + ], + "assets": { + "web.assets_backend": [ + "web_widget_product_label_section_and_note/static/src/components/**/*", + ], + }, + "installable": True, + "auto_install": False, + "license": "AGPL-3", +} diff --git a/web_widget_product_label_section_and_note/i18n/web_widget_product_label_section_and_note.pot b/web_widget_product_label_section_and_note/i18n/web_widget_product_label_section_and_note.pot new file mode 100644 index 000000000..e249e0afb --- /dev/null +++ b/web_widget_product_label_section_and_note/i18n/web_widget_product_label_section_and_note.pot @@ -0,0 +1,63 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * web_widget_product_label_section_and_note +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \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: web_widget_product_label_section_and_note +#. odoo-javascript +#: code:addons/web_widget_product_label_section_and_note/static/src/components/product_label_section_and_note_field/product_label_section_and_note_field.xml:0 +#, python-format +msgid "Click or press enter to add a description" +msgstr "" + +#. module: web_widget_product_label_section_and_note +#. odoo-javascript +#: code:addons/web_widget_product_label_section_and_note/static/src/components/product_label_section_and_note_field/product_label_section_and_note_field.xml:0 +#, python-format +msgid "Click to remove/add the product name from the description." +msgstr "" + +#. module: web_widget_product_label_section_and_note +#. odoo-javascript +#: code:addons/web_widget_product_label_section_and_note/static/src/components/product_label_section_and_note_field/product_label_section_and_note_field.xml:0 +#: code:addons/web_widget_product_label_section_and_note/static/src/components/product_label_section_and_note_field/product_label_section_and_note_field.xml:0 +#: code:addons/web_widget_product_label_section_and_note/static/src/components/product_label_section_and_note_field/product_label_section_and_note_field.xml:0 +#: code:addons/web_widget_product_label_section_and_note/static/src/components/product_label_section_and_note_field/product_label_section_and_note_field.xml:0 +#, python-format +msgid "Enter a description" +msgstr "" + +#. module: web_widget_product_label_section_and_note +#. odoo-javascript +#: code:addons/web_widget_product_label_section_and_note/static/src/components/product_label_section_and_note_field/product_label_section_and_note_field.xml:0 +#: code:addons/web_widget_product_label_section_and_note/static/src/components/product_label_section_and_note_field/product_label_section_and_note_field.xml:0 +#, python-format +msgid "Internal link" +msgstr "" + +#. module: web_widget_product_label_section_and_note +#. odoo-javascript +#: code:addons/web_widget_product_label_section_and_note/static/src/components/product_label_section_and_note_field/product_label_section_and_note_field.xml:0 +#: code:addons/web_widget_product_label_section_and_note/static/src/components/product_label_section_and_note_field/product_label_section_and_note_field.xml:0 +#: code:addons/web_widget_product_label_section_and_note/static/src/components/product_label_section_and_note_field/product_label_section_and_note_field.xml:0 +#, python-format +msgid "Scan barcode" +msgstr "" + +#. module: web_widget_product_label_section_and_note +#. odoo-javascript +#: code:addons/web_widget_product_label_section_and_note/static/src/components/product_label_section_and_note_field/product_label_section_and_note_field.esm.js:0 +#: code:addons/web_widget_product_label_section_and_note/static/src/components/product_label_section_and_note_field/product_label_section_and_note_field.xml:0 +#, python-format +msgid "Search a product" +msgstr "" diff --git a/web_widget_product_label_section_and_note/readme/CONFIGURE.rst b/web_widget_product_label_section_and_note/readme/CONFIGURE.rst new file mode 100644 index 000000000..9dec51ac9 --- /dev/null +++ b/web_widget_product_label_section_and_note/readme/CONFIGURE.rst @@ -0,0 +1,8 @@ +To extend this functionality to other modules (for example, Purchase), +in a new module, it is necessary to create an inherited view to change the widget for these fields: + +- `one2many` field with `widget="product_label_section_and_note_field_o2m"`. + +- `product_id` or product_tmpl_id field with `widget="product_label_section_and_note_field"`. + +- `name` field with `widget="section_and_note_text"`. diff --git a/web_widget_product_label_section_and_note/readme/CONTRIBUTORS.rst b/web_widget_product_label_section_and_note/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000..2ab4a0d07 --- /dev/null +++ b/web_widget_product_label_section_and_note/readme/CONTRIBUTORS.rst @@ -0,0 +1,4 @@ +* `Tecnativa `_: + + * Pedro M. Baeza + * Carlos Lopez \ No newline at end of file diff --git a/web_widget_product_label_section_and_note/readme/DESCRIPTION.rst b/web_widget_product_label_section_and_note/readme/DESCRIPTION.rst new file mode 100644 index 000000000..08fd5b2bd --- /dev/null +++ b/web_widget_product_label_section_and_note/readme/DESCRIPTION.rst @@ -0,0 +1 @@ +This module unifies the product and name into a single column, making it more user-friendly and space-saving. \ No newline at end of file diff --git a/web_widget_product_label_section_and_note/readme/ROADMAP.rst b/web_widget_product_label_section_and_note/readme/ROADMAP.rst new file mode 100644 index 000000000..f534a99ea --- /dev/null +++ b/web_widget_product_label_section_and_note/readme/ROADMAP.rst @@ -0,0 +1,3 @@ +- Add compatibility with `sale` module +- Add compatibility with `purchase_product_matrix` module +- When this module is installed, the PDF report will always display the column `name` as a combination of the `product code`, `product name`, and `description`. This behavior may differ from the expected behavior, where only the column `name` is displayed and can be customized independently. \ No newline at end of file diff --git a/web_widget_product_label_section_and_note/readme/USAGE.rst b/web_widget_product_label_section_and_note/readme/USAGE.rst new file mode 100644 index 000000000..a70feee9e --- /dev/null +++ b/web_widget_product_label_section_and_note/readme/USAGE.rst @@ -0,0 +1,3 @@ +- Go to the **Invoicing > Customers > Invoices** and add a new line. +- The product label must be displayed just after the product name. +- If the product does not have a description, a button must be displayed to add one. \ No newline at end of file diff --git a/web_widget_product_label_section_and_note/static/description/icon.png b/web_widget_product_label_section_and_note/static/description/icon.png new file mode 100644 index 000000000..3a0328b51 Binary files /dev/null and b/web_widget_product_label_section_and_note/static/description/icon.png differ diff --git a/web_widget_product_label_section_and_note/static/description/index.html b/web_widget_product_label_section_and_note/static/description/index.html new file mode 100644 index 000000000..0555ba047 --- /dev/null +++ b/web_widget_product_label_section_and_note/static/description/index.html @@ -0,0 +1,460 @@ + + + + + +Web widget product label section and note + + + +
+

Web widget product label section and note

+ + +

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

+

This module unifies the product and name into a single column, making it more user-friendly and space-saving.

+

Table of contents

+ +
+

Configuration

+

To extend this functionality to other modules (for example, Purchase), +in a new module, it is necessary to create an inherited view to change the widget for these fields:

+
    +
  • one2many field with widget=”product_label_section_and_note_field_o2m”.
  • +
  • product_id or product_tmpl_id field with widget=”product_label_section_and_note_field”.
  • +
  • name field with widget=”section_and_note_text”.
  • +
+
+
+

Usage

+
    +
  • Go to the Invoicing > Customers > Invoices and add a new line.
  • +
  • The product label must be displayed just after the product name.
  • +
  • If the product does not have a description, a button must be displayed to add one.
  • +
+
+
+

Known issues / Roadmap

+
    +
  • Add compatibility with sale module
  • +
  • Add compatibility with purchase_product_matrix module
  • +
  • When this module is installed, the PDF report will always display the column name as a combination of the product code, product name, and description. This behavior may differ from the expected behavior, where only the column name is displayed and can be customized independently.
  • +
+
+
+

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

+
    +
  • Tecnativa
  • +
  • Odoo S.A.
  • +
+
+
+

Contributors

+
    +
  • Tecnativa:

    +
    +
      +
    • Pedro M. Baeza
    • +
    • Carlos Lopez
    • +
    +
    +
  • +
+
+
+

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

+

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

+
+
+
+ + diff --git a/web_widget_product_label_section_and_note/static/src/components/product_label_section_and_note_field/product_label_section_and_note_field.esm.js b/web_widget_product_label_section_and_note/static/src/components/product_label_section_and_note_field/product_label_section_and_note_field.esm.js new file mode 100644 index 000000000..7fffe74df --- /dev/null +++ b/web_widget_product_label_section_and_note/static/src/components/product_label_section_and_note_field/product_label_section_and_note_field.esm.js @@ -0,0 +1,271 @@ +/** @odoo-module **/ +/* Copyright Odoo S.A. + * Copyright 2024 Tecnativa - Carlos Lopez + * License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). */ + +import { + onMounted, + onPatched, + onWillUnmount, + onWillUpdateProps, + useEffect, + useRef, + useState, +} from "@odoo/owl"; +import {AutoComplete} from "@web/core/autocomplete/autocomplete"; +import {Many2OneField} from "@web/views/fields/many2one/many2one_field"; +import {Many2XAutocomplete} from "@web/views/fields/relational_utils"; +import {SectionAndNoteListRenderer} from "@account/components/section_and_note_fields_backend/section_and_note_fields_backend"; +import {X2ManyField} from "@web/views/fields/x2many/x2many_field"; +import {_t} from "@web/core/l10n/translation"; +import {getActiveHotkey} from "@web/core/hotkeys/hotkey_service"; +import {registry} from "@web/core/registry"; + +export class ProductLabelSectionAndNoteListRender extends SectionAndNoteListRenderer { + setup() { + this.productColumns = ["product_id", "product_template_id"]; + // We need to store the titleField in a temporary variable + // because the titleField is set in the parent setup method + // and the function getActiveColumns is called before the setup method in the account module + this.titleField_tmp = ""; + super.setup(); + if (this.titleField_tmp) { + this.titleField = this.titleField_tmp; + } + } + + getCellTitle(column, record) { + // When using this list renderer, we don't want the product_id cell to have a tooltip with its label. + if (this.productColumns.includes(column.name)) { + return; + } + super.getCellTitle(column, record); + } + + getActiveColumns(list) { + let activeColumns = super.getActiveColumns(list); + const productCol = activeColumns.find((col) => + this.productColumns.includes(col.name) + ); + const labelCol = activeColumns.find((col) => col.name === "name"); + + if (productCol) { + if (labelCol) { + list.records.forEach( + (record) => (record.columnIsProductAndLabel = true) + ); + } else { + list.records.forEach( + (record) => (record.columnIsProductAndLabel = false) + ); + } + activeColumns = activeColumns.filter((col) => col.name !== "name"); + this.titleField_tmp = productCol.name; + } else { + this.titleField_tmp = "name"; + } + + return activeColumns; + } +} + +export class ProductLabelSectionAndNoteOne2Many extends X2ManyField {} +ProductLabelSectionAndNoteOne2Many.components = { + ...X2ManyField.components, + ListRenderer: ProductLabelSectionAndNoteListRender, +}; +ProductLabelSectionAndNoteOne2Many.additionalClasses = + SectionAndNoteListRenderer.additionalClasses; + +registry + .category("fields") + .add( + "product_label_section_and_note_field_o2m", + ProductLabelSectionAndNoteOne2Many + ); + +export class ProductLabelSectionAndNoteAutocomplete extends AutoComplete { + setup() { + super.setup(); + this.labelTextarea = useRef("labelNodeRef"); + } + onInputKeydown(event) { + super.onInputKeydown(event); + const hotkey = getActiveHotkey(event); + const labelVisibilityButton = document.getElementById( + "labelVisibilityButtonId" + ); + if (hotkey === "enter") { + if (labelVisibilityButton && !this.labelTextarea.el) { + labelVisibilityButton.click(); + event.stopPropagation(); + event.preventDefault(); + } + } + } +} + +export class ProductLabelSectionAndNoteFieldAutocomplete extends Many2XAutocomplete { + setup() { + super.setup(); + this.input = useRef("section_and_note_input"); + } + + get isSectionOrNote() { + return this.props.isSection || this.props.isNote; + } + + get isSection() { + return this.props.isSection; + } +} + +ProductLabelSectionAndNoteFieldAutocomplete.components = { + ...Many2XAutocomplete.components, + AutoComplete: ProductLabelSectionAndNoteAutocomplete, +}; +ProductLabelSectionAndNoteFieldAutocomplete.template = + "web_widget_product_label_section_and_note.ProductLabelSectionAndNoteFieldAutocomplete"; + +export class ProductLabelSectionAndNoteField extends Many2OneField { + setup() { + super.setup(); + this.isPrintMode = useState({value: false}); + this.labelVisibility = useState({value: false}); + this.isProductVisible = useState({value: false}); + this.switchToLabel = false; + this.columnIsProductAndLabel = useState({ + value: this.props.record.columnIsProductAndLabel, + }); + this.labelNode = useRef("labelNodeRef"); + this.productNode = useRef("productNodeRef"); + + useEffect( + () => { + this.columnIsProductAndLabel.value = + this.props.record.columnIsProductAndLabel; + }, + () => [this.props.record.columnIsProductAndLabel] + ); + + onPatched(() => { + if (this.labelNode.el && this.switchToLabel) { + this.switchToLabel = false; + this.labelNode.el.focus(); + } + }); + + this.onBeforePrint = () => { + this.isPrintMode.value = true; + }; + + this.onAfterPrint = () => { + this.isPrintMode.value = false; + }; + + // The following hooks are used to make a div visible only in the print view. This div is necessary in the + // print view in order not to have scroll bars but can't be displayed in the normal view because it adds + // an empty line. This is done by switching an attribute to true only during the print view life cycle and + // including the said div in a t-if depending on that attribute. + onMounted(() => { + window.addEventListener("beforeprint", this.onBeforePrint); + window.addEventListener("afterprint", this.onAfterPrint); + }); + + onWillUnmount(() => { + window.removeEventListener("beforeprint", this.onBeforePrint); + window.removeEventListener("afterprint", this.onAfterPrint); + }); + onWillUpdateProps((newProps) => { + const label = newProps.record.data.name || ""; + this.isProductVisible.value = label.includes(this.productName); + }); + } + + get productName() { + return this.props.record.data[this.props.name][1]; + } + + get label() { + let label = this.props.record.data.name || ""; + if (label.includes(this.productName)) { + label = label.replace(this.productName, ""); + if (label.includes("\n")) { + label = label.replace("\n", ""); + } + } + return label; + } + + get Many2XAutocompleteProps() { + const props = super.Many2XAutocompleteProps; + props.isSection = this.isSection(this.props.record); + props.isNote = this.isNote(this.props.record); + props.placeholder = _t("Search a product"); + props.updateLabel = this.updateLabel.bind(this); + return props; + } + + get isProductClickable() { + return this.props.record.evalContext.parent.state !== "draft"; + } + + get isSectionOrNote() { + return this.isSection(this.props.record) || this.isNote(this.props.record); + } + + get sectionAndNoteClasses() { + if (this.isSection()) { + return "fw-bold"; + } else if (this.isNote()) { + return "fst-italic"; + } + return ""; + } + + isSection(record = null) { + record = record || this.props.record; + return record.data.display_type === "line_section"; + } + + isNote(record = null) { + record = record || this.props.record; + return record.data.display_type === "line_note"; + } + + switchLabelVisibility() { + this.labelVisibility.value = !this.labelVisibility.value; + this.switchToLabel = true; + } + + switchProductVisibility() { + let new_name = ""; + if (this.isProductVisible.value && this.productName) { + new_name = this.label; + } else { + new_name = this.productName + "\n" + this.label; + } + this.props.record.update({name: new_name}); + this.isProductVisible.value = !this.isProductVisible.value; + } + + updateLabel(value) { + this.props.record.update({ + name: + this.productName && this.productName !== value + ? `${this.productName}\n${value}` + : value, + }); + } +} + +ProductLabelSectionAndNoteField.components = { + ...Many2OneField.components, + Many2XAutocomplete: ProductLabelSectionAndNoteFieldAutocomplete, +}; +ProductLabelSectionAndNoteField.template = + "web_widget_product_label_section_and_note.ProductLabelSectionAndNoteField"; + +registry + .category("fields") + .add("product_label_section_and_note_field", ProductLabelSectionAndNoteField); diff --git a/web_widget_product_label_section_and_note/static/src/components/product_label_section_and_note_field/product_label_section_and_note_field.scss b/web_widget_product_label_section_and_note/static/src/components/product_label_section_and_note_field/product_label_section_and_note_field.scss new file mode 100644 index 000000000..da2eeef8c --- /dev/null +++ b/web_widget_product_label_section_and_note/static/src/components/product_label_section_and_note_field/product_label_section_and_note_field.scss @@ -0,0 +1,5 @@ +.o_field_product_label_section_and_note_cell { + textarea { + resize: none; + } +} diff --git a/web_widget_product_label_section_and_note/static/src/components/product_label_section_and_note_field/product_label_section_and_note_field.xml b/web_widget_product_label_section_and_note/static/src/components/product_label_section_and_note_field/product_label_section_and_note_field.xml new file mode 100644 index 000000000..3e512fd94 --- /dev/null +++ b/web_widget_product_label_section_and_note/static/src/components/product_label_section_and_note_field/product_label_section_and_note_field.xml @@ -0,0 +1,219 @@ + + + + +
+ + + + + + + + + + Search a product + + +