284 lines
9.9 KiB
Python
284 lines
9.9 KiB
Python
# 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 decimal import Decimal
|
|
|
|
from trytond.model import (
|
|
ModelSQL, ModelStorage, ModelView, fields, sequence_ordered)
|
|
from trytond.modules.product import round_price
|
|
from trytond.pool import Pool, PoolMeta
|
|
from trytond.pyson import Bool, Eval, If
|
|
|
|
|
|
class Template(metaclass=PoolMeta):
|
|
__name__ = "product.template"
|
|
|
|
components = fields.One2Many(
|
|
'product.component', 'parent_template', "Components")
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super().__setup__()
|
|
cls.type.selection.append(('kit', "Kit"))
|
|
|
|
@classmethod
|
|
def _cost_price_method_domain_per_type(cls):
|
|
types_cost_method = super()._cost_price_method_domain_per_type()
|
|
types_cost_method['kit'] = [('cost_price_method', '=', 'fixed')]
|
|
return types_cost_method
|
|
|
|
@fields.depends('type', 'cost_price_method')
|
|
def on_change_type(self):
|
|
super().on_change_type()
|
|
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):
|
|
__name__ = "product.product"
|
|
|
|
components = fields.One2Many(
|
|
'product.component', 'parent_product', "Components")
|
|
|
|
def get_multivalue(self, name, **pattern):
|
|
pool = Pool()
|
|
Uom = pool.get('product.uom')
|
|
value = super().get_multivalue(name, **pattern)
|
|
if name == 'cost_price' and self.type == 'kit':
|
|
value = Decimal(0)
|
|
for component in self.components_used:
|
|
cost_price = component.product.get_multivalue(
|
|
'cost_price', **pattern)
|
|
cost_price = Uom.compute_price(
|
|
component.product.default_uom, cost_price, component.unit)
|
|
value += cost_price * Decimal(str(component.quantity))
|
|
value = round_price(value)
|
|
return value
|
|
|
|
@property
|
|
def components_used(self):
|
|
if self.components:
|
|
yield from self.components
|
|
else:
|
|
for component in self.template.components:
|
|
if not component.parent_product:
|
|
yield component
|
|
|
|
@classmethod
|
|
def get_quantity(cls, products, name):
|
|
pool = Pool()
|
|
Uom = pool.get('product.uom')
|
|
kits = [p for p in products if p.type == 'kit']
|
|
quantities = super().get_quantity(products, name)
|
|
for kit in kits:
|
|
qties = []
|
|
for component in kit.components_used:
|
|
component_qty = Uom.compute_qty(
|
|
component.product.default_uom,
|
|
getattr(component.product, name),
|
|
component.unit, round=False)
|
|
if not component.fixed:
|
|
component_qty /= component.quantity
|
|
qties.append(component_qty)
|
|
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):
|
|
|
|
parent_type = fields.Function(fields.Selection(
|
|
'get_product_types', "Parent Type"), 'on_change_with_parent_type')
|
|
product = fields.Many2One(
|
|
'product.product', "Product", required=True,
|
|
domain=[
|
|
('components', '=', None),
|
|
('template.components', '=', None),
|
|
If(Eval('parent_type') == 'kit',
|
|
('type', '=', 'goods'),
|
|
()),
|
|
])
|
|
product_unit_category = fields.Function(
|
|
fields.Many2One('product.uom.category', "Product Unit Category"),
|
|
'on_change_with_product_unit_category')
|
|
quantity = fields.Float("Quantity", digits='unit', required=True)
|
|
unit = fields.Many2One('product.uom', "Unit", required=True,
|
|
domain=[
|
|
If(Bool(Eval('product_unit_category')),
|
|
('category', '=', Eval('product_unit_category')),
|
|
('category', '!=', -1)),
|
|
],
|
|
depends={'product'})
|
|
fixed = fields.Boolean("Fixed",
|
|
help="Check to make the quantity of the component independent "
|
|
"of the kit quantity.")
|
|
|
|
@classmethod
|
|
def get_product_types(cls):
|
|
pool = Pool()
|
|
Product = pool.get('product.product')
|
|
return Product.fields_get(['type'])['type']['selection']
|
|
|
|
def on_change_with_parent_type(self, name):
|
|
raise NotImplementedError
|
|
|
|
@property
|
|
def parent_uom(self):
|
|
raise NotImplementedError
|
|
|
|
@fields.depends('product', 'unit', 'quantity',
|
|
methods=['on_change_with_product_unit_category'])
|
|
def on_change_product(self):
|
|
if self.product:
|
|
self.product_unit_category = (
|
|
self.on_change_with_product_unit_category())
|
|
if (not self.unit
|
|
or self.unit.category != self.product_unit_category):
|
|
self.unit = self.product.default_uom
|
|
|
|
@fields.depends('product')
|
|
def on_change_with_product_unit_category(self, name=None):
|
|
return self.product.default_uom.category if self.product else None
|
|
|
|
def get_line(self, Line, quantity, unit, **values):
|
|
pool = Pool()
|
|
Uom = pool.get('product.uom')
|
|
line = Line(product=self.product, **values)
|
|
line.unit = self.unit
|
|
if self.fixed:
|
|
line.quantity = self.quantity
|
|
else:
|
|
quantity = Uom.compute_qty(
|
|
unit, quantity, self.parent_uom, round=False)
|
|
line.quantity = self.unit.round(quantity * self.quantity)
|
|
return line
|
|
|
|
def get_rec_name(self, name):
|
|
pool = Pool()
|
|
Lang = pool.get('ir.lang')
|
|
lang = Lang.get()
|
|
return (lang.format_number_symbol(
|
|
self.quantity, self.unit, digits=self.unit.digits)
|
|
+ ' %s' % self.product.rec_name)
|
|
|
|
@classmethod
|
|
def search_rec_name(cls, name, clause):
|
|
return [
|
|
('product.rec_name', *clause[1:]),
|
|
]
|
|
|
|
|
|
class Component(ComponentMixin, ModelSQL, ModelView):
|
|
__name__ = "product.component"
|
|
|
|
parent_template = fields.Many2One(
|
|
'product.template', "Parent Product",
|
|
required=True, ondelete='CASCADE',
|
|
domain=[
|
|
If(Bool(Eval('parent_product')),
|
|
('products', '=', Eval('parent_product')),
|
|
()),
|
|
])
|
|
parent_product = fields.Many2One(
|
|
'product.product', "Parent Variant", ondelete='CASCADE',
|
|
domain=[
|
|
If(Bool(Eval('parent_template')),
|
|
('template', '=', Eval('parent_template')),
|
|
()),
|
|
])
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super().__setup__()
|
|
cls.__access__.add('parent_template')
|
|
|
|
@fields.depends(
|
|
'parent_product', '_parent_parent_product.template')
|
|
def on_change_parent_product(self):
|
|
if self.parent_product:
|
|
self.parent_template = self.parent_product.template
|
|
|
|
@fields.depends(
|
|
'parent_template', '_parent_parent_template.type',
|
|
'parent_product', '_parent_parent_product.type')
|
|
def on_change_with_parent_type(self, name=None):
|
|
if self.parent_product:
|
|
return self.parent_product.type
|
|
elif self.parent_template:
|
|
return self.parent_template.type
|
|
|
|
@property
|
|
def parent_uom(self):
|
|
if self.parent_product:
|
|
return self.parent_product.default_uom
|
|
elif self.parent_template:
|
|
return self.parent_template.default_uom
|
|
|
|
def get_rec_name(self, name):
|
|
return super().get_rec_name(name) + (
|
|
' @ %s' % (
|
|
self.parent_product.rec_name if self.parent_product
|
|
else self.parent_template.rec_name))
|
|
|
|
@classmethod
|
|
def search_rec_name(cls, name, clause):
|
|
return super().search_rec_name(name, clause) + [
|
|
('parent_product.rec_name',) + tuple(clause[1:]),
|
|
('parent_template.rec_name',) + tuple(clause[1:]),
|
|
]
|