From 9b255e2f6341ab91abc8c674aa42bbcfad6b55c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Krier?= Date: Thu, 2 Apr 2026 17:26:12 +0200 Subject: [PATCH] Use mixin to copy of One2Many from product linked by template and product --- modules/product_kit/product.py | 70 +++---------------- modules/purchase/product.py | 63 ++--------------- modules/sale_product_customer/product.py | 65 ++--------------- modules/sale_rental/product.py | 55 ++------------- modules/stock_product_location/product.py | 61 ++-------------- .../stock_product_location_place/product.py | 53 ++------------ 6 files changed, 37 insertions(+), 330 deletions(-) diff --git a/modules/product_kit/product.py b/modules/product_kit/product.py index 4274fbb0f7..80e53a8b39 100644 --- a/modules/product_kit/product.py +++ b/modules/product_kit/product.py @@ -4,12 +4,16 @@ from decimal import Decimal from trytond.model import ( ModelSQL, ModelStorage, ModelView, fields, sequence_ordered) -from trytond.modules.product import round_price +from trytond.modules.product import ( + copy_product_filtered, copy_template_filtered, round_price) from trytond.pool import Pool, PoolMeta from trytond.pyson import Bool, Eval, If -class Template(metaclass=PoolMeta): +class Template( + copy_template_filtered( + 'components', 'parent_template', 'parent_product'), + metaclass=PoolMeta): __name__ = "product.template" components = fields.One2Many( @@ -32,34 +36,11 @@ class Template(metaclass=PoolMeta): if self.type == 'kit': self.cost_price_method = 'fixed' - @classmethod - def copy(cls, templates, default=None): - pool = Pool() - Component = pool.get('product.component') - if default is None: - default = {} - else: - default = default.copy() - copy_components = 'components' not in default - default.setdefault('components', None) - new_templates = super().copy(templates, default) - if copy_components: - old2new = {} - to_copy = [] - for template, new_template in zip(templates, new_templates): - to_copy.extend( - c for c in template.components if not c.parent_product) - old2new[template.id] = new_template.id - if to_copy: - Component.copy(to_copy, { - 'parent_template': (lambda d: - old2new[d['parent_template']]), - }) - return new_templates - - -class Product(metaclass=PoolMeta): +class Product( + copy_product_filtered( + 'components', 'parent_template', 'parent_product'), + metaclass=PoolMeta): __name__ = "product.product" components = fields.One2Many( @@ -108,37 +89,6 @@ class Product(metaclass=PoolMeta): quantities[kit.id] = kit.default_uom.floor(min(qties, default=0)) return quantities - @classmethod - def copy(cls, products, default=None): - pool = Pool() - Component = pool.get('product.component') - if default is None: - default = {} - else: - default = default.copy() - - copy_components = 'components' not in default - if 'template' in default: - default.setdefault('components', None) - new_products = super().copy(products, default) - if 'template' in default and copy_components: - template2new = {} - product2new = {} - to_copy = [] - for product, new_product in zip(products, new_products): - if product.components: - to_copy.extend(product.components) - template2new[product.template.id] = new_product.template.id - product2new[product.id] = new_product.id - if to_copy: - Component.copy(to_copy, { - 'parent_product': (lambda d: - product2new[d['parent_product']]), - 'parent_template': (lambda d: - template2new[d['parent_template']]), - }) - return new_products - class ComponentMixin(sequence_ordered(), ModelStorage): diff --git a/modules/purchase/product.py b/modules/purchase/product.py index fdae36a308..34cfe29ca7 100644 --- a/modules/purchase/product.py +++ b/modules/purchase/product.py @@ -10,7 +10,8 @@ from trytond.model import ( Index, MatchMixin, ModelSQL, ModelView, fields, sequence_ordered) from trytond.modules.currency.fields import Monetary from trytond.modules.product import ( - ProductDeactivatableMixin, price_digits, round_price) + ProductDeactivatableMixin, copy_product_filtered, copy_template_filtered, + price_digits, round_price) from trytond.pool import Pool, PoolMeta from trytond.pyson import Bool, Eval, If, TimeDelta from trytond.tools import is_full_text, lstrip_wildcard @@ -19,7 +20,8 @@ from trytond.transaction import Transaction from .exceptions import PurchaseUOMWarning -class Template(metaclass=PoolMeta): +class Template( + copy_template_filtered('product_suppliers'), metaclass=PoolMeta): __name__ = "product.template" purchasable = fields.Boolean("Purchasable") product_suppliers = fields.One2Many( @@ -84,33 +86,9 @@ class Template(metaclass=PoolMeta): raise PurchaseUOMWarning( name, gettext('purchase.msg_change_purchase_uom')) - @classmethod - def copy(cls, templates, default=None): - pool = Pool() - ProductSupplier = pool.get('purchase.product_supplier') - if default is None: - default = {} - else: - default = default.copy() - copy_suppliers = 'product_suppliers' not in default - default.setdefault('product_suppliers', None) - new_templates = super().copy(templates, default) - if copy_suppliers: - old2new = {} - to_copy = [] - for template, new_template in zip(templates, new_templates): - to_copy.extend( - ps for ps in template.product_suppliers if not ps.product) - old2new[template.id] = new_template.id - if to_copy: - ProductSupplier.copy(to_copy, { - 'template': lambda d: old2new[d['template']], - }) - return new_templates - - -class Product(metaclass=PoolMeta): +class Product( + copy_product_filtered('product_suppliers'), metaclass=PoolMeta): __name__ = 'product.product' product_suppliers = fields.One2Many( @@ -274,35 +252,6 @@ class Product(metaclass=PoolMeta): prices[product.id] = unit_price return prices - @classmethod - def copy(cls, products, default=None): - pool = Pool() - ProductSupplier = pool.get('purchase.product_supplier') - if default is None: - default = {} - else: - default = default.copy() - - copy_suppliers = 'product_suppliers' not in default - if 'template' in default: - default.setdefault('product_suppliers', None) - new_products = super().copy(products, default) - if 'template' in default and copy_suppliers: - template2new = {} - product2new = {} - to_copy = [] - for product, new_product in zip(products, new_products): - if product.product_suppliers: - to_copy.extend(product.product_suppliers) - template2new[product.template.id] = new_product.template.id - product2new[product.id] = new_product.id - if to_copy: - ProductSupplier.copy(to_copy, { - 'product': lambda d: product2new[d['product']], - 'template': lambda d: template2new[d['template']], - }) - return new_products - class ProductSupplier( sequence_ordered(), ProductDeactivatableMixin, MatchMixin, diff --git a/modules/sale_product_customer/product.py b/modules/sale_product_customer/product.py index 0c3c31c783..c0c11c5629 100644 --- a/modules/sale_product_customer/product.py +++ b/modules/sale_product_customer/product.py @@ -3,8 +3,9 @@ from trytond.model import ( MatchMixin, ModelSQL, ModelView, fields, sequence_ordered) -from trytond.modules.product import ProductDeactivatableMixin -from trytond.pool import Pool, PoolMeta +from trytond.modules.product import ( + ProductDeactivatableMixin, copy_product_filtered, copy_template_filtered) +from trytond.pool import PoolMeta from trytond.pyson import Bool, Eval, If from trytond.tools import is_full_text, lstrip_wildcard @@ -85,7 +86,8 @@ class ProductCustomer( ] -class Template(metaclass=PoolMeta): +class Template( + copy_template_filtered('product_customers'), metaclass=PoolMeta): __name__ = 'product.template' product_customers = fields.One2Many( 'sale.product_customer', 'template', "Customers", @@ -98,33 +100,9 @@ class Template(metaclass=PoolMeta): if product_customer.match(pattern): yield product_customer - @classmethod - def copy(cls, templates, default=None): - pool = Pool() - ProductCustomer = pool.get('sale.product_customer') - if default is None: - default = {} - else: - default = default.copy() - copy_customers = 'product_customers' not in default - default.setdefault('product_customers', None) - new_templates = super().copy(templates, default) - if copy_customers: - old2new = {} - to_copy = [] - for template, new_template in zip(templates, new_templates): - to_copy.extend( - pc for pc in template.product_customers if not pc.product) - old2new[template.id] = new_template.id - if to_copy: - ProductCustomer.copy(to_copy, { - 'template': lambda d: old2new[d['template']], - }) - return new_templates - - -class Product(metaclass=PoolMeta): +class Product( + copy_product_filtered('product_customers'), metaclass=PoolMeta): __name__ = 'product.product' product_customers = fields.One2Many( 'sale.product_customer', 'product', "Customers", @@ -141,32 +119,3 @@ class Product(metaclass=PoolMeta): yield product_customer pattern['product'] = None yield from self.template.product_customer_used(**pattern) - - @classmethod - def copy(cls, products, default=None): - pool = Pool() - ProductCustomer = pool.get('sale.product_customer') - if default is None: - default = {} - else: - default = default.copy() - - copy_customers = 'product_customers' not in default - if 'template' in default: - default.setdefault('product_customers', None) - new_products = super().copy(products, default) - if 'template' in default and copy_customers: - template2new = {} - product2new = {} - to_copy = [] - for product, new_product in zip(products, new_products): - if product.product_customers: - to_copy.extend(product.product_customers) - template2new[product.template.id] = new_product.template.id - product2new[product.id] = new_product.id - if to_copy: - ProductCustomer.copy(to_copy, { - 'product': lambda d: product2new[d['product']], - 'template': lambda d: template2new[d['template']], - }) - return new_products diff --git a/modules/sale_rental/product.py b/modules/sale_rental/product.py index ef397f4727..8f2c2d406f 100644 --- a/modules/sale_rental/product.py +++ b/modules/sale_rental/product.py @@ -12,7 +12,8 @@ from trytond.modules.account_product.exceptions import TaxError from trytond.modules.account_product.product import ( account_used, template_property) from trytond.modules.currency.fields import Monetary -from trytond.modules.product import price_digits, round_price +from trytond.modules.product import ( + copy_product_filtered, copy_template_filtered, price_digits, round_price) from trytond.pool import Pool, PoolMeta from trytond.pyson import Eval, Id, If, TimeDelta from trytond.transaction import Transaction @@ -97,7 +98,7 @@ class CategoryCustomerRentalTax(ModelSQL): cls.__access__.add('tax') -class Template(metaclass=PoolMeta): +class Template(copy_template_filtered('rental_prices'), metaclass=PoolMeta): __name__ = 'product.template' rentable = fields.Boolean( @@ -160,30 +161,8 @@ class Template(metaclass=PoolMeta): 'invisible': ~Eval('rentable', False), })] - @classmethod - def copy(cls, templates, default=None): - pool = Pool() - RentalPrice = pool.get('product.rental.price') - default = default.copy() if default is not None else {} - copy_rental_prices = 'rental_prices' not in default - default.setdefault('rental_prices', None) - new_templates = super().copy(templates, default=default) - if copy_rental_prices: - old2new = {} - to_copy = [] - for template, new_template in zip(templates, new_templates): - to_copy.extend( - rp for rp in template.rental_prices if not rp.product) - old2new[template.id] = new_template.id - if to_copy: - RentalPrice.copy(to_copy, { - 'template': lambda d: old2new[d['template']], - }) - return new_templates - - -class Product(metaclass=PoolMeta): +class Product(copy_product_filtered('rental_prices'), metaclass=PoolMeta): __name__ = 'product.product' rental_prices = fields.One2Many( @@ -261,32 +240,6 @@ class Product(metaclass=PoolMeta): if price.match(quantity, duration, pattern): return price.get_unit_price(self.rental_unit) - @classmethod - def copy(cls, products, default=None): - pool = Pool() - RentalPrice = pool.get('product.rental.price') - default = default.copy() if default is not None else {} - - copy_rental_prices = 'rental_prices' not in default - if 'template' in default: - default.setdefault('rental_prices', None) - new_products = super().copy(products, default=default) - if 'template' in default and copy_rental_prices: - template2new = {} - product2new = {} - to_copy = [] - for product, new_product in zip(products, new_products): - if product.rental_prices: - to_copy.extend(product.rental_prices) - template2new[product.template.id] = new_product.template.id - product2new[product.id] = new_product.id - if to_copy: - RentalPrice.copy(to_copy, { - 'product': lambda d: product2new[d['product']], - 'template': lambda d: template2new[d['template']], - }) - return new_products - class RentalPrice(sequence_ordered(), ModelSQL, ModelView, MatchMixin): __name__ = 'product.rental.price' diff --git a/modules/stock_product_location/product.py b/modules/stock_product_location/product.py index 8c87fcbab5..6475f93563 100644 --- a/modules/stock_product_location/product.py +++ b/modules/stock_product_location/product.py @@ -1,11 +1,13 @@ # 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 fields -from trytond.pool import Pool, PoolMeta +from trytond.modules.product import ( + copy_product_filtered, copy_template_filtered) +from trytond.pool import PoolMeta from trytond.pyson import Eval -class Template(metaclass=PoolMeta): +class Template(copy_template_filtered('locations'), metaclass=PoolMeta): __name__ = 'product.template' locations = fields.One2Many('stock.product.location', 'template', "Default Locations", @@ -13,32 +15,8 @@ class Template(metaclass=PoolMeta): 'invisible': ~Eval('type').in_(['goods', 'assets']), }) - @classmethod - def copy(cls, templates, default=None): - pool = Pool() - ProductLocation = pool.get('stock.product.location') - if default is None: - default = {} - else: - default = default.copy() - copy_locations = 'locations' not in default - default.setdefault('locations', None) - new_templates = super().copy(templates, default) - if copy_locations: - old2new = {} - to_copy = [] - for template, new_template in zip(templates, new_templates): - to_copy.extend(l for l in template.locations if not l.product) - old2new[template.id] = new_template.id - if to_copy: - ProductLocation.copy(to_copy, { - 'template': lambda d: old2new[d['template']], - }) - return new_templates - - -class Product(metaclass=PoolMeta): +class Product(copy_product_filtered('locations'), metaclass=PoolMeta): __name__ = 'product.product' locations = fields.One2Many('stock.product.location', 'product', "Default Locations", @@ -48,32 +26,3 @@ class Product(metaclass=PoolMeta): states={ 'invisible': ~Eval('type').in_(['goods', 'assets']), }) - - @classmethod - def copy(cls, products, default=None): - pool = Pool() - ProductLocation = pool.get('stock.product.location') - if default is None: - default = {} - else: - default = default.copy() - - copy_locations = 'locations' not in default - if 'template' in default: - default.setdefault('locations', None) - new_products = super().copy(products, default) - if 'template' in default and copy_locations: - template2new = {} - product2new = {} - to_copy = [] - for product, new_product in zip(products, new_products): - if product.locations: - to_copy.extend(product.locations) - template2new[product.template.id] = new_product.template.id - product2new[product.id] = new_product.id - if to_copy: - ProductLocation.copy(to_copy, { - 'product': lambda d: product2new[d['product']], - 'template': lambda d: template2new[d['template']], - }) - return new_products diff --git a/modules/stock_product_location_place/product.py b/modules/stock_product_location_place/product.py index 2247730594..0d775ae03f 100644 --- a/modules/stock_product_location_place/product.py +++ b/modules/stock_product_location_place/product.py @@ -2,11 +2,13 @@ # this repository contains the full copyright notices and license terms. from trytond.model import fields -from trytond.pool import Pool, PoolMeta +from trytond.modules.product import ( + copy_product_filtered, copy_template_filtered) +from trytond.pool import PoolMeta from trytond.pyson import Eval -class Template(metaclass=PoolMeta): +class Template(copy_template_filtered('location_places'), metaclass=PoolMeta): __name__ = 'product.template' location_places = fields.One2Many( @@ -22,29 +24,8 @@ class Template(metaclass=PoolMeta): and place.location == location): return place - @classmethod - def copy(cls, templates, default=None): - pool = Pool() - ProductLocationPlace = pool.get('stock.product.location.place') - default = default.copy() if default is not None else {} - copy_location_places = 'location_places' not in default - default.setdefault('location_places', None) - new_templates = super().copy(templates, default=default) - if copy_location_places: - old2new, to_copy = {}, [] - for template, new_template in zip(templates, new_templates): - to_copy.extend( - p for p in template.location_places if not p.product) - old2new[template.id] = new_template.id - if to_copy: - ProductLocationPlace.copy(to_copy, { - 'template': lambda d: old2new[d['template']], - }) - return new_templates - - -class Product(metaclass=PoolMeta): +class Product(copy_product_filtered('location_places'), metaclass=PoolMeta): __name__ = 'product.product' location_places = fields.One2Many( @@ -59,27 +40,3 @@ class Product(metaclass=PoolMeta): if place.location == location: return place return self.template.get_place(location) - - @classmethod - def copy(cls, products, default=None): - pool = Pool() - ProductLocationPlace = pool.get('stock.product.location.place') - default = default.copy() if default is not None else {} - - copy_location_places = 'location_places' not in default - if 'template' in default: - default.setdefault('location_places', None) - new_products = super().copy(products, default=default) - if 'template' in default and copy_location_places: - template2new, product2new, to_copy = {}, {}, [] - for product, new_product in zip(products, new_products): - if product.location_places: - to_copy.extend(product.location_places) - template2new[product.template.id] = new_product.template.id - product2new[product.id] = new_product.id - if to_copy: - ProductLocationPlace.copy(to_copy, { - 'product': lambda d: product2new[d['product']], - 'template': lambda d: template2new[d['template']], - }) - return new_products