diff --git a/product_print_category/README.rst b/product_print_category/README.rst new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/product_print_category/__init__.py b/product_print_category/__init__.py new file mode 100644 index 000000000000..50235cbb122a --- /dev/null +++ b/product_print_category/__init__.py @@ -0,0 +1,3 @@ +from . import models +from . import wizard +from . import report diff --git a/product_print_category/__manifest__.py b/product_print_category/__manifest__.py new file mode 100644 index 000000000000..0b21deb8987f --- /dev/null +++ b/product_print_category/__manifest__.py @@ -0,0 +1,45 @@ +# Copyright (C) 2012-Today GRAP (http://www.grap.coop) +# Copyright (C) 2016-Today: La Louve () +# Copyright (C) 2021-Today: Coop IT Easy () +# @author: Sylvain LE GAL (https://twitter.com/legalsylvain) +# @author: Rémy TAYMANS () +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +{ + "name": "Product - Print Categories", + "summary": "Define print categories for products" + "and automate products print, when data has changed", + "version": "16.0.1.0.1", + "category": "Product", + "license": "AGPL-3", + "website": "https://github.com/OCA/product-attribute", + "maintainers": ["legalsylvain"], + "author": "GRAP, " + "La Louve, " + "Coop IT Easy SC, " + "Odoo Community Association (OCA)", + "depends": [ + "sale_management", + "product", + ], + "demo": [ + "demo/res_groups.xml", + "demo/qweb_template.xml", + "demo/product_print_category.xml", + "demo/product_product.xml", + ], + "data": [ + "security/ir_module_category.xml", + "security/res_groups.xml", + "security/ir.model.access.csv", + "data/report_paperformat.xml", + "report/report_pricetag.xml", + "report/ir_actions_report.xml", + "wizard/view_product_print_wizard.xml", + "views/view_product_product.xml", + "views/view_product_template.xml", + "views/view_res_company.xml", + "views/view_product_print_category.xml", + ], + "installable": True, +} diff --git a/product_print_category/data/report_paperformat.xml b/product_print_category/data/report_paperformat.xml new file mode 100644 index 000000000000..e0e8b85835d3 --- /dev/null +++ b/product_print_category/data/report_paperformat.xml @@ -0,0 +1,24 @@ + + + + + + Products Print Format + A4 + 0 + 0 + Portrait + 5 + 5 + 2 + 2 + + 10 + 90 + + + diff --git a/product_print_category/demo/product_print_category.xml b/product_print_category/demo/product_print_category.xml new file mode 100644 index 000000000000..4b9f917063e3 --- /dev/null +++ b/product_print_category/demo/product_print_category.xml @@ -0,0 +1,32 @@ + + + + + + Demo Category 1 + + + + + + + Demo Category 2 + + + + + + diff --git a/product_print_category/demo/product_product.xml b/product_print_category/demo/product_product.xml new file mode 100644 index 000000000000..78c44c1faf54 --- /dev/null +++ b/product_print_category/demo/product_product.xml @@ -0,0 +1,19 @@ + + + + + + Demo Product With Print category 1 + + + + + Demo Product With Print category 2 + + + + diff --git a/product_print_category/demo/qweb_template.xml b/product_print_category/demo/qweb_template.xml new file mode 100644 index 000000000000..f720fe0e7ff5 --- /dev/null +++ b/product_print_category/demo/qweb_template.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + Code: + + + + + + Price: + + + + + + + + + + + + + + + + + + Code: + + + + + + Price: + + + + + + + + diff --git a/product_print_category/demo/res_groups.xml b/product_print_category/demo/res_groups.xml new file mode 100644 index 000000000000..1c96cb01e67c --- /dev/null +++ b/product_print_category/demo/res_groups.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/product_print_category/i18n/fr.po b/product_print_category/i18n/fr.po new file mode 100644 index 000000000000..4c098728b6e7 --- /dev/null +++ b/product_print_category/i18n/fr.po @@ -0,0 +1,325 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * product_print_category +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2022-10-24 20:15+0000\n" +"PO-Revision-Date: 2022-10-24 20:15+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: product_print_category +#: model:ir.actions.report,print_report_name:product_print_category.pricetag +msgid "'Products Labels'" +msgstr "Etiquettes d'articles" + +#. module: product_print_category +#: model_terms:ir.ui.view,arch_db:product_print_category.view_product_print_category_form_2 +msgid "Products" +msgstr "Articles" + +#. module: product_print_category +#: model_terms:ir.ui.view,arch_db:product_print_category.view_product_print_category_form_2 +msgid "To print" +msgstr "A imprimer" + +#. module: product_print_category +#: model_terms:ir.ui.view,arch_db:product_print_category.qweb_template_demo_1 +#: model_terms:ir.ui.view,arch_db:product_print_category.qweb_template_demo_2 +msgid "Code:" +msgstr "Code :" + +#. module: product_print_category +#: model_terms:ir.ui.view,arch_db:product_print_category.qweb_template_demo_1 +#: model_terms:ir.ui.view,arch_db:product_print_category.qweb_template_demo_2 +msgid "Price:" +msgstr "Prix :" + +#. module: product_print_category +#: model:ir.model,name:product_print_category.model_product_print_category_mixin +msgid "Abstract Model for Product Print Categories" +msgstr "Modèle abstrait pouir les catégories d'impression d'articles" + +#. module: product_print_category +#: model:ir.model.fields,field_description:product_print_category.field_product_print_category__active +msgid "Active" +msgstr "Actif" + +#. module: product_print_category +#: model_terms:ir.ui.view,arch_db:product_print_category.view_product_print_wizard_form +msgid "Cancel" +msgstr "Annuler" + +#. module: product_print_category +#: model:ir.model,name:product_print_category.model_res_company +msgid "Companies" +msgstr "Sociétés" + +#. module: product_print_category +#: model:ir.model.fields,field_description:product_print_category.field_product_print_category__company_id +msgid "Company" +msgstr "Société" + +#. module: product_print_category +#: model:ir.model.fields,field_description:product_print_category.field_product_print_category__create_uid +#: model:ir.model.fields,field_description:product_print_category.field_product_print_wizard__create_uid +#: model:ir.model.fields,field_description:product_print_category.field_product_print_wizard_line__create_uid +msgid "Created by" +msgstr "Créé par" + +#. module: product_print_category +#: model:ir.model.fields,field_description:product_print_category.field_product_print_category__create_date +#: model:ir.model.fields,field_description:product_print_category.field_product_print_wizard__create_date +#: model:ir.model.fields,field_description:product_print_category.field_product_print_wizard_line__create_date +msgid "Created on" +msgstr "Créé le" + +#. module: product_print_category +#: model:ir.model.fields,field_description:product_print_category.field_res_company__print_category_id +msgid "Default Print Category" +msgstr "Catégorie d'impression par défaut" + +#. module: product_print_category +#: model:product.print.category,name:product_print_category.demo_category_1 +msgid "Demo Category 1" +msgstr "Catégorie 1" + +#. module: product_print_category +#: model:product.print.category,name:product_print_category.demo_category_2 +msgid "Demo Category 2" +msgstr "Catégorie 2" + +#. module: product_print_category +#: model:product.product,name:product_print_category.demo_product_1 +#: model:product.template,name:product_print_category.demo_product_1_product_template +msgid "Demo Product With Print category 1" +msgstr "Article de démo avec catégorie 1" + +#. module: product_print_category +#: model:product.product,name:product_print_category.demo_product_2 +#: model:product.template,name:product_print_category.demo_product_2_product_template +msgid "Demo Product With Print category 2" +msgstr "Article de démo avec catégorie 2" + +#. module: product_print_category +#: model:ir.model.fields,field_description:product_print_category.field_product_print_category__display_name +#: model:ir.model.fields,field_description:product_print_category.field_product_print_wizard__display_name +#: model:ir.model.fields,field_description:product_print_category.field_product_print_wizard_line__display_name +msgid "Display Name" +msgstr "Nom affiché" + +#. module: product_print_category +#: model:ir.model.fields,field_description:product_print_category.field_product_print_category__field_ids +msgid "Fields related to printing" +msgstr "Champs déclenchant l'impression" + +#. module: product_print_category +#: model:ir.model.fields,field_description:product_print_category.field_product_print_category__id +#: model:ir.model.fields,field_description:product_print_category.field_product_print_wizard__id +#: model:ir.model.fields,field_description:product_print_category.field_product_print_wizard_line__id +msgid "ID" +msgstr "" + +#. module: product_print_category +#: model:ir.model.fields,field_description:product_print_category.field_product_print_category____last_update +#: model:ir.model.fields,field_description:product_print_category.field_product_print_wizard____last_update +#: model:ir.model.fields,field_description:product_print_category.field_product_print_wizard_line____last_update +msgid "Last Modified on" +msgstr "Dernière modification le" + +#. module: product_print_category +#: model:ir.model.fields,field_description:product_print_category.field_product_print_category__write_uid +#: model:ir.model.fields,field_description:product_print_category.field_product_print_wizard__write_uid +#: model:ir.model.fields,field_description:product_print_category.field_product_print_wizard_line__write_uid +msgid "Last Updated by" +msgstr "Dernière mise à jour par" + +#. module: product_print_category +#: model:ir.model.fields,field_description:product_print_category.field_product_print_category__write_date +#: model:ir.model.fields,field_description:product_print_category.field_product_print_wizard__write_date +#: model:ir.model.fields,field_description:product_print_category.field_product_print_wizard_line__write_date +msgid "Last Updated on" +msgstr "Dernière mise à jour le" + +#. module: product_print_category +#: model:ir.model.fields,field_description:product_print_category.field_product_print_wizard__line_ids +msgid "Lines" +msgstr "Lignes" + +#. module: product_print_category +#: model:ir.model.fields,field_description:product_print_category.field_product_print_category__name +#: model_terms:ir.ui.view,arch_db:product_print_category.view_product_print_category_form_2 +msgid "Name" +msgstr "Nom" + +#. module: product_print_category +#: code:addons/product_print_category/wizard/product_print_wizard.py:0 +#, python-format +msgid "" +"Please set a print category for the following lines \n" +"\n" +"- %s" +msgstr "" +"Veuillez renseigner la catégorie d'impression pour les lignes suivantes \n" +"\n" +"- %s" + +#. module: product_print_category +#: model:ir.model,name:product_print_category.model_report_product_print_category_report_pricetag +msgid "Pricetag report" +msgstr "Rapport d'étiquette" + +#. module: product_print_category +#: model:ir.actions.report,name:product_print_category.pricetag +#: model:ir.module.category,name:product_print_category.module_product_print_category +msgid "Pricetags" +msgstr "Etiquettes" + +#. module: product_print_category +#: model_terms:ir.ui.view,arch_db:product_print_category.view_product_print_wizard_form +msgid "Print" +msgstr "Imprimer" + +#. module: product_print_category +#: model_terms:ir.ui.view,arch_db:product_print_category.view_product_print_category_form_2 +msgid "Print All Products" +msgstr "Imprimer tous les articles" + +#. module: product_print_category +#: model:ir.actions.act_window,name:product_print_category.action_product_print_category +#: model:ir.ui.menu,name:product_print_category.menu_product_print_category +msgid "Print Categories" +msgstr "Catégories d'impression" + +#. module: product_print_category +#: model:ir.model.fields,field_description:product_print_category.field_product_print_wizard_line__print_category_id +#: model:ir.model.fields,field_description:product_print_category.field_product_product__print_category_id +#: model:ir.model.fields,field_description:product_print_category.field_product_template__print_category_id +msgid "Print Category" +msgstr "Catégorie d'impression" + +#. module: product_print_category +#: model:res.groups,name:product_print_category.manager +msgid "Print Category Manager" +msgstr "Responsable de catégorie d'impression" + +#. module: product_print_category +#: model:res.groups,name:product_print_category.user +msgid "Print Category User" +msgstr "Utilisateur·rice de catégorie d'impression" + +#. module: product_print_category +#: model:ir.model,name:product_print_category.model_product_print_category +msgid "Print Category for Product" +msgstr "Catégorie d'impression pour les articles" + +#. module: product_print_category +#: model_terms:ir.ui.view,arch_db:product_print_category.view_product_print_category_form_2 +msgid "Print Obsolete Products" +msgstr "Imprimer les articles obsolètes" + +#. module: product_print_category +#: model:ir.actions.act_window,name:product_print_category.action_product_product_2_product_print_wizard +#: model:ir.actions.act_window,name:product_print_category.action_product_template_2_product_print_wizard +#: model_terms:ir.ui.view,arch_db:product_print_category.view_product_print_wizard_form +msgid "Print Products" +msgstr "Imprimer les articles" + +#. module: product_print_category +#: model_terms:ir.ui.view,arch_db:product_print_category.view_product_print_category_form_2 +msgid "Print category name" +msgstr "Nom de la catégorie d'impression" + +#. module: product_print_category +#: model_terms:ir.ui.view,arch_db:product_print_category.view_product_product_form +#: model_terms:ir.ui.view,arch_db:product_print_category.view_product_template_only_form +msgid "Print options" +msgstr "Options d'impression" + +#. module: product_print_category +#: model_terms:ir.ui.view,arch_db:product_print_category.view_product_template_only_form +msgid "Print options should be configured on Product Variants" +msgstr "" +"Les options d'impression doivent être configurées au niveau de la variante " +"d'article" + +#. module: product_print_category +#: model:ir.model,name:product_print_category.model_product_template +#: model:ir.model.fields,field_description:product_print_category.field_product_print_category__product_ids +#: model:ir.model.fields,field_description:product_print_category.field_product_print_wizard_line__product_id +msgid "Product" +msgstr "Article" + +#. module: product_print_category +#: model:ir.model.fields,field_description:product_print_category.field_product_print_category__product_to_print_ids +msgid "Product To Print" +msgstr "Article à réimprimer" + +#. module: product_print_category +#: model:ir.model,name:product_print_category.model_product_product +msgid "Product Variant" +msgstr "Variante d'article" + +#. module: product_print_category +#: model:ir.model.fields,field_description:product_print_category.field_product_print_category__product_qty +msgid "Products" +msgstr "Articles" + +#. module: product_print_category +#: model:ir.model.fields,field_description:product_print_category.field_product_print_category__product_to_print_qty +msgid "Products To Print" +msgstr "Articles à réimprimer" + +#. module: product_print_category +#: model:ir.model.fields,field_description:product_print_category.field_product_print_wizard_line__quantity +msgid "Quantity" +msgstr "Quantité" + +#. module: product_print_category +#: model:ir.model.fields,field_description:product_print_category.field_product_print_category__qweb_view_id +msgid "Qweb View" +msgstr "Vue Qweb" + +#. module: product_print_category +#: model:ir.model.fields,field_description:product_print_category.field_product_product__to_print +#: model:ir.model.fields,field_description:product_print_category.field_product_template__to_print +msgid "To Print" +msgstr "À imprimer" + +#. module: product_print_category +#: model:product.product,uom_name:product_print_category.demo_product_1 +#: model:product.product,uom_name:product_print_category.demo_product_2 +#: model:product.template,uom_name:product_print_category.demo_product_1_product_template +#: model:product.template,uom_name:product_print_category.demo_product_2_product_template +msgid "Units" +msgstr "Unités" + +#. module: product_print_category +#: model:ir.model.fields,field_description:product_print_category.field_product_print_wizard_line__wizard_id +msgid "Wizard" +msgstr "Assistant" + +#. module: product_print_category +#: model:ir.model,name:product_print_category.model_product_print_wizard +msgid "Wizard for printing products" +msgstr "assistant pour l'impression d'articles" + +#. module: product_print_category +#: model:ir.model,name:product_print_category.model_product_print_wizard_line +msgid "Wizard line for printing products" +msgstr "Ligne d'assistant pour l'impression d'articles" + +#. module: product_print_category +#: model_terms:ir.ui.view,arch_db:product_print_category.view_product_print_wizard_form +msgid "" +"⚠️ If you change Print Category here, it will be changed in the product." +msgstr "" +"⚠️ Si vous changez une catégorie d'impression ici, elle sera changée dans la" +" fiche article." diff --git a/product_print_category/models/__init__.py b/product_print_category/models/__init__.py new file mode 100644 index 000000000000..8049316c8497 --- /dev/null +++ b/product_print_category/models/__init__.py @@ -0,0 +1,5 @@ +from . import res_company +from . import product_print_category +from . import product_print_category_mixin +from . import product_product +from . import product_template diff --git a/product_print_category/models/product_print_category.py b/product_print_category/models/product_print_category.py new file mode 100644 index 000000000000..6c89aa344416 --- /dev/null +++ b/product_print_category/models/product_print_category.py @@ -0,0 +1,89 @@ +# Copyright (C) 2016-Today: La Louve () +# Copyright (C) 2022-Today: GRAP (http://www.grap.coop) +# @author: Sylvain LE GAL (https://twitter.com/legalsylvain) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import api, fields, models + + +class ProductPrintCategory(models.Model): + _name = "product.print.category" + _description = "Print Category for Product" + _order = "name" + + # Fields Section + name = fields.Char(required=True, translate=True) + + active = fields.Boolean(default=True) + + company_id = fields.Many2one( + string="Company", + comodel_name="res.company", + index=True, + default=lambda self: self.env["res.company"]._company_default_get(), + ) + + product_ids = fields.One2many( + comodel_name="product.product", inverse_name="print_category_id" + ) + + product_qty = fields.Integer(string="Products", compute="_compute_product_qty") + + product_to_print_ids = fields.One2many( + comodel_name="product.product", + compute="_compute_to_print", + ) + + product_to_print_qty = fields.Integer( + compute="_compute_to_print", + string="Products To Print", + ) + + field_ids = fields.Many2many( + string="Fields related to printing", + comodel_name="ir.model.fields", + column1="category_id", + column2="field_id", + domain="['|', ('model', '=', 'product.product'),\ + ('model', '=', 'product.product')]", + ) + + qweb_view_id = fields.Many2one( + comodel_name="ir.ui.view", + string="Qweb View", + domain="[('type', '=', 'qweb')]", + required=True, + ) + + # Compute Section + @api.depends("product_ids.print_category_id") + def _compute_product_qty(self): + for category in self: + category.product_qty = len(category.product_ids) + + def _compute_to_print(self): + product_obj = self.env["product.product"] + for category in self: + products = product_obj.search( + [ + ("print_category_id", "=", category.id), + ("to_print", "=", True), + ] + ) + category.product_to_print_qty = len(products) + category.product_to_print_ids = products + + # Action Section + def action_view_product_product(self): + self.ensure_one() + action = self.env.ref("product.product_normal_action") + action_data = action.read()[0] + if self.env.context["to_print"]: + action_data["domain"] = ( + "['&', ('print_category_id','=', " + + str(self.id) + + "), ('to_print','=', True)]" + ) + else: + action_data["domain"] = "[('print_category_id','=', " + str(self.id) + ")]" + return action_data diff --git a/product_print_category/models/product_print_category_mixin.py b/product_print_category/models/product_print_category_mixin.py new file mode 100644 index 000000000000..312894bfeae2 --- /dev/null +++ b/product_print_category/models/product_print_category_mixin.py @@ -0,0 +1,28 @@ +# Copyright (C) 2018-Today: La Louve () +# Copyright (C) 2022-Today: GRAP (http://www.grap.coop) +# @author: Sylvain LE GAL (https://twitter.com/legalsylvain) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import models + + +class ProductPrintCategoryMixin(models.AbstractModel): + _name = "product.print.category.mixin" + _description = "Abstract Model for Product Print Categories" + + def _update_to_print_values(self, vals): + # This function work for item that are product.product and product.template + to_update_item_ids = [] + # Set 'To print' if we change one field choosen in print_category + for item in self.filtered(lambda x: x.print_category_id): + triggering_fields = item.print_category_id.field_ids.mapped("name") + [ + "print_category_id" + ] + if len(list(set(vals.keys()) & set(triggering_fields))): + to_update_item_ids.append(item.id) + to_update_items = self.browse(to_update_item_ids) + # This function is called from the write of the current RecordSet : + # prevent an infinite loop by calling the function super + return super(ProductPrintCategoryMixin, to_update_items).write( + {"to_print": True} + ) diff --git a/product_print_category/models/product_product.py b/product_print_category/models/product_product.py new file mode 100644 index 000000000000..911342cbc916 --- /dev/null +++ b/product_print_category/models/product_product.py @@ -0,0 +1,38 @@ +# Copyright (C) 2016-Today: La Louve () +# Copyright (C) 2022-Today: GRAP (http://www.grap.coop) +# @author: Sylvain LE GAL (https://twitter.com/legalsylvain) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import api, fields, models + + +class ProductProduct(models.Model): + _name = "product.product" + _inherit = ["product.product", "product.print.category.mixin"] + + # Columns Section + print_category_id = fields.Many2one( + string="Print Category", + comodel_name="product.print.category", + default=lambda s: s._default_print_category_id(), + ) + + to_print = fields.Boolean() + + # Default Section + def _default_print_category_id(self): + return self.env.user.company_id.print_category_id + + @api.model_create_multi + def create(self, vals_list): + for vals in vals_list: + if vals.get("print_category_id", False): + vals["to_print"] = True + return super().create(vals) + + def write(self, vals): + res = super( + ProductProduct, self.with_context(update_to_print_category=False) + ).write(vals) + self._update_to_print_values(vals) + return res diff --git a/product_print_category/models/product_template.py b/product_print_category/models/product_template.py new file mode 100644 index 000000000000..c3c97d341da9 --- /dev/null +++ b/product_print_category/models/product_template.py @@ -0,0 +1,59 @@ +# Copyright (C) 2021-Today: Coop IT Easy () +# Copyright (C) 2022-Today: GRAP (http://www.grap.coop) +# @author: Rémy TAYMANS () +# @author: Sylvain LE GAL (https://twitter.com/legalsylvain) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import api, fields, models + + +class ProductTemplate(models.Model): + _name = "product.template" + _inherit = ["product.template", "product.print.category.mixin"] + + print_category_id = fields.Many2one( + string="Print Category", + comodel_name="product.print.category", + compute="_compute_print_category_id", + inverse="_inverse_print_category_id", + ) + + to_print = fields.Boolean(compute="_compute_to_print", inverse="_inverse_to_print") + + @api.depends("product_variant_ids", "product_variant_ids.print_category_id") + def _compute_print_category_id(self): + unique_variants = self.filtered( + lambda template: len(template.product_variant_ids) == 1 + ) + for template in unique_variants: + template.print_category_id = template.product_variant_ids.print_category_id + for template in self - unique_variants: + template.print_category_id = False + + def _inverse_print_category_id(self): + for template in self: + if len(template.product_variant_ids) == 1: + template.product_variant_ids.print_category_id = ( + template.print_category_id + ) + + @api.depends("product_variant_ids", "product_variant_ids.to_print") + def _compute_to_print(self): + unique_variants = self.filtered( + lambda template: len(template.product_variant_ids) == 1 + ) + for template in unique_variants: + template.to_print = template.product_variant_ids.to_print + for template in self - unique_variants: + template.to_print = False + + def _inverse_to_print(self): + for template in self: + if len(template.product_variant_ids) == 1: + template.product_variant_ids.to_print = template.to_print + + def write(self, vals): + res = super().write(vals) + if self.env.context.get("update_to_print_category", True): + self._update_to_print_values(vals) + return res diff --git a/product_print_category/models/res_company.py b/product_print_category/models/res_company.py new file mode 100644 index 000000000000..4cfab37fde84 --- /dev/null +++ b/product_print_category/models/res_company.py @@ -0,0 +1,14 @@ +# Copyright (C) 2018-Today: La Louve () +# Copyright (C) 2022-Today: GRAP (http://www.grap.coop) +# @author: Sylvain LE GAL (https://twitter.com/legalsylvain) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import fields, models + + +class ResCompany(models.Model): + _inherit = "res.company" + + print_category_id = fields.Many2one( + string="Default Print Category", comodel_name="product.print.category" + ) diff --git a/product_print_category/readme/CONFIGURE.rst b/product_print_category/readme/CONFIGURE.rst new file mode 100644 index 000000000000..618f4ea8a831 --- /dev/null +++ b/product_print_category/readme/CONFIGURE.rst @@ -0,0 +1,11 @@ +* Add yourself to the 'Print Category Manager' group in the 'Pricetags' category. + +* Go to 'Sales' > 'Products' > 'Print Categories' + +* Find or create new print categories of products + * Set the fields that will trigger to print again the product label (e.g : name, sale price etc.) + * Set the qweb view that will be used for this category + +.. figure:: ../static/description/product_print_category_form.png + +Alternatively, you can create product print categories and qweb in a custom module. diff --git a/product_print_category/readme/CONTRIBUTORS.rst b/product_print_category/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000000..09438e192be1 --- /dev/null +++ b/product_print_category/readme/CONTRIBUTORS.rst @@ -0,0 +1,2 @@ +* Sylvain LE GAL +* Quentin DUPONT diff --git a/product_print_category/readme/DESCRIPTION.rst b/product_print_category/readme/DESCRIPTION.rst new file mode 100644 index 000000000000..c903fef1566d --- /dev/null +++ b/product_print_category/readme/DESCRIPTION.rst @@ -0,0 +1,8 @@ +This module is designed to extend product printing features. It allows +user to create new print categories of products depending of the data that are +on the labels of the products variants. + +When a field has changed, the product will be set as 'To print' automatically. + +This module requires to install custom modules to have pricetags templates, +or alternatively to create pricetag reports templates by UI. diff --git a/product_print_category/readme/USAGE.rst b/product_print_category/readme/USAGE.rst new file mode 100644 index 000000000000..8c030c9697b5 --- /dev/null +++ b/product_print_category/readme/USAGE.rst @@ -0,0 +1,23 @@ +**To affect products to the categories** + +* Go to "Sales" > "Products" > "Product variants" (or "Products" if + variants are not activated). +* Go to your product in General Information tab +* Set a print category + +.. figure:: ../static/description/product_product_form.png + +* You can set a default print category in res_company configuration settings + For each new product, it will be assigned to the print category. + +.. figure:: ../static/description/res_company_form.png + +**To print your products** + +* Go to "Sales" > "Products" > "Print Categories" +* Choose between "Print Obsolete Products" or "Print All Products" + +.. figure:: ../static/description/product_print_wizard_form.png + +* Alternatively you can select product in product or variants list view and click + on "Action" > "Print Products" diff --git a/product_print_category/report/__init__.py b/product_print_category/report/__init__.py new file mode 100644 index 000000000000..000e9934289f --- /dev/null +++ b/product_print_category/report/__init__.py @@ -0,0 +1 @@ +from . import report_pricetag diff --git a/product_print_category/report/ir_actions_report.xml b/product_print_category/report/ir_actions_report.xml new file mode 100644 index 000000000000..5041a7558de9 --- /dev/null +++ b/product_print_category/report/ir_actions_report.xml @@ -0,0 +1,21 @@ + + + + + + Pricetags + product.print.wizard.line + qweb-pdf + product_print_category.report_pricetag + product_print_category.report_pricetag + + 'Products Labels' + + report + + + diff --git a/product_print_category/report/report_pricetag.py b/product_print_category/report/report_pricetag.py new file mode 100644 index 000000000000..70116efba9e9 --- /dev/null +++ b/product_print_category/report/report_pricetag.py @@ -0,0 +1,47 @@ +from odoo import api, models + + +class ReportPricetag(models.AbstractModel): + _name = "report.product_print_category.report_pricetag" + _description = "Pricetag report" + + @api.model + def _get_report_values(self, docids, data=None): + WizardLine = self.env["product.print.wizard.line"] + # Prepare data to print + docargs = { + "categories_data": self._prepare_categories_data(docids, data=data), + } + + # mark the selected products as Up To Date if print succeed + lines = WizardLine.browse(docids) + lines.mapped("product_id").write({"to_print": False}) + return docargs + + @api.model + def _prepare_categories_data(self, docids, data=None): + PrintCategory = self.env["product.print.category"] + WizardLine = self.env["product.print.wizard.line"] + + # ordering data to print + lines_dict = {} + for line_id in docids: + line = WizardLine.browse(int(line_id)) + category = line.product_id.print_category_id + if category.id not in lines_dict: + lines_dict[category.id] = [line.id] + else: + lines_dict[category.id].append(line.id) + + # Computing data to transfer + categories_data = [] + for category_id, line_ids in lines_dict.items(): + category = PrintCategory.browse(category_id) + categories_data.append( + { + "print_category": category, + "report_model": "report_pricetag_custom", + "lines": WizardLine.browse(line_ids), + } + ) + return categories_data diff --git a/product_print_category/report/report_pricetag.xml b/product_print_category/report/report_pricetag.xml new file mode 100644 index 000000000000..1be2a8a3d203 --- /dev/null +++ b/product_print_category/report/report_pricetag.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + diff --git a/product_print_category/security/ir.model.access.csv b/product_print_category/security/ir.model.access.csv new file mode 100644 index 000000000000..d255dc5c193b --- /dev/null +++ b/product_print_category/security/ir.model.access.csv @@ -0,0 +1,5 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +ir_model_access_product_print_category_user,Print Category User,product_print_category.model_product_print_category,product_print_category.user,1,0,0,0 +ir_model_access_product_print_category_manager,Print Category Manager,product_print_category.model_product_print_category,product_print_category.manager,1,1,1,1 +product_print_category.access_product_print_wizard,access_product_print_wizard,product_print_category.model_product_print_wizard,product_print_category.user,1,1,1,1 +product_print_category.access_product_print_wizard_line,access_product_print_wizard_line,product_print_category.model_product_print_wizard_line,product_print_category.user,1,1,1,1 diff --git a/product_print_category/security/ir_module_category.xml b/product_print_category/security/ir_module_category.xml new file mode 100644 index 000000000000..ab9140306403 --- /dev/null +++ b/product_print_category/security/ir_module_category.xml @@ -0,0 +1,15 @@ + + + + + + Pricetags + 80 + + + diff --git a/product_print_category/security/res_groups.xml b/product_print_category/security/res_groups.xml new file mode 100644 index 000000000000..50ef83f0fe31 --- /dev/null +++ b/product_print_category/security/res_groups.xml @@ -0,0 +1,21 @@ + + + + + + Print Category User + + + + + Print Category Manager + + + + + diff --git a/product_print_category/static/description/index.png b/product_print_category/static/description/index.png new file mode 100644 index 000000000000..90907f66b72c Binary files /dev/null and b/product_print_category/static/description/index.png differ diff --git a/product_print_category/static/description/product_print_category_form.png b/product_print_category/static/description/product_print_category_form.png new file mode 100644 index 000000000000..e6e10fdf22d0 Binary files /dev/null and b/product_print_category/static/description/product_print_category_form.png differ diff --git a/product_print_category/static/description/product_print_wizard_form.png b/product_print_category/static/description/product_print_wizard_form.png new file mode 100644 index 000000000000..11339162d697 Binary files /dev/null and b/product_print_category/static/description/product_print_wizard_form.png differ diff --git a/product_print_category/static/description/product_product_form.png b/product_print_category/static/description/product_product_form.png new file mode 100644 index 000000000000..bc6f9f71ff96 Binary files /dev/null and b/product_print_category/static/description/product_product_form.png differ diff --git a/product_print_category/static/description/res_company_form.png b/product_print_category/static/description/res_company_form.png new file mode 100644 index 000000000000..cb6159e90485 Binary files /dev/null and b/product_print_category/static/description/res_company_form.png differ diff --git a/product_print_category/tests/__init__.py b/product_print_category/tests/__init__.py new file mode 100644 index 000000000000..748c092c3ac8 --- /dev/null +++ b/product_print_category/tests/__init__.py @@ -0,0 +1 @@ +from . import test_product_print_category diff --git a/product_print_category/tests/test_product_print_category.py b/product_print_category/tests/test_product_print_category.py new file mode 100644 index 000000000000..8f434e9af758 --- /dev/null +++ b/product_print_category/tests/test_product_print_category.py @@ -0,0 +1,98 @@ +# Copyright (C) 2018 - Today: GRAP (http://www.grap.coop) +# @author: Sylvain LE GAL (https://twitter.com/legalsylvain) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo.tests.common import TransactionCase + + +class TestProductPrintCategory(TransactionCase): + """Tests for 'Product Print Category' Module""" + + def setUp(self): + super().setUp() + self.ProductPrintWizard = self.env["product.print.wizard"] + self.ProductProduct = self.env["product.product"] + self.ProductTemplate = self.env["product.template"] + self.CustomReport = self.env["report.product_print_category.report_pricetag"] + self.print_category_1 = self.env.ref("product_print_category.demo_category_1") + + # Test Section + def test_01_product_product_to_print_value(self): + product = self.ProductProduct.create( + { + "name": "Demo Product Product Name", + } + ) + self.assertEqual(product.to_print, False) + + product.print_category_id = self.print_category_1.id + self.assertEqual(product.to_print, True) + + product.to_print = False + product.name = "Demo Product Product Name Changed" + self.assertEqual(product.to_print, True) + + def test_02_product_template_to_print_value(self): + template = self.ProductTemplate.create( + { + "name": "Demo Product Template Name", + } + ) + self.assertEqual(template.to_print, False) + + template.print_category_id = self.print_category_1.id + self.assertEqual(template.to_print, True) + + template.to_print = False + template.name = "Demo Product Template Name Changed" + self.assertEqual(template.to_print, True) + + def test_10_test_wizard_obsolete(self): + products = self.ProductProduct.search( + [ + ("to_print", "=", True), + ("print_category_id", "=", self.print_category_1.id), + ] + ) + self.assertTrue(len(products) > 0) + wizard = self.ProductPrintWizard.with_context( + active_model="product.print.category", + active_ids=[self.print_category_1.id], + ).create({}) + self.assertEqual( + len(wizard.line_ids), + len(products), + "Print obsolete product should propose 1 product", + ) + + wizard.print_report() + self.env.ref("product_print_category.pricetag")._render_qweb_pdf( + "product_print_category.report_pricetag", + wizard.line_ids.ids, + ) + + products = self.ProductProduct.search( + [ + ("to_print", "=", True), + ("print_category_id", "=", self.print_category_1.id), + ] + ) + self.assertTrue(len(products) == 0) + + def test_11_test_wizard_all(self): + products = self.ProductProduct.search( + [ + ("print_category_id", "=", self.print_category_1.id), + ] + ) + wizard = self.ProductPrintWizard.with_context( + active_model="product.print.category", + active_ids=[self.print_category_1.id], + all_products=True, + ).create({}) + + self.assertEqual( + len(wizard.line_ids), + len(products), + "Print all products should propose 3 products", + ) diff --git a/product_print_category/views/view_product_print_category.xml b/product_print_category/views/view_product_print_category.xml new file mode 100644 index 000000000000..cae2c49a6b94 --- /dev/null +++ b/product_print_category/views/view_product_print_category.xml @@ -0,0 +1,123 @@ + + + + + + product.print.category + + + + + + + + + + + + product.print.category + + + + + + + + + + + + + + + To print + + + + + + + + + Products + + + + + + + + + + + + + + + + + + + + + + + + Print Categories + product.print.category + tree,form + + + + + diff --git a/product_print_category/views/view_product_product.xml b/product_print_category/views/view_product_product.xml new file mode 100644 index 000000000000..196a7395fe2a --- /dev/null +++ b/product_print_category/views/view_product_product.xml @@ -0,0 +1,27 @@ + + + + + + product.product + + + + + + + + + + + + diff --git a/product_print_category/views/view_product_template.xml b/product_print_category/views/view_product_template.xml new file mode 100644 index 000000000000..5f1c24cf72f5 --- /dev/null +++ b/product_print_category/views/view_product_template.xml @@ -0,0 +1,38 @@ + + + + + + product.template + + + + + + + + Print options should be configured on Product Variants + + + + + + diff --git a/product_print_category/views/view_res_company.xml b/product_print_category/views/view_res_company.xml new file mode 100644 index 000000000000..a4c022a47b28 --- /dev/null +++ b/product_print_category/views/view_res_company.xml @@ -0,0 +1,19 @@ + + + + + + res.company + + + + + + + + + diff --git a/product_print_category/wizard/__init__.py b/product_print_category/wizard/__init__.py new file mode 100644 index 000000000000..36146ca0e5a5 --- /dev/null +++ b/product_print_category/wizard/__init__.py @@ -0,0 +1,2 @@ +from . import product_print_wizard +from . import product_print_wizard_line diff --git a/product_print_category/wizard/product_print_wizard.py b/product_print_category/wizard/product_print_wizard.py new file mode 100644 index 000000000000..823482db7804 --- /dev/null +++ b/product_print_category/wizard/product_print_wizard.py @@ -0,0 +1,86 @@ +# Copyright (C) 2012-Today GRAP (http://www.grap.coop) +# @author Julien WESTE +# @author: Sylvain LE GAL (https://twitter.com/legalsylvain) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError + + +class ProductPrintWizard(models.TransientModel): + _name = "product.print.wizard" + _description = "Wizard for printing products" + + line_ids = fields.One2many( + comodel_name="product.print.wizard.line", + inverse_name="wizard_id", + string="Lines", + default=lambda s: s._default_line_ids(), + ) + + @api.model + def _default_line_ids(self): + lines_vals = [] + context = self.env.context + ProductProduct = self.env["product.product"] + if context.get("active_model", False) == "product.print.category": + domain = [ + ("print_category_id.id", "in", context.get("active_ids", [])), + ] + if not context.get("all_products", False): + domain.append(("to_print", "=", True)) + products = ProductProduct.search(domain) + + elif context.get("active_model", False) == "product.product": + product_ids = context.get("active_ids", []) + products = ProductProduct.browse(product_ids) + elif context.get("active_model", False) == "product.template": + template_ids = context.get("active_ids", []) + products = ProductProduct.search([("product_tmpl_id", "in", template_ids)]) + else: + return False + + for product in products: + lines_vals.append( + ( + 0, + 0, + { + "product_id": product.id, + "print_category_id": product.print_category_id.id, + "quantity": 1, + }, + ) + ) + return lines_vals + + def print_report(self): + self.ensure_one() + lines_without_category = self.mapped("line_ids").filtered( + lambda x: not x.print_category_id + ) + if lines_without_category: + raise ValidationError( + _("Please set a print category for the following lines \n\n- %s") + % ("\n- ".join(lines_without_category.mapped("product_id.name"))) + ) + self._prepare_data() + return self.env.ref("product_print_category.pricetag").report_action( + self.line_ids + ) + + def _prepare_data(self): + self.ensure_one() + return { + "line_data": [x.id for x in self.line_ids], + } + + def _prepare_product_data(self): + self.ensure_one() + product_data = {} + for line in self.line_ids: + if line.product_id.id not in product_data: + product_data[line.product_id.id] = line.quantity + else: + product_data[line.product_id.id] += line.quantity + return product_data diff --git a/product_print_category/wizard/product_print_wizard_line.py b/product_print_category/wizard/product_print_wizard_line.py new file mode 100644 index 000000000000..b8a0a22626dc --- /dev/null +++ b/product_print_category/wizard/product_print_wizard_line.py @@ -0,0 +1,27 @@ +# Copyright (C) 2012-Today GRAP (http://www.grap.coop) +# @author Julien WESTE +# @author: Sylvain LE GAL (https://twitter.com/legalsylvain) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import fields, models + + +class ProductPrintWizardLine(models.TransientModel): + _name = "product.print.wizard.line" + _description = "Wizard line for printing products" + _rec_name = "product_id" + + wizard_id = fields.Many2one(comodel_name="product.print.wizard") + + product_id = fields.Many2one( + comodel_name="product.product", string="Product", required=True + ) + + print_category_id = fields.Many2one( + comodel_name="product.print.category", + string="Print Category", + related="product_id.print_category_id", + readonly=False, + ) + + quantity = fields.Integer(required=True, default=1) diff --git a/product_print_category/wizard/view_product_print_wizard.xml b/product_print_category/wizard/view_product_print_wizard.xml new file mode 100644 index 000000000000..9448e29f11b4 --- /dev/null +++ b/product_print_category/wizard/view_product_print_wizard.xml @@ -0,0 +1,62 @@ + + + + + + product.print.wizard + + + + ⚠️ If you change Print Category here, it will be changed in the product. + + + + + + + + + + + + + + + Print Products + product.print.wizard + form + new + + list,form + + + + Print Products + product.print.wizard + form + new + + list,form + + + diff --git a/setup/product_print_category/odoo/addons/product_print_category b/setup/product_print_category/odoo/addons/product_print_category new file mode 120000 index 000000000000..b08f4ae8d94c --- /dev/null +++ b/setup/product_print_category/odoo/addons/product_print_category @@ -0,0 +1 @@ +../../../../product_print_category \ No newline at end of file diff --git a/setup/product_print_category/setup.py b/setup/product_print_category/setup.py new file mode 100644 index 000000000000..28c57bb64031 --- /dev/null +++ b/setup/product_print_category/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +)
Print options should be configured on Product Variants
+ ⚠️ If you change Print Category here, it will be changed in the product. +