[IMP] product : Improve variant usability

- Add an option in the General Settings : Use product variant.
- If this option is disabled : don't show any reference to variants
- When going on the product template view, and clinking on the stat button under the tab Variants,
  the form view should display only the information related to the variant
- Fields moves from product_template to product_product :  weight, volume, cost price,
- DO NOT display these fields on the product_template if there are more than 1 variants.
- In product variant view, set some fields readonly and add a link to edit it from Template.
This commit is contained in:
Yannick Tivisse
2015-07-17 16:22:27 +02:00
parent e9ad290221
commit 3befe66f43
10 changed files with 180 additions and 44 deletions
+1 -1
View File
@@ -29,7 +29,7 @@
</div>
</group>
<group>
<label for="id" string="Email"/>
<label for="id" string="Email" name="email_label"/>
<div name="email">
<div name="email.outgoing_server">
<button type="action"
+3 -3
View File
@@ -365,7 +365,7 @@
<group>
<group>
<field name="product_tmpl_id" on_change="onchange_product_tmpl_id(product_tmpl_id, product_qty, context)"/>
<field name="product_id"/>
<field name="product_id" groups="product.group_product_variant"/>
<label for="product_qty" string="Quantity"/>
<div>
<field name="product_qty" class="oe_inline" on_change="onchange_product_tmpl_id(product_tmpl_id, product_qty, context)"/>
@@ -400,7 +400,7 @@
<field name="product_efficiency" groups="mrp.group_rounding_efficiency"/>
<field name="date_start"/>
<field name="date_stop"/>
<field name="attribute_value_ids" widget="many2many_tags" domain="[('product_ids.product_tmpl_id', '=', parent.product_tmpl_id)]"/>
<field name="attribute_value_ids" widget="many2many_tags" domain="[('product_ids.product_tmpl_id', '=', parent.product_tmpl_id)]" groups="product.group_product_variant"/>
<field name="property_ids" widget="many2many_tags"/>
</tree>
</field>
@@ -513,7 +513,7 @@
<field name="date_start"/>
<field name="date_stop"/>
<field name="routing_id"/>
<field name="attribute_value_ids" widget="many2many_tags"/>
<field name="attribute_value_ids" widget="many2many_tags" groups="product.group_product_variant"/>
<field name="property_ids" widget="many2many_tags"/>
</tree>
</field>
+2 -2
View File
@@ -73,7 +73,7 @@
<tree string="Products Listprices Items">
<field name="sequence"/>
<field colspan="4" name="name"/>
<field name="product_id"/>
<field name="product_id" groups="product.group_product_variant"/>
<field name="product_tmpl_id"/>
<field name="categ_id"/>
<field name="min_quantity"/>
@@ -91,7 +91,7 @@
<h1><field name="name"/></h1>
<group col="4">
<field name="product_id" on_change="product_id_change(product_id)"/>
<field name="product_id" on_change="product_id_change(product_id)" groups="product.group_product_variant"/>
<field name="product_tmpl_id"/>
<field name="categ_id"/>
<field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/>
+49 -9
View File
@@ -470,6 +470,24 @@ class product_template(osv.osv):
res[product.id] = len(product.product_variant_ids)
return res
def _compute_product_template_field(self, cr, uid, ids, names, arg, context=None):
''' Compute the field from the product_variant if there is only one variant, otherwise returns 0.0 '''
res = {id: {} for id in ids}
templates = self.browse(cr, uid, ids, context=context)
unique_templates = [template.id for template in templates if template.product_variant_count == 1]
for template in templates:
for name in names:
res[template.id][name] = getattr(template.product_variant_ids[0], name) if template.id in unique_templates else 0.0
return res
def _set_product_template_field(self, cr, uid, product_tmpl_id, name, value, args, context=None):
''' Set the standard price modification on the variant if there is only one variant '''
template = self.pool['product.template'].browse(cr, uid, product_tmpl_id, context=context)
if template.product_variant_count == 1:
variant = self.pool['product.product'].browse(cr, uid, template.product_variant_ids.id, context=context)
return variant.write({name: value})
return {}
_columns = {
'name': fields.char('Name', required=True, translate=True, select=True),
'sequence': fields.integer('Sequence', help='Gives the sequence order when displaying a product list'),
@@ -488,13 +506,12 @@ class product_template(osv.osv):
'price': fields.function(_product_template_price, fnct_inv=_set_product_template_price, type='float', string='Price', digits_compute=dp.get_precision('Product Price')),
'list_price': fields.float('Sale Price', digits_compute=dp.get_precision('Product Price'), help="Base price to compute the customer price. Sometimes called the catalog price."),
'lst_price' : fields.related('list_price', type="float", string='Public Price', digits_compute=dp.get_precision('Product Price')),
'standard_price': fields.property(type = 'float', digits_compute=dp.get_precision('Product Price'),
'standard_price': fields.function(_compute_product_template_field, fnct_inv=_set_product_template_field, multi='_compute_product_template_field', type='float', string='Cost Price', digits_compute=dp.get_precision('Product Price'),
help="Cost price of the product template used for standard stock valuation in accounting and used as a base price on purchase orders. "
"Expressed in the default unit of measure of the product.",
string="Cost Price"),
'volume': fields.float('Volume', help="Volume is the amount of space that an item you are measuring takes up."),
'weight': fields.float('Gross Weight', digits_compute=dp.get_precision('Stock Weight'), help="The total weight, including contents, packaging, etc."),
'weight_net': fields.float('Net Weight', digits_compute=dp.get_precision('Stock Weight'), help="The weight of the contents, not including any packaging, etc."),
"Expressed in the default unit of measure of the product..", groups="base.group_user", store=True),
'volume': fields.function(_compute_product_template_field, fnct_inv=_set_product_template_field, multi='_compute_product_template_field', type='float', string='Volume', help="The volume in m3.", store=True),
'weight': fields.function(_compute_product_template_field, fnct_inv=_set_product_template_field, multi='_compute_product_template_field', type='float', string='Gross Weight', digits_compute=dp.get_precision('Stock Weight'), help="The gross weight in Kg.", store=True),
'weight_net': fields.function(_compute_product_template_field, fnct_inv=_set_product_template_field, multi='_compute_product_template_field', type='float', string='Net Weight', digits_compute=dp.get_precision('Stock Weight'), help="The net weight in Kg.", store=True),
'warranty': fields.float('Warranty'),
'sale_ok': fields.boolean('Can be Sold', help="Specify if the product can be selected in a sales order line."),
'pricelist_id': fields.dummy(string='Pricelist', relation='product.pricelist', type='many2one'),
@@ -622,7 +639,6 @@ class product_template(osv.osv):
def create_variant_ids(self, cr, uid, ids, context=None):
product_obj = self.pool.get("product.product")
ctx = context and context.copy() or {}
if ctx.get("create_product_variant"):
return None
@@ -703,6 +719,14 @@ class product_template(osv.osv):
related_vals['barcode'] = vals['barcode']
if vals.get('default_code'):
related_vals['default_code'] = vals['default_code']
if vals.get('standard_price'):
related_vals['standard_price'] = vals['standard_price']
if vals.get('volume'):
related_vals['volume'] = vals['volume']
if vals.get('weight'):
related_vals['weight'] = vals['weight']
if vals.get('weight_net'):
related_vals['weight_net'] = vals['weight_net']
if related_vals:
self.write(cr, uid, product_template_id, related_vals, context=context)
@@ -819,6 +843,16 @@ class product_product(osv.osv):
res.setdefault(id, 0.0)
return res
def open_product_template(self, cr, uid, ids, context=None):
""" Utility method used to add an "Open Template" button in product views """
product_product = self.browse(cr, uid, ids[0], context=context)
return {'type': 'ir.actions.act_window',
'res_model': 'product.template',
'view_mode': 'form',
'res_id': product_product.product_tmpl_id.id,
'target': 'current',
'flags': {'form': {'action_buttons': True}}}
def view_header_get(self, cr, uid, view_id, view_type, context=None):
if context is None:
context = {}
@@ -929,9 +963,8 @@ class product_product(osv.osv):
'product.template': (_get_name_template_ids, ['name'], 10),
'product.product': (lambda self, cr, uid, ids, c=None: ids, [], 10),
}, select=True),
'attribute_value_ids': fields.many2many('product.attribute.value', id1='prod_id', id2='att_id', string='Attributes', readonly=True, ondelete='restrict'),
'attribute_value_ids': fields.many2many('product.attribute.value', id1='prod_id', id2='att_id', string='Attributes', ondelete='restrict'),
'is_product_variant': fields.function( _is_product_variant_impl, type='boolean', string='Is a product variant'),
# image: all image fields are base64 encoded and PIL-supported
'image_variant': fields.binary("Variant Image",
help="This field holds the image used as image for the product variant, limited to 1024x1024px."),
@@ -946,6 +979,13 @@ class product_product(osv.osv):
'image_medium': fields.function(_get_image_variant, fnct_inv=_set_image_variant,
string="Medium-sized image", type="binary",
help="Image of the product variant (Medium-sized image of product template if false)."),
'standard_price': fields.property(type = 'float', digits_compute=dp.get_precision('Product Price'),
help="Cost price of the product template used for standard stock valuation in accounting and used as a base price on purchase orders. "
"Expressed in the default unit of measure of the product.",
groups="base.group_user", string="Cost Price"),
'volume': fields.float('Volume', help="The volume in m3."),
'weight': fields.float('Gross Weight', digits_compute=dp.get_precision('Stock Weight'), help="The gross weight in Kg."),
'weight_net': fields.float('Net Weight', digits_compute=dp.get_precision('Stock Weight'), help="The net weight in Kg."),
}
_defaults = {
+104 -25
View File
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<menuitem id="base.menu_product" name="Product Variants" parent="base.menu_base_partner" sequence="9"/>
<menuitem id="base.menu_product" name="Product Variants" parent="base.menu_base_partner" sequence="9" groups="product.group_product_variant"/>
<!-- templates -->
@@ -16,7 +16,7 @@
<separator/>
<filter string="Can be Sold" name="filter_to_sell" icon="terp-accessories-archiver-minus" domain="[('sale_ok','=',1)]"/>
<field name="categ_id" filter_domain="[('categ_id', 'child_of', self)]"/>
<field string="Product Variant" name="product_variant_ids" filter_domain="['|', ('product_variant_ids.name','ilike',self), ('product_variant_ids.attribute_value_ids.attribute_id.name','ilike',self)]"/>
<field string="Product Variant" name="product_variant_ids" filter_domain="['|', ('product_variant_ids.name','ilike',self), ('product_variant_ids.attribute_value_ids.attribute_id.name','ilike',self)]" groups="product.group_product_variant"/>
<field name="company_id"/>
<field name="pricelist_id" widget="selection" context="{'pricelist': self}" filter_domain="[]" groups="product.group_sale_pricelist"/> <!-- Keep widget=selection on this field to pass numeric `self` value, which is not the case for regular m2o widgets! -->
<group expand='0' name="group_by" string='Group by...'>
@@ -34,6 +34,10 @@
<field name="arch" type="xml">
<tree string="Product">
<field name="name"/>
<field name="standard_price"/>
<field name="volume"/>
<field name="weight"/>
<field name="weight_net"/>
<field name="categ_id"/>
<field name="type"/>
<field name="state"/>
@@ -85,7 +89,7 @@
<page string="Procurements" name="procurements" groups="base.group_user">
<group name="procurement">
<group name="general" col="4">
<group name="standard_price">
<group name="standard_price" attrs="{'invisible':[('product_variant_count','>',1)]}">
<label for="standard_price"/>
<div>
<field name="standard_price" class="oe_inline"/>
@@ -107,7 +111,7 @@
<field name="state"/>
<field name="product_manager"/>
</group>
<group name="weight" string="Weights" attrs="{'invisible':[('type','=','service')]}">
<group name="weight" string="Weights" attrs="{'invisible':['|', ('type','=','service'), ('product_variant_count','>',1)]}">
<field digits="(14, 3)" name="volume"/>
<field name="weight"/>
<field name="weight_net"/>
@@ -153,7 +157,7 @@
<field name="inherit_id" ref="product.product_template_search_view"/>
<field name="arch" type="xml">
<field name="product_variant_ids" position="replace">
<field name="attribute_value_ids"/>
<field name="attribute_value_ids" groups="product.group_product_variant"/>
</field>
<field name="name" position="after">
<field name="product_tmpl_id" string="Product Template"/>
@@ -190,7 +194,7 @@
</record>
<record id="product_normal_action" model="ir.actions.act_window">
<field name="name">Product Variants</field>
<field name="name">Product</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">product.product</field>
<field name="view_mode">tree,form,kanban</field>
@@ -211,17 +215,66 @@
</p>
</field>
</record>
<record id="product_variant_easy_edit_view" model="ir.ui.view">
<field name="name">product.product.view.form.easy</field>
<field name="model">product.product</field>
<field name="mode">primary</field>
<field name="arch" type="xml">
<form string="Variant Information">
<sheet>
<div class="oe_left" style="width: 500px;">
<field name="image_medium" widget="image" class="oe_avatar oe_left"/>
<div class="oe_title" style="width: 390px;">
<label class="oe_edit_only" for="name" string="Product Name"/>
<h1><field name="name" class="oe_inline"/></h1>
</div>
</div>
<group>
<p>All general settings about this product are managed on <button name="open_product_template" type="object" string="the product template" class="oe_link"/></p>
</group>
<group string="Attributes">
<field name="attribute_value_ids" widget="many2many_tags"/>
</group>
<group>
<group name="codes" string="Codes">
<field name="barcode"/>
<field name="default_code"/>
</group>
<group>
<field name="active"/>
</group>
</group>
<group>
<group name="pricing" string="Pricing">
<field name="lst_price"/>
<field name="standard_price"/>
</group>
<group name="weight">
<group name="weight" string="Weights">
<field digits="(14, 3)" name="volume"/>
<field digits="(14, 3)" name="weight"/>
<field digits="(14, 3)" name="weight_net"/>
</group>
</group>
</group>
</sheet>
</form>
</field>
</record>
<record id="product_variant_action" model="ir.actions.act_window">
<field name="name">Product Variants</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">product.product</field>
<field name="view_mode">tree,form,kanban</field>
<field name="view_type">form</field>
<field name="context">{'search_default_product_tmpl_id': [active_id], 'default_product_tmpl_id': active_id}</field>
<field name="search_view_id" ref="product_search_form_view"/>
<field name="view_id" eval="False"/> <!-- Force empty -->
<field name="domain" eval="False"/> <!-- Force empty -->
<field name="help" type="html">
<field name="search_view_id" ref="product_search_form_view"/>
<field name="view_ids"
eval="[(5, 0, 0),
(0, 0, {'view_mode': 'tree'}),
(0, 0, {'view_mode': 'form', 'view_id': ref('product_variant_easy_edit_view')}),
(0, 0, {'view_mode': 'kanban'})]"/>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to define a new product.
</p><p>
@@ -236,8 +289,6 @@
</field>
</record>
<!-- view specific to product.template -->
<record id="product_template_only_form_view" model="ir.ui.view">
<field name="name">product.template.product.form</field>
<field name="model">product.template</field>
@@ -252,21 +303,23 @@
<field name="barcode" attrs="{'invisible': [('product_variant_count', '>', 1)]}"/>
<field name="default_code" attrs="{'invisible': [('product_variant_count', '>', 1)]}"/>
</field>
<div name="button_box" position="inside">
<button class="oe_stat_button" string="Variant Prices" name="%(variants_template_action)d" type="action" icon="fa-usd"/>
<button class="oe_stat_button" name="%(product.product_variant_action)d" type="action" icon="fa-sitemap">
<button class="oe_stat_button" string="Variant Prices" name="%(variants_template_action)d" type="action" icon="fa-usd" groups="product.group_product_variant"/>
<button class="oe_stat_button" name="%(product.product_variant_action)d" type="action" icon="fa-sitemap" groups="product.group_product_variant">
<field string="List of Variants" name="product_variant_count" widget="statinfo" />
</button>
</div>
<xpath expr="//page[@name='sales']" position="after">
<page name="variants" string="Variants">
<page name="variants" string="Variants" groups="product.group_product_variant">
<p class="oe_grey">
<strong>Warning</strong>: adding or deleting attributes
will delete and recreate existing variants and lead
to the loss of their possible customizations.
</p>
<field name="attribute_line_ids" widget="one2many_list" context="{'show_attribute': False}">
<tree string="Variants" editable="bottom">
<tree string="Variants" editable="bottom">
<field name="attribute_id"/>
<field name="value_ids" widget="many2many_tags" options="{'no_create_edit': True}" domain="[('attribute_id', '=', attribute_id)]" context="{'default_attribute_id': attribute_id}"/>
</tree>
@@ -297,8 +350,13 @@
<span t-if="record.default_code.value">[<field name="default_code"/>]</span>
<field name="name"/>
</strong>
<div>
<a name="%(product.product_variant_action)d" type="action" groups="product.group_product_variant">
<t t-esc="record.product_variant_count.value"/> Variant(s)
</a>
</div>
<div name="tags"/>
<ul>
<li><t t-esc="record.product_variant_count.value"/> Variant(s)</li>
<li>Price: <field name="lst_price"></field></li>
</ul>
<div name="tags"/>
@@ -347,7 +405,7 @@
<tree string="Product Variants">
<field name="default_code"/>
<field name="name"/>
<field name="attribute_value_ids" widget="many2many_tags"/>
<field name="attribute_value_ids" widget="many2many_tags" groups="product.group_product_variant"/>
<field name="lst_price"/>
<field name="price" invisible="not context.get('pricelist',False)"/>
<field name="uom_id"/>
@@ -368,6 +426,17 @@
<form position="attributes">
<attribute name="string">Product Variant</attribute>
</form>
<notebook position="before">
<group>
<p>All general settings about this product are managed on <button name="open_product_template" type="object" string="the product template" class="oe_link"/></p>
</group>
</notebook>
<field name="seller_ids" position="replace">
<field name="seller_ids" readonly="1"/>
</field>
<field name="description_purchase" position="replace">
<field name="description_purchase" placeholder="This note will be displayed on requests for quotation..." readonly="1"/>
</field>
<field name="active" position="after">
<field name="barcode"/>
<field name="default_code"/>
@@ -379,8 +448,18 @@
<field name="name" attrs="{'invisible': [('id', '!=', False)]}"/>
<field name="product_tmpl_id" class="oe_inline" readonly="1" attrs="{'invisible': [('id', '=', False)], 'required': [('id', '!=', False)]}"/>
</field>
<field name="standard_price" position="replace">
<field name="standard_price" attrs="{'invisible':[('product_variant_count','=', 1)]}"/>
</field>
<group name="weight" position="replace">
<group name="weight" string="Weights" attrs="{'invisible':['|', ('type','=','service'), ('product_variant_count','=',1)]}">
<field digits="(14, 3)" name="volume"/>
<field digits="(14, 3)" name="weight"/>
<field digits="(14, 3)" name="weight_net"/>
</group>
</group>
<xpath expr="//div[@class='oe_title']" position="inside">
<field name="attribute_value_ids" widget="many2many_tags"/>
<field name="attribute_value_ids" widget="many2many_tags" groups="product.group_product_variant"/>
</xpath>
</field>
</record>
@@ -396,7 +475,7 @@
<t t-name="kanban-box">
<div class="o_kanban_two_column_tags oe_kanban_global_click">
<div class="o_kanban_tags_section">
<field name="attribute_value_ids"/>
<field name="attribute_value_ids" groups="product.group_product_variant"/>
</div>
<div class="o_kanban_image">
<img t-att-src="kanban_image('product.product', 'image_small', record.id.value)" class="o_kanban_image"/>
@@ -438,7 +517,7 @@
<menuitem action="attribute_action"
id="menu_attribute_action"
parent="product.prod_config_main" sequence="4" />
parent="product.prod_config_main" sequence="4" groups="product.group_product_variant"/>
<record id="variants_tree_view" model="ir.ui.view">
<field name="name">product.attribute.value.tree</field>
@@ -462,7 +541,7 @@
<menuitem action="variants_action"
id="menu_variants_action"
parent="product.prod_config_main" sequence="5" />
parent="product.prod_config_main" sequence="5" groups="product.group_product_variant"/>
<!-- -->
@@ -490,7 +569,7 @@
</field>
</record>
<menuitem id="product.menu_products" action="product.product_normal_action_sell" parent="base.menu_sales" groups="base.group_no_one" sequence="99"/>
<menuitem id="product.menu_products" action="product.product_normal_action_sell" parent="base.menu_sales" groups="product.group_product_variant" sequence="99"/>
<record id="product_category_search_view" model="ir.ui.view">
<field name="name">product.category.search</field>
+3
View File
@@ -9,6 +9,9 @@ class base_config_settings(osv.TransientModel):
help="Share your product to all companies defined in your instance.\n"
" * Checked : Product are visible for every company, even if a company is defined on the partner.\n"
" * Unchecked : Each company can see only its product (product where company is defined). Product not related to a company are visible for all companies."),
'group_product_variant': fields.boolean('Manage Product Variants',
help='Work with product variant allows you to define some variant of the same products, an ease the product management in the ecommerce for example',
implied_group='product.group_product_variant'),
}
def get_default_company_share_product(self, cr, uid, fields, context=None):
+9
View File
@@ -15,6 +15,15 @@
</div>
</div>
</xpath>
<xpath expr="//label[@name='email_label']" position="before">
<label for="id" string="Products"/>
<div>
<div>
<field name="group_product_variant" class="oe_inline"/>
<label for="group_product_variant"/>
</div>
</div>
</xpath>
</field>
</record>
@@ -32,6 +32,11 @@
<field name="category_id" ref="base.module_category_hidden"/>
</record>
<record id="group_product_variant" model="res.groups">
<field name="name">Manage Product Variants</field>
<field name="category_id" ref="base.module_category_hidden"/>
</record>
</data>
<data noupdate="1">
+2 -2
View File
@@ -70,8 +70,8 @@ class report_intrastat(osv.osv):
else 0
end) as value,
sum(
case when uom.category_id != puom.category_id then (pt.weight_net * inv_line.quantity)
else (pt.weight_net * inv_line.quantity * uom.factor) end
case when uom.category_id != puom.category_id then (pp.weight_net * inv_line.quantity)
else (pp.weight_net * inv_line.quantity * uom.factor) end
) as weight,
sum(
case when uom.category_id != puom.category_id then inv_line.quantity
+2 -2
View File
@@ -27,8 +27,8 @@
<field name="arch" type="xml">
<xpath expr="//group[@name='standard_price']" position="replace">
<field name="cost_method" groups="stock_account.group_inventory_valuation" attrs="{'invisible': [('type','=','service')]}"/>
<label string="Cost Price" for="standard_price" align="1.0" groups="base.group_user"/>
<div groups="base.group_user">
<label string="Cost Price" for="standard_price" align="1.0" groups="base.group_user" attrs="{'invisible':[('product_variant_count','>',1)]}"/>
<div groups="base.group_user" attrs="{'invisible':[('product_variant_count','>',1)]}">
<field name="standard_price" attrs="{'readonly':['&amp;', ('valuation','=','real_time'), ('cost_method', 'in', ['standard', 'average'])]}" class="oe_inline" nolabel="1"/>
<label string="/" class="oe_inline" groups="product.group_uom"/>
<field name="uom_rel_id" class="oe_inline" groups="product.group_uom"/>