diff --git a/base_product_merge/__init__.py b/base_product_merge/__init__.py new file mode 100644 index 000000000..35e5825cc --- /dev/null +++ b/base_product_merge/__init__.py @@ -0,0 +1 @@ +import wizard diff --git a/base_product_merge/__openerp__.py b/base_product_merge/__openerp__.py new file mode 100644 index 000000000..8e4b97098 --- /dev/null +++ b/base_product_merge/__openerp__.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Copyright (c) 2011 Camptocamp SA (http://www.camptocamp.com) +# All Right Reserved +# +# Author : Guewen Baconnier (Camptocamp) +# +# WARNING: This program as such is intended to be used by professional +# programmers who take the whole responsability of assessing all potential +# consequences resulting from its eventual inadequacies and bugs +# End users who are looking for a ready-to-use solution with commercial +# garantees and support are strongly adviced to contract a Free Software +# Service Company +# +# This program is Free Software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +############################################################################## + +{ + "name" : "Base Products Merge", + "version" : "1.0", + "author" : "Camptocamp", + "category" : "Generic Modules/Base", + "description":""" +To merge 2 products, select them in the list view and execute the Action "Merge Products". + +The selected products are deactivated and a new one is created with : +- When a value is the same on each resources : the value +- When a value is different between the resources : you can choose the value to keep in a selection list +- When a value is set on a resource and is empty on the second one : the value is set on the resource +- All many2many relations of the 2 resources are created on the new resource. +- All the one2many relations (invoices, sale_orders, ...) are updated in order to link to the new resource. + +""", + "website": "http://camptocamp.com", + "depends" : ['product'], + "init_xml" : [], + "demo_xml" : [], + "update_xml" : [ + "wizard/base_product_merge_view.xml", + ], + "active": False, + "installable": True +} diff --git a/base_product_merge/wizard/__init__.py b/base_product_merge/wizard/__init__.py new file mode 100644 index 000000000..31d078526 --- /dev/null +++ b/base_product_merge/wizard/__init__.py @@ -0,0 +1 @@ +import base_product_merge diff --git a/base_product_merge/wizard/base_product_merge.py b/base_product_merge/wizard/base_product_merge.py new file mode 100644 index 000000000..d89cef478 --- /dev/null +++ b/base_product_merge/wizard/base_product_merge.py @@ -0,0 +1,242 @@ +# -*- coding: utf-8 -*- +# -*- coding: utf-8 -*- +############################################################################## +# +# Copyright (c) 2011 Camptocamp SA +# @author Guewen Baconnier +# Original code from module base_partner_merge by Tiny and Camptocamp +# +# WARNING: This program as such is intended to be used by professional +# programmers who take the whole responsability of assessing all potential +# consequences resulting from its eventual inadequacies and bugs +# End users who are looking for a ready-to-use solution with commercial +# garantees and support are strongly adviced to contract a Free Software +# Service Company +# +# This program is Free Software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +############################################################################## + +# TODO : create a base_merge module to provide abstractions for merges ? + +from osv import fields, osv +from tools.translate import _ +import tools + + +class base_product_merge(osv.osv_memory): + """ + Merges two products + """ + _name = 'base.product.merge' + _description = 'Merges two products' + + _columns = { + } + + _values = {} + + MERGE_SKIP_FIELDS = ['product_tmpl_id'] + + def _build_form(self, cr, uid, field_datas, value1, value2): + formxml = ''' +
""" + return formxml, update_fields, update_values, columns + + def check_resources_to_merge(self, cr, uid, resource_ids, context): + """ Check validity of selected resources. + Hook for other checks + """ + if not len(resource_ids) == 2: + raise osv.except_osv(_('Error!'), _('You must select only two resources')) + return True + + def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False): + res = super(base_product_merge, self).fields_view_get(cr, uid, view_id, view_type, context=context, toolbar=toolbar, submenu=submenu) + resource_ids = context.get('active_ids') or [] + + self.check_resources_to_merge(cr, uid, resource_ids, context) + + if not len(resource_ids) == 2: + return res + obj = self.pool.get('product.product') + cr.execute("SELECT id, name, field_description, ttype, required, relation, readonly from ir_model_fields where model in ('product.product', 'product.template')") + field_datas = cr.fetchall() + obj1 = obj.browse(cr, uid, resource_ids[0], context=context) + obj2 = obj.browse(cr, uid, resource_ids[1], context=context) + myxml, merge_fields, self._values, columns = self._build_form(cr, uid, field_datas, obj1, obj2) + self._columns.update(columns) + res['arch'] = myxml + res['fields'] = merge_fields + return res + + def cast_many2one_fields(self, cr, uid, data_record, context=None): + """ Some fields are many2one and the ORM expect them to be integer or in the form + 'relation,1' wher id is the id. + As some fields are displayed as selection in the view, we cast them in integer. + """ + cr.execute("SELECT name from ir_model_fields where model in ('product.product', 'product.template') and ttype='many2one'") + fields = cr.fetchall() + for field in fields: + if data_record.get(field[0], False): + data_record[field[0]] = int(data_record[field[0]]) + return data_record + + def action_merge(self, cr, uid, ids, context=None): + """ + Merges two resources and create 3rd and changes references of old resources with new + @param self: The object pointer + @param cr: the current row, from the database cursor, + @param uid: the current user s ID for security checks, + @param ids: id of the wizard + @param context: A standard dictionary for contextual values + + @return : dict to open the new product in a view + """ + record_id = context and context.get('active_id', False) or False + pool = self.pool + if not record_id: + return {} + res = self.read(cr, uid, ids, context = context)[0] + + res.update(self._values) + resource_ids = context.get('active_ids') or [] + + self.check_resources_to_merge(cr, uid, resource_ids, context) + + resource1 = resource_ids[0] + resource2 = resource_ids[1] + + obj, obj_parent = pool.get('product.product'), pool.get('product.template') + + remove_field = {} + # for uniqueness constraint: empty the field in the old resources + c_names = [] + for check_obj in (obj, obj_parent): + if hasattr(check_obj, '_sql_constraints'): + remove_field = {} + for const in check_obj._sql_constraints: + c_names.append(check_obj._name.replace('.', '_') + '_' + const[0]) + if c_names: + c_names = tuple(map(lambda x: "'"+ x +"'", c_names)) + cr.execute("""select column_name from \ + information_schema.constraint_column_usage u \ + join pg_constraint p on (p.conname=u.constraint_name) \ + where u.constraint_name in (%s) and p.contype='u' """ % c_names) + for i in cr.fetchall(): + remove_field[i[0]] = False + + remove_field.update({'active': False}) + + obj.write(cr, uid, [resource1, resource2], remove_field, context=context) + + res = self.cast_many2one_fields(cr, uid, res, context) + + res_id = obj.create(cr, uid, res, context=context) + + self.custom_updates(cr, uid, res_id, [resource1, resource2], context) + + # For one2many fields on the resource + cr.execute("select name, model from ir_model_fields where relation in ('product.product', 'product.template') and ttype not in ('many2many', 'one2many');") + for name, model_raw in cr.fetchall(): + if hasattr(pool.get(model_raw), '_auto'): + if not pool.get(model_raw)._auto: + continue + elif hasattr(pool.get(model_raw), '_check_time'): + continue + else: + if hasattr(pool.get(model_raw), '_columns'): + from osv import fields + if pool.get(model_raw)._columns.get(name, False) and isinstance(pool.get(model_raw)._columns[name], fields.many2one): + model = model_raw.replace('.', '_') + if name not in self.MERGE_SKIP_FIELDS: + cr.execute("update "+model+" set "+name+"="+str(res_id)+" where "+ tools.ustr(name) +" in ("+ tools.ustr(resource1) +", "+tools.ustr(resource2)+")") + + value = { + 'domain': str([('id', '=', res_id)]), + 'view_type': 'form', + 'view_mode': 'tree,form', + 'res_model': 'product.product', + 'view_id': False, + 'type': 'ir.actions.act_window', + 'res_id': res_id + } + return value + + def custom_updates(self, cr, uid, resource_id, old_resources_ids, context): + """Hook for special updates on old resources and new resource + """ + pass + +base_product_merge() + diff --git a/base_product_merge/wizard/base_product_merge_view.xml b/base_product_merge/wizard/base_product_merge_view.xml new file mode 100644 index 000000000..fafb33554 --- /dev/null +++ b/base_product_merge/wizard/base_product_merge_view.xml @@ -0,0 +1,39 @@ + +