Commits

grasbauer committed 9573298

Comments (0)

Files changed (5)

 #This file is part of Tryton.  The COPYRIGHT file at the top level of
 #this repository contains the full copyright notices and license terms.
 {
-    'name' : 'Product Variants',
-    'version' : '2.0.2',
-    'author' : 'grasbauer',
+    'name': 'Product Variants',
+    'version': '2.3.0',
+    'author': 'grasbauer',
     'email': 'info@grasbauer.com',
     'website': 'http://www.grasbauer.com/',
-    'description': 'one click variant generation',
-    'depends' : [
+    'description': """Manage variants of products with automatic 
+generation based on attributes.""",
+    'depends': [
         'ir',
         'res',
         'product',
-            ],
-    'xml' : [
-             'configuration.xml',
-             'product.xml',
-
-            ]
+    ],
+    'xml': [
+        'configuration.xml',
+        'product.xml',
+    ]
 }
 
 #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 ModelView, ModelSQL, ModelSingleton, fields
-from trytond.pyson import Eval
 
 class Configuration(ModelSingleton, ModelSQL, ModelView):
-    'Product Configuration'
+    'Variant Configuration'
     _name = 'product.variant.configuration'
     _description = __doc__
 
-    identifier_seperator = fields.Property(fields.Char('Identifier seperator'))
-    code_seperator = fields.Property(fields.Char('Code seperator'))
+    code_separator = fields.Char('Code Separator')
 
 Configuration()

configuration.xml

 <?xml version="1.0"?>
-<!-- This file is part of Tryton.  The COPYRIGHT file at the top level of
-this repository contains the full copyright notices and license terms. -->
+    <!--
+        This file is part of Tryton. The COPYRIGHT file at the top level of
+        this repository contains the full copyright notices and license terms.
+    -->
 <tryton>
     <data>
-        <record model="ir.ui.view" id="product_configuration_view_form">
+        <menuitem name="Variant Configuration" 
+            parent="product.menu_main_product"
+            sequence="10" id="menu_product_configuration" 
+            groups="product.group_product_admin"/>
+
+        <record model="ir.ui.view" id="configuration_view_form">
             <field name="model">variant.configuration</field>
             <field name="type">form</field>
             <field name="arch" type="xml">
                 <![CDATA[
-                <form string="Variant Configuration">
-                    <label name="identifier_seperator"/>
-                    <field name="identifier_seperator"/>
-                    <label name="code_seperator" />
-                    <field name="code_seperator" />
+                <form string="Code Separator">
+                    <label name="code_separator"/>
+                    <field name="code_separator"/>
                 </form>
                 ]]>
             </field>
         </record>
-        
-         <record model="ir.action.act_window" 
-                 id="act_variant_configuration_form">
+
+        <record model="ir.action.act_window" id="act_variant_configuration_form">
             <field name="name">Variant Configuration</field>
             <field name="res_model">product.variant.configuration</field>
         </record>
-       
-        <record model="ir.action.act_window.view"
-            id="act_product_configuration_view1">
-            <field name="sequence" eval="1"/>
+
+        <record model="ir.action.act_window.view" id="act_product_configuration_view1">
+            <field name="sequence" eval="10"/>
             <field name="view" ref="act_variant_configuration_form"/>
             <field name="act_window" ref="act_variant_configuration_form"/>
         </record>
         
-        <menuitem parent="product.menu_main_product"
+        <menuitem parent="menu_product_configuration" id="menu_variant_configuration"
             action="act_variant_configuration_form"
-            id="menu_variant_configuration"
-            sequence="0" icon="tryton-list"
+            sequence="10" icon="tryton-list"
             groups="product.group_product_admin"/>
-      
+
     </data>
 </tryton>
-#-*- coding:utf-8 -*-
 #This file is part of Tryton.  The COPYRIGHT file at the top level of
 #this repository contains the full copyright notices and license terms.
 import copy
 import itertools
 
 from trytond.model import ModelView, ModelSQL, fields
-from trytond.pyson import Equal, Eval, Greater, Not, In, If, Get, Bool, Or, And
+from trytond.pyson import Eval, Greater, Or, And, Not, Bool, PYSONEncoder
 from trytond.transaction import Transaction
 
 class Product(ModelSQL, ModelView):
     _name = 'product.product'
 
-    identifier = fields.Char('Identifier', readonly=True)
+    attribute_values = fields.Many2Many('product.product-attribute.value',
+        'product', 'value', 'Values', readonly=True)
+    template = fields.Many2One('product.template', 'Product Template',
+        ondelete='CASCADE', select=1,
+        states={
+            'required':Greater(Eval('active_id', 0), 0),
+            'invisible':Not(Greater(Eval('active_id', 0), 0)),
+            'readonly':Not(Bool(Eval('variant')))
+        })
 
-    def __init__(self):
-        super(Product, self).__init__()
-        fields = self._columns
-        for field in self._inherit_fields.itervalues():
-            if not field in fields:
-                field[2].states['readonly'] = Eval('variant')
-        self.template = copy.copy(self.template)
-        self.template.states = copy.copy(self.template.states)
-        self.template.states['required'] = Greater(Eval('active_id', 0), 0)
-        self.template.states['invisible'] = Not(Greater(Eval('active_id', 0), 0))
-        self._reset_columns()
-
-    def fields_get(self, fields_names=None):
-        res = super(Product, self).fields_get(fields_names)
-        if 'codebase' in res.keys(): del res['codebase']
-        return res
-
-    def create(self, vals):
-        if not vals.get('template'):
-            vals.pop('template')
-        return super(Product, self).create(vals)
+    def create(self, values):
+        if not values.get('template'):
+            values = values.copy()
+            values.pop('template')
+        return super(Product, self).create(values)
 
     def delete(self, ids):
         if isinstance(ids, (int, long)):
 
 Product()
 
+
 class Template(ModelSQL, ModelView):
     _name = "product.template"
 
-    codebase = fields.Char('Code')
+    basecode = fields.Char('Basecode',
+        states={
+            'invisible': Not(Bool(Eval('attributes')))
+        }, depends=['attributes'])
     attributes = fields.Many2Many('product.template-product.attribute',
-                                  'template', 'attribute', 'Attributes')
-
-    variant = fields.Function(fields.Boolean('Variant'),
-            'get_variants')
-
-    description = fields.Text("Description", translate=True)
+        'template', 'attribute', 'Attributes')
+    variant = fields.Function(fields.Boolean('Variant', select=1),
+        'get_variants', searcher="search_variant")
+    basedescription = fields.Text("Basedescription", translate=True)
 
     def __init__(self):
         super(Template, self).__init__()
         self._rpc.update({
-            'generate_contained_products': True,
-                   })
+            'generate_variants': True,
+        })
+
+        for column in self._columns.itervalues():
+            already = False
+            if 'readonly' in column.states:
+                already = column.states['readonly']
+            column.states = {'readonly': Or(And(Bool(Eval('template')),
+                Bool(Eval('variant'))), already)}
 
     def delete(self, ids):
         if isinstance(ids, (int, long)):
             ids = [ids]
         templates = self.browse(ids)
         for template in templates:
-            if template.products or Transaction().context.get('save_templates'):
+            if template.products or \
+            Transaction().context.get('save_templates'):
                 return
         res = super(Template, self).delete(ids)
         return res
                 res[template.id] = True
         return res
 
-    def generate_contained_products(self, ids):
-        """
-        generate all variants - if not already done
-        """
-        product_obj = self.pool.get('product.product')
+    def search_variant(self, name, clause):
+        res = []
+        ids = self.search([('id', '>', 0)])
+        records = self.browse(ids)
+        if clause[2] == True:
+            for template in records:
+                if len(template.products) > 1:
+                    res.append(template.id)
+        else:
+            for template in records:
+                if len(template.products) == 1:
+                    res.append(template.id)
+        return [('id', 'in', res)]
+
+    def create_code(self, basecode, variant):
         config_obj = self.pool.get('product.variant.configuration')
         config = config_obj.browse(1)
+        sep = config.code_seperator or ''
+        code = '%s%s' % (basecode or '', ['', sep][bool(basecode)])
+        code = code + sep.join(i.code for i in variant)
+        return code
+
+    def create_product(self, template, variant):
+        "create the product"
+        product_obj = self.pool.get('product.product')
+        value_obj = self.pool.get('product.product-attribute.value')
+        code = self.create_code(template.basecode, variant)
+        new_id = product_obj.create({'template':template.id,
+            'code':code})
+        for value in variant:
+            value_obj.create({'product':new_id, 'value':value.id})
+        return True
+
+    def generate_variants(self, ids):
+        """generate variants"""
         if not ids:
             return {}
         res = {}
         for template in self.browse(ids):
             if not template.attributes:
                 continue
-            already = [p.code for p in template.products]
-            #map?
-            identifier = [(i.sequence, i.values) for i in template.attributes]
-            identifier.sort()
-            identifier = [i[1] for i in identifier]
-            variants = list(itertools.product(*identifier))
+            already = set(tuple(p.attribute_values) for p in template.products)
+            values = [i.values for i in template.attributes]
+            variants = itertools.product(*values)
+            for variant in variants:
+                if not variant in already:
+                    self.create_product(template, variant)
+        return res
 
-            for variant in variants:
-                sep = config.identifier_seperator or ''
-                identifier = sep.join([i.name for i in variant])
-                code = '%s%s' % (template.codebase or '',
-                                 config.code_seperator or '')
-                sep = config.code_seperator or ''
-                code = code + sep.join([i.code for i in variant])
-                if not code in already:
-                    product_obj.create({'template':template.id,
-                                        'identifier':identifier,
-                                        'code':code})
-        return res
 Template()
 
+
 class ProductAttribute(ModelSQL, ModelView):
-    "Possible Attribute"
+    "Product Attribute"
     _name = "product.attribute"
     _description = __doc__
 
     sequence = fields.Integer('Sequence')
-    name = fields.Char('Name', size=None, required=True, translate=True,
-            select=1)
+    name = fields.Char('Name', required=True, translate=True, select=1)
     values = fields.One2Many('product.attribute.value', 'attribute', 'Values')
 
     def __init__(self):
         super(ProductAttribute, self).__init__()
+        self._order.insert(0, ('sequence', 'ASC'))
 
 ProductAttribute()
 
 
-class ProductAttributeValue(ModelSQL, ModelView):
+class AttributeValue(ModelSQL, ModelView):
     "Values for Attributes"
     _name = "product.attribute.value"
     _description = __doc__
     name = fields.Char('Name', required=True, select=1)
     code = fields.Char('Code', required=True)
     attribute = fields.Many2One('product.attribute', 'Product Attribute',
-            required=True, ondelete='CASCADE', select=1)
+        required=True)
+
     def __init__(self):
-        super(ProductAttributeValue, self).__init__()
+        super(AttributeValue, self).__init__()
         self._order.insert(0, ('sequence', 'ASC'))
 
-ProductAttributeValue()
+AttributeValue()
 
 
 class ProductTemplateAttribute(ModelSQL, ModelView):
-    "Product Template Product Attribute"
+    "Product Template - Product Attribute"
     _name = "product.template-product.attribute"
     _description = __doc__
 
-
     attribute = fields.Many2One('product.attribute', 'Product Attribute',
-            ondelete='CASCADE', select=1, required=True)
+            ondelete='RESTRICT', required=True)
     template = fields.Many2One('product.template', 'Product template',
-            ondelete='CASCADE', select=1, required=True)
+            ondelete='CASCADE', required=True)
 
 ProductTemplateAttribute()
 
 
+class ProductAttributeValue(ModelSQL, ModelView):
+    "Product - Product Attribute Value"
+    _name = "product.product-attribute.value"
+    _description = __doc__
 
+    product = fields.Many2One('product.product', 'Product',
+            ondelete='CASCADE', required=True)
+    value = fields.Many2One('product.attribute.value', 'Attribute Value',
+            ondelete='CASCADE', required=True)
+
+ProductAttributeValue()
 <?xml version="1.0"?>
-	<!--
-		This file is part of Tryton. The COPYRIGHT file at the top level of
-		this repository contains the full copyright notices and license terms.
-	-->
+    <!--
+        This file is part of Tryton. The COPYRIGHT file at the top level of
+        this repository contains the full copyright notices and license terms.
+    -->
 <tryton>
-	<data>
-		<!-- productview anpassen -->
-		<record model="ir.ui.view" id="product_view_form">
-			<field name="model">product.product</field>
-			<field name="inherit" ref="product.product_view_form" />
-			<field name="arch" type="xml">
-				<![CDATA[
-				<data>
-					<xpath expr="/form/notebook/page[@id=&quot;products&quot;]"
-						position="replace">
-					</xpath>
-					<xpath expr="/form/notebook/page[@id=&quot;description&quot;]"
-						position="replace">
-					</xpath>
-					<xpath
-						expr="/form/notebook/page[@id=&quot;general&quot;]/label[@name=&quot;code&quot;]"
-						position="before">
-						<label name="template" />
-						<field name="template" />
-						<label name="variant" />
-						<field name="variant" />
-
-					</xpath>
-					<xpath
-						expr="/form/notebook/page[@id=&quot;general&quot;]/field[@name=&quot;list_price&quot;]"
-						position="after">
-						<label name="identifier" />
-						<field name="identifier" />
-					</xpath>
-				</data>
-
-				]]>
-			</field>
-		</record>
-
-		<record model="ir.ui.view" id="product_view_tree2">
-			<field name="model">product.product</field>
-			<field name="inherit" ref="product.product_view_tree" />
-			<field name="arch" type="xml">
+    <data>
+        <record model="ir.ui.view" id="product_view_form">
+            <field name="model">product.product</field>
+            <field name="inherit" ref="product.product_view_form"/>
+            <field name="arch" type="xml">
                 <![CDATA[
                 <data>
                     <xpath
-                        expr="/tree/field[@name=&quot;code&quot;]"
-                        position="after">
-                         <field name="identifier"/>
+                        expr="/form/notebook/page[@id=&quot;general&quot;]/label[@name=&quot;code&quot;]"
+                        position="before">
+                        <label name="template"/>
+                        <field name="template"/>
+                        <label name="variant"/>
+                        <field name="variant"/>
                     </xpath>
-                    
-                    
+                   
                 </data>
                 ]]>
-			</field>
-		</record>
+            </field>
+        </record>
+        
+        <record model="ir.ui.view" id="product_view_tree">
+            <field name="model">product.product</field>
+            <field name="inherit" ref="product.product_view_tree"/>
+            <field name="arch" type="xml">
+                <![CDATA[
+                <data>
+                    <xpath
+                        expr="/tree/field[@name=&quot;variant&quot;]"
+                        position="replace"/>
+                </data>
+                ]]>
+            </field>
+        </record>
 
-		
-		<!--  access to product templates -->
+        <record model="ir.ui.view" id="template_view_form">
+            <field name="model">product.template</field>
+            <field name="inherit" ref="product.template_view_form"/>
+            <field name="arch" type="xml">
+                <![CDATA[
+                <data>
+                    <xpath expr="/form/field[@name=&quot;active&quot;]"
+                        position="after">
+                        <label name="basecode"/>
+                        <field name="basecode"/>
+                    </xpath>
+                    <xpath expr="/form/notebook/page[@id=&quot;general&quot;]"
+                        position="after">
+                        <page id="attribute_values" string="Attribute Values"
+                            states="{'invisible': 
+                                Or(Not(Bool(Eval('attribute_values'))),
+                                Not(Bool(Eval('variant'))))}">
+                            <field name="attribute_values"/>
+                        </page>
+                    </xpath>
+                    <xpath expr="/form/notebook/page[@id=&quot;general&quot;]"
+                        position="after">
+                        <page string="Basedescription" col="4" id="description"
+                            states="{'invisible': Not(Bool(Eval('variant')))}">
+                            <field name="basedescription" colspan="4"/>
+                        </page>
+                    </xpath>
+                    <xpath expr="/form/notebook/page[@id=&quot;general&quot;]"
+                        position="after">
+                        <page string="Variants" col="4" id="products"
+                            states="{'invisible': Bool(Eval('template'))}">
+                            <field name="products" colspan="4"/>
+                            <field name="attributes" colspan="4"/>
+                            <button name="generate_variants" 
+                                string="Generate Variants"
+                                type="object" icon="tryton-ok"/>
+                        </page>
+                    </xpath>
+                </data>
+                ]]>
+            </field>
+        </record>
 
-		<record model="ir.action.act_window" id="act_product_template_form">
-			<field name="name">Product Template</field>
-			<field name="res_model">product.template</field>
-
-
-		</record>
-
-		<menuitem name="Product Template" parent="product.menu_main_product"
-			sequence="0" id="menu_product_template" action="act_product_template_form"
-			groups="product.group_product_admin" />
-		
-
-		<record model="ir.ui.view" id="template_view_form">
-			<field name="model">product.template</field>
-			<field name="inherit" ref="product.template_view_form" />
-			<field name="arch" type="xml">
-				<![CDATA[
-				<data>
-					<xpath expr="/form/field[@name=&quot;active&quot;]" position="after">
-						<label name="codebase" />
-						<field name="codebase" />
-					</xpath>
-
-					<xpath expr="/form/notebook/page[@id=&quot;general&quot;]"
-						position="after">
-						<page string="Description" col="4" id="description">
-							<field name="description" colspan="4" />
-
-						</page>
-					</xpath>
-					<xpath expr="/form/notebook/page[@id=&quot;general&quot;]"
-						position="after">
-						<page string="Products" col="4" id="products">
-							<field name="products" colspan="4" />
-							<field name="attributes" colspan="4" />
-							<button name="generate_contained_products" 
-							        string="Generate Variants"
-								    type="object" 
-								    icon="tryton-ok" />
-						</page>
-					</xpath>
-				</data>
-				]]>
-			</field>
-		</record>
-
-		<record model="ir.ui.view" id="template_view_tree">
-			<field name="model">product.template</field>
-			<field name="inherit" ref="product.template_view_tree" />
-			<field name="arch" type="xml">
+        <record model="ir.ui.view" id="template_view_tree">
+            <field name="model">product.template</field>
+            <field name="inherit" ref="product.template_view_tree"/>
+            <field name="arch" type="xml">
                 <![CDATA[
                 <data>
                     <xpath
                         expr="/tree/field[@name=&quot;name&quot;]"
                         position="after">
-                        <field name="variants"/>   
+                        <field name="variant"/>   
                     </xpath>
-                    
                 </data>
                 ]]>
-			</field>
-		</record>
+            </field>
+        </record>
 
+        <record model="ir.action.act_window" id="act_template_form">
+            <field name="name">Product Template</field>
+            <field name="res_model">product.template</field>
+            <field name="search_value">{'variant': ['=', True]}</field>
+        </record>
 
+        <record model="ir.action.act_window.view" id="act_template_list_view">
+            <field name="sequence" eval="100"/>
+            <field name="view" ref="template_view_tree"/>
+            <field name="act_window" ref="act_template_form"/>
+        </record>
 
-		<!-- Attribute -->
-		<record model="ir.action.act_window" id="act_product_attribute_form">
-			<field name="name">Product Attributes</field>
-			<field name="res_model">product.attribute</field>
-		</record>
-		<record model="ir.ui.view" id="product_attribute_view_form">
-			<field name="model">product.attribute</field>
-			<field name="type">form</field>
-			<field name="arch" type="xml">
+        <record model="ir.action.act_window.view" id="act_template_form_view">
+            <field name="sequence" eval="110"/>
+            <field name="view" ref="template_view_form"/>
+            <field name="act_window" ref="act_template_form"/>
+        </record>
+
+        <menuitem name="Templates" parent="product.menu_main_product"
+            sequence="10" id="menu_product_template" 
+            action="act_template_form"/>
+
+        <record model="ir.action.act_window" id="act_product_attribute_form">
+            <field name="name">Product Attributes</field>
+            <field name="res_model">product.attribute</field>
+        </record>
+
+        <record model="ir.ui.view" id="product_attribute_view_form">
+            <field name="model">product.attribute</field>
+            <field name="type">form</field>
+            <field name="arch" type="xml">
                 <![CDATA[
                 <form string="Product Attributes">
                 <label name="name"/>
                 <newline/>
                 <field name="values" colspan="4"/>
                 </form>
-                
                 ]]>
-			</field>
-		</record>
+            </field>
+        </record>
 
-		<record model="ir.ui.view" id="product_attribute_view_tree">
-			<field name="model">product.attribute</field>
-			<field name="type">tree</field>
-			<field name="arch" type="xml">
+        <record model="ir.ui.view" id="product_attribute_view_tree">
+            <field name="model">product.attribute</field>
+            <field name="type">tree</field>
+            <field name="arch" type="xml">
                 <![CDATA[
-                <tree string="Product Attributes">
+                <tree string="Product Attributes" sequence="sequence">
                 <field name="sequence"/>
                 <field name="name"/>
                 <field name="values"/>
                 </tree>
                 ]]>
-			</field>
-		</record>
-
-		<menuitem name="Product Attributes" parent="product.menu_main_product"
-			sequence="2" id="menu_product_attribute" action="act_product_attribute_form"
-			groups="product.group_product_admin" />
-
-		<record model="ir.ui.view" id="product_attribute_value_view_form">
-			<field name="model">product.attribute.value</field>
-			<field name="type">form</field>
-			<field name="arch" type="xml">
-                <![CDATA[
-                <form string="Product Attribute Values">
-                <label name="name"/>
-                <field name="name"/>
-                <label name="code"/>
-                <field name="code"/>
-                </form>
-                ]]>
-			</field>
-		</record>
-		<record model="ir.ui.view" id="product_attribute_value_view_tree">
-			<field name="model">product.attribute.value</field>
-			<field name="type">tree</field>
-			<field name="arch" type="xml">
-                <![CDATA[
-                <tree string="Product Attribute Values" sequence="sequence">
-                <field name="sequence" tree_invisible="1"/>
-                <field name="name"/>
-                <field name="code"/>
-                </tree>
-                ]]>
-			</field>
-		</record>
-
-
-	</data>
+            </field>
+        </record>
+    </data>
 </tryton>