Merge lp:~akretion-team/openerp-product-variant/product-variant-multi-attribute into lp:openerp-product-variant/trunk

Proposed by Sébastien BEAU - http://www.akretion.com
Status: Needs review
Proposed branch: lp:~akretion-team/openerp-product-variant/product-variant-multi-attribute
Merge into: lp:openerp-product-variant/trunk
Diff against target: 2965 lines (+1659/-870)
32 files modified
__unported__/product_variant_multi_advanced/__openerp__.py (+1/-1)
__unported__/product_variant_multi_advanced/product.py (+5/-5)
product_display/__init__.py (+23/-0)
product_display/__openerp__.py (+49/-0)
product_display/product.py (+47/-0)
product_display/product_view.xml (+28/-0)
product_variant_display_generator/__init__.py (+23/-0)
product_variant_display_generator/__openerp__.py (+45/-0)
product_variant_display_generator/product_variant_display.py (+130/-0)
product_variant_display_generator/product_view.xml (+44/-0)
product_variant_generator/__init__.py (+1/-0)
product_variant_generator/__openerp__.py (+8/-6)
product_variant_generator/product_data.xml (+29/-0)
product_variant_generator/product_variant.py (+455/-516)
product_variant_generator/product_view.xml (+97/-252)
product_variant_generator/security/ir.model.access.csv (+2/-6)
product_variant_generator/wizard/__init__.py (+24/-0)
product_variant_generator/wizard/product_template_add_option.py (+69/-0)
product_variant_generator/wizard/product_template_add_option_view.xml (+29/-0)
product_variant_generator_price/__init__.py (+23/-0)
product_variant_generator_price/__openerp__.py (+40/-0)
product_variant_generator_price/product_variant.py (+119/-0)
product_variant_generator_price/product_view.xml (+79/-0)
product_variant_multi/demo_data.xml (+0/-84)
product_variant_simple/__init__.py (+3/-0)
product_variant_simple/__openerp__.py (+47/-0)
product_variant_simple/product.py (+32/-0)
product_variant_simple/product_view.xml (+92/-0)
product_variant_simple/security/security.xml (+13/-0)
sale_product_display/__init__.py (+23/-0)
sale_product_display/__openerp__.py (+40/-0)
sale_product_display/sale.py (+39/-0)
To merge this branch: bzr merge lp:~akretion-team/openerp-product-variant/product-variant-multi-attribute
Reviewer Review Type Date Requested Status
Alexandre Fayolle - camptocamp Needs Resubmitting
Review via email: mp+223611@code.launchpad.net

Description of the change

It's a total refactor of product variant multi.
The idea is too split the module in order to be more flexible.

As you can see a new concept have been added "Product display", this are the equivalent of "Configurable Product" on Magento side.

We do not map the "Product Template" with "Configurable Product" because it's not the same concept. Template are here for generating all of the variant, and the "Product Display" is just a way to show your product in a catalog.

For example, you can have 1 template (t-shirt color: red/yellow/blue size:S/M/L/XL) and then on magento you want to create 3 configurables products, one for each color. this case can be supported as you will create 3 displays from the product template.

To post a comment you must log in.
249. By Sébastien BEAU - http://www.akretion.com

[FIX] add missing file data for having 2 default template for code/name

250. By Chafique DELLI

[REF] refactor module product_variant_generator_price

Revision history for this message
Alexandre Fayolle - camptocamp (alexandre-fayolle-c2c) wrote :

Hello,

The management of the project has moved to Github: https://github.com/OCA/product-variant

Please migrate your merge proposal to Github. You may want to check https://github.com/OCA/maintainers-tools/wiki/How-to-move-a-Merge-Proposal-to-GitHub for an explanation on how to proceed.

Thanks for contributing to the project

review: Needs Resubmitting

Unmerged revisions

250. By Chafique DELLI

[REF] refactor module product_variant_generator_price

249. By Sébastien BEAU - http://www.akretion.com

[FIX] add missing file data for having 2 default template for code/name

248. By Sébastien BEAU - http://www.akretion.com

[CLEAN] pep8 clean up

247. By Sébastien BEAU - http://www.akretion.com

[REF] refactor module as product.variant.dimension have been removed

246. By Sébastien BEAU - http://www.akretion.com

[CLEAN] pep8 clean up

245. By Sébastien BEAU - http://www.akretion.com

[REF] rename generate_display_from_dim_id into main_dim_id

244. By Sébastien BEAU - http://www.akretion.com

[CLEAN] pep8 clean up

243. By Sébastien BEAU - http://www.akretion.com

[REF] refactor product template code/name generation. Now you can create template generator and use/share it

242. By Sébastien BEAU - http://www.akretion.com

[REF] UI refactor, having the dimension is too much as we already have the attribute set

241. By Sébastien BEAU - http://www.akretion.com

[CLEAN] pep8 clean up

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added directory '__unported__'
=== renamed directory 'product_variant_multi_advanced' => '__unported__/product_variant_multi_advanced'
=== modified file '__unported__/product_variant_multi_advanced/__openerp__.py'
--- product_variant_multi_advanced/__openerp__.py 2013-09-19 11:46:57 +0000
+++ __unported__/product_variant_multi_advanced/__openerp__.py 2014-06-24 09:37:35 +0000
@@ -39,6 +39,6 @@
39 'init_xml': [],39 'init_xml': [],
40 'update_xml': ['product.xml'],40 'update_xml': ['product.xml'],
41 'demo_xml': [],41 'demo_xml': [],
42 'installable': True,42 'installable': False,
43 'active': False,43 'active': False,
44}44}
4545
=== modified file '__unported__/product_variant_multi_advanced/product.py'
--- product_variant_multi_advanced/product.py 2013-09-19 15:50:40 +0000
+++ __unported__/product_variant_multi_advanced/product.py 2014-06-24 09:37:35 +0000
@@ -41,13 +41,13 @@
41duplicated_fields = ['description_sale', 'name']41duplicated_fields = ['description_sale', 'name']
4242
4343
44class product_template(orm.Model):44class ProductTemplate(orm.Model):
45 _inherit = "product.template"45 _inherit = "product.template"
4646
47 def button_generate_variants(self, cr, uid, ids, context=None):47 def button_generate_variants(self, cr, uid, ids, context=None):
48 if context is None:48 if context is None:
49 context = {}49 context = {}
50 super(product_template, self).button_generate_variants(cr, uid, ids, context=context)50 super(ProductTemplate, self).button_generate_variants(cr, uid, ids, context=context)
51 product_ids = self.get_products_from_product_template(cr, uid, ids, context=context)51 product_ids = self.get_products_from_product_template(cr, uid, ids, context=context)
52 # generate/update sale description52 # generate/update sale description
53 _logger.info("Starting to generate/update product sale descriptions...")53 _logger.info("Starting to generate/update product sale descriptions...")
@@ -57,7 +57,7 @@
57 return True57 return True
5858
5959
60class product_product(orm.Model):60class ProductProduct(orm.Model):
61 _inherit = "product.product"61 _inherit = "product.product"
6262
63 def write(self, cr, uid, ids, vals, context=None):63 def write(self, cr, uid, ids, vals, context=None):
@@ -65,7 +65,7 @@
65 ids = [ids]65 ids = [ids]
66 if context is None:66 if context is None:
67 context = {}67 context = {}
68 res = super(product_product, self).write(cr, uid, ids, vals.copy(), context=context)68 res = super(ProductProduct, self).write(cr, uid, ids, vals.copy(), context=context)
6969
70 ids_simple = self.search(70 ids_simple = self.search(
71 cr, uid,71 cr, uid,
@@ -98,7 +98,7 @@
98 # and not on the product_product98 # and not on the product_product
9999
100 #take care to use vals.copy() if not the vals will be changed by calling the super method100 #take care to use vals.copy() if not the vals will be changed by calling the super method
101 ids = super(product_product, self).create(cr, uid, vals.copy(), context=context)101 ids = super(ProductProduct, self).create(cr, uid, vals.copy(), context=context)
102 ####### write the value in the product_product102 ####### write the value in the product_product
103 ctx = context.copy()103 ctx = context.copy()
104 ctx['iamthechild'] = True104 ctx['iamthechild'] = True
105105
=== added directory 'product_display'
=== added file 'product_display/__init__.py'
--- product_display/__init__.py 1970-01-01 00:00:00 +0000
+++ product_display/__init__.py 2014-06-24 09:37:35 +0000
@@ -0,0 +1,23 @@
1# -*- coding: utf-8 -*-
2###############################################################################
3#
4# Module for OpenERP
5# Copyright (C) 2013 Akretion (http://www.akretion.com).
6# @author Chafique Delli <chafique.delli@akretion.com>
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU Affero General Public License as
10# published by the Free Software Foundation, either version 3 of the
11# License, or (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU Affero General Public License for more details.
17#
18# You should have received a copy of the GNU Affero General Public License
19# along with this program. If not, see <http://www.gnu.org/licenses/>.
20#
21###############################################################################
22
23import product
024
=== added file 'product_display/__openerp__.py'
--- product_display/__openerp__.py 1970-01-01 00:00:00 +0000
+++ product_display/__openerp__.py 2014-06-24 09:37:35 +0000
@@ -0,0 +1,49 @@
1# -*- coding: utf-8 -*-
2##############################################################################
3#
4# Author: Chafique Delli
5# Copyright 2013 Akretion
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU Affero General Public License as
9# published by the Free Software Foundation, either version 3 of the
10# License, or (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU Affero General Public License for more details.
16#
17# You should have received a copy of the GNU Affero General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20##############################################################################
21{'name' : 'Product Display',
22 'version' : '1.0',
23 "author": "Akretion",
24 'category': 'Sales Management',
25 'description': """
26Product Display
27================
28
29A simple module for handling the product display.
30It provides view for product display,
31so they can be created manually. It doesn't provide
32all the bells and whistles of the product_variant_display
33module (creator wizards, ...).
34
35""",
36 'author' : 'Akretion',
37 'maintainer': 'Akretion',
38 'website': 'http://www.akretion.com/',
39 'depends' : [
40 'product',
41 ],
42 'data': [
43 'product_view.xml',
44 ],
45 'test': [],
46 'installable': True,
47 'auto_install': False,
48 'application': True,
49 }
050
=== added file 'product_display/product.py'
--- product_display/product.py 1970-01-01 00:00:00 +0000
+++ product_display/product.py 2014-06-24 09:37:35 +0000
@@ -0,0 +1,47 @@
1# -*- coding: utf-8 -*-
2###############################################################################
3#
4# Module for OpenERP
5# Copyright (C) 2013 Akretion (http://www.akretion.com).
6# @author Chafique Delli <chafique.delli@akretion.com>
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU Affero General Public License as
10# published by the Free Software Foundation, either version 3 of the
11# License, or (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU Affero General Public License for more details.
17#
18# You should have received a copy of the GNU Affero General Public License
19# along with this program. If not, see <http://www.gnu.org/licenses/>.
20#
21###############################################################################
22
23from openerp.osv import orm, fields
24
25
26class ProductProduct(orm.Model):
27 _inherit = 'product.product'
28
29 _columns = {
30 'display_for_product_ids': fields.many2many(
31 'product.product',
32 'product_template_option_rel',
33 'product_display_id',
34 'product_id',
35 string='Products'),
36 'display_ids': fields.many2many(
37 'product.product',
38 'product_template_option_rel',
39 'product_id',
40 'product_display_id',
41 string='Products Display'),
42 'is_display': fields.boolean('Is Display'),
43 }
44
45 _defaults = {
46 'is_display': False,
47 }
048
=== added file 'product_display/product_view.xml'
--- product_display/product_view.xml 1970-01-01 00:00:00 +0000
+++ product_display/product_view.xml 2014-06-24 09:37:35 +0000
@@ -0,0 +1,28 @@
1<?xml version="1.0" encoding="utf-8"?>
2<openerp>
3 <data>
4
5
6 <!--Product Display -->
7
8 <record id="product_display_form_view" model="ir.ui.view">
9 <field name="name">product.display.form</field>
10 <field name="model">product.product</field>
11 <field name="inherit_id" ref="product.product_normal_form_view" />
12 <field name="arch" type="xml">
13 <xpath expr="//label[@for='sale_ok']" position="after">
14 <field name="is_display"/>
15 <label for="is_display" />
16 </xpath>
17 <notebook position="inside">
18 <page string="Product Display" attrs="{'invisible':[('is_display','=',False)]}">
19 <group colspan="2" col="2">
20 <field name="display_for_product_ids" nolabel="1"/>
21 </group>
22 </page>
23 </notebook>
24 </field>
25 </record>
26
27 </data>
28</openerp>
029
=== added directory 'product_variant_display_generator'
=== added file 'product_variant_display_generator/__init__.py'
--- product_variant_display_generator/__init__.py 1970-01-01 00:00:00 +0000
+++ product_variant_display_generator/__init__.py 2014-06-24 09:37:35 +0000
@@ -0,0 +1,23 @@
1# -*- coding: utf-8 -*-
2###############################################################################
3#
4# Module for OpenERP
5# Copyright (C) 2013 Akretion (http://www.akretion.com).
6# @author Chafique Delli <chafique.delli@akretion.com>
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU Affero General Public License as
10# published by the Free Software Foundation, either version 3 of the
11# License, or (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU Affero General Public License for more details.
17#
18# You should have received a copy of the GNU Affero General Public License
19# along with this program. If not, see <http://www.gnu.org/licenses/>.
20#
21###############################################################################
22
23import product_variant_display
024
=== added file 'product_variant_display_generator/__openerp__.py'
--- product_variant_display_generator/__openerp__.py 1970-01-01 00:00:00 +0000
+++ product_variant_display_generator/__openerp__.py 2014-06-24 09:37:35 +0000
@@ -0,0 +1,45 @@
1# -*- coding: utf-8 -*-
2###############################################################################
3#
4# Module for OpenERP
5# Copyright (C) 2013 Akretion (http://www.akretion.com).
6# @author Chafique Delli <chafique.delli@akretion.com>
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU Affero General Public License as
10# published by the Free Software Foundation, either version 3 of the
11# License, or (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU Affero General Public License for more details.
17#
18# You should have received a copy of the GNU Affero General Public License
19# along with this program. If not, see <http://www.gnu.org/licenses/>.
20#
21###############################################################################
22{
23 "name": "Product Variant Display Generator",
24 "version": "1.0",
25 "author": "OpenERP SA, Akretion",
26 "category": "Sales Management",
27 "license": "AGPL-3",
28 "summary": "Products Displays with multi-dimension variants ",
29 "description": """
30Multi-axial display product support for OpenERP
31===============================================
32
33
34 """,
35 "depends" : [
36 "product_custom_attributes",
37 "product_variant_generator",
38 "product_display"
39 ],
40 "data" : ["product_view.xml",
41 ],
42 "application": True,
43 "active": False,
44 "installable": True,
45}
046
=== added file 'product_variant_display_generator/product_variant_display.py'
--- product_variant_display_generator/product_variant_display.py 1970-01-01 00:00:00 +0000
+++ product_variant_display_generator/product_variant_display.py 2014-06-24 09:37:35 +0000
@@ -0,0 +1,130 @@
1# -*- coding: utf-8 -*-
2###############################################################################
3#
4# Module for OpenERP
5# Copyright (C) 2013 Akretion (http://www.akretion.com).
6# @author Chafique Delli <chafique.delli@akretion.com>
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU Affero General Public License as
10# published by the Free Software Foundation, either version 3 of the
11# License, or (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU Affero General Public License for more details.
17#
18# You should have received a copy of the GNU Affero General Public License
19# along with this program. If not, see <http://www.gnu.org/licenses/>.
20#
21###############################################################################
22
23from openerp.osv import orm, fields
24
25
26import logging
27_logger = logging.getLogger(__name__)
28
29
30class ProductTemplate(orm.Model):
31 _inherit = "product.template"
32
33 _columns = {
34 'generate_main_display': fields.boolean('Generate Main Display'),
35 'main_dim_id': fields.many2one(
36 'attribute.attribute',
37 string='Main Dimension',
38 help=('This dimension will be used for generating '
39 'the product display')),
40 'display_variant_ids': fields.one2many(
41 'product.product',
42 'product_tmpl_id',
43 domain=[
44 ('is_display', '=', True),
45 '|',
46 ('active', '=', True),
47 ('active', '=', False),
48 ], string='Display Variants'),
49 'product_variant_ids': fields.one2many(
50 'product.product',
51 'product_tmpl_id',
52 domain=[
53 ('is_display', '=', False),
54 '|',
55 ('active', '=', True),
56 ('active', '=', False),
57 ], string='Product Variants'),
58 }
59
60 def _get_combinaison(self, cr, uid, product_temp, context=None):
61 if context.get('product_display'):
62 fields = [dim.name for dim in product_temp.dimension_ids]
63 num_of_fields = len(fields)
64 combinaisons = []
65 if product_temp.main_dim_id:
66 for value in product_temp.value_ids:
67 if value.dimension_id.id == product_temp.main_dim_id.id:
68 combinaisons.append(
69 [value.option_id.id] + [None]*(num_of_fields - 1)
70 )
71 if product_temp.generate_main_display:
72 combinaisons.append([None]*num_of_fields)
73 return combinaisons
74 return super(ProductTemplate, self)._get_combinaison(
75 cr, uid, product_temp, context=context)
76
77 def _prepare_variant_vals(self, cr, uid, product_temp, combinaison,
78 context=None):
79 product_obj = self.pool['product.product']
80 vals = super(ProductTemplate, self)._prepare_variant_vals(
81 cr, uid, product_temp, combinaison, context=context)
82 if context.get('product_display'):
83 vals['is_display'] = True
84 domain = [
85 ['product_tmpl_id', '=', vals['product_tmpl_id']],
86 ['is_display', '=', False],
87 ]
88 dimension = product_temp.main_dim_id
89 if dimension and dimension.name in vals:
90 domain.append([dimension.name, '=', vals[dimension.name]])
91 product_ids = product_obj.search(cr, uid, domain, context=context)
92 vals['display_for_product_ids'] = [(6, 0, product_ids)]
93 return vals
94
95 def _create_variant(self, cr, uid, product_temp, existing_product_ids,
96 context=None):
97 created_product_ids = super(ProductTemplate, self)._create_variant(
98 cr, uid, product_temp, existing_product_ids, context=context)
99 if created_product_ids:
100 self.pool['product.product'].update_existing_product_display(
101 cr, uid, created_product_ids, context=context)
102
103 ctx = context.copy()
104 ctx['product_display'] = True
105 created_product_display_ids = super(ProductTemplate, self).\
106 _create_variant(cr, uid, product_temp, existing_product_ids,
107 context=ctx)
108 return created_product_ids + created_product_display_ids
109
110
111class ProductProduct(orm.Model):
112 _inherit = 'product.product'
113
114 def update_existing_product_display(self, cr, uid, ids, context=None):
115 ids = self.search(cr, uid, [
116 ['id', 'in', ids],
117 ['is_display', '=', True]
118 ], context=context)
119
120 for product in self.browse(cr, uid, ids, context=context):
121 domain = [
122 ['product_tmpl_id', '=', product.product_tmpl_id.id],
123 ['is_display', '=', False],
124 ]
125 dim = product.main_dim_id
126 if dim and product[dim.name]:
127 domain.append([dim.name, '=', product[dim.name].name])
128 product_ids = self.search(cr, uid, domain, context=context)
129 product.write({'display_for_product_ids': [(6, 0, product_ids)]})
130 return True
0131
=== added file 'product_variant_display_generator/product_view.xml'
--- product_variant_display_generator/product_view.xml 1970-01-01 00:00:00 +0000
+++ product_variant_display_generator/product_view.xml 2014-06-24 09:37:35 +0000
@@ -0,0 +1,44 @@
1<?xml version="1.0" encoding="utf-8"?>
2<openerp>
3 <data>
4
5<!--
6 Module for OpenERP
7 The licence is in the file __openerp__.py
8 @author Chafique Delli <chafique.delli@akretion.com>
9-->
10
11 <!-- PRODUCT TEMPLATE -->
12 <record id="product_variant_multi_product_display_form_view" model="ir.ui.view">
13 <field name="name">product.variant.multi.product.display.form</field>
14 <field name="model">product.template</field>
15 <field name="inherit_id" ref="product_variant_generator.product_variant_multi_product_template_form_view" />
16 <field name="arch" type="xml">
17 <xpath expr="//field[@name='do_not_update_variant']/.." position="after">
18 <group string="Displays" colspan="1" col="4" attrs="{'invisible':[('is_multi_variants','=',False)]}">
19 <field name="generate_main_display"/>
20 <field name="dimension_ids" invisible="1"/>
21 <field name="main_dim_id" domain="[('id', 'in', dimension_ids and dimension_ids[0][2])]"/>
22 <field name="display_variant_ids" nolabel="1" colspan="4">
23 <tree string="Display Variants">
24 <field name="code" />
25 <field name="name" />
26 <field name="variants" string="Dimension Values" />
27 </tree>
28 </field>
29 </group>
30 </xpath>
31 <field name="variant_ids" position="replace">
32 <field name="product_variant_ids" nolabel="1" colspan="4">
33 <tree string="Product Variants">
34 <field name="code" />
35 <field name="name" />
36 <field name="variants" string="Dimension Values" />
37 </tree>
38 </field>
39 </field>
40 </field>
41 </record>
42
43 </data>
44</openerp>
045
=== renamed directory 'product_variant_multi' => 'product_variant_generator'
=== modified file 'product_variant_generator/__init__.py'
--- product_variant_multi/__init__.py 2013-09-19 11:46:57 +0000
+++ product_variant_generator/__init__.py 2014-06-24 09:37:35 +0000
@@ -22,3 +22,4 @@
22# flake8: noqa22# flake8: noqa
2323
24from . import product_variant24from . import product_variant
25from . import wizard
2526
=== modified file 'product_variant_generator/__openerp__.py'
--- product_variant_multi/__openerp__.py 2013-09-19 11:46:57 +0000
+++ product_variant_generator/__openerp__.py 2014-06-24 09:37:35 +0000
@@ -20,7 +20,7 @@
20#20#
21##############################################################################21##############################################################################
22{22{
23 "name": "Product Variant Multi",23 "name": "Product Variant Generator",
24 "version": "1.0",24 "version": "1.0",
25 "author": "OpenERP SA, Akretion",25 "author": "OpenERP SA, Akretion",
26 "category": "Sales Management",26 "category": "Sales Management",
@@ -44,8 +44,6 @@
44the space of the variants. You could also choose to populate only some combinations44the space of the variants. You could also choose to populate only some combinations
45by hand instead.45by hand instead.
4646
47Each variant can have an extra price that will be taken into account when computing
48the base listed price. Yet to be implemented: a price extra per variant dimension value.
49Finally, this module is better used along with the product_variant_configurator which47Finally, this module is better used along with the product_variant_configurator which
50will help the salesman selecting the appropriate variant in the sale order line48will help the salesman selecting the appropriate variant in the sale order line
51using dimension criteria instead of having to crawl the full space of variants.49using dimension criteria instead of having to crawl the full space of variants.
@@ -60,11 +58,15 @@
60and only from product.template if not found on product.product. But at least you58and only from product.template if not found on product.product. But at least you
61will have been warned.59will have been warned.
62 """,60 """,
63 "depends": ["product"],61 "depends" : [
64 "demo": ["demo_data.xml"],62 "product_custom_attributes",
65 "data": [63 "product_variant_simple"
64 ],
65 "data" : [
66 "wizard/product_template_add_option_view.xml",
66 "security/ir.model.access.csv",67 "security/ir.model.access.csv",
67 "product_view.xml",68 "product_view.xml",
69 "product_data.xml",
68 ],70 ],
69 "application": True,71 "application": True,
70 "active": False,72 "active": False,
7173
=== added file 'product_variant_generator/product_data.xml'
--- product_variant_generator/product_data.xml 1970-01-01 00:00:00 +0000
+++ product_variant_generator/product_data.xml 2014-06-24 09:37:35 +0000
@@ -0,0 +1,29 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<openerp>
3 <data noupdate="0">
4
5 <record id="product_name_generator" model="string.template">
6 <field name="name">Default Name Generator</field>
7 <field name="type">product_name</field>
8 <field name="code">name = []
9for dimension in o.dimension_ids:
10 if o[dimension.name].name:
11 name.append("%s - %s" %(dimension.field_description, o[dimension.name].name))
12result = " | ".join(name)</field>
13 </record>
14
15 <record id="product_code_generator" model="string.template">
16 <field name="name">Default Code Generator</field>
17 <field name="type">product_code</field>
18 <field name="code">code = []
19if o.base_default_code:
20 code.append(o.base_default_code)
21for dimension in o.dimension_ids:
22 if o[dimension.name].name:
23 code.append(o[dimension.name].name)
24result = " - ".join(code)</field>
25 </record>
26
27 </data>
28</openerp>
29
030
=== modified file 'product_variant_generator/product_variant.py'
--- product_variant_multi/product_variant.py 2013-09-19 15:50:40 +0000
+++ product_variant_generator/product_variant.py 2014-06-24 09:37:35 +0000
@@ -7,6 +7,7 @@
7# @author Sebatien Beau <sebastien.beau@akretion.com>7# @author Sebatien Beau <sebastien.beau@akretion.com>
8# @author Raphaël Valyi <raphael.valyi@akretion.com>8# @author Raphaël Valyi <raphael.valyi@akretion.com>
9# @author Alexis de Lattre <alexis.delattre@akretion.com>9# @author Alexis de Lattre <alexis.delattre@akretion.com>
10# @author Chafique Delli <chafique.delli@akretion.com>
10# update to use a single "Generate/Update" button & price computation code11# update to use a single "Generate/Update" button & price computation code
11#12#
12# This program is free software: you can redistribute it and/or modify13# This program is free software: you can redistribute it and/or modify
@@ -25,215 +26,308 @@
25##############################################################################26##############################################################################
2627
27from openerp.osv import fields, osv, orm28from openerp.osv import fields, osv, orm
28import openerp.addons.decimal_precision as dp
29# Lib to eval python code with security29# Lib to eval python code with security
30from openerp.tools.translate import _
31from collections import defaultdict
32from openerp.tools import config
30from openerp.tools.safe_eval import safe_eval33from openerp.tools.safe_eval import safe_eval
31from openerp.tools.translate import _34import datetime
35
3236
33import logging37import logging
34_logger = logging.getLogger(__name__)38_logger = logging.getLogger(__name__)
3539
3640
37class product_variant_dimension_type(orm.Model):41class AttributeAttribute(orm.Model):
38 _name = "product.variant.dimension.type"42 _inherit = 'attribute.attribute'
39 _description = "Dimension Type"
4043
41 _columns = {44 _columns = {
42 'description': fields.char('Description', size=64, translate=True),45 'sequence': fields.integer(
43 'name': fields.char('Dimension Type Name', size=64, required=True),46 'Sequence',
44 'sequence': fields.integer('Sequence', help=("The product 'variants' code will "47 help=("The product 'variants' code will "
45 "use this to order the dimension values")),48 "use this to order the dimension values")),
46 'option_ids': fields.one2many('product.variant.dimension.option', 'dimension_id',49 'mandatory_dimension': fields.boolean(
47 'Dimension Options'),50 'Mandatory Dimension',
48 'product_tmpl_id': fields.many2many('product.template', 'product_template_dimension_rel',51 help=("If false, variant products will be created "
49 'dimension_id', 'template_id', 'Product Template'),52 "with and without this dimension")),
50 'allow_custom_value': fields.boolean('Allow Custom Value',53 'is_dimension': fields.boolean('Is dimension', help='Help note'),
51 help=("If true, custom values can be entered "
52 "in the product configurator")),
53 'mandatory_dimension': fields.boolean('Mandatory Dimension',
54 help=("If false, variant products will be created "
55 "with and without this dimension")),
56 }54 }
5755
58 _defaults = {56 _defaults = {
59 'mandatory_dimension': 1,57 'mandatory_dimension': 1,
60 }58 'attribute_type': 'select',
6159 }
62 _order = "sequence, name"60
6361 _order = "sequence"
64 def name_search(self, cr, uid, name='', args=None, operator='ilike', context=None, limit=None):62
65 if not context.get('product_tmpl_id', False):63
66 args = None64class DimensionValue(orm.Model):
67 return super(product_variant_dimension_type,65 _name = "dimension.value"
68 self).name_search(cr, uid, '', args, 'ilike', None, None)
69
70
71class product_variant_dimension_option(orm.Model):
72 _name = "product.variant.dimension.option"
73 _description = "Dimension Option"
74
75 def _get_dimension_values(self, cr, uid, ids, context=None):
76 dimvalue_obj = self.pool.get('product.variant.dimension.value')
77 return dimvalue_obj.search(cr, uid, [('dimension_id', 'in', ids)], context=context)
78
79 _columns = {
80 'name': fields.char('Dimension Option Name', size=64, required=True),
81 'code': fields.char('Code', size=64),
82 'sequence': fields.integer('Sequence'),
83 'dimension_id': fields.many2one('product.variant.dimension.type',
84 'Dimension Type', ondelete='cascade'),
85 }
86
87 _order = "dimension_id, sequence, name"
88
89
90class product_variant_dimension_value(orm.Model):
91 _name = "product.variant.dimension.value"
92 _description = "Dimension Value"66 _description = "Dimension Value"
9367
94 def unlink(self, cr, uid, ids, context=None):68 def unlink(self, cr, uid, ids, context=None):
69 product_obj = self.pool['product.product']
95 for value in self.browse(cr, uid, ids, context=context):70 for value in self.browse(cr, uid, ids, context=context):
96 if value.product_ids:71 product_ids = product_obj.search(cr, uid, [
97 product_names = [product.name for product in value.product_ids]72 ['product_tmpl_id', '=', value.product_tmpl_id.id],
73 [value.dimension_id.name, '=', value.option_id.name],
74 ], context=context)
75 if product_ids:
76 products = product_obj.browse(cr, uid, product_ids,
77 context=context)
78 product_names = [product.name for product in products]
98 product_list = '\n - ' + '\n - '.join(product_names)79 product_list = '\n - ' + '\n - '.join(product_names)
99 raise osv.except_osv(_('Dimension value can not be removed'),80 raise osv.except_osv(
100 _("The value %s is used by the products : %s \n "81 _('Dimension value can not be removed'),
101 "Please remove these products before removing the value.")82 _("The value %s is used by the products : %s \n "
102 % (value.option_id.name, product_list))83 "Please remove these products before removing "
103 return super(product_variant_dimension_value, self).unlink(cr, uid, ids, context)84 "the value.") % (value.option_id.name, product_list))
85 return super(DimensionValue, self).\
86 unlink(cr, uid, ids, context)
10487
105 def _get_values_from_types(self, cr, uid, ids, context=None):88 def _get_values_from_types(self, cr, uid, ids, context=None):
106 dimvalue_obj = self.pool.get('product.variant.dimension.value')89 dimvalue_obj = self.pool.get('dimension.value')
107 return dimvalue_obj.search(cr, uid, [('dimension_id', 'in', ids)], context=context)90 return dimvalue_obj.search(cr, uid, [
10891 ('dimension_id', 'in', ids),
109 def _get_values_from_options(self, cr, uid, ids, context=None):92 ], context=context)
110 dimvalue_obj = self.pool.get('product.variant.dimension.value')
111 return dimvalue_obj.search(cr, uid, [('option_id', 'in', ids)], context=context)
11293
113 _columns = {94 _columns = {
114 'option_id': fields.many2one('product.variant.dimension.option', 'Option', required=True),95 # TODO option_id add param in context
115 'name': fields.related('option_id', 'name', type='char',96 # to have a full name 'dimension + option'
116 relation='product.variant.dimension.option',97 # exemple address on sale order
117 string="Dimension Value", readonly=True),98 'option_id': fields.many2one(
99 'attribute.option',
100 'Option',
101 required=True),
102 'name': fields.related(
103 'option_id',
104 'name',
105 type='char',
106 string="Dimension Value",
107 readonly=True),
118 'sequence': fields.integer('Sequence'),108 'sequence': fields.integer('Sequence'),
119 'price_extra': fields.float('Sale Price Extra',109 'dimension_id': fields.many2one(
120 digits_compute=dp.get_precision('Sale Price')),110 'attribute.attribute',
121 'price_margin': fields.float('Sale Price Margin',111 'Dimension',
122 digits_compute=dp.get_precision('Sale Price')),112 required=True),
123 'cost_price_extra': fields.float('Cost Price Extra',113 'product_tmpl_id': fields.many2one(
124 digits_compute=dp.get_precision('Purchase Price')),114 'product.template',
125 'dimension_id': fields.related('option_id', 'dimension_id', type="many2one",115 'Product Template',
126 relation="product.variant.dimension.type",116 ondelete='cascade'),
127 string="Dimension Type",117 'dimension_sequence': fields.related(
128 readonly=True,118 'dimension_id',
129 store={'product.variant.dimension.value':119 'sequence',
130 (lambda self, cr, uid, ids, c={}:120 type='integer',
131 ids, ['option_id'], 10),121 string="Related Dimension Sequence",
132 'product.variant.dimension.option':122 store={
133 (_get_values_from_options, ['dimension_id'], 20)}),123 'attribute.attribute': (
134 'product_tmpl_id': fields.many2one('product.template', 'Product Template',124 _get_values_from_types,
135 ondelete='cascade'),125 ['sequence'],
136 'dimension_sequence': fields.related('dimension_id', 'sequence', type='integer',126 10),
137 relation='product.variant.dimension.type',127 'dimension.value': (
138 #used for ordering purposes in the "variants"128 lambda self, cr, uid, ids, c={}: ids,
139 string="Related Dimension Sequence",129 ['dimension_id'],
140 store={'product.variant.dimension.type':130 10),
141 (_get_values_from_types, ['sequence'], 10)}),131 }),
142 'product_ids': fields.many2many('product.product', 'product_product_dimension_rel',132 'active': fields.boolean(
143 'dimension_id', 'product_id', 'Variant', readonly=True),133 'Active',
144 'active': fields.boolean('Active', help=("If false, this value will not be "134 help=("If false, this value will not be "
145 "used anymore to generate variants.")),135 "used anymore to generate variants.")),
146 }136 }
147137
148 _defaults = {138 _defaults = {
149 'active': True,139 'active': True,
150 }140 }
151141
152 _sql_constraints = [('opt_dim_tmpl_uniq',142 _sql_constraints = [
153 'UNIQUE(option_id, dimension_id, product_tmpl_id)',143 (
154 _("The combination option and dimension type "144 'opt_dim_tmpl_uniq',
155 "already exists for this product template !")), ]145 'UNIQUE(option_id, dimension_id, product_tmpl_id)',
156146 _("The combination option and dimension type "
157 _order = "dimension_sequence, dimension_id, sequence, option_id"147 "already exists for this product template !")
158148 ),
159149 ]
160class product_template(orm.Model):150
151 _order = "dimension_sequence, sequence, option_id"
152
153 def on_dimension_change(self, cr, uid, ids, dimension_id, context=None):
154 dim_obj = self.pool['attribute.attribute']
155 dim = dim_obj.browse(cr, uid, dimension_id, context=context)
156 return {
157 'domain': {
158 'option_id': [
159 ('id', 'in', [option.id for option in dim.option_ids])
160 ]
161 },
162 'value': {'option_id': False},
163 }
164
165
166class StringTemplate(orm.Model):
167 _name = 'string.template'
168
169 def __get_type(self, cr, uid, context=None):
170 return self._get_type(cr, uid, context=context)
171
172 def _get_type(self, cr, uid, context=None):
173 return [
174 ('product_code', 'Product Code'),
175 ('product_name', 'Product Name'),
176 ]
177
178 _columns = {
179 'name': fields.char('name', required=True),
180 'type': fields.selection(__get_type, 'Type', required=True),
181 'code': fields.text('Code', required=True),
182 }
183
184 _defaults = {
185 'code': """# Python code. Use result='YOUR_RESULT' to return your value.
186 # You can use the following variables :
187 # - self: ORM model of the record which is checked
188 # - o: browse_record of product template
189 # - pool: ORM model pool (i.e. self.pool)
190 # - datetime: Python datetime module
191 # - cr: database cursor
192 # - uid: current user id
193 # - context: current context
194 """
195 }
196
197 def _eval_context(self, cr, uid, obj, context=None):
198 if context is None:
199 context = {}
200 user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
201 return {
202 'self': self.pool.get(obj._name),
203 'o': obj,
204 'pool': self.pool,
205 'cr': cr,
206 'uid': uid,
207 'user': user,
208 'datetime': datetime,
209 # copy context to prevent side-effects of eval
210 'context': context.copy(),
211 }
212
213 def _build(self, cr, uid, template_id, obj, context=None):
214 if isinstance(template_id, (tuple, list)):
215 template_id = template_id[0]
216 template = self.browse(cr, uid, template_id, context=context)
217 expr = template.code
218 space = self._eval_context(cr, uid, obj, context=context)
219 try:
220 safe_eval(expr,
221 space,
222 mode='exec',
223 nocopy=True) # nocopy allows to return 'result'
224 except Exception, e:
225 if config['debug_mode']: raise
226 raise orm.except_orm(
227 _('Error'),
228 _('Error when evaluating the template:\n %s \n(%s)')
229 % (template.name, e))
230 return space.get('result', False)
231
232
233class ProductTemplate(orm.Model):
161 _inherit = "product.template"234 _inherit = "product.template"
162
163 _order = "name"235 _order = "name"
164236
237 def _get_dimension_ids(self, cr, uid, ids, field_name, args, context=None):
238 result = {}
239 for product_tmpl in self.browse(cr, uid, ids, context=context):
240 attr_ids = []
241 if product_tmpl.attribute_set_id:
242 for group in product_tmpl.attribute_set_id.attribute_group_ids:
243 for attr in group.attribute_ids:
244 attr_ids.append(attr.attribute_id.id)
245 result[product_tmpl.id] = attr_ids
246 return result
247
165 _columns = {248 _columns = {
166 'name': fields.char('Name', size=128, translate=True, select=True, required=False),249 'name': fields.char(
167 'dimension_type_ids': fields.many2many('product.variant.dimension.type',250 'Name', size=128,
168 'product_template_dimension_rel',251 translate=True,
169 'template_id', 'dimension_id', 'Dimension Types'),252 select=True,
170 'value_ids': fields.one2many('product.variant.dimension.value',253 required=False),
171 'product_tmpl_id',254 'value_ids': fields.one2many(
172 'Dimension Values'),255 'dimension.value',
173 'variant_ids': fields.one2many('product.product', 'product_tmpl_id', 'Variants'),256 'product_tmpl_id',
174 'variant_model_name': fields.char('Variant Model Name', size=64, required=True,257 'Dimension Values'),
175 help=('[_o.dimension_id.name_] will be replaced with the'258 'dimension_ids': fields.function(
176 ' name of the dimension and [_o.option_id.code_] '259 _get_dimension_ids,
177 'by the code of the option. Example of Variant '260 string='Dimension',
178 'Model Name : "[_o.dimension_id.name_] - '261 type='many2many',
179 '[_o.option_id.code_]"')),262 relation='attribute.attribute'),
180 'variant_model_name_separator': fields.char('Variant Model Name Separator', size=64,263 'variant_ids': fields.one2many(
181 help=('Add a separator between the elements '264 'product.product',
182 'of the variant name')),265 'product_tmpl_id',
183 'code_generator': fields.char('Code Generator', size=256,266 'Variants'),
184 help=('enter the model for the product code, all parameter'267 'template_name_id': fields.many2one(
185 ' between [_o.my_field_] will be replace by the '268 'string.template',
186 'product field. Example product_code model : '269 'Template Name',
187 'prefix_[_o.variants_]_suffixe ==> result : '270 required=True,
188 'prefix_2S2T_suffix')),271 domain=[('type', '=', 'product_name')],
272 ondelete='restrict'),
273 'template_code_id': fields.many2one(
274 'string.template',
275 'Template Code',
276 required=True,
277 domain=[('type', '=', 'product_code')],
278 ondelete='restrict'),
279 'base_default_code': fields.char(
280 'Base Default Code',
281 size=256,
282 help=('Base Default Code of the template '
283 'used for generating the product code')),
189 'is_multi_variants': fields.boolean('Is Multi Variants'),284 'is_multi_variants': fields.boolean('Is Multi Variants'),
190 'variant_track_production': fields.boolean('Track Production Lots on variants ?'),285 'variant_track_production': fields.boolean(
191 'variant_track_incoming': fields.boolean('Track Incoming Lots on variants ?'),286 'Track Production Lots on variants ?'),
192 'variant_track_outgoing': fields.boolean('Track Outgoing Lots on variants ?'),287 'variant_track_incoming': fields.boolean(
193 'do_not_update_variant': fields.boolean("Don't Update Variant"),288 'Track Incoming Lots on variants ?'),
194 'do_not_generate_new_variant': fields.boolean("Don't Generate New Variant"),289 'variant_track_outgoing': fields.boolean(
290 'Track Outgoing Lots on variants ?'),
291 'do_not_update_variant': fields.boolean(
292 "Don't Update Variant"),
293 'do_not_generate_new_variant': fields.boolean(
294 "Don't Generate New Variant"),
195 }295 }
196296
297 def _get_default_template_name(self, cr, uid, context=None):
298 tmpl_id = self.pool['string.template'].search(cr, uid, [
299 ('type', '=', 'product_name'),
300 ], context=context)
301 return tmpl_id and tmpl_id[0] or False
302
303 def _get_default_template_code(self, cr, uid, context=None):
304 tmpl_id = self.pool['string.template'].search(cr, uid, [
305 ('type', '=', 'product_code'),
306 ], context=context)
307 return tmpl_id and tmpl_id[0] or False
308
197 _defaults = {309 _defaults = {
198 'variant_model_name': '[_o.dimension_id.name_] - [_o.option_id.name_]',
199 'variant_model_name_separator': ' - ',
200 'is_multi_variants': False,310 'is_multi_variants': False,
201 'code_generator': ("[_'-'.join([x.option_id.name for x in o.dimension_value_ids] "311 'template_name_id': _get_default_template_name,
202 "or ['CONF'])_]"),312 'template_code_id': _get_default_template_code,
203 }313 }
204314
205 def unlink(self, cr, uid, ids, context=None):315 def unlink(self, cr, uid, ids, context=None):
206 if context and context.get('unlink_from_product_product', False):316 if context and context.get('unlink_from_product_product', False):
207 for template in self.browse(cr, uid, ids, context):317 for template in self.browse(cr, uid, ids, context):
208 if not template.is_multi_variants:318 if not template.is_multi_variants:
209 super(product_template, self).unlink(cr, uid, [template.id], context)319 super(ProductTemplate, self).\
320 unlink(cr, uid, [template.id], context)
210 else:321 else:
211 for template in self.browse(cr, uid, ids, context):322 for template in self.browse(cr, uid, ids, context):
212 if template.variant_ids == []:323 if template.variant_ids == []:
213 super(product_template, self).unlink(cr, uid, [template.id], context)324 super(ProductTemplate, self).\
325 unlink(cr, uid, [template.id], context)
214 else:326 else:
215 raise osv.except_osv(_("Cannot delete template"),327 raise osv.except_osv(
216 _("This template has existing corresponding products..."))328 _("Cannot delete template"),
217 return True329 _("This template has existing corresponding "
218330 "products..."))
219 def add_all_option(self, cr, uid, ids, context=None):
220 #Reactive all unactive values
221 value_obj = self.pool.get('product.variant.dimension.value')
222 for template in self.browse(cr, uid, ids, context=context):
223 values_ids = value_obj.search(cr, uid, [['product_tmpl_id', '=', template.id],
224 '|', ['active', '=', False],
225 ['active', '=', True]], context=context)
226 value_obj.write(cr, uid, values_ids,
227 {'active': True},
228 context=context)
229 values = value_obj.browse(cr, uid, values_ids, context=context)
230 existing_option_ids = [value.option_id.id for value in values]
231 vals = {'value_ids': []}
232 for dim in template.dimension_type_ids:
233 for option in dim.option_ids:
234 if not option.id in existing_option_ids:
235 vals['value_ids'] += [[0, 0, {'option_id': option.id}]]
236 self.write(cr, uid, [template.id], vals, context=context)
237 return True331 return True
238332
239 def get_products_from_product_template(self, cr, uid, ids, context=None):333 def get_products_from_product_template(self, cr, uid, ids, context=None):
@@ -245,12 +339,14 @@
245 default = {}339 default = {}
246 default = default.copy()340 default = default.copy()
247 default.update({'variant_ids': False, })341 default.update({'variant_ids': False, })
248 new_id = super(product_template, self).copy(cr, uid, id, default, context)342 new_id = super(ProductTemplate, self).\
343 copy(cr, uid, id, default, context=context)
249344
250 val_obj = self.pool.get('product.variant.dimension.value')345 val_obj = self.pool.get('dimension.value')
251 template = self.read(cr, uid, new_id, ['value_ids'], context=context)346 template = self.read(cr, uid, new_id, ['value_ids'], context=context)
252 # Making sure the values we duplicated are no longer linked via the347 # Making sure the values we duplicated are no longer linked via the
253 # m2m 'product_ids' with the product.product variants from the original template348 # m2m 'product_ids' with the product.product variants
349 # from the original template
254 val_obj.write(cr, uid, template['value_ids'], {350 val_obj.write(cr, uid, template['value_ids'], {
255 'product_ids': [(6, 0, [])],351 'product_ids': [(6, 0, [])],
256 }, context=context)352 }, context=context)
@@ -260,384 +356,227 @@
260 def copy_translations(self, cr, uid, old_id, new_id, context=None):356 def copy_translations(self, cr, uid, old_id, new_id, context=None):
261 if context is None:357 if context is None:
262 context = {}358 context = {}
263 # avoid recursion through already copied records in case of circular relationship359 # avoid recursion through already copied records in case
360 # of circular relationship
264 seen_map = context.setdefault('__copy_translations_seen', {})361 seen_map = context.setdefault('__copy_translations_seen', {})
265 if old_id in seen_map.setdefault(self._name, []):362 if old_id in seen_map.setdefault(self._name, []):
266 return363 return
267 seen_map[self._name].append(old_id)364 seen_map[self._name].append(old_id)
268 return super(product_template, self).copy_translations(cr, uid, old_id, new_id,365 return super(ProductTemplate, self).\
269 context=context)366 copy_translations(cr, uid, old_id, new_id, context=context)
270367
271 def _create_variant_list(self, cr, ids, uid, vals, context=None):368 def _create_variant_list(self, cr, uid, vals, context=None):
272369
273 def cartesian_product(args):370 def cartesian_product(args):
274 if len(args) == 1:371 if len(args) == 1:
275 return [x and [x] or [] for x in args[0]]372 return [x and [x] or [] for x in args[0]]
276 return [(i and [i] or []) + j for j in cartesian_product(args[1:]) for i in args[0]]373 return [(i and [i] or []) + j for j in cartesian_product(args[1:])
374 for i in args[0]]
277375
278 return cartesian_product(vals)376 return cartesian_product(vals)
279377
378 def _get_combinaison(self, cr, uid, product_temp, context=None):
379 res = defaultdict(list)
380
381 for value in product_temp.value_ids:
382 res[value.dimension_id].append(value.option_id.id)
383
384 temp_val_list = []
385 for dim in res:
386 temp_val_list += [
387 res[dim]
388 + (not dim.mandatory_dimension and [None] or [])
389 ]
390
391 # example temp_val_list is equal to [['red', 'blue', 'yellow'],
392 # ['L', 'XL', 'M']]
393 # In reallity it's not a string value but the id of the value
394
395 if not temp_val_list:
396 return []
397
398 combinaisons = self._create_variant_list(
399 cr, uid, temp_val_list, context)
400 return combinaisons
401
402 def _get_combinaisons_to_create(self, cr, uid, product_temp,
403 existing_product_ids, context=None):
404 variants_obj = self.pool['product.product']
405
406 fields = set([dimension_value.dimension_id.name
407 for dimension_value in product_temp.value_ids])
408
409 combinaisons = self._get_combinaison(
410 cr, uid, product_temp, context=context)
411
412 for combinaison in combinaisons:
413 combinaison.sort()
414
415 existing_products = variants_obj.read(
416 cr, uid, existing_product_ids, fields, context=context)
417
418 existing_combinaisons = []
419 for existing_product in existing_products:
420 existing_combinaison = []
421 for field in fields:
422 if existing_product[field]:
423 existing_combinaison.append(existing_product[field][0])
424 else:
425 existing_combinaison.append(None)
426 existing_combinaison.sort()
427 existing_combinaisons.append(existing_combinaison)
428
429 combinaisons_to_create = [x for x in combinaisons
430 if not x in existing_combinaisons]
431
432 _logger.debug("variant existing : %s, variant to create : %s",
433 len(existing_combinaisons),
434 len(combinaisons_to_create))
435 return combinaisons_to_create
436
437 def _prepare_variant_vals(self, cr, uid, product_temp, combinaison,
438 context=None):
439 option_obj = self.pool['attribute.option']
440 vals = {
441 'name': product_temp.name,
442 'track_production': product_temp.variant_track_production,
443 'track_incoming': product_temp.variant_track_incoming,
444 'track_outgoing': product_temp.variant_track_outgoing,
445 'product_tmpl_id': product_temp.id,
446 }
447 option_ids = [option_id for option_id in combinaison if option_id]
448 for option in option_obj.browse(cr, uid, option_ids, context=context):
449 vals[option.attribute_id.name] = option.id
450 return vals
451
452 def _create_variant(self, cr, uid, product_temp, existing_product_ids,
453 context=None):
454 variants_obj = self.pool['product.product']
455 created_product_ids = []
456 combinaisons_to_create = self.\
457 _get_combinaisons_to_create(
458 cr, uid, product_temp, existing_product_ids,
459 context=context)
460
461 count = 0
462 for combinaison in combinaisons_to_create:
463 count += 1
464 vals = self._prepare_variant_vals(
465 cr, uid, product_temp, combinaison,
466 context=context)
467
468 cr.execute("SAVEPOINT pre_variant_save")
469 try:
470 product_id = variants_obj.create(cr, uid, vals, {
471 'generate_from_template': True,
472 })
473 created_product_ids.append(product_id)
474 if count % 50 == 0:
475 _logger.debug("product created : %s", count)
476 except Exception, e:
477 if config['debug_mode']:
478 raise
479 _logger.error("Error creating product variant: %s",
480 e, exc_info=True)
481 _logger.debug("Values used to attempt creation of "
482 "product variant: %s", vals)
483 cr.execute("ROLLBACK TO SAVEPOINT pre_variant_save")
484 cr.execute("RELEASE SAVEPOINT pre_variant_save")
485
486 _logger.debug("product created : %s", len(created_product_ids))
487 return created_product_ids
488
489 def _generate_variant_for_template(self, cr, uid, product_temp,
490 context=None):
491 variants_obj = self.pool['product.product']
492 created_product_ids = []
493
494 existing_product_ids = variants_obj.search(cr, uid, [
495 ('product_tmpl_id', '=', product_temp.id)
496 ], context=context)
497
498 if not product_temp.do_not_generate_new_variant:
499 created_product_ids = self._create_variant(
500 cr, uid, product_temp, existing_product_ids,
501 context=context)
502
503 product_ids = existing_product_ids + created_product_ids
504
505 _logger.debug("Starting to generate/update variant names...")
506 variants_obj.update_variant(cr, uid, product_ids, context=context)
507 return True
508
280 def button_generate_variants(self, cr, uid, ids, context=None):509 def button_generate_variants(self, cr, uid, ids, context=None):
281 variants_obj = self.pool.get('product.product')510 for product_temp in self.browse(cr, uid, ids, context=context):
282511 self._generate_variant_for_template(
283 for product_temp in self.browse(cr, uid, ids, context):512 cr, uid, product_temp, context=context)
284 res = {}
285 temp_val_list = []
286 for value in product_temp.value_ids:
287 if res.get(value.dimension_id, False):
288 res[value.dimension_id] += [value.id]
289 else:
290 res[value.dimension_id] = [value.id]
291 for dim in res:
292 temp_val_list += [res[dim] + (not dim.mandatory_dimension and [None] or [])]
293
294 existing_product_ids = variants_obj.search(cr, uid,
295 [('product_tmpl_id', '=', product_temp.id)])
296 created_product_ids = []
297 if temp_val_list and not product_temp.do_not_generate_new_variant:
298 list_of_variants = self._create_variant_list(cr, uid, ids, temp_val_list, context)
299 existing_product_dim_value = variants_obj.read(cr, uid, existing_product_ids,
300 ['dimension_value_ids'])
301 list_of_variants_existing = [x['dimension_value_ids']
302 for x in existing_product_dim_value]
303 for x in list_of_variants_existing:
304 x.sort()
305 for x in list_of_variants:
306 x.sort()
307 list_of_variants_to_create = [x for x in list_of_variants
308 if not x in list_of_variants_existing]
309
310 _logger.debug("variant existing : %s, variant to create : %s",
311 len(list_of_variants_existing),
312 len(list_of_variants_to_create))
313 count = 0
314 for variant in list_of_variants_to_create:
315 count += 1
316
317 vals = {
318 'name': product_temp.name,
319 'track_production': product_temp.variant_track_production,
320 'track_incoming': product_temp.variant_track_incoming,
321 'track_outgoing': product_temp.variant_track_outgoing,
322 'product_tmpl_id': product_temp.id,
323 'dimension_value_ids': [(6, 0, variant)],
324 }
325
326 cr.execute("SAVEPOINT pre_variant_save")
327 try:
328 product_id = variants_obj.create(cr, uid, vals,
329 {'generate_from_template': True})
330 created_product_ids.append(product_id)
331 if count % 50 == 0:
332 _logger.debug("product created : %s", count)
333 except Exception, e:
334 _logger.error("Error creating product variant: %s",
335 e, exc_info=True)
336 _logger.debug("Values used to attempt creation of product variant: %s",
337 vals)
338 cr.execute("ROLLBACK TO SAVEPOINT pre_variant_save")
339 cr.execute("RELEASE SAVEPOINT pre_variant_save")
340
341 _logger.debug("product created : %s", count)
342
343 if not product_temp.do_not_update_variant:
344 product_ids = existing_product_ids + created_product_ids
345 else:
346 product_ids = created_product_ids
347
348 # FIRST, Generate/Update variant names ('variants' field)
349 _logger.debug("Starting to generate/update variant names...")
350 self.pool.get('product.product').build_variants_name(cr, uid, product_ids,
351 context=context)
352 _logger.debug("End of the generation/update of variant names.")
353 # SECOND, Generate/Update product codes and properties (we may need variants name)
354 _logger.debug("Starting to generate/update product codes and properties...")
355 self.pool.get('product.product').build_product_code_and_properties(cr, uid,
356 product_ids,
357 context=context)
358 _logger.debug("End of the generation/update of product codes and properties.")
359 # THIRD, Generate/Update product names (we may need variants name for that)
360 _logger.debug("Starting to generate/update product names...")
361 self.pool.get('product.product').build_product_name(cr, uid, product_ids,
362 context=context)
363 _logger.debug("End of generation/update of product names.")
364 return True513 return True
365514
366515
367class product_product(orm.Model):516class ProductProduct(orm.Model):
368 _inherit = "product.product"517 _inherit = "product.product"
369518
370 def init(self, cr):519 def init(self, cr):
371 #For the first installation if you already have product in your database,520 #TODO do it only for the first installation
372 # the name of the existing product will be empty, so we fill it521 #For the first installation if you already have product in
373 cr.execute("update product_product set name=name_template where name is null;")522 # your database, the name of the existing product will be empty,
523 # so we fill it
524 cr.execute("UPDATE product_product SET name=name_template "
525 "WHERE name is null;")
374 return True526 return True
375527
376 def unlink(self, cr, uid, ids, context=None):528 def unlink(self, cr, uid, ids, context=None):
377 if not context:529 if not context:
378 context = {}530 context = {}
379 context['unlink_from_product_product'] = True531 context['unlink_from_product_product'] = True
380 return super(product_product, self).unlink(cr, uid, ids, context)532 return super(ProductProduct, self).unlink(cr, uid, ids, context)
381533
382 def build_product_name(self, cr, uid, ids, context=None):534 def update_variant(self, cr, uid, ids, context=None):
383 return self.build_product_field(cr, uid, ids, 'name', context=None)535 lang_obj = self.pool.get('res.lang')
384536 lang_ids = lang_obj.search(cr, uid, [
385 def build_product_field(self, cr, uid, ids, field, context=None):537 ('translatable', '=', True),
386 def get_description_sale(product):538 ], context=context)
387 return self.parse(cr, uid, product,539 for lang in lang_obj.browse(cr, uid, lang_ids, context=context):
388 product.product_tmpl_id.description_sale,540 context['lang'] = lang.code
389 context=context)541 for product in self.browse(cr, uid, ids, context=context):
390542 self._update_variant(cr, uid, product, context=context)
391 def get_name(product):543 return True
392 return (product.product_tmpl_id.name or '') + ' ' + (product.variants or '')544
393545 def _update_variant(self, cr, uid, product, context=None):
394 if not context:546 vals = self._prepare_update_vals(cr, uid, product, context=context)
395 context = {}547 vals = self._remove_not_updated(cr, uid, product, vals, context=context)
548 if vals:
549 product.write(vals)
550 return True
551
552 def _prepare_update_vals(self, cr, uid, product, context=None):
396 context['is_multi_variants'] = True553 context['is_multi_variants'] = True
397 obj_lang = self.pool.get('res.lang')554 string_template_obj = self.pool['string.template']
398 lang_ids = obj_lang.search(cr, uid, [('translatable', '=', True)], context=context)555 vals = {
399 lang_code = [x['code']556 'variants': string_template_obj._build(
400 for x in obj_lang.read(cr, uid, lang_ids, ['code'], context=context)]557 cr, uid, product.template_name_id.id, product),
401 for code in lang_code:558 'default_code': string_template_obj._build(
402 context['lang'] = code559 cr, uid, product.template_code_id.id, product),
403 for product in self.browse(cr, uid, ids, context=context):560 }
404 new_field_value = eval("get_" + field + "(product)") # TODO convert to safe_eval561 vals['name'] = "%s %s" % (
405 cur_field_value = safe_eval("product." + field, {'product': product})562 product.product_tmpl_id.name or '',
406 if new_field_value != cur_field_value:563 vals['variants'])
407 self.write(cr, uid, [product.id], {field: new_field_value}, context=context)564 return vals
408 return True565
409566 def _remove_not_updated(self, cr, uid, product, vals, context=None):
410 def parse(self, cr, uid, o, text, context=None):567 vals_to_write = {}
411 if not text:568 for key in vals:
412 return ''569 if vals[key] != product[key]:
413 vals = text.split('[_')570 vals_to_write[key] = vals[key]
414 description = ''571 return vals_to_write
415 for val in vals:
416 if '_]' in val:
417 sub_val = val.split('_]')
418 try:
419 description += (safe_eval(sub_val[0], {'o': o, 'context': context})
420 or ''
421 ) + sub_val[1]
422 except AttributeError:
423 raise osv.except_osv(_('Bad expression'),
424 _("One of your expressions contains "
425 "a non existing attribute: %s"
426 ) % sub_val[0])
427 else:
428 description += val
429 return description
430
431 def generate_product_code(self, cr, uid, product_obj, code_generator, context=None):
432 '''I wrote this stupid function to be able to inherit it in a custom module !'''
433 return self.parse(cr, uid, product_obj, code_generator, context=context)
434
435 def build_product_code_and_properties(self, cr, uid, ids, context=None):
436 for product in self.browse(cr, uid, ids, context=context):
437 new_default_code = self.generate_product_code(cr, uid, product,
438 product.product_tmpl_id.code_generator,
439 context=context)
440 current_values = {
441 'default_code': product.default_code,
442 #'track_production': product.track_production,
443 #'track_outgoing': product.track_outgoing,
444 #'track_incoming': product.track_incoming,
445 }
446 new_values = {
447 'default_code': new_default_code,
448 #'track_production': product.product_tmpl_id.variant_track_production,
449 #'track_outgoing': product.product_tmpl_id.variant_track_outgoing,
450 #'track_incoming': product.product_tmpl_id.variant_track_incoming,
451 }
452 if new_values != current_values:
453 self.write(cr, uid, [product.id], new_values, context=context)
454 return True
455
456 def product_ids_variant_changed(self, cr, uid, ids, res, context=None):
457 '''it's a hook for product_variant_multi advanced'''
458 return True
459
460 def generate_variant_name(self, cr, uid, product_id, context=None):
461 '''Do the generation of the variant name in a dedicated function, so that we can
462 inherit this function to hack the code generation'''
463 product = self.browse(cr, uid, product_id, context=context)
464 model = product.variant_model_name
465 r = map(lambda dim: [dim.dimension_id.sequence,
466 self.parse(cr, uid, dim, model, context=context)],
467 product.dimension_value_ids)
468 r.sort()
469 r = [x[1] for x in r]
470 new_variant_name = (product.variant_model_name_separator or '').join(r)
471 return new_variant_name
472
473 def build_variants_name(self, cr, uid, ids, context=None):
474 for product in self.browse(cr, uid, ids, context=context):
475 new_variant_name = self.generate_variant_name(cr, uid, product.id, context=context)
476 if new_variant_name != product.variants:
477 self.write(cr, uid, [product.id], {'variants': new_variant_name}, context=context)
478 return True
479
480 def _check_dimension_values(self, cr, uid, ids):
481 # TODO: check that all dimension_types of the product_template
482 # have a corresponding dimension_value ??
483 for product in self.browse(cr, uid, ids, {}):
484 buffer = []
485 for value in product.dimension_value_ids:
486 buffer.append(value.dimension_id)
487 unique_set = set(buffer)
488 if len(unique_set) != len(buffer):
489 raise orm.except_orm(_('Constraint error :'),
490 _("On product '%s', there are several dimension values "
491 "for the same dimension type.") % product.name)
492 return True
493
494 def compute_product_dimension_extra_price(self, cr, uid, product_id,
495 product_price_extra=False, dim_price_margin=False,
496 dim_price_extra=False, context=None):
497 if context is None:
498 context = {}
499 dimension_extra = 0.0
500 product = self.browse(cr, uid, product_id, context=context)
501 for dim in product.dimension_value_ids:
502 if product_price_extra and dim_price_margin and dim_price_extra:
503 dimension_extra += (safe_eval('product.' + product_price_extra,
504 {'product': product})
505 * safe_eval('dim.' + dim_price_margin,
506 {'dim': dim})
507 + safe_eval('dim.' + dim_price_extra,
508 {'dim': dim}))
509 elif not product_price_extra and not dim_price_margin and dim_price_extra:
510 dimension_extra += safe_eval('dim.' + dim_price_extra, {'dim': dim})
511 elif product_price_extra and dim_price_margin and not dim_price_extra:
512 dimension_extra += (safe_eval('product.' + product_price_extra,
513 {'product': product})
514 * safe_eval('dim.' + dim_price_margin,
515 {'dim': dim}))
516 elif product_price_extra and not dim_price_margin and dim_price_extra:
517 dimension_extra += (safe_eval('product.' + product_price_extra,
518 {'product': product})
519 + safe_eval('dim.' + dim_price_extra, {'dim': dim}))
520
521 if 'uom' in context:
522 product_uom_obj = self.pool.get('product.uom')
523 uom = product.uos_id or product.uom_id
524 dimension_extra = product_uom_obj._compute_price(cr, uid, uom.id,
525 dimension_extra, context['uom'])
526 return dimension_extra
527
528 def compute_dimension_extra_price(self, cr, uid, ids, result, product_price_extra=False,
529 dim_price_margin=False, dim_price_extra=False, context=None):
530 if context is None:
531 context = {}
532 for product in self.browse(cr, uid, ids, context=context):
533 dimension_extra = self.compute_product_dimension_extra_price(
534 cr, uid, product.id,
535 product_price_extra=product_price_extra,
536 dim_price_margin=dim_price_margin,
537 dim_price_extra=dim_price_extra,
538 context=context)
539 result[product.id] += dimension_extra
540 return result
541
542 def price_get(self, cr, uid, ids, ptype='list_price', context=None):
543 if context is None:
544 context = {}
545 result = super(product_product, self).price_get(cr, uid, ids, ptype, context=context)
546 if ptype == 'list_price':
547 #TODO check if the price_margin on the dimension is very usefull,
548 # maybe we will remove it
549 result = self.compute_dimension_extra_price(
550 cr, uid, ids, result,
551 product_price_extra='price_extra',
552 dim_price_margin='price_margin',
553 dim_price_extra='price_extra',
554 context=context)
555 elif ptype == 'standard_price':
556 result = self.compute_dimension_extra_price(
557 cr, uid, ids, result,
558 product_price_extra='cost_price_extra',
559 dim_price_extra='cost_price_extra',
560 context=context)
561 return result
562
563 def _product_lst_price(self, cr, uid, ids, name, arg, context=None):
564 if context is None:
565 context = {}
566 result = super(product_product, self)._product_lst_price(cr, uid, ids, name, arg,
567 context=context)
568 result = self.compute_dimension_extra_price(cr, uid, ids, result,
569 product_price_extra='price_extra',
570 dim_price_margin='price_margin',
571 dim_price_extra='price_extra',
572 context=context)
573 return result
574
575 def copy(self, cr, uid, id, default=None, context=None):
576 if default is None:
577 default = {}
578 default = default.copy()
579 default.update({'variant_ids': False})
580 return super(product_product, self).copy(cr, uid, id, default, context)
581
582 def _product_compute_weight_volume(self, cr, uid, ids, fields, arg, context=None):
583 result = {}
584 for product in self.browse(cr, uid, ids, context=context):
585 result[product.id] = p = {}
586 p['total_weight'] = product.weight + product.additional_weight
587 p['total_weight_net'] = product.weight_net + product.additional_weight_net
588 p['total_volume'] = product.volume + product.additional_volume
589 return result
590572
591 _columns = {573 _columns = {
592 'name': fields.char('Name', size=128, translate=True, select=True),574 'name': fields.char(
593 'variants': fields.char('Variants', size=128),575 'Name',
594 'dimension_value_ids': fields.many2many(576 size=128,
595 'product.variant.dimension.value',577 translate=True,
596 'product_product_dimension_rel',578 select=True),
597 'product_id', 'dimension_id',579 'variants': fields.char(
598 'Dimensions',580 'Variants',
599 domain="[('product_tmpl_id','=',product_tmpl_id)]"),581 size=128),
600 'cost_price_extra': fields.float('Purchase Extra Cost',
601 digits_compute=dp.get_precision('Purchase Price')),
602 'lst_price': fields.function(_product_lst_price,
603 method=True,
604 type='float',
605 string='List Price',
606 digits_compute=dp.get_precision('Sale Price')),
607 #the way the weight are implemented are not clean at all,
608 #we should redesign the module product form the addons
609 #in order to get something correclty.
610 #indeed some field of the template have to be overwrited
611 #like weight, name, weight_net, volume.
612 #in order to have a consitent api we should use the same field for getting the weight,
613 #now we have to use "weight" or "total_weight"
614 #not clean at all with external syncronization
615 'total_weight': fields.function(_product_compute_weight_volume,
616 method=True,
617 type='float',
618 string='Total Gross Weight',
619 help="The gross weight in Kg.",
620 multi='weight_volume'),
621 'total_weight_net': fields.function(_product_compute_weight_volume,
622 method=True,
623 type='float',
624 string='Total Net Weight',
625 help="The net weight in Kg.",
626 multi='weight_volume'),
627 'total_volume': fields.function(_product_compute_weight_volume,
628 method=True,
629 type='float',
630 string='Total Volume',
631 help="The volume in m3.",
632 multi='weight_volume'),
633 'additional_weight': fields.float('Additional Gross weight',
634 help="The additional gross weight in Kg."),
635 'additional_weight_net': fields.float('Additional Net weight',
636 help="The additional net weight in Kg."),
637 'additional_volume': fields.float('Additional Volume',
638 help="The additional volume in Kg."),
639 }582 }
640
641 _constraints = [
642 (_check_dimension_values, 'Error msg in raise', ['dimension_value_ids']),
643 ]
644583
=== modified file 'product_variant_generator/product_view.xml'
--- product_variant_multi/product_view.xml 2013-04-22 21:00:07 +0000
+++ product_variant_generator/product_view.xml 2014-06-24 09:37:35 +0000
@@ -3,224 +3,108 @@
3 <data>3 <data>
44
5<!--5<!--
6 "Product variant multi" module for OpenERP6 "Product variant generator" module for OpenERP
7 The licence is in the file __openerp__.py7 The licence is in the file __openerp__.py
8 @author Alexis de Lattre <alexis.delattre@akretion.com>8 @author Alexis de Lattre <alexis.delattre@akretion.com>
9 @author Sebastien Beau <sebastien.beau@akretion.com>9 @author Sebastien Beau <sebastien.beau@akretion.com>
10 @author Chafique Delli <chafique.delli@akretion.com>
10-->11-->
1112
12
13 <menuitem name="Variant Dimensions" id="menu_variant_dimension" parent="product.prod_config_main" />
14
15
16 <!-- DIMENSION OPTION -->
17 <record id="product_variant_multi_dimension_option_seach" model="ir.ui.view">
18 <field name="name">product.variant.multi.dimension.option.search</field>
19 <field name="model">product.variant.dimension.option</field>
20 <field name="arch" type="xml">
21 <search string="Dimension Options Search">
22 <field name="name"/>
23 <field name="dimension_id"/>
24 </search>
25 </field>
26 </record>
27
28 <record id="product_variant_multi_dimension_option_tree" model="ir.ui.view">
29 <field name="name">product.variant.multi.dimension.option.tree</field>
30 <field name="model">product.variant.dimension.option</field>
31 <field name="arch" type="xml">
32 <tree string="Dimension Options" editable="top">
33 <field name="dimension_id" invisible="not context.get('dimension_option_main_view', False)" />
34 <field name="name" />
35 <field name="sequence" />
36 <field name="code" />
37 </tree>
38 </field>
39 </record>
40
41 <record id="product_variant_multi_dimension_option_form" model="ir.ui.view">
42 <field name="name">product.variant.multi.dimension.option.form</field>
43 <field name="model">product.variant.dimension.option</field>
44 <field name="arch" type="xml">
45 <form string="Dimension Options">
46 <field name="dimension_id" invisible="not context.get('dimension_option_main_view', False)" />
47 <newline />
48 <field name="sequence" />
49 <field name="name" />
50 <field name="code" />
51 </form>
52 </field>
53 </record>
54
55 <record id="action_dimension_option" model="ir.actions.act_window">
56 <field name="name">Dimension Options</field>
57 <field name="res_model">product.variant.dimension.option</field>
58 <field name="view_type">form</field>
59 <field name="view_mode">tree,form</field>
60 <field name="context">{'dimension_option_main_view': True}</field>
61 </record>
62
63 <menuitem id="menu_variant_dimension_option" parent="menu_variant_dimension" action="action_dimension_option" />
64
65
66 <!-- DIMENSION VALUES -->13 <!-- DIMENSION VALUES -->
67 <record id="product_variant_multi_dimension_value_search" model="ir.ui.view">14 <record id="dimension_value_tree" model="ir.ui.view">
68 <field name="name">product.variant.multi.dimension.value.search</field>15 <field name="model">dimension.value</field>
69 <field name="model">product.variant.dimension.value</field>
70 <field name="arch" type="xml">
71 <search string="Dimension Values Search">
72 <field name="product_tmpl_id"/>
73 <field name="option_id"/>
74 </search>
75 </field>
76 </record>
77
78
79 <record id="product_variant_multi_dimension_value_tree" model="ir.ui.view">
80 <field name="name">product.variant.multi.dimension.value.tree</field>
81 <field name="model">product.variant.dimension.value</field>
82 <field name="arch" type="xml">16 <field name="arch" type="xml">
83 <tree string="Dimension Values" editable="top">17 <tree string="Dimension Values" editable="top">
84 <field name="product_tmpl_id" invisible="not context.get('dimension_value_main_view', False)" />18 <field name="product_tmpl_id"
19 invisible="not context.get('dimension_value_main_view', False)"/>
85 <field name="active" />20 <field name="active" />
86 <field name="dimension_id" />
87 <field name="option_id" />21 <field name="option_id" />
88 <field name="sequence" />22 <field name="sequence" />
89 <field name="cost_price_extra" />
90 <field name="price_extra" />
91 </tree>23 </tree>
92 </field>24 </field>
93 </record>25 </record>
9426
95 <record id="product_variant_multi_dimension_value_form" model="ir.ui.view">27 <record id="product_variant_multi_dimension_value_form" model="ir.ui.view">
96 <field name="name">product.variant.multi.dimension.value.form</field>28 <field name="model">dimension.value</field>
97 <field name="model">product.variant.dimension.value</field>
98 <field name="arch" type="xml">29 <field name="arch" type="xml">
99 <form string="Dimension Values">30 <form string="Dimension Values">
100 <field name="product_tmpl_id" invisible="not context.get('dimension_value_main_view', False)" />31 <field name="product_tmpl_id"
32 invisible="not context.get('dimension_value_main_view', False)"/>
101 <field name="active" />33 <field name="active" />
102 <field name="dimension_id" />
103 <newline />34 <newline />
104 <field name="option_id" />35 <field name="option_id" />
105 <field name="sequence" />36 <field name="sequence" />
106 <field name="cost_price_extra" />
107 <field name="price_extra" />
108 <field name="price_margin" />
109 </form>37 </form>
110 </field>38 </field>
111 </record>39 </record>
11240
113 <record id="action_dimension_value" model="ir.actions.act_window">41 <record id="action_dimension_value" model="ir.actions.act_window">
114 <field name="name">Dimension values</field>42 <field name="name">Dimension values</field>
115 <field name="res_model">product.variant.dimension.value</field>43 <field name="res_model">dimension.value</field>
116 <field name="view_type">form</field>44 <field name="view_type">form</field>
117 <field name="view_mode">tree,form</field>45 <field name="view_mode">tree,form</field>
118 <field name="context">{'dimension_value_main_view': True}</field>46 <field name="context">{'dimension_value_main_view': True}</field>
119 </record>47 </record>
12048
12149 <record id="attribute_attribute_form_view" model="ir.ui.view">
122 <!-- DIMENSION TYPE -->50 <field name="model">attribute.attribute</field>
123 <record id="product_variant_multi_dimension_type_search" model="ir.ui.view">51 <field name="inherit_id" ref="base_custom_attributes.attribute_attribute_form_view"/>
124 <field name="name">product.variant.multi.dimension.type.search</field>52 <field name="arch" type="xml">
125 <field name="model">product.variant.dimension.type</field>53 <field name="translate" position="after">
126 <field name="arch" type="xml">54 <field name="is_dimension"/>
127 <search string="Dimension Type Search">55 <field name="sequence"
128 <field name="name"/>56 attrs="{'invisible': [('is_dimension','=', False)]}"/>
129 </search>57 <field name="mandatory_dimension"
130 </field>58 attrs="{'invisible': [('is_dimension','=', False)]}"/>
131 </record>59 </field>
13260 </field>
133 <record id="product_variant_multi_dimension_type_tree" model="ir.ui.view">61 </record>
134 <field name="name">product.variant.multi.dimension.type.tree</field>
135 <field name="model">product.variant.dimension.type</field>
136 <field name="arch" type="xml">
137 <tree string="Dimension Types">
138 <field name="name" />
139 <field name="description" />
140 </tree>
141 </field>
142 </record>
143
144 <record id="product_variant_multi_dimension_type_form" model="ir.ui.view">
145 <field name="name">product.variant.multi.dimension.type.form</field>
146 <field name="model">product.variant.dimension.type</field>
147 <field name="arch" type="xml">
148 <form string="Dimension Types" version="7.0">
149 <sheet>
150 <div class="oe_title">
151 <label for="name" string="Dimension Type Name" class="oe_edit_only"/>
152 <h1>
153 <field name="name" />
154 </h1>
155 </div>
156 <group>
157 <field name="description" />
158 <field name="sequence" />
159 <field name="allow_custom_value" />
160 <field name="mandatory_dimension" />
161 <field name="option_ids" nolabel="1" colspan="4"/>
162 </group>
163 </sheet>
164 </form>
165 </field>
166 </record>
167
168
169 <record id="action_dimension_type" model="ir.actions.act_window">
170 <field name="name">Dimension Types</field>
171 <field name="res_model">product.variant.dimension.type</field>
172 <field name="view_type">form</field>
173 <field name="view_mode">tree,form</field>
174 <field name="context">{'dimension_type_main_view': True}</field>
175 </record>
176
177 <menuitem id="menu_variant_dimension_types" parent="menu_variant_dimension" action="action_dimension_type" />
178
17962
180 <!-- PRODUCT TEMPLATE -->63 <!-- PRODUCT TEMPLATE -->
181
182<!-- TODO remove useless view of product.template in the module product or fusion this view with the existing view in product, it's depend how the inheritage on this view will be managed by the other module-->
183
184<!--TODO add a wizard to add the option-->
185<!--<button name="add_some_option" string="Add " type="object" colspan="2"/>-->
186
187 <record id="product_search_form_view_template" model="ir.ui.view">
188 <field name="name">product.search.form.template</field>
189 <field name="model">product.template</field>
190 <field name="arch" type="xml">
191 <search string="Product Template">
192 <field name="name"/>
193 </search>
194 </field>
195 </record>
196
197 <record id="product_variant_multi_product_template_form_view" model="ir.ui.view">64 <record id="product_variant_multi_product_template_form_view" model="ir.ui.view">
198 <field name="name">product.variant.multi.product.template.form</field>
199 <field name="model">product.template</field>65 <field name="model">product.template</field>
200 <field name="inherit_id" ref="product.product_template_form_view" />66 <field name="inherit_id" ref="product.product_template_form_view" />
201 <field name="arch" type="xml">67 <field name="arch" type="xml">
202 <xpath expr="//field[@name='categ_id']/.." position="after">68 <xpath expr="//field[@name='categ_id']/.." position="after">
203 <group>69 <group>
204 <field name="is_multi_variants"/>70 <field name="is_multi_variants"/>
71 <field name="attribute_set_id"
72 attrs="{'invisible':[('is_multi_variants','=',False)]}"/>
205 </group>73 </group>
206 </xpath>74 </xpath>
207 <xpath expr="//form/notebook" position="inside">75 <xpath expr="//form/notebook" position="inside">
208 <page string="Variants">76 <page string="Variants">
209 <group colspan="1" col="4" attrs="{'invisible':[('is_multi_variants','=',False)]}">77 <group colspan="1" col="4" attrs="{'invisible':[('is_multi_variants','=',False)]}">
210 <field name="dimension_type_ids" nolabel="1" colspan="4" />78 <button name="%(product_variant_generator.action_add_option)d"
211 <button name="add_all_option" string="Add All Options" type="object" colspan="2"/>79 string="Add Option"
212 <field name="value_ids" nolabel="1" colspan="4" />80 type="action"/>
213 <field name="variant_model_name" colspan="4"/>81 <field name="value_ids" nolabel="1" colspan="4">
214 <field name="variant_model_name_separator" colspan="4"/>82 <tree string="Values" editable="top">
215 <field name="code_generator" colspan="4"/>83 <field name="active"/>
84 <field name="dimension_id"
85 on_change="on_dimension_change(dimension_id, context)"
86 domain="[('dimension_id','in',parent.dimension_ids)]" />
87 <field name="option_id"/>
88 </tree>
89 </field>
90 <field name="base_default_code" colspan="4"/>
91 <field name="template_name_id" colspan="4"/>
92 <field name="template_code_id" colspan="4"/>
216 <field name="variant_track_production" colspan="4"/>93 <field name="variant_track_production" colspan="4"/>
217 <field name="variant_track_incoming" colspan="4"/>94 <field name="variant_track_incoming" colspan="4"/>
218 <field name="variant_track_outgoing" colspan="4"/>95 <field name="variant_track_outgoing" colspan="4"/>
219 <field name="do_not_generate_new_variant" colspan="4"/>96 <field name="do_not_generate_new_variant" colspan="4"/>
220 <field name="do_not_update_variant" colspan="4"/>97 <field name="do_not_update_variant" colspan="4"/>
221 <button name="button_generate_variants" string="Generate / Update Variants" type="object" colspan="4"/>98 <button name="button_generate_variants"
99 string="Generate / Update Variants"
100 type="object"
101 colspan="4"/>
222 </group>102 </group>
223 <field name="variant_ids" string="Variants" nolabel="1" colspan="1">103 <separator string="Variants"/>
104 <field name="variant_ids"
105 string="Variants"
106 nolabel="1"
107 colspan="1">
224 <tree string="Variants">108 <tree string="Variants">
225 <field name="code" />109 <field name="code" />
226 <field name="name" />110 <field name="name" />
@@ -240,27 +124,8 @@
240 <field name="view_mode">tree,form</field>124 <field name="view_mode">tree,form</field>
241 </record>125 </record>
242126
243 <menuitem action="product_template" id="menu_template" parent="product.prod_config_main" />
244
245
246 <!-- PRODUCT VARIANTS -->127 <!-- PRODUCT VARIANTS -->
247
248 <!-- TODO remove useless view of product.product in the module product or move this correct view in product, it's depend how the heritage on this view will be managed by the other module -->
249
250 <record id="product_search_form_view_variants" model="ir.ui.view">
251 <field name="name">product.search.form.variants</field>
252 <field name="model">product.product</field>
253 <field name="inherit_id" ref="product.product_search_form_view"/>
254 <field name="arch" type="xml">
255 <field name="categ_id" position="after">
256 <field name="product_tmpl_id"/>
257 <field name="variants"/>
258 </field>
259 </field>
260 </record>
261
262 <record id="product_variant_form_view" model="ir.ui.view">128 <record id="product_variant_form_view" model="ir.ui.view">
263 <field name="name">product.variant.form</field>
264 <field name="model">product.product</field>129 <field name="model">product.product</field>
265 <field name="arch" type="xml">130 <field name="arch" type="xml">
266 <form string="Product Variant" version="7.0">131 <form string="Product Variant" version="7.0">
@@ -280,27 +145,12 @@
280 <field name="default_code" />145 <field name="default_code" />
281 <field name="active" />146 <field name="active" />
282 </group>147 </group>
283 <notebook colspan="4">
284 <page string="Dimensions">
285 <separator string="Dimension Values" colspan="4"/>
286 <field name="dimension_value_ids" context="{'product_tmpl_id':product_tmpl_id}" nolabel="1" colspan="4"/>
287 </page>
288 <page string="Prices">
289 <group>
290 <field name="list_price" string="Template Sale Price" readonly="1"/>
291 <field name="price_margin" />
292 <field name="price_extra" />
293 <field name="cost_price_extra"/>
294 </group>
295 </page>
296 </notebook>
297 </sheet>148 </sheet>
298 </form>149 </form>
299 </field>150 </field>
300 </record>151 </record>
301152
302 <record id="product_variant_tree_view" model="ir.ui.view">153 <record id="product_variant_tree_view" model="ir.ui.view">
303 <field name="name">product.variant.tree</field>
304 <field name="model">product.product</field>154 <field name="model">product.product</field>
305 <field name="arch" type="xml">155 <field name="arch" type="xml">
306 <tree string="Product Variant">156 <tree string="Product Variant">
@@ -308,37 +158,11 @@
308 <field name="name"/>158 <field name="name"/>
309 <field name="product_tmpl_id"/>159 <field name="product_tmpl_id"/>
310 <field name="variants"/>160 <field name="variants"/>
311 <field name="price_extra"/>
312 <field name="cost_price_extra"/>
313 </tree>161 </tree>
314 </field>162 </field>
315 </record>163 </record>
316164
317 <record id="product_variant" model="ir.actions.act_window">
318 <field name="name">Product Variants</field>
319 <field name="res_model">product.product</field>
320 <field name="view_type">form</field>
321 <field name="view_mode">tree,form</field>
322 </record>
323
324 <record id="action_variant_tree" model="ir.actions.act_window.view">
325 <field name="sequence" eval="10" />
326 <field name="view_mode">tree</field>
327 <field name="act_window_id" ref="product_variant" />
328 <field name="view_id" ref="product_variant_tree_view" />
329 </record>
330
331 <record id="action_variant_form" model="ir.actions.act_window.view">
332 <field name="sequence" eval="20" />
333 <field name="view_mode">form</field>
334 <field name="act_window_id" ref="product_variant" />
335 <field name="view_id" ref="product_variant_form_view" />
336 </record>
337
338 <menuitem action="product_variant" id="menu_variant" parent="product.prod_config_main" />
339
340 <record id="product_normal_variant_form_view" model="ir.ui.view">165 <record id="product_normal_variant_form_view" model="ir.ui.view">
341 <field name="name">product.normal.variant.form</field>
342 <field name="model">product.product</field>166 <field name="model">product.product</field>
343 <field name="inherit_id" ref="product.product_normal_form_view" />167 <field name="inherit_id" ref="product.product_normal_form_view" />
344 <field name="arch" type="xml">168 <field name="arch" type="xml">
@@ -349,33 +173,54 @@
349 <field name="is_multi_variants" readonly="1"/>173 <field name="is_multi_variants" readonly="1"/>
350 <label for="is_multi_variants" />174 <label for="is_multi_variants" />
351 </div>175 </div>
352 <field name="standard_price" position="replace">176 </field>
353 <group name='cost_prices' colspan="2" col="2">177 </record>
354 <field name="standard_price" attrs="{'readonly':[('cost_method','=','average')]}"/>178
355 <field name="cost_price_extra" groups="product.group_product_variant"/>179 <record id="view_model_string_template_search" model="ir.ui.view">
356 </group>180 <field name="model">string.template</field>
357 </field>181 <field name="arch" type="xml">
358 <group name="Weights" position="replace">182 <search string="String Template">
359 <group colspan="4" col="6" name="Weights" groups="product.group_stock_packaging">183 <field name="name"/>
360 <group name="template_weights" string="Template Weights">184 </search>
361 <field digits="(14, 3)" name="volume" attrs="{'readonly':[('type','=','service')]}"/>185 </field>
362 <field name="weight" attrs="{'readonly':[('type','=','service')]}"/>186 </record>
363 <field name="weight_net" attrs="{'readonly':[('type','=','service')]}"/>187
364 </group>188 <record id="view_model_string_template_tree" model="ir.ui.view">
365 <group name="variant_weights" string="Variant Weights" attrs="{'invisible':[('is_multi_variants','=',False)]}">189 <field name="model">string.template</field>
366 <field digits="(14, 3)" name="additional_volume" attrs="{'readonly':[('type','=','service')]}"/>190 <field name="arch" type="xml">
367 <field name="additional_weight" attrs="{'readonly':[('type','=','service')]}"/>191 <tree string="String Template">
368 <field name="additional_weight_net" attrs="{'readonly':[('type','=','service')]}"/>192 <field name="name"/>
369 </group>193 <field name="type"/>
370 <group name="total_weights" string="Total Weights" attrs="{'invisible':[('is_multi_variants','=',False)]}">194 </tree>
371 <field digits="(14, 3)" name="total_volume"/>195 </field>
372 <field name="total_weight"/>196 </record>
373 <field name="total_weight_net"/>197
374 </group>198 <record id="view_model_string_template_form" model="ir.ui.view">
375 </group>199 <field name="model">string.template</field>
376 </group>200 <field name="arch" type="xml">
377 </field>201 <form string="Label" version="7.0">
378 </record>202 <field name="name"/>
203 <field name="type"/>
204 <field name="code"/>
205 </form>
206 </field>
207 </record>
208
209 <record id="string_template_form_action" model="ir.actions.act_window">
210 <field name="res_model">string.template</field>
211 <field name="view_type">form</field>
212 <field name="view_mode">tree,form</field>
213 <field name="view_id" ref="view_model_string_template_tree"/>
214 <field name="search_view_id" ref="view_model_string_template_search"/>
215 <field name="help"></field>
216 </record>
217
218 <menuitem
219 name="Product Code/Name generator"
220 action="string_template_form_action"
221 id="menu_string_template_action"
222 parent="product.prod_config_main"
223 sequence="40"/>
379224
380 </data>225 </data>
381</openerp>226</openerp>
382227
=== modified file 'product_variant_generator/security/ir.model.access.csv'
--- product_variant_multi/security/ir.model.access.csv 2013-04-22 21:00:07 +0000
+++ product_variant_generator/security/ir.model.access.csv 2014-06-24 09:37:35 +0000
@@ -1,7 +1,3 @@
1"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink"1"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink"
2"access_product_variant_dimension_type_user","Read access on product.variant.dimension.type for employees","model_product_variant_dimension_type","base.group_user",1,0,0,02"access_dimension_value_user","Read access on dimension.value for employees","model_dimension_value","base.group_user",1,0,0,0
3"access_product_variant_dimension_value_user","Read access on product.variant.dimension.value for employees","model_product_variant_dimension_value","base.group_user",1,0,0,03"access_dimension_value_sale_manager","Full rights on dimension.value for sale manager","model_dimension_value","base.group_sale_manager",1,1,1,1
4"access_product_variant_dimension_option_user","Read access on product.variant.dimension.option for employees","model_product_variant_dimension_option","base.group_user",1,0,0,0
5"access_product_variant_dimension_type_sale_manager","Full rights on product.variant.dimension.type for sale manager","model_product_variant_dimension_type","base.group_sale_manager",1,1,1,1
6"access_product_variant_dimension_value_sale_manager","Full rights on product.variant.dimension.value for sale manager","model_product_variant_dimension_value","base.group_sale_manager",1,1,1,1
7"access_product_variant_dimension_option_sale_manager","Full rights on product.variant.dimension.option for sale manager","model_product_variant_dimension_option","base.group_sale_manager",1,1,1,1
84
=== added directory 'product_variant_generator/wizard'
=== added file 'product_variant_generator/wizard/__init__.py'
--- product_variant_generator/wizard/__init__.py 1970-01-01 00:00:00 +0000
+++ product_variant_generator/wizard/__init__.py 2014-06-24 09:37:35 +0000
@@ -0,0 +1,24 @@
1# -*- coding: utf-8 -*-
2###############################################################################
3#
4# Module for OpenERP
5# Copyright (C) 2014 Akretion (http://www.akretion.com).
6# @author Sébastien BEAU <sebastien.beau@akretion.com>
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU Affero General Public License as
10# published by the Free Software Foundation, either version 3 of the
11# License, or (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU Affero General Public License for more details.
17#
18# You should have received a copy of the GNU Affero General Public License
19# along with this program. If not, see <http://www.gnu.org/licenses/>.
20#
21###############################################################################
22
23from . import product_template_add_option
24
025
=== added file 'product_variant_generator/wizard/product_template_add_option.py'
--- product_variant_generator/wizard/product_template_add_option.py 1970-01-01 00:00:00 +0000
+++ product_variant_generator/wizard/product_template_add_option.py 2014-06-24 09:37:35 +0000
@@ -0,0 +1,69 @@
1# -*- coding: utf-8 -*-
2###############################################################################
3#
4# Module for OpenERP
5# Copyright (C) 2014 Akretion (http://www.akretion.com).
6# @author Sébastien BEAU <sebastien.beau@akretion.com>
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU Affero General Public License as
10# published by the Free Software Foundation, either version 3 of the
11# License, or (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU Affero General Public License for more details.
17#
18# You should have received a copy of the GNU Affero General Public License
19# along with this program. If not, see <http://www.gnu.org/licenses/>.
20#
21###############################################################################
22
23from openerp.osv import fields, orm
24
25
26class ProductTemplateAddOption(orm.TransientModel):
27 _name = 'product.template.add.option'
28 _description = 'Product Template Add Option'
29
30 def _get_option_domain(self, cr, uid, tmpl, context=None):
31 domain = [('attribute_id', 'in', [x.id for x in tmpl.dimension_ids])]
32 existing_option_ids = [value.option_id.id for value in tmpl.value_ids]
33 if existing_option_ids:
34 domain.append(('id', 'not in', existing_option_ids))
35 return domain
36
37 def fields_view_get(self, cr, uid, view_id=None, view_type='form',
38 context=None, toolbar=False, submenu=False):
39 res = super(ProductTemplateAddOption, self).fields_view_get(
40 cr, uid, view_id=view_id, view_type=view_type,
41 context=context, toolbar=toolbar, submenu=submenu,
42 )
43 tmpl_id = context.get('active_id')
44 if tmpl_id and view_type == 'form':
45 tmpl_obj = self.pool['product.template']
46 tmpl = tmpl_obj.browse(cr, uid, tmpl_id, context=context)
47 res['fields']['option_ids']['domain'] = self._get_option_domain(
48 cr, uid, tmpl, context=context)
49 return res
50
51 _columns = {
52 'option_ids': fields.many2many(
53 'attribute.option',
54 string='Option'),
55 }
56
57 def add_option(self, cr, uid, ids, context=None):
58 tmpl_obj = self.pool['product.template']
59 for wizard in self.browse(cr, uid, ids, context=context):
60 values = []
61 for option in wizard.option_ids:
62 values.append([0, 0, {
63 'dimension_id': option.attribute_id.id,
64 'option_id': option.id,
65 }])
66 tmpl_obj.write(cr, uid, context['active_id'], {
67 'value_ids': values,
68 }, context=context)
69 return True
070
=== added file 'product_variant_generator/wizard/product_template_add_option_view.xml'
--- product_variant_generator/wizard/product_template_add_option_view.xml 1970-01-01 00:00:00 +0000
+++ product_variant_generator/wizard/product_template_add_option_view.xml 2014-06-24 09:37:35 +0000
@@ -0,0 +1,29 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<openerp>
3 <data>
4
5 <record id="action_add_option" model="ir.actions.act_window">
6 <field name="name">Add Option</field>
7 <field name="res_model">product.template.add.option</field>
8 <field name="view_type">form</field>
9 <field name="view_mode">form</field>
10 <field name="target">new</field>
11 </record>
12
13 <record id="view_product_template_add_option_form" model="ir.ui.view">
14 <field name="model">product.template.add.option</field>
15 <field name="arch" type="xml">
16 <form string="Add Option" version="7.0">
17 <field name="option_ids"/>
18 <footer>
19 <button name="add_option" string="Confirm" type="object" class="oe_highlight"/>
20 or
21 <button string="Cancel" class="oe_link" special="cancel" />
22 </footer>
23
24 </form>
25 </field>
26 </record>
27
28 </data>
29</openerp>
030
=== added directory 'product_variant_generator_price'
=== added file 'product_variant_generator_price/__init__.py'
--- product_variant_generator_price/__init__.py 1970-01-01 00:00:00 +0000
+++ product_variant_generator_price/__init__.py 2014-06-24 09:37:35 +0000
@@ -0,0 +1,23 @@
1# -*- encoding: utf-8 -*-
2##############################################################################
3#
4# OpenERP, Open Source Management Solution
5# Copyright (C) 2014 Akretion (http://www.akretion.com)
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by
9# the Free Software Foundation, either version 3 of the License, or
10# (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20##############################################################################
21# flake8: noqa
22
23from . import product_variant
024
=== added file 'product_variant_generator_price/__openerp__.py'
--- product_variant_generator_price/__openerp__.py 1970-01-01 00:00:00 +0000
+++ product_variant_generator_price/__openerp__.py 2014-06-24 09:37:35 +0000
@@ -0,0 +1,40 @@
1# -*- encoding: utf-8 -*-
2##############################################################################
3#
4# OpenERP, Open Source Management Solution
5# Copyright (C) 2014 Akretion (http://www.akretion.com)
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by
9# the Free Software Foundation, either version 3 of the License, or
10# (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20##############################################################################
21{
22 "name": "Product Variant Generator Price",
23 "version": "1.0",
24 "author": "OpenERP SA, Akretion",
25 "category": "Sales Management",
26 "license": "AGPL-3",
27 "summary": "Product Prices with multi-dimension variants",
28 "description": """
29
30 """,
31 "depends" : [
32 "product_variant_generator",
33 ],
34 "data" : [
35 "product_view.xml",
36 ],
37 "application": True,
38 "active": False,
39 "installable": True,
40}
041
=== added file 'product_variant_generator_price/product_variant.py'
--- product_variant_generator_price/product_variant.py 1970-01-01 00:00:00 +0000
+++ product_variant_generator_price/product_variant.py 2014-06-24 09:37:35 +0000
@@ -0,0 +1,119 @@
1# -*- encoding: utf-8 -*-
2##############################################################################
3#
4# OpenERP, Open Source Management Solution
5# Copyright (C) 2014 Akretion (www.akretion.com). All Rights Reserved
6# @author Chafique Delli <chafique.delli@akretion.com>
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU General Public License as published by
10# the Free Software Foundation, either version 3 of the License, or
11# (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU General Public License for more details.
17#
18# You should have received a copy of the GNU General Public License
19# along with this program. If not, see <http://www.gnu.org/licenses/>.
20#
21##############################################################################
22
23from openerp.osv import fields, orm
24
25
26class ProductTemplate(orm.Model):
27 _inherit = "product.template"
28
29 _columns = {
30 'automatic_price_extra': fields.boolean("Automatic Price Extra"),
31 }
32
33
34class ProductProduct(orm.Model):
35 _inherit = "product.product"
36
37 def _get_price_extra(self, cr, uid, ids, field_names=None, arg=False,
38 context=None):
39 res = {}
40 for product in self.browse(cr, uid, ids, context=context):
41 price_extra = 0.00
42 if product.automatic_price_extra:
43 for dimension in product.dimension_ids:
44 option = product[dimension.name]
45 if option:
46 for dimension_value in product.value_ids:
47 if dimension_value.option_id.id == option.id:
48 price_extra += dimension_value.price_extra
49 break
50 else:
51 price_extra = product.manual_price_extra
52 res[product.id] = price_extra
53 return res
54
55 def _set_extra_price(self, cr, uid, ids, field_names=None, value=None,
56 arg=False, context=None):
57 if isinstance(ids, (int, long)):
58 ids = [ids]
59 for product in self.browse(cr, uid, ids, context=context):
60 if not product.automatic_price_extra:
61 self.write(
62 cr, uid, product.id, {'manual_price_extra': value},
63 context=context)
64 else:
65 price = self._get_price_extra(
66 cr, uid, [product.id], field_names=field_names, arg=arg,
67 context=context)[product.id]
68 cr.execute("""update product_product set
69 price_extra=%s where id=%s""", (price, product.id))
70 return True
71
72 def _get_products_from_product_template(self, cr, uid, ids, context=None):
73 return self.get_products_from_product_template(
74 cr, uid, ids, context=context)
75
76 def _get_products_from_dimension_value(self, cr, uid, ids, context=None):
77 res = []
78 for value in self.browse(cr, uid, ids, context=context):
79 res += self.pool['product.product'].search(cr, uid,
80 [('product_tmpl_id', '=', value.product_tmpl_id.id),
81 (value.dimension_id.name, '=', value.option_id.id)],
82 context=context)
83 return res
84
85 _columns = {
86 'price_extra': fields.function(
87 _get_price_extra,
88 type='float',
89 fnct_inv=_set_extra_price,
90 string='Price Extra',
91 store ={
92 'product.product': (
93 lambda self, cr, uid, ids, c={}: ids,
94 ['product_tmpl_id', 'manual_price_extra'],
95 10),
96 'product.template': (
97 _get_products_from_product_template,
98 ['automatic_price_extra'],
99 10),
100 'dimension.value': (
101 _get_products_from_dimension_value,
102 ['price_extra'],
103 10)
104 },
105 ),
106 'manual_price_extra': fields.float('Manual Price Extra'),
107 }
108
109
110class DimensionValue(orm.Model):
111 _inherit = "dimension.value"
112
113 _columns = {
114 'price_extra': fields.float('Price Extra'),
115 }
116
117 _defaults = {
118 'price_extra': 0,
119 }
0120
=== added file 'product_variant_generator_price/product_view.xml'
--- product_variant_generator_price/product_view.xml 1970-01-01 00:00:00 +0000
+++ product_variant_generator_price/product_view.xml 2014-06-24 09:37:35 +0000
@@ -0,0 +1,79 @@
1<?xml version="1.0" encoding="utf-8"?>
2<openerp>
3 <data>
4
5<!--
6 "Product variant generator price" module for OpenERP
7 The licence is in the file __openerp__.py
8 @author Chafique Delli <chafique.delli@akretion.com>
9-->
10
11 <!-- PRODUCT TEMPLATE -->
12 <record id="product_variant_price_product_template_form_view" model="ir.ui.view">
13 <field name="name">product.variant.price.product.template.form</field>
14 <field name="model">product.template</field>
15 <field name="inherit_id" ref="product_variant_generator.product_variant_multi_product_template_form_view"/>
16 <field name="arch" type="xml">
17 <field name="option_id" position="after">
18 <field name="price_extra"/>
19 </field>
20 <xpath expr="//field[@name='variant_ids']/tree[@string='Variants']/field[@name='variants']" position="after">
21 <field name="price_extra"/>
22 </xpath>
23 <field name="attribute_set_id" position="after">
24 <field name="automatic_price_extra"
25 attrs="{'invisible':[('is_multi_variants', '=', False)]}"/>
26 </field>
27 </field>
28 </record>
29
30 <!-- PRODUCT PRODUCT -->
31 <record id="product_variant_price_product_normal_form_view" model="ir.ui.view">
32 <field name="name">product.variant.price.product.normal.form</field>
33 <field name="model">product.product</field>
34 <field name="inherit_id" ref="product.product_normal_form_view"/>
35 <field name="arch" type="xml">
36 <field name="list_price" position="replace">
37 <field name="lst_price" string="Sale Price"/>
38 </field>
39 <field name="price_margin" position="replace">
40 <field name="list_price" string="Base Price"/>
41 </field>
42 <div name="options" position="inside">
43 <field name="automatic_price_extra" invisible="True"/>
44 </div>
45 <field name="price_extra" position="replace">
46 <field name="price_extra"
47 attrs="{'readonly':[('automatic_price_extra', '=', True)]}"/>
48 </field>
49 </field>
50 </record>
51
52 <!-- PRODUCT VARIANT -->
53 <record id="product_variant_form_view" model="ir.ui.view">
54 <field name="model">product.product</field>
55 <field name="inherit_id" ref="product.product_variant_form_view"/>
56 <field name="arch" type="xml">
57 <field name="price_extra" position="after">
58 <field name="automatic_price_extra" invisible="True"/>
59 </field>
60 <field name="price_extra" position="replace">
61 <field name="price_extra"
62 attrs="{'readonly':[('automatic_price_extra', '=', True)]}"/>
63 </field>
64 </field>
65 </record>
66
67 <!-- DIMENSION VALUES -->
68 <record id="product_variant_multi_dimension_value_form" model="ir.ui.view">
69 <field name="model">dimension.value</field>
70 <field name="inherit_id" ref="product_variant_generator.product_variant_multi_dimension_value_form"/>
71 <field name="arch" type="xml">
72 <field name="option_id" position="after">
73 <field name="price_extra"/>
74 </field>
75 </field>
76 </record>
77
78 </data>
79</openerp>
080
=== removed file 'product_variant_multi/demo_data.xml'
--- product_variant_multi/demo_data.xml 2011-08-23 07:46:17 +0000
+++ product_variant_multi/demo_data.xml 1970-01-01 00:00:00 +0000
@@ -1,84 +0,0 @@
1<?xml version="1.0" ?>
2<openerp>
3
4 <data noupdate="1">
5<!--
6 <record id="product_template_mezzanine" model="product.template">
7 <field eval="1.0" name="list_price"/>
8 <field eval="1.0" name="standard_price"/>
9 <field eval="&quot;&quot;&quot;fixed&quot;&quot;&quot;" name="mes_type"/>
10 <field name="uom_id" ref="product.product_uom_unit"/>
11 <field eval="1.0" name="uos_coeff"/>
12 <field eval="1" name="sale_ok"/>
13 <field eval="1" name="purchase_ok"/>
14 <field name="company_id" ref="base.main_company"/>
15 <field name="uom_po_id" ref="product.product_uom_unit"/>
16 <field eval="&quot;&quot;&quot;product&quot;&quot;&quot;" name="type"/>
17 <field eval="&quot;&quot;&quot;Mezzanine&quot;&quot;&quot;" name="name"/>
18 <field name="cost_method">standard</field>
19 <field name="supply_method">produce</field>
20 <field name="procure_method">make_to_order</field>
21 <field eval="0" name="rental"/>
22 <field eval="7.0" name="sale_delay"/>
23 <field name="categ_id" ref="product.cat1"/>
24 <field eval="1.0" name="produce_delay"/>
25 </record>
26 <record id="product_variant_dimension_type_hauteur0" model="product.variant.dimension.type">
27 <field name="product_tmpl_id" ref="product_template_mezzanine"/>
28 <field eval="1" name="allow_custom_value"/>
29 <field eval="&quot;&quot;&quot;Hauteur&quot;&quot;&quot;" name="name"/>
30 <field eval="0" name="sequence"/>
31 </record>
32 <record id="product_variant_dimension_type_largeur0" model="product.variant.dimension.type">
33 <field name="product_tmpl_id" ref="product_template_mezzanine"/>
34 <field eval="1" name="allow_custom_value"/>
35 <field eval="&quot;&quot;&quot;Largeur&quot;&quot;&quot;" name="name"/>
36 <field eval="1" name="sequence"/>
37 </record>
38 <record id="product_variant_dimension_value_0" model="product.variant.dimension.value">
39 <field eval="&quot;&quot;&quot;10&quot;&quot;&quot;" name="name"/>
40 <field name="dimension_id" ref="product_variant_dimension_type_hauteur0"/>
41 <field model="product.template" name="product_tmpl_id" search="[('name','=','Mezzanine')]"/>
42 </record>
43 <record id="product_variant_dimension_value_1" model="product.variant.dimension.value">
44 <field eval="&quot;&quot;&quot;20&quot;&quot;&quot;" name="name"/>
45 <field name="dimension_id" ref="product_variant_dimension_type_hauteur0"/>
46 <field model="product.template" name="product_tmpl_id" search="[('name','=','Mezzanine')]"/>
47 </record>
48
49 <record id="product_variant_dimension_value_2" model="product.variant.dimension.value">
50 <field eval="&quot;&quot;&quot;22&quot;&quot;&quot;" name="name"/>
51 <field name="dimension_id" ref="product_variant_dimension_type_largeur0"/>
52 <field eval="1.0" name="dimension_sequence"/>
53 <field model="product.template" name="product_tmpl_id" search="[('name','=','Mezzanine')]"/>
54 </record>
55 <record id="product_variant_dimension_value_3" model="product.variant.dimension.value">
56 <field eval="&quot;&quot;&quot;33&quot;&quot;&quot;" name="name"/>
57 <field name="dimension_id" ref="product_variant_dimension_type_largeur0"/>
58 <field eval="1.0" name="dimension_sequence"/>
59 <field model="product.template" name="product_tmpl_id" search="[('name','=','Mezzanine')]"/>
60 </record>
61
62 <record id="product_product_mezzanine0" model="product.product">
63 <field eval="1" name="active"/>
64 <field name="product_tmpl_id" ref="product_template_mezzanine"/>
65 <field eval="[(6,0,[ref('product_variant_dimension_value_0'),ref('product_variant_dimension_value_2')])]" name="dimension_value_ids"/>
66 </record>
67 <record id="product_product_mezzanine1" model="product.product">
68 <field eval="1" name="active"/>
69 <field name="product_tmpl_id" ref="product_template_mezzanine"/>
70 <field eval="[(6,0,[ref('product_variant_dimension_value_0'),ref('product_variant_dimension_value_3')])]" name="dimension_value_ids"/>
71 </record>
72 <record id="product_product_mezzanine2" model="product.product">
73 <field eval="1" name="active"/>
74 <field name="product_tmpl_id" ref="product_template_mezzanine"/>
75 <field eval="[(6,0,[ref('product_variant_dimension_value_1'),ref('product_variant_dimension_value_2')])]" name="dimension_value_ids"/>
76 </record>
77 <record id="product_product_mezzanine3" model="product.product">
78 <field eval="1" name="active"/>
79 <field name="product_tmpl_id" ref="product_template_mezzanine"/>
80 <field eval="[(6,0,[ref('product_variant_dimension_value_1'),ref('product_variant_dimension_value_3')])]" name="dimension_value_ids"/>
81 </record>
82-->
83 </data>
84</openerp>
850
=== added directory 'product_variant_simple'
=== added file 'product_variant_simple/__init__.py'
--- product_variant_simple/__init__.py 1970-01-01 00:00:00 +0000
+++ product_variant_simple/__init__.py 2014-06-24 09:37:35 +0000
@@ -0,0 +1,3 @@
1# -*- coding: utf-8 -*-
2
3from . import product
04
=== added file 'product_variant_simple/__openerp__.py'
--- product_variant_simple/__openerp__.py 1970-01-01 00:00:00 +0000
+++ product_variant_simple/__openerp__.py 2014-06-24 09:37:35 +0000
@@ -0,0 +1,47 @@
1# -*- coding: utf-8 -*-
2##############################################################################
3#
4# Author: Yannick Vaucher
5# Copyright 2013 Camptocamp SA
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU Affero General Public License as
9# published by the Free Software Foundation, either version 3 of the
10# License, or (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU Affero General Public License for more details.
16#
17# You should have received a copy of the GNU Affero General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20##############################################################################
21{'name' : 'Simple Product Variants',
22 'version' : '1.0',
23 'category': '',
24 'description': """
25Simple Product Variants
26=======================
27
28A simple module for handling the product variants.
29It provides views for templates and for variants,
30so they can be created manually. It doesn't provide
31all the bells and whistles of the product_variant_multi
32module (generators wizards, ...).
33
34""",
35 'author' : 'Camptocamp',
36 'maintainer': 'Camptocamp',
37 'website': 'http://www.camptocamp.com/',
38 'depends' : ['product',
39 ],
40 'data': ['product_view.xml',
41 'security/security.xml',
42 ],
43 'test': [],
44 'installable': True,
45 'auto_install': False,
46 'application': True,
47 }
048
=== added file 'product_variant_simple/product.py'
--- product_variant_simple/product.py 1970-01-01 00:00:00 +0000
+++ product_variant_simple/product.py 2014-06-24 09:37:35 +0000
@@ -0,0 +1,32 @@
1# -*- coding: utf-8 -*-
2##############################################################################
3#
4# Author: Guewen Baconnier
5# Copyright 2013 Camptocamp SA
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU Affero General Public License as
9# published by the Free Software Foundation, either version 3 of the
10# License, or (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU Affero General Public License for more details.
16#
17# You should have received a copy of the GNU Affero General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20##############################################################################
21
22from openerp.osv import orm, fields
23
24
25class ProductTemplate(orm.Model):
26 _inherit = 'product.template'
27 _columns = {
28 'variant_ids': fields.one2many(
29 'product.product',
30 'product_tmpl_id',
31 string='Variants'),
32 }
033
=== added file 'product_variant_simple/product_view.xml'
--- product_variant_simple/product_view.xml 1970-01-01 00:00:00 +0000
+++ product_variant_simple/product_view.xml 2014-06-24 09:37:35 +0000
@@ -0,0 +1,92 @@
1<?xml version="1.0" encoding="utf-8"?>
2<openerp>
3 <data>
4
5 <!-- Templates -->
6
7 <record id="product_template_search_view" model="ir.ui.view">
8 <field name="name">product.template.search</field>
9 <field name="model">product.template</field>
10 <field name="arch" type="xml">
11 <search string="Product Templates">
12 <field name="name" string="Product"/>
13 <filter string="Services" icon="terp-accessories-archiver" domain="[('type','=','service')]"/>
14 <filter string="Consumable" name="consumable" icon="terp-accessories-archiver" domain="[('type','=','consu')]" help="Consumable products"/>
15 <field name="categ_id"/>
16 <group expand='0' string='Group by...'>
17 <filter string='Category'
18 icon="terp-stock_symbol-selection"
19 domain="[]"
20 context="{'group_by': 'categ_id'}"/>
21 <filter string='Type'
22 icon="terp-stock_symbol-selection"
23 domain="[]"
24 context="{'group_by': 'type'}"/>
25 </group>
26 </search>
27 </field>
28 </record>
29
30 <record id="product_template" model="ir.actions.act_window">
31 <field name="name">Product Templates</field>
32 <field name="res_model">product.template</field>
33 <field name="view_type">form</field>
34 <field name="domain">[]</field>
35 <field name="view_mode">tree,form</field>
36 <field name="search_view_id" ref="product_template_search_view"/>
37 </record>
38
39 <menuitem action="product_template" id="menu_template"
40 sequence="15"
41 parent="base.menu_product" />
42
43 <!-- Variants -->
44
45 <record id="product_variant_search_view" model="ir.ui.view">
46 <field name="name">product.variant.search</field>
47 <field name="model">product.product</field>
48 <field name="arch" type="xml">
49 <search string="Product Variants">
50 <field name="product_tmpl_id" string="Product"/>
51 <field name="name" string="Variant"
52 filter_domain="['|', '|',
53 ('variants', 'ilike', self),
54 ('default_code', 'ilike', self)]"/>
55 <group expand='0' string='Group by...'>
56 <filter string='Template'
57 icon="terp-stock_symbol-selection"
58 domain="[]"
59 context="{'group_by': 'product_tmpl_id'}"/>
60 </group>
61 </search>
62 </field>
63 </record>
64
65 <record id="product_variant" model="ir.actions.act_window">
66 <field name="name">Product Variants</field>
67 <field name="res_model">product.product</field>
68 <field name="view_type">form</field>
69 <field name="view_mode">tree,form</field>
70 <field name="search_view_id" ref="product_variant_search_view"/>
71 </record>
72
73 <record id="action_variant_tree" model="ir.actions.act_window.view">
74 <field name="sequence" eval="10" />
75 <field name="view_mode">tree</field>
76 <field name="act_window_id" ref="product_variant" />
77 <field name="view_id" ref="product.product_variant_tree_view" />
78 </record>
79
80 <record id="action_variant_form" model="ir.actions.act_window.view">
81 <field name="sequence" eval="20" />
82 <field name="view_mode">form</field>
83 <field name="act_window_id" ref="product_variant" />
84 <field name="view_id" ref="product.product_variant_form_view" />
85 </record>
86
87 <menuitem action="product_variant" id="menu_variant"
88 sequence="20"
89 parent="base.menu_product" />
90
91 </data>
92</openerp>
093
=== added directory 'product_variant_simple/security'
=== added file 'product_variant_simple/security/security.xml'
--- product_variant_simple/security/security.xml 1970-01-01 00:00:00 +0000
+++ product_variant_simple/security/security.xml 2014-06-24 09:37:35 +0000
@@ -0,0 +1,13 @@
1<?xml version="1.0" encoding="utf-8"?>
2<openerp>
3 <data noupdate="1">
4
5 <!-- Activate the Product Variant group on the Sales Manager -->
6 <record model="res.groups" id="base.group_sale_manager">
7 <field name="implied_ids" eval="[(4, ref('product.group_product_variant'))]"/>
8 </record>
9
10 </data>
11</openerp>
12
13
014
=== added directory 'sale_product_display'
=== added file 'sale_product_display/__init__.py'
--- sale_product_display/__init__.py 1970-01-01 00:00:00 +0000
+++ sale_product_display/__init__.py 2014-06-24 09:37:35 +0000
@@ -0,0 +1,23 @@
1# -*- coding: utf-8 -*-
2###############################################################################
3#
4# Module for OpenERP
5# Copyright (C) 2013 Akretion (http://www.akretion.com).
6# @author Chafique Delli <chafique.delli@akretion.com>
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU Affero General Public License as
10# published by the Free Software Foundation, either version 3 of the
11# License, or (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU Affero General Public License for more details.
17#
18# You should have received a copy of the GNU Affero General Public License
19# along with this program. If not, see <http://www.gnu.org/licenses/>.
20#
21###############################################################################
22
23import sale
024
=== added file 'sale_product_display/__openerp__.py'
--- sale_product_display/__openerp__.py 1970-01-01 00:00:00 +0000
+++ sale_product_display/__openerp__.py 2014-06-24 09:37:35 +0000
@@ -0,0 +1,40 @@
1# -*- coding: utf-8 -*-
2##############################################################################
3#
4# Author: Chafique Delli
5# Copyright 2013 Akretion
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU Affero General Public License as
9# published by the Free Software Foundation, either version 3 of the
10# License, or (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU Affero General Public License for more details.
16#
17# You should have received a copy of the GNU Affero General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20##############################################################################
21{'name' : 'Sale Product Display',
22 'version' : '1.0',
23 "author": "Akretion",
24 'category': 'Sales Management',
25 'description': """
26Sale Product Display
27====================
28
29A simple module for preventing validation of a command that contains a product display.
30
31""",
32 'author' : 'Akretion',
33 'maintainer': 'Akretion',
34 'website': 'http://www.akretion.com/',
35 'depends' : [
36 'sale',
37 'product_display'
38 ],
39 'auto_install': True,
40 }
041
=== added file 'sale_product_display/sale.py'
--- sale_product_display/sale.py 1970-01-01 00:00:00 +0000
+++ sale_product_display/sale.py 2014-06-24 09:37:35 +0000
@@ -0,0 +1,39 @@
1# -*- coding: utf-8 -*-
2###############################################################################
3#
4# Module for OpenERP
5# Copyright (C) 2013 Akretion (http://www.akretion.com).
6# @author Chafique Delli <chafique.delli@akretion.com>
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU Affero General Public License as
10# published by the Free Software Foundation, either version 3 of the
11# License, or (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU Affero General Public License for more details.
17#
18# You should have received a copy of the GNU Affero General Public License
19# along with this program. If not, see <http://www.gnu.org/licenses/>.
20#
21###############################################################################
22
23from openerp.osv import orm, osv
24from openerp.tools.translate import _
25
26
27class SaleOrderLine(orm.Model):
28 _inherit = 'sale.order.line'
29
30
31 def _check_product_display(self, cr, uid, ids, context=None):
32 for record in self.browse(cr, uid, ids, context=context):
33 if record.product_id.is_display:
34 raise osv.except_osv(_('Error'), _('You cannot validate the order with product display %s.')% (record.product_id.name))
35 return True
36
37 _constraints = [
38 (_check_product_display, 'You cannot validate an order with a product display.',
39 [])]

Subscribers

People subscribed via source and target branches