odoo 合并同模型两条记录功能

合并用户功能,将其他单据的关联也替换掉

借鉴了odoo 14 的联系人合并功能开发,延申拓展,万能用法
所见即所得 !!!!!看到就拆下!!!!!
# -*- coding: utf-8 -*-
# Odoo的一部分。我又拆了下来作为通用功能,所见即所得!!!!!!!!!!!!!
# 作者:颜sk

from ast import literal_eval
import functools
import itertools
import logging
import psycopg2
import datetime

from odoo import api, fields, models
from odoo import SUPERUSER_ID, _
from odoo.exceptions import ValidationError, UserError
from odoo.tools import mute_logger

_logger = logging.getLogger('odoo.addons.base.users.merge')


class MergeUsersLine(models.TransientModel):
    _name = 'base.users.merge.line'
    _description = 'Merge users Line'
    _order = 'min_id asc'

    wizard_id = fields.Many2one('base.users.merge.automatic.wizard', 'Wizard')
    min_id = fields.Integer('MinID')
    aggr_ids = fields.Char('Ids', required=True)


class MergeUsersAutomatic(models.TransientModel):
    """
        这个向导背后的思想是创建一个要合并的潜在用户列表。
        我们使用两个对象,第一个是最终用户的向导。第二个将包含要合并的用户列表。
    """

    _name = 'base.users.merge.automatic.wizard'
    _description = '合并用户向导'

    @api.model
    def default_get(self, fields):
        res = super(MergeUsersAutomatic, self).default_get(fields)
        active_ids = self.env.context.get('active_ids')
        if self.env.context.get('active_model') == 'res.users' and active_ids:
            if 'state' in fields:
                res['state'] = 'selection'
            if 'user_ids' in fields:
                res['user_ids'] = [(6, 0, active_ids)]
            if 'dst_user_id' in fields:
                res['dst_user_id'] = self._get_ordered_users(active_ids)[-1].id
        return res

    # Group by
    group_by_email = fields.Boolean('Email')
    group_by_name = fields.Boolean('名称')
    group_by_is_company = fields.Boolean('Is Company')
    group_by_vat = fields.Boolean('VAT')
    group_by_parent_id = fields.Boolean('Parent Company')

    state = fields.Selection([
        ('option', '新建'),
        ('selection', '进行中'),
        ('finished', '已完成')
    ], readonly=True, required=True, string='状态', default='option')

    number_group = fields.Integer('用户', readonly=True)
    current_line_id = fields.Many2one('base.users.merge.line', string='当前行')
    line_ids = fields.One2many('base.users.merge.line', 'wizard_id', string='Lines')
    user_ids = fields.Many2many('res.users', string='用户')
    dst_user_id = fields.Many2one('res.users', string='目的用户')

    exclude_contact = fields.Boolean('与该联系人关联的用户')
    exclude_journal_item = fields.Boolean('与联系人关联的日志项目')
    maximum_group = fields.Integer('联系人群组的最大值')

    # ----------------------------------------
    # Update method. Core methods to merge steps
    # ----------------------------------------

    def _get_fk_on(self, table):
        """
        返回一个包含many2one与给定表的关系的列表。
        :param
        table:要返回的sql表的名称。返回一个元组'table name''column name'的列表。
        """
        query = """
            SELECT cl1.relname as table, att1.attname as column
            FROM pg_constraint as con, pg_class as cl1, pg_class as cl2, pg_attribute as att1, pg_attribute as att2
            WHERE con.conrelid = cl1.oid
                AND con.confrelid = cl2.oid
                AND array_lower(con.conkey, 1) = 1
                AND con.conkey[1] = att1.attnum
                AND att1.attrelid = cl1.oid
                AND cl2.relname = %s
                AND att2.attname = 'id'
                AND array_lower(con.confkey, 1) = 1
                AND con.confkey[1] = att2.attnum
                AND att2.attrelid = cl2.oid
                AND con.contype = 'f'
        """
        self._cr.execute(query, (table,))
        return self._cr.fetchall()

    @api.model
    def _update_foreign_keys(self, src_userss, dst_users):
        """ Update all foreign key from the src_users to dst_users. All many2one fields will be updated.
            :param src_userss : merge source res.users recordset (does not include destination one)
            :param dst_users : record of destination res.users
        """
        _logger.debug('_update_foreign_keys for dst_users: %s for src_userss: %s', dst_users.id, str(src_userss.ids))

        # find the many2one relation to a users
        users = self.env['res.users']
        relations = self._get_fk_on('res_users')

        self.flush()

        for table, column in relations:
            if 'base_users_merge_' in table:  # ignore two tables
                continue

            # get list of columns of current table (exept the current fk column)
            query = "SELECT column_name FROM information_schema.columns WHERE table_name LIKE '%s'" % (table)
            self._cr.execute(query, ())
            columns = []
            for data in self._cr.fetchall():
                if data[0] != column:
                    columns.append(data[0])

            # do the update for the current table/column in SQL
            query_dic = {
                'table': table,
                'column': column,
                'value': columns[0],
            }
            if len(columns) <= 1:
                # unique key treated
                query = """
                    UPDATE "%(table)s" as ___tu
                    SET "%(column)s" = %%s
                    WHERE
                        "%(column)s" = %%s AND
                        NOT EXISTS (
                            SELECT 1
                            FROM "%(table)s" as ___tw
                            WHERE
                                "%(column)s" = %%s AND
                                ___tu.%(value)s = ___tw.%(value)s
                        )""" % query_dic
                for users in src_userss:
                    self._cr.execute(query, (dst_users.id, users.id, dst_users.id))
            else:
                try:
                    with mute_logger('odoo.sql_db'), self._cr.savepoint():
                        query = 'UPDATE "%(table)s" SET "%(column)s" = %%s WHERE "%(column)s" IN %%s' % query_dic
                        self._cr.execute(query, (dst_users.id, tuple(src_userss.ids),))

                        # handle the recursivity with parent relation
                        if column == users._parent_name and table == 'res_users':
                            query = """
                                WITH RECURSIVE cycle(id, parent_id) AS (
                                        SELECT id, parent_id FROM res_users
                                    UNION
                                        SELECT  cycle.id, res_users.parent_id
                                        FROM    res_users, cycle
                                        WHERE   res_users.id = cycle.parent_id AND
                                                cycle.id != cycle.parent_id
                                )
                                SELECT id FROM cycle WHERE id = parent_id AND id = %s
                            """
                            self._cr.execute(query, (dst_users.id,))
                            # NOTE JEM : shouldn't we fetch the data ?
                except psycopg2.Error:
                    # updating fails, most likely due to a violated unique constraint
                    # keeping record with nonexistent users_id is useless, better delete it
                    query = 'DELETE FROM "%(table)s" WHERE "%(column)s" IN %%s' % query_dic
                    self._cr.execute(query, (tuple(src_userss.ids),))

        self.invalidate_cache()

    @api.model
    def _update_reference_fields(self, src_userss, dst_users):
        """ Update all reference fields from the src_users to dst_users.
            :param src_userss : merge source res.users recordset (does not include destination one)
            :param dst_users : record of destination res.users
        """
        _logger.debug('_update_reference_fields for dst_users: %s for src_userss: %r', dst_users.id, src_userss.ids)

        def update_records(model, src, field_model='model', field_id='res_id'):
            Model = self.env[model] if model in self.env else None
            if Model is None:
                return
            records = Model.sudo().search([(field_model, '=', 'res.users'), (field_id, '=', src.id)])
            try:
                with mute_logger('odoo.sql_db'), self._cr.savepoint(), self.env.clear_upon_failure():
                    records.sudo().write({field_id: dst_users.id})
                    records.flush()
            except psycopg2.Error:
                # updating fails, most likely due to a violated unique constraint
                # keeping record with nonexistent users_id is useless, better delete it
                records.sudo().unlink()

        update_records = functools.partial(update_records)

        for users in src_userss:
            update_records('calendar', src=users, field_model='model_id.model')
            update_records('ir.attachment', src=users, field_model='res_model')
            update_records('mail.followers', src=users, field_model='res_model')
            update_records('mail.message', src=users)
            update_records('ir.model.data', src=users)

        records = self.env['ir.model.fields'].search([('ttype', '=', 'reference')])
        for record in records.sudo():
            try:
                Model = self.env[record.model]
                field = Model._fields[record.name]
            except KeyError:
                # unknown model or field => skip
                continue

            if field.compute is not None:
                continue

            for users in src_userss:
                if not Model._auto:
                    break
                records_ref = Model.sudo().search([(record.name, '=', 'res.users,%d' % users.id)])
                values = {
                    record.name: 'res.users,%d' % dst_users.id,
                }
                records_ref.sudo().write(values)

        self.flush()

    def _get_summable_fields(self):
        """ Returns the list of fields that should be summed when merging userss
        """
        return []

    @api.model
    def _update_values(self, src_userss, dst_users):
        """ Update values of dst_users with the ones from the src_userss.
            :param src_userss : recordset of source res.users
            :param dst_users : record of destination res.users
        """
        _logger.debug('_update_values for dst_users: %s for src_userss: %r', dst_users.id, src_userss.ids)

        model_fields = dst_users.fields_get().keys()
        summable_fields = self._get_summable_fields()

        def write_serializer(item):
            if isinstance(item, models.BaseModel):
                return item.id
            else:
                return item

        # get all fields that are not computed or x2many
        values = dict()
        for column in model_fields:
            if 'sel_group' in column or 'in_group' in column:
                break
            field = dst_users._fields[column]
            if field.type not in ('many2many', 'one2many') and field.compute is None:
                for item in itertools.chain(src_userss, [dst_users]):
                    if item[column]:
                        if column in summable_fields and values.get(column):
                            values[column] += write_serializer(item[column])
                        else:
                            values[column] = write_serializer(item[column])
        # remove fields that can not be updated (id and parent_id)
        values.pop('id', None)
        parent_id = values.pop('parent_id', None)
        dst_users.write(values)
        # try to update the parent_id
        if parent_id and parent_id != dst_users.id:
            try:
                dst_users.write({'parent_id': parent_id})
            except ValidationError:
                _logger.info('跳过用户parent_id %s的递归用户层次结构: %s', parent_id, dst_users.id)

    def _merge(self, user_ids, dst_users=None, extra_checks=True):
        """ private实现合并用户
        :param user_ids:要合并的用户id
        :param dst_users:目的res的记录。
        :param extra_checks:通过False绕过额外的完整性检查(如电子邮件地址)
        """
        # super-admin can be used to bypass extra checks
        if self.env.is_admin():
            extra_checks = False

        users = self.env['res.users']
        user_ids = users.browse(user_ids).exists()
        if len(user_ids) < 2:
            return

        if len(user_ids) > 3:
            raise UserError(_("出于安全原因,不能合并超过3个用户。如果需要,您可以多次重新打开向导。"))

        # check if the list of userss to merge contains child/parent relation
        child_ids = self.env['res.users']
        for users_id in user_ids:
            child_ids |= users.search([('id', 'child_of', [users_id.id])]) - users_id
        if user_ids & child_ids:
            raise UserError(_("You cannot merge a contact with one of his parent."))

        if extra_checks and len(set(users.email for users in user_ids)) > 1:
            raise UserError(_("所有联系人必须具有相同的电子邮件。只有管理员可以将联系人与不同的邮件合并。"))

        # remove dst_users from userss to merge
        if dst_users and dst_users in user_ids:
            src_userss = user_ids - dst_users
        else:
            ordered_userss = self._get_ordered_users(user_ids.ids)
            dst_users = ordered_userss[-1]
            src_userss = ordered_userss[:-1]
        _logger.info("dst_users: %s", dst_users.id)

        # FIXME: is it still required to make and exception for account.move.line since accounting v9.0 ?
        if extra_checks and 'account.move.line' in self.env and self.env['account.move.line'].sudo().search(
                [('users_id', 'in', [users.id for users in src_userss])]):
            raise UserError(_("只有目标联系人可以链接到现有的日记账项目。请询问管理员是否需要合并链接到现有日志项目的几个用户。"))

        # Make the company of all related users consistent with destination users company
        if dst_users.company_id:
            user_ids.mapped('user_ids').sudo().write({
                'company_ids': [(4, dst_users.company_id.id)],
                'company_id': dst_users.company_id.id
            })

        # call sub methods to do the merge
        self._update_foreign_keys(src_userss, dst_users)
        self._update_reference_fields(src_userss, dst_users)
        self._update_values(src_userss, dst_users)

        self._log_merge_operation(src_userss, dst_users)

        # delete source users, since they are merged
        src_userss.unlink()

    def _log_merge_operation(self, src_userss, dst_users):
        _logger.info('(uid = %s) 合并用户%r和%s', self._uid, src_userss.ids, dst_users.id)

    # ----------------------------------------
    # Helpers
    # ----------------------------------------

    @api.model
    def _generate_query(self, fields, maximum_group=100):
        """ Build the SQL query on res.users table to group them according to given criteria
            :param fields : list of column names to group by the userss
            :param maximum_group : limit of the query
        """
        # make the list of column to group by in sql query
        sql_fields = []
        for field in fields:
            if field in ['email', 'name']:
                sql_fields.append('lower(%s)' % field)
            elif field in ['vat']:
                sql_fields.append("replace(%s, ' ', '')" % field)
            else:
                sql_fields.append(field)
        group_fields = ', '.join(sql_fields)

        # where clause : for given group by columns, only keep the 'not null' record
        filters = []
        for field in fields:
            if field in ['email', 'name', 'vat']:
                filters.append((field, 'IS NOT', 'NULL'))
        criteria = ' AND '.join('%s %s %s' % (field, operator, value) for field, operator, value in filters)

        # build the query
        text = [
            "SELECT min(id), array_agg(id)",
            "FROM res_users",
        ]

        if criteria:
            text.append('WHERE %s' % criteria)

        text.extend([
            "GROUP BY %s" % group_fields,
            "HAVING COUNT(*) >= 2",
            "ORDER BY min(id)",
        ])

        if maximum_group:
            text.append("LIMIT %s" % maximum_group, )

        return ' '.join(text)

    @api.model
    def _compute_selected_groupby(self):
        """ Returns the list of field names the users can be grouped (as merge
            criteria) according to the option checked on the wizard
        """
        groups = []
        group_by_prefix = 'group_by_'

        for field_name in self._fields:
            if field_name.startswith(group_by_prefix):
                if getattr(self, field_name, False):
                    groups.append(field_name[len(group_by_prefix):])

        if not groups:
            raise UserError(_("您必须为您的选择指定一个过滤器。"))

        return groups

    @api.model
    def _users_use_in(self, aggr_ids, models):
        """ Check if there is no occurence of this group of users in the selected model
            :param aggr_ids : stringified list of users ids separated with a comma (sql array_agg)
            :param models : dict mapping a model name with its foreign key with res_users table
        """
        return any(
            self.env[model].search_count([(field, 'in', aggr_ids)])
            for model, field in models.items()
        )

    @api.model
    def _get_ordered_users(self, user_ids):
        """ Helper : returns a `res.users` recordset ordered by create_date/active fields
            :param user_ids : list of users ids to sort
        """
        return self.env['res.users'].browse(user_ids).sorted(
            key=lambda p: (p.active, (p.create_date or datetime.datetime(1970, 1, 1))),
            reverse=True,
        )

    def _compute_models(self):
        """ Compute the different models needed by the system if you want to exclude some userss. """
        model_mapping = {}
        if self.exclude_contact:
            model_mapping['res.users'] = 'users_id'
        if 'account.move.line' in self.env and self.exclude_journal_item:
            model_mapping['account.move.line'] = 'users_id'
        return model_mapping

    # ----------------------------------------
    # Actions
    # ----------------------------------------

    def action_skip(self):
        """ Skip this wizard line. Don't compute any thing, and simply redirect to the new step."""
        if self.current_line_id:
            self.current_line_id.unlink()
        return self._action_next_screen()

    def _action_next_screen(self):
        """ return the action of the next screen ; this means the wizard is set to treat the
            next wizard line. Each line is a subset of users that can be merged together.
            If no line left, the end screen will be displayed (but an action is still returned).
        """
        self.invalidate_cache()  # FIXME: is this still necessary?
        values = {}
        if self.line_ids:
            # in this case, we try to find the next record.
            current_line = self.line_ids[0]
            current_user_ids = literal_eval(current_line.aggr_ids)
            values.update({
                'current_line_id': current_line.id,
                'user_ids': [(6, 0, current_user_ids)],
                'dst_user_id': self._get_ordered_users(current_user_ids)[-1].id,
                'state': 'selection',
            })
        else:
            values.update({
                'current_line_id': False,
                'user_ids': [],
                'state': 'finished',
            })

        self.write(values)

        return {
            'type': 'ir.actions.act_window',
            'res_model': self._name,
            'res_id': self.id,
            'view_mode': 'form',
            'target': 'new',
        }

    def _process_query(self, query):
        """ Execute the select request and write the result in this wizard
            :param query : the SQL query used to fill the wizard line
        """
        self.ensure_one()
        model_mapping = self._compute_models()

        # group users query
        self._cr.execute(query)

        counter = 0
        for min_id, aggr_ids in self._cr.fetchall():
            # To ensure that the used userss are accessible by the user
            userss = self.env['res.users'].search([('id', 'in', aggr_ids)])
            if len(userss) < 2:
                continue

            # exclude users according to options
            if model_mapping and self._users_use_in(userss.ids, model_mapping):
                continue

            self.env['base.users.merge.line'].create({
                'wizard_id': self.id,
                'min_id': min_id,
                'aggr_ids': userss.ids,
            })
            counter += 1

        self.write({
            'state': 'selection',
            'number_group': counter,
        })

        _logger.info("counter: %s", counter)

    def action_start_manual_process(self):
        """ Start the process 'Merge with Manual Check'. Fill the wizard according to the group_by and exclude
            options, and redirect to the first step (treatment of first wizard line). After, for each subset of
            users to merge, the wizard will be actualized.
                - Compute the selected groups (with duplication)
                - If the user has selected the 'exclude_xxx' fields, avoid the userss
        """
        self.ensure_one()
        groups = self._compute_selected_groupby()
        query = self._generate_query(groups, self.maximum_group)
        self._process_query(query)
        return self._action_next_screen()

    def action_start_automatic_process(self):
        """ Start the process 'Merge Automatically'. This will fill the wizard with the same mechanism as 'Merge
            with Manual Check', but instead of refreshing wizard with the current line, it will automatically process
            all lines by merging users grouped according to the checked options.
        """
        self.ensure_one()
        self.action_start_manual_process()  # here we don't redirect to the next screen, since it is automatic process
        self.invalidate_cache()  # FIXME: is this still necessary?

        for line in self.line_ids:
            user_ids = literal_eval(line.aggr_ids)
            self._merge(user_ids)
            line.unlink()
            self._cr.commit()  # TODO JEM : explain why

        self.write({'state': 'finished'})
        return {
            'type': 'ir.actions.act_window',
            'res_model': self._name,
            'res_id': self.id,
            'view_mode': 'form',
            'target': 'new',
        }

    def parent_migration_process_cb(self):
        self.ensure_one()

        query = """
            SELECT
                min(p1.id),
                array_agg(DISTINCT p1.id)
            FROM
                res_users as p1
            INNER join
                res_users as p2
            ON
                p1.email = p2.email AND
                p1.name = p2.name AND
                (p1.parent_id = p2.id OR p1.id = p2.parent_id)
            WHERE
                p2.id IS NOT NULL
            GROUP BY
                p1.email,
                p1.name,
                CASE WHEN p1.parent_id = p2.id THEN p2.id
                    ELSE p1.id
                END
            HAVING COUNT(*) >= 2
            ORDER BY
                min(p1.id)
        """

        self._process_query(query)

        for line in self.line_ids:
            user_ids = literal_eval(line.aggr_ids)
            self._merge(user_ids)
            line.unlink()
            self._cr.commit()

        self.write({'state': 'finished'})

        self._cr.execute("""
            UPDATE
                res_users
            SET
                is_company = NULL,
                parent_id = NULL
            WHERE
                parent_id = id
        """)

        return {
            'type': 'ir.actions.act_window',
            'res_model': self._name,
            'res_id': self.id,
            'view_mode': 'form',
            'target': 'new',
        }

    def action_update_all_process(self):
        self.ensure_one()
        self.parent_migration_process_cb()

        # NOTE JEM : seems louche to create a new wizard instead of reuse the current one with updated options.
        # since it is like this from the initial commit of this wizard, I don't change it. yet ...
        wizard = self.create({'group_by_vat': True, 'group_by_email': True, 'group_by_name': True})
        wizard.action_start_automatic_process()

        # NOTE JEM : no idea if this query is usefull
        self._cr.execute("""
            UPDATE
                res_users
            SET
                is_company = NULL
            WHERE
                parent_id IS NOT NULL AND
                is_company IS NOT NULL
        """)

        return self._action_next_screen()

    def action_merge(self):
        """ Merge Contact button. Merge the selected userss, and redirect to
            the end screen (since there is no other wizard line to process.
        """
        if not self.user_ids:
            self.write({'state': 'finished'})
            return {
                'type': 'ir.actions.act_window',
                'res_model': self._name,
                'res_id': self.id,
                'view_mode': 'form',
                'target': 'new',
            }

        # 先合并联系人,后合并用户,做到两者皆修改的功能
        self.env['base.partner.merge.automatic.wizard'].sudo()._merge(self.user_ids.mapped('partner_id.id'),
                                                                      self.dst_user_id.partner_id)
        self._merge(self.user_ids.ids, self.dst_user_id)

        if self.current_line_id:
            self.current_line_id.unlink()

        return self._action_next_screen()




#  xml部分
 <record id="action_users_deduplicate" model="ir.actions.act_window">
        <field name="name">删除处理用户</field>
        <field name="res_model">base.users.merge.automatic.wizard</field>
        <field name="view_mode">form</field>
        <field name="target">new</field>
        <field name="context">{'active_test': False}</field>
    </record>

    <record id="base_users_merge_automatic_wizard_form" model="ir.ui.view">
        <field name='name'>base.users.merge.automatic.wizard.form</field>
        <field name='model'>base.users.merge.automatic.wizard</field>
        <field name='arch' type='xml'>
            <form string='自动合并向导'>
                <sheet>
                    <group attrs="{'invisible': [('state', '!=', 'finished')]}" col="1">
                        <h2>There are no more contacts to merge for this request</h2>
                        <button name="%(action_users_deduplicate)d" string="重复删除其他用户" class="oe_highlight"
                                type="action"/>
                    </group>
                    <p class="oe_grey" attrs="{'invisible': [('state', '!=', ('option'))]}">
                        选择用于搜索重复记录的字段列表。如果你选择了几个字段,
                        建议你只合并那些所有这些字段相同的。(不是字段之一)</p>
                    <group attrs="{'invisible': ['|', ('state', 'not in', ('selection', 'finished')), ('number_group', '=', 0)]}">
                        <field name="state" invisible="1"/>
                        <field name="number_group"/>
                    </group>
                    <group string="搜索基于重复数据的用户"
                           attrs="{'invisible': [('state', 'not in', ('option',))]}">
                        <field name='group_by_email'/>
                        <field name='group_by_name'/>
                        <field name='group_by_is_company'/>
                        <field name='group_by_vat'/>
                        <field name='group_by_parent_id'/>
                    </group>
                    <group string="排除"
                           attrs="{'invisible': [('state', 'not in', ('option',))]}">
                        <field name='exclude_contact'/>
                        <field name='exclude_journal_item'/>
                    </group>
                    <separator string="选择" attrs="{'invisible': [('state', 'not in', ('option',))]}"/>
                    <group attrs="{'invisible': [('state', 'not in', ('option','finished'))]}">
                        <field name='maximum_group' attrs="{'readonly': [('state', 'in', ('finished'))]}"/>
                    </group>
                    <separator string="合并以下用户"
                               attrs="{'invisible': [('state', 'in', ('option', 'finished'))]}"/>
                    <group attrs="{'invisible': [('state', 'in', ('option', 'finished'))]}" col="1">
                        <p class="oe_grey">
                            选中的联系人将被合并在一起。所有链接到其中一个联系人的文档都将被重定向到目标联系人。
                            您可以从此列表中删除联系人,以避免合并它们。
                        </p>
                        <group col="2">
                            <field name="dst_user_id" domain="[('id', 'in', user_ids or False)]"
                                   attrs="{'required': [('state', '=', 'selection')]}"/>
                        </group>
                        <field name="user_ids" nolabel="1">
                            <tree string="用户">
                                <field name="id"/>
                                <field name="display_name"/>
                                <field name="email"/>
                                <field name="country_id"/>
                                <field name="state"/>
                            </tree>
                        </field>
                    </group>
                </sheet>
                <footer>
                    <button name='action_merge' string='合并用户'
                            class='oe_highlight'
                            type='object'
                            attrs="{'invisible': [('state', 'in', ('option', 'finished' ))]}"/>
                    <button name='action_skip' string='跳过这些用户'
                            type='object'
                            attrs="{'invisible': [('state', '!=', 'selection')]}"/>
                    <button name='action_start_manual_process'
                            string='手动检查合并'
                            type='object' class='oe_highlight'
                            attrs="{'invisible': [('state', '!=', 'option')]}"/>
                    <button name='action_start_automatic_process'
                            string='自动合并'
                            type='object' class='oe_highlight'
                            confirm="您确定要执用户的自动合并吗?"
                            attrs="{'invisible': [('state', '!=', 'option')]}"/>
                    <button name='action_update_all_process'
                            string='自动合并所有'
                            type='object'
                            confirm="您确定要执行用户的自动合并列表吗 ?"
                            attrs="{'invisible': [('state', '!=', 'option')]}"/>
                    <button special="cancel" string="取消" type="object" class="btn btn-secondary oe_inline"
                            attrs="{'invisible': [('state', '=', 'finished')]}"/>
                    <button special="cancel" string="取消" type="object" class="btn btn-secondary oe_inline"
                            attrs="{'invisible': [('state', '!=', 'finished')]}"/>
                </footer>
            </form>
        </field>
    </record>

    <record id="action_users_merge" model="ir.actions.act_window">
        <field name="name">合并</field>
        <field name="res_model">base.users.merge.automatic.wizard</field>
        <field name="view_mode">form</field>
        <field name="target">new</field>
        <field name="binding_model_id" ref="base.model_res_users"/>
        <field name="binding_view_types">list</field>
    </record>
</odoo>
已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 深蓝海洋 设计师:CSDN官方博客 返回首页