From ed718e31a36a3590f7c0662ff325ea51f52a6dcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Krier?= Date: Tue, 27 Feb 2024 17:44:29 +0100 Subject: [PATCH] Add code on product category --- modules/product/CHANGELOG | 1 + modules/product/category.py | 75 ++++++++++++++++++- modules/product/category.xml | 12 +++ modules/product/configuration.py | 6 ++ modules/product/message.xml | 3 + modules/product/view/category_form.xml | 14 ++-- modules/product/view/category_list.xml | 3 +- .../product/view/category_product_form.xml | 4 +- modules/product/view/category_tree.xml | 3 +- modules/product/view/configuration_form.xml | 3 + 10 files changed, 113 insertions(+), 11 deletions(-) diff --git a/modules/product/CHANGELOG b/modules/product/CHANGELOG index 7d0657182d..4d8eb49430 100644 --- a/modules/product/CHANGELOG +++ b/modules/product/CHANGELOG @@ -1,3 +1,4 @@ +* Add code on product category * Add product deactivable mixins Version 7.0.0 - 2023-10-30 diff --git a/modules/product/category.py b/modules/product/category.py index d3c0512864..7c4406b82d 100644 --- a/modules/product/category.py +++ b/modules/product/category.py @@ -1,13 +1,27 @@ # This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. -from trytond.model import ModelSQL, ModelView, fields, tree -from trytond.pyson import PYSONEncoder + +from sql.conditionals import NullIf +from sql.functions import CharLength +from sql.operators import Equal + +from trytond.model import Exclude, ModelSQL, ModelView, fields, tree +from trytond.pool import Pool +from trytond.pyson import Eval, PYSONEncoder +from trytond.tools import is_full_text, lstrip_wildcard class Category(tree(separator=' / '), ModelSQL, ModelView): "Product Category" __name__ = "product.category" name = fields.Char('Name', required=True, translate=True) + code = fields.Char( + "Code", + states={ + 'readonly': Eval('code_readonly', False), + }) + code_readonly = fields.Function( + fields.Boolean("Code Readonly"), 'get_code_readonly') parent = fields.Many2One( 'product.category', "Parent", help="Used to add structure above the category.") @@ -20,7 +34,13 @@ class Category(tree(separator=' / '), ModelSQL, ModelView): @classmethod def __setup__(cls): + cls.code.search_unaccented = False super(Category, cls).__setup__() + t = cls.__table__() + cls._sql_constraints += [ + ('code_unique', Exclude(t, (NullIf(t.code, ''), Equal)), + 'product.msg_category_code_unique'), + ] cls._order.insert(0, ('name', 'ASC')) cls._buttons.update({ 'add_products': { @@ -28,6 +48,39 @@ class Category(tree(separator=' / '), ModelSQL, ModelView): }, }) + @classmethod + def default_code_readonly(cls): + pool = Pool() + Configuration = pool.get('product.configuration') + config = Configuration(1) + return bool(config.category_sequence) + + def get_code_readonly(self, name): + return self.default_code_readonly() + + @classmethod + def order_code(cls, tables): + table, _ = tables[None] + if cls.default_code_readonly(): + return [CharLength(table.code), table.code] + else: + return [table.code] + + @classmethod + def search_rec_name(cls, name, clause): + _, operator, operand, *extra = clause + if operator.startswith('!') or operator.startswith('not '): + bool_op = 'AND' + else: + bool_op = 'OR' + code_value = operand + if operator.endswith('like') and is_full_text(operand): + code_value = lstrip_wildcard(operand) + return [bool_op, + ('name', operator, operand, *extra), + ('code', operator, code_value, *extra), + ] + @classmethod @ModelView.button_action('product.act_category_product') def add_products(cls, categories): @@ -37,8 +90,26 @@ class Category(tree(separator=' / '), ModelSQL, ModelView): [('id', '=', categories[0].id)]), } + @classmethod + def _new_code(cls): + pool = Pool() + Configuration = pool.get('product.configuration') + config = Configuration(1) + sequence = config.category_sequence + if sequence: + return sequence.get() + + @classmethod + def create(cls, vlist): + vlist = [v.copy() for v in vlist] + for values in vlist: + if not values.get('code'): + values['code'] = cls._new_code() + return super().create(vlist) + @classmethod def copy(cls, categories, default=None): default = default.copy() if default is not None else {} default.setdefault('templates') + default.setdefault('code', None) return super().copy(categories, default=default) diff --git a/modules/product/category.xml b/modules/product/category.xml index b8854bfe3c..52c2fa294e 100644 --- a/modules/product/category.xml +++ b/modules/product/category.xml @@ -102,5 +102,17 @@ this repository contains the full copyright notices and license terms. --> + + + Category + + + + + + + + + diff --git a/modules/product/configuration.py b/modules/product/configuration.py index df9c6166ab..f6bc3ea2e4 100644 --- a/modules/product/configuration.py +++ b/modules/product/configuration.py @@ -35,6 +35,12 @@ class Configuration(ModelSingleton, ModelSQL, ModelView, MultiValueMixin): ('sequence_type', '=', Id('product', 'sequence_type_template')), ], help="Used to generate the first part of the product code.") + category_sequence = fields.Many2One( + 'ir.sequence', "Category Sequence", + domain=[ + ('sequence_type', '=', Id('product', 'sequence_type_category')), + ], + help="Used to generate the category code.") @classmethod def default_default_cost_price_method(cls, **pattern): diff --git a/modules/product/message.xml b/modules/product/message.xml index 3546fcea27..06c383774d 100644 --- a/modules/product/message.xml +++ b/modules/product/message.xml @@ -30,5 +30,8 @@ this repository contains the full copyright notices and license terms. --> Code of active product must be unique. + + The code on product category must be unique. + diff --git a/modules/product/view/category_form.xml b/modules/product/view/category_form.xml index 74d63b56cb..955f9d3c9e 100644 --- a/modules/product/view/category_form.xml +++ b/modules/product/view/category_form.xml @@ -1,16 +1,20 @@ -
+