示例#1
0
class MergeOpportunity(models.TransientModel):
    """
        Merge opportunities together.
        If we're talking about opportunities, it's just because it makes more sense
        to merge opps than leads, because the leads are more ephemeral objects.
        But since opportunities are leads, it's also possible to merge leads
        together (resulting in a new lead), or leads and opps together (resulting
        in a new opp).
    """

    _name = 'crm.merge.opportunity'
    _description = 'Merge Opportunities'

    @api.model
    def default_get(self, fields):
        """ Use active_ids from the context to fetch the leads/opps to merge.
            In order to get merged, these leads/opps can't be in 'Dead' or 'Closed'
        """
        record_ids = self._context.get('active_ids')
        result = super(MergeOpportunity, self).default_get(fields)

        if record_ids:
            if 'opportunity_ids' in fields:
                opp_ids = self.env['crm.lead'].browse(record_ids).filtered(
                    lambda opp: opp.probability < 100).ids
                result['opportunity_ids'] = opp_ids

        return result

    opportunity_ids = fields.Many2many('crm.lead',
                                       'merge_opportunity_rel',
                                       'merge_id',
                                       'opportunity_id',
                                       string='Leads/Opportunities')
    user_id = fields.Many2one('res.users', 'Salesperson', index=True)
    team_id = fields.Many2one('crm.team', 'Sales Team', index=True)

    def action_merge(self):
        self.ensure_one()
        merge_opportunity = self.opportunity_ids.merge_opportunity(
            self.user_id.id, self.team_id.id)
        return merge_opportunity.redirect_lead_opportunity_view()

    @api.onchange('user_id')
    def _onchange_user(self):
        """ When changing the user, also set a team_id or restrict team id
            to the ones user_id is member of. """
        team_id = False
        if self.user_id:
            user_in_team = False
            if self.team_id:
                user_in_team = self.env['crm.team'].search_count([
                    ('id', '=', self.team_id.id), '|',
                    ('user_id', '=', self.user_id.id),
                    ('member_ids', '=', self.user_id.id)
                ])
            if not user_in_team:
                team_id = self.env['crm.team'].search([
                    '|', ('user_id', '=', self.user_id.id),
                    ('member_ids', '=', self.user_id.id)
                ],
                                                      limit=1)
        self.team_id = team_id
示例#2
0
class IrRule(models.Model):
    _name = 'ir.rule'
    _description = 'Record Rule'
    _order = 'model_id DESC'
    _MODES = ['read', 'write', 'create', 'unlink']

    name = fields.Char(index=True)
    active = fields.Boolean(default=True, help="If you uncheck the active field, it will disable the record rule without deleting it (if you delete a native record rule, it may be re-created when you reload the module).")
    model_id = fields.Many2one('ir.model', string='Object', index=True, required=True, ondelete="cascade")
    groups = fields.Many2many('res.groups', 'rule_group_rel', 'rule_group_id', 'group_id')
    domain_force = fields.Text(string='Domain')
    perm_read = fields.Boolean(string='Apply for Read', default=True)
    perm_write = fields.Boolean(string='Apply for Write', default=True)
    perm_create = fields.Boolean(string='Apply for Create', default=True)
    perm_unlink = fields.Boolean(string='Apply for Delete', default=True)

    _sql_constraints = [
        ('no_access_rights',
         'CHECK (perm_read!=False or perm_write!=False or perm_create!=False or perm_unlink!=False)',
         'Rule must have at least one checked access right !'),
    ]

    def _eval_context_for_combinations(self):
        """Returns a dictionary to use as evaluation context for
           ir.rule domains, when the goal is to obtain python lists
           that are easier to parse and combine, but not to
           actually execute them."""
        return {'user': tools.unquote('user'),
                'time': tools.unquote('time')}

    @api.model
    def _eval_context(self):
        """Returns a dictionary to use as evaluation context for
           ir.rule domains."""
        return {'user': self.env.user, 'time': time}

    @api.depends('groups')
    def _compute_global(self):
        for rule in self:
            rule['global'] = not rule.groups

    @api.constrains('model_id')
    def _check_model_transience(self):
        if any(self.env[rule.model_id.model].is_transient() for rule in self):
            raise ValidationError(_('Rules can not be applied on Transient models.'))

    @api.constrains('model_id')
    def _check_model_name(self):
        # Don't allow rules on rules records (this model).
        if any(rule.model_id.model == self._name for rule in self):
            raise ValidationError(_('Rules can not be applied on the Record Rules model.'))

    def _compute_domain_keys(self):
        """ Return the list of context keys to use for caching ``_compute_domain``. """
        return []

    @api.model
    @tools.conditional(
        'xml' not in config['dev_mode'],
        tools.ormcache('self._uid', 'model_name', 'mode',
                       'tuple(self._context.get(k) for k in self._compute_domain_keys())'),
    )
    def _compute_domain(self, model_name, mode="read"):
        if mode not in self._MODES:
            raise ValueError('Invalid mode: %r' % (mode,))

        if self._uid == SUPERUSER_ID:
            return None

        query = """ SELECT r.id FROM ir_rule r JOIN ir_model m ON (r.model_id=m.id)
                    WHERE m.model=%s AND r.active AND r.perm_{mode}
                    AND (r.id IN (SELECT rule_group_id FROM rule_group_rel rg
                                  JOIN res_groups_users_rel gu ON (rg.group_id=gu.gid)
                                  WHERE gu.uid=%s)
                         OR r.global)
                """.format(mode=mode)
        self._cr.execute(query, (model_name, self._uid))
        rule_ids = [row[0] for row in self._cr.fetchall()]
        if not rule_ids:
            return []

        # browse user and rules as SUPERUSER_ID to avoid access errors!
        eval_context = self._eval_context()
        user_groups = self.env.user.groups_id
        global_domains = []                     # list of domains
        group_domains = []                      # list of domains
        for rule in self.browse(rule_ids).sudo():
            # evaluate the domain for the current user
            dom = safe_eval(rule.domain_force, eval_context) if rule.domain_force else []
            dom = expression.normalize_domain(dom)
            if not rule.groups:
                global_domains.append(dom)
            elif rule.groups & user_groups:
                group_domains.append(dom)

        # combine global domains and group domains
        if not group_domains:
            return expression.AND(global_domains)
        return expression.AND(global_domains + [expression.OR(group_domains)])

    @api.model
    def clear_cache(self):
        """ Deprecated, use `clear_caches` instead. """
        self.clear_caches()

    @api.model
    def domain_get(self, model_name, mode='read'):
        dom = self._compute_domain(model_name, mode)
        if dom:
            # _where_calc is called as superuser. This means that rules can
            # involve objects on which the real uid has no acces rights.
            # This means also there is no implicit restriction (e.g. an object
            # references another object the user can't see).
            query = self.env[model_name].sudo()._where_calc(dom, active_test=False)
            return query.where_clause, query.where_clause_params, query.tables
        return [], [], ['"%s"' % self.env[model_name]._table]

    @api.multi
    def unlink(self):
        res = super(IrRule, self).unlink()
        self.clear_caches()
        return res

    @api.model_create_multi
    def create(self, vals_list):
        res = super(IrRule, self).create(vals_list)
        self.clear_caches()
        return res

    @api.multi
    def write(self, vals):
        res = super(IrRule, self).write(vals)
        self.clear_caches()
        return res
class KsDashboardNinjaBoard(models.Model):
    _name = 'ks_dashboard_ninja.board'

    name = fields.Char(string="Dashboard Name", required=True, size=35)
    ks_dashboard_items_ids = fields.One2many('ks_dashboard_ninja.item', 'ks_dashboard_ninja_board_id',
                                             string='Dashboard Items')
    ks_dashboard_menu_name = fields.Char(string="Menu Name")
    ks_dashboard_top_menu_id = fields.Many2one('ir.ui.menu', domain="[('parent_id','=',False)]",
                                               string="Show Under Menu")
    ks_dashboard_client_action_id = fields.Many2one('ir.actions.client')
    ks_dashboard_menu_id = fields.Many2one('ir.ui.menu')
    ks_dashboard_state = fields.Char()
    ks_dashboard_active = fields.Boolean(string="Active", default=True)
    ks_dashboard_group_access = fields.Many2many('res.groups', string="Group Access")

    # DateFilter Fields
    ks_dashboard_start_date = fields.Datetime()
    ks_dashboard_end_date = fields.Datetime()
    ks_date_filter_selection = fields.Selection([
        ('l_none', 'All Time'),
        ('l_day', 'Today'),
        ('t_week', 'This Week'),
        ('t_month', 'This Month'),
        ('t_quarter', 'This Quarter'),
        ('t_year', 'This Year'),
        ('ls_day', 'Last Day'),
        ('ls_week', 'Last Week'),
        ('ls_month', 'Last Month'),
        ('ls_quarter', 'Last Quarter'),
        ('ls_year', 'Last Year'),
        ('l_week', 'Last 7 days'),
        ('l_month', 'Last 30 days'),
        ('l_quarter', 'Last 90 days'),
        ('l_year', 'Last 365 days'),
        ('l_custom', 'Custom Filter'),
    ], default='l_none')

    ks_gridstack_config = fields.Char('Item Configurations')
    ks_dashboard_default_template = fields.Many2one('ks_dashboard_ninja.board_template',
                                                    default=lambda self: self.env.ref('ks_dashboard_ninja.ks_blank',
                                                                                      False),
                                                    string="Dashboard Template", required=True)

    ks_set_interval = fields.Selection([
        (15000, '15 Seconds'),
        (30000, '30 Seconds'),
        (45000, '45 Seconds'),
        (60000, '1 minute'),
        (120000, '2 minute'),
        (300000, '5 minute'),
        (600000, '10 minute'),
    ], string="Update Interval")

    @api.model
    def create(self, vals):
        record = super(KsDashboardNinjaBoard, self).create(vals)
        if 'ks_dashboard_top_menu_id' in vals and 'ks_dashboard_menu_name' in vals:
            action_id = {
                'name': vals['ks_dashboard_menu_name'] + " Action",
                'res_model': 'ks_dashboard_ninja.board',
                'tag': 'ks_dashboard_ninja',
                'params': {'ks_dashboard_id': record.id},
            }
            record.ks_dashboard_client_action_id = self.env['ir.actions.client'].sudo().create(action_id)

            record.ks_dashboard_menu_id = self.env['ir.ui.menu'].sudo().create({
                'name': vals['ks_dashboard_menu_name'],
                'active': vals.get('ks_dashboard_active', True),
                'parent_id': vals['ks_dashboard_top_menu_id'],
                'action': "ir.actions.client," + str(record.ks_dashboard_client_action_id.id),
                'groups_id': vals.get('ks_dashboard_group_access', False),
            })

        if record.ks_dashboard_default_template.ks_item_count:
            ks_gridstack_config = {}
            template_data = json.loads(record.ks_dashboard_default_template.ks_gridstack_config)
            for item_data in template_data:
                dashboard_item = self.env.ref(item_data['item_id']).copy({'ks_dashboard_ninja_board_id': record.id})
                ks_gridstack_config[dashboard_item.id] = item_data['data']
            record.ks_gridstack_config = json.dumps(ks_gridstack_config)
        return record

    @api.multi
    def write(self, vals):
        record = super(KsDashboardNinjaBoard, self).write(vals)
        for rec in self:
            if 'ks_dashboard_menu_name' in vals:
                if self.env.ref('ks_dashboard_ninja.ks_my_default_dashboard_board') and self.env.ref(
                        'ks_dashboard_ninja.ks_my_default_dashboard_board').sudo().id == rec.id:
                    if self.env.ref('ks_dashboard_ninja.board_menu_root', False):
                        self.env.ref('ks_dashboard_ninja.board_menu_root').sudo().name = vals['ks_dashboard_menu_name']
                else:
                    rec.ks_dashboard_menu_id.sudo().name = vals['ks_dashboard_menu_name']
            if 'ks_dashboard_group_access' in vals:
                if self.env.ref('ks_dashboard_ninja.ks_my_default_dashboard_board').id == rec.id:
                    if self.env.ref('ks_dashboard_ninja.board_menu_root', False):
                        self.env.ref('ks_dashboard_ninja.board_menu_root').groups_id = vals['ks_dashboard_group_access']
                else:
                    rec.ks_dashboard_menu_id.sudo().groups_id = vals['ks_dashboard_group_access']
            if 'ks_dashboard_active' in vals and rec.ks_dashboard_menu_id:
                rec.ks_dashboard_menu_id.sudo().active = vals['ks_dashboard_active']

            if 'ks_dashboard_top_menu_id' in vals:
                rec.ks_dashboard_menu_id.write(
                    {'parent_id': vals['ks_dashboard_top_menu_id']}
                )

        return record

    @api.multi
    def unlink(self):
        if self.env.ref('ks_dashboard_ninja.ks_my_default_dashboard_board').id in self.ids:
            raise ValidationError(_("Default Dashboard can't be deleted."))
        else:
            for rec in self:
                rec.ks_dashboard_client_action_id.sudo().unlink()
                rec.ks_dashboard_menu_id.sudo().unlink()
        res = super(KsDashboardNinjaBoard, self).unlink()
        return res

    @api.model
    def ks_fetch_dashboard_data(self, ks_dashboard_id):
        self.ks_set_date(ks_dashboard_id)
        has_group_ks_dashboard_manager = self.env.user.has_group('ks_dashboard_ninja.ks_dashboard_ninja_group_manager')
        dashboard_data = {
            'name': self.browse(ks_dashboard_id).name,
            'ks_dashboard_manager': has_group_ks_dashboard_manager,
            'ks_dashboard_list': self.search_read([], ['id', 'name']),
            'ks_dashboard_start_date': self.browse(ks_dashboard_id).ks_dashboard_start_date,
            'ks_dashboard_end_date': self.browse(ks_dashboard_id).ks_dashboard_end_date,
            'ks_date_filter_selection': self.browse(ks_dashboard_id).ks_date_filter_selection,
            'ks_gridstack_config': self.browse(ks_dashboard_id).ks_gridstack_config,
            'ks_set_interval': self.browse(ks_dashboard_id).ks_set_interval,
        }

        if len(self.browse(ks_dashboard_id).ks_dashboard_items_ids) < 1:
            dashboard_data['ks_item_data'] = False
        else:
            items = self.ks_fetch_item(self.browse(ks_dashboard_id).ks_dashboard_items_ids.ids)
            dashboard_data['ks_item_data'] = items

        return dashboard_data

    @api.model
    def ks_fetch_item(self, item_list):
        """
        :rtype: object
        :param item_list: list of item ids.
        :return: {'id':[item_data]}
        """
        items = {}
        item_model = self.env['ks_dashboard_ninja.item']
        for item_id in item_list:
            item = self.ks_fetch_item_data(item_model.browse(item_id))
            items[item['id']] = item
        return items

    # fetching Item info (Divided to make function inherit easily)
    def ks_fetch_item_data(self, rec):
        """
        :rtype: object
        :param item_id: item object
        :return: object with formatted item data
        """
        item = {
            'name': rec.name if rec.name else rec.ks_model_id.name if rec.ks_model_id else "Name",
            'ks_background_color': rec.ks_background_color,
            'ks_font_color': rec.ks_font_color,
            # 'ks_domain': rec.ks_domain.replace('"%UID"', str(
            #     self.env.user.id)) if rec.ks_domain and "%UID" in rec.ks_domain else rec.ks_domain,
            'ks_domain': rec.ks_convert_into_proper_domain(rec.ks_domain,rec),
            'ks_icon': rec.ks_icon,
            'ks_model_id': rec.ks_model_id.id,
            'ks_model_name': rec.ks_model_name,
            'ks_model_display_name': rec.ks_model_id.name,
            'ks_record_count_type': rec.ks_record_count_type,
            'ks_record_count': rec.ks_record_count,
            'id': rec.id,
            'ks_layout': rec.ks_layout,
            'ks_icon_select': rec.ks_icon_select,
            'ks_default_icon': rec.ks_default_icon,
            'ks_default_icon_color': rec.ks_default_icon_color,
            # Pro Fields
            'ks_dashboard_item_type': rec.ks_dashboard_item_type,
            'ks_chart_item_color': rec.ks_chart_item_color,
            'ks_chart_groupby_type': rec.ks_chart_groupby_type,
            'ks_chart_relation_groupby': rec.ks_chart_relation_groupby.id,
            'ks_chart_relation_groupby_name': rec.ks_chart_relation_groupby.name,
            'ks_chart_date_groupby': rec.ks_chart_date_groupby,
            'ks_record_field': rec.ks_record_field.id if rec.ks_record_field else False,
            'ks_chart_data': rec.ks_chart_data,
            'ks_list_view_data': rec.ks_list_view_data,
            'ks_chart_data_count_type': rec.ks_chart_data_count_type,
            'ks_bar_chart_stacked': rec.ks_bar_chart_stacked,
            'ks_semi_circle_chart': rec.ks_semi_circle_chart,
            'ks_list_view_type': rec.ks_list_view_type,
            'ks_list_view_group_fields': rec.ks_list_view_group_fields.ids if rec.ks_list_view_group_fields else False,
            'ks_previous_period': rec.ks_previous_period,
            'ks_kpi_data': rec.ks_kpi_data,
            'ks_goal_enable': rec.ks_goal_enable,
            'ks_model_id_2': rec.ks_model_id_2.id,
            'ks_record_field_2': rec.ks_record_field_2.id,
            'ks_data_comparison': rec.ks_data_comparison,
            'ks_target_view': rec.ks_target_view,
            'ks_date_filter_selection': rec.ks_date_filter_selection,
        }
        return item

    def ks_set_date(self, ks_dashboard_id):
        ks_date_filter_selection = self.browse(ks_dashboard_id).ks_date_filter_selection

        if ks_date_filter_selection not in ['l_custom', 'l_none']:
            ks_date_data = ks_get_date(ks_date_filter_selection)

            self.browse(ks_dashboard_id).write({'ks_dashboard_end_date': ks_date_data["selected_end_date"],
                                                'ks_dashboard_start_date': ks_date_data["selected_start_date"]})

    @api.multi
    def load_previous_data(self):

        for rec in self:
            if rec.ks_dashboard_menu_id and rec.ks_dashboard_menu_id.action._table == 'ir_act_window':
                action_id = {
                    'name': rec['ks_dashboard_menu_name'] + " Action",
                    'res_model': 'ks_dashboard_ninja.board',
                    'tag': 'ks_dashboard_ninja',
                    'params': {'ks_dashboard_id': rec.id},
                }
                rec.ks_dashboard_client_action_id = self.env['ir.actions.client'].sudo().create(action_id)
                rec.ks_dashboard_menu_id.write(
                    {'action': "ir.actions.client," + str(rec.ks_dashboard_client_action_id.id)})

    # fetching Item info (Divided to make function inherit easily)
    def ks_export_item_data(self, rec):
        ks_chart_measure_field = []
        ks_chart_measure_field_2 = []
        for res in rec.ks_chart_measure_field:
            ks_chart_measure_field.append(res.name)
        for res in rec.ks_chart_measure_field_2:
            ks_chart_measure_field_2.append(res.name)

        ks_list_view_group_fields = []
        for res in rec.ks_list_view_group_fields:
            ks_list_view_group_fields.append(res.name)

        ks_goal_lines = []
        for res in rec.ks_goal_lines:
            goal_line = {
                'ks_goal_date': datetime.datetime.strftime(res.ks_goal_date, '%b %d, %Y'),
                'ks_goal_value': res.ks_goal_value
            }
            ks_goal_lines.append(goal_line)

        ks_list_view_field = []
        for res in rec.ks_list_view_fields:
            ks_list_view_field.append(res.name)
        item = {
            'name': rec.name if rec.name else rec.ks_model_id.name if rec.ks_model_id else "Name",
            'ks_background_color': rec.ks_background_color,
            'ks_font_color': rec.ks_font_color,
            'ks_domain': rec.ks_domain,
            'ks_icon': rec.ks_icon,
            'ks_id': rec.id,
            'ks_model_id': rec.ks_model_name,
            'ks_record_count': rec.ks_record_count,
            'ks_layout': rec.ks_layout,
            'ks_icon_select': rec.ks_icon_select,
            'ks_default_icon': rec.ks_default_icon,
            'ks_default_icon_color': rec.ks_default_icon_color,
            'ks_record_count_type': rec.ks_record_count_type,
            # Pro Fields
            'ks_dashboard_item_type': rec.ks_dashboard_item_type,
            'ks_chart_item_color': rec.ks_chart_item_color,
            'ks_chart_groupby_type': rec.ks_chart_groupby_type,
            'ks_chart_relation_groupby': rec.ks_chart_relation_groupby.name,
            'ks_chart_date_groupby': rec.ks_chart_date_groupby,
            'ks_record_field': rec.ks_record_field.name,
            'ks_chart_sub_groupby_type': rec.ks_chart_sub_groupby_type,
            'ks_chart_relation_sub_groupby': rec.ks_chart_relation_sub_groupby.name,
            'ks_chart_date_sub_groupby': rec.ks_chart_date_sub_groupby,
            'ks_chart_data_count_type': rec.ks_chart_data_count_type,
            'ks_chart_measure_field': ks_chart_measure_field,
            'ks_chart_measure_field_2': ks_chart_measure_field_2,
            'ks_list_view_fields': ks_list_view_field,
            'ks_list_view_group_fields': ks_list_view_group_fields,
            'ks_list_view_type': rec.ks_list_view_type,
            'ks_record_data_limit': rec.ks_record_data_limit,
            'ks_sort_by_order': rec.ks_sort_by_order,
            'ks_sort_by_field': rec.ks_sort_by_field.name,
            'ks_date_filter_field': rec.ks_date_filter_field.name,
            'ks_goal_enable': rec.ks_goal_enable,
            'ks_standard_goal_value': rec.ks_standard_goal_value,
            'ks_goal_liness': ks_goal_lines,
            'ks_date_filter_selection': rec.ks_date_filter_selection,
            'ks_item_start_date': datetime.datetime.strftime(rec.ks_item_start_date,
                                                             '%b %d, %Y') if rec.ks_item_start_date else False,
            'ks_item_end_date': datetime.datetime.strftime(rec.ks_item_end_date,
                                                           '%b %d, %Y') if rec.ks_item_end_date else False,
            'ks_date_filter_selection_2': rec.ks_date_filter_selection_2,
            'ks_item_start_date_2': datetime.datetime.strftime(rec.ks_item_start_date_2,
                                                             '%b %d, %Y') if rec.ks_item_start_date_2 else False,
            'ks_item_end_date_2': datetime.datetime.strftime(rec.ks_item_end_date_2,
                                                           '%b %d, %Y') if rec.ks_item_end_date_2 else False,
            'ks_previous_period': rec.ks_previous_period,
            'ks_target_view': rec.ks_target_view,
            'ks_data_comparison': rec.ks_data_comparison,
            'ks_record_count_type_2': rec.ks_record_count_type_2,
            'ks_record_field_2': rec.ks_record_field_2.name,
            'ks_model_id_2': rec.ks_model_id_2.model,
            'ks_date_filter_field_2': rec.ks_date_filter_field_2.name,
        }
        return item

    @api.model
    def ks_dashboard_export(self, ks_dashboard_ids):
        ks_dashboard_data = []
        ks_dashboard_export_data = {}
        ks_dashboard_ids = json.loads(ks_dashboard_ids)
        for ks_dashboard_id in ks_dashboard_ids:
            dashboard_data = {
                'name': self.browse(ks_dashboard_id).name,
                'ks_dashboard_menu_name': self.browse(ks_dashboard_id).ks_dashboard_menu_name,
                'ks_gridstack_config': self.browse(ks_dashboard_id).ks_gridstack_config,
            }
            if len(self.browse(ks_dashboard_id).ks_dashboard_items_ids) < 1:
                dashboard_data['ks_item_data'] = False
            else:
                items = []
                for rec in self.browse(ks_dashboard_id).ks_dashboard_items_ids:
                    item = self.ks_export_item_data(rec)
                    items.append(item)

                dashboard_data['ks_item_data'] = items

            ks_dashboard_data.append(dashboard_data)

            ks_dashboard_export_data = {
                'ks_file_format': 'ks_dashboard_ninja_export_file',
                'ks_dashboard_data': ks_dashboard_data
            }
        return ks_dashboard_export_data

    @api.model
    def ks_import_dashboard(self, file):
        try:
            # ks_dashboard_data = json.loads(file)
            ks_dashboard_file_read = json.loads(file)
        except:
            raise ValidationError(_("This file is not supported"))

        if 'ks_file_format' in ks_dashboard_file_read and ks_dashboard_file_read[
            'ks_file_format'] == 'ks_dashboard_ninja_export_file':
            ks_dashboard_data = ks_dashboard_file_read['ks_dashboard_data']
        else:
            raise ValidationError(_("Current Json File is not properly formatted according to Dashboard Ninja Model."))

        ks_dashboard_key = ['name', 'ks_dashboard_menu_name', 'ks_gridstack_config']
        ks_dashboard_item_key = ['ks_model_id', 'ks_chart_measure_field', 'ks_list_view_fields', 'ks_record_field',
                                 'ks_chart_relation_groupby', 'ks_id']

        # Fetching dashboard model info
        for data in ks_dashboard_data:
            if not all(key in data for key in ks_dashboard_key):
                raise ValidationError(
                    _("Current Json File is not properly formatted according to Dashboard Ninja Model."))
            vals = {
                'name': data['name'],
                'ks_dashboard_menu_name': data['ks_dashboard_menu_name'],
                'ks_dashboard_top_menu_id': self.env.ref("ks_dashboard_ninja.board_menu_root").id,
                'ks_dashboard_active': True,
                'ks_gridstack_config': data['ks_gridstack_config'],
                'ks_dashboard_default_template': self.env.ref("ks_dashboard_ninja.ks_blank").id,
                'ks_dashboard_group_access': False,

            }
            # Creating Dashboard
            dashboard_id = self.create(vals)

            if data['ks_gridstack_config']:
                ks_gridstack_config = eval(data['ks_gridstack_config'])
            ks_grid_stack_config = {}

            if data['ks_item_data']:
                # Fetching dashboard item info
                for item in data['ks_item_data']:
                    if not all(key in item for key in ks_dashboard_item_key):
                        raise ValidationError(
                            _("Current Json File is not properly formatted according to Dashboard Ninja Model."))

                    ks_model = item['ks_model_id'].replace(".", "_")

                    ks_measure_field_ids = []
                    ks_measure_field_2_ids = []

                    model = self.env['ir.model'].search([('model', '=', item['ks_model_id'])])



                    if not model:
                        raise ValidationError(_(
                            "Please Install the Module which contains the following Model : %s " % item['ks_model_id']))

                    if item['ks_model_id_2']:
                        model_2 = self.env['ir.model'].search([('model', '=', item['ks_model_id_2'])])
                        if not model_2:
                            raise ValidationError(_(
                                "Please Install the Module which contains the following Model : %s " % item['ks_model_id_2']))

                    for ks_measure in item['ks_chart_measure_field']:
                        for x in self.env['ir.model'].search([('model', '=', item['ks_model_id'])]).modules.split(", "):
                            measure_id = x + '.field_' + ks_model + "__" + ks_measure
                            ks_measure_id = self.env.ref(measure_id, False)
                            if ks_measure_id:
                                ks_measure_field_ids.append(ks_measure_id.id)
                    item['ks_chart_measure_field'] = [(6, 0, ks_measure_field_ids)]

                    for ks_measure in item['ks_chart_measure_field_2']:
                        for x in self.env['ir.model'].search([('model', '=', item['ks_model_id'])]).modules.split(", "):
                            measure_id = x + '.field_' + ks_model + "__" + ks_measure
                            ks_measure_id = self.env.ref(measure_id, False)
                            if ks_measure_id:
                                ks_measure_field_2_ids.append(ks_measure_id.id)
                    item['ks_chart_measure_field_2'] = [(6, 0, ks_measure_field_2_ids)]

                    ks_list_view_group_fields = []
                    for ks_measure in item['ks_list_view_group_fields']:
                        for x in self.env['ir.model'].search([('model', '=', item['ks_model_id'])]).modules.split(", "):
                            measure_id = x + '.field_' + ks_model + "__" + ks_measure
                            ks_measure_id = self.env.ref(measure_id, False)
                            if ks_measure_id:
                                ks_list_view_group_fields.append(ks_measure_id.id)
                    item['ks_list_view_group_fields'] = [(6, 0, ks_list_view_group_fields)]

                    ks_list_view_field_ids = []
                    for ks_list_field in item['ks_list_view_fields']:
                        for x in self.env['ir.model'].search([('model', '=', item['ks_model_id'])]).modules.split(", "):
                            list_field_id = x + '.field_' + ks_model + "__" + ks_list_field
                            ks_list_field_id = self.env.ref(list_field_id, False)
                            if ks_list_field_id:
                                ks_list_view_field_ids.append(ks_list_field_id.id)
                    item['ks_list_view_fields'] = [(6, 0, ks_list_view_field_ids)]

                    if item['ks_record_field']:
                        ks_record_field = item['ks_record_field']
                        for x in self.env['ir.model'].search([('model', '=', item['ks_model_id'])]).modules.split(", "):
                            record_id = x + '.field_' + ks_model + "__" + ks_record_field
                            ks_record_id = self.env.ref(record_id, False)
                            if ks_record_id:
                                item['ks_record_field'] = ks_record_id.id

                    if item['ks_date_filter_field']:
                        ks_date_filter_field = item['ks_date_filter_field']
                        for x in self.env['ir.model'].search([('model', '=', item['ks_model_id'])]).modules.split(", "):
                            record_id = x + '.field_' + ks_model + "__" + ks_date_filter_field
                            ks_record_id = self.env.ref(record_id, False)
                            if ks_record_id:
                                item['ks_date_filter_field'] = ks_record_id.id



                    if item['ks_chart_relation_groupby']:
                        ks_group_by = item['ks_chart_relation_groupby']
                        for x in self.env['ir.model'].search([('model', '=', item['ks_model_id'])]).modules.split(", "):
                            field_id = x + '.field_' + ks_model + "__" + ks_group_by
                            ks_chart_relation_groupby = self.env.ref(field_id, False)
                            if ks_chart_relation_groupby:
                                item['ks_chart_relation_groupby'] = ks_chart_relation_groupby.id

                    if item['ks_chart_relation_sub_groupby']:
                        ks_group_by = item['ks_chart_relation_sub_groupby']
                        for x in self.env['ir.model'].search([('model', '=', item['ks_model_id'])]).modules.split(", "):
                            field_id = x + '.field_' + ks_model + "__" + ks_group_by
                            ks_chart_relation_sub_groupby = self.env.ref(field_id, False)
                            if ks_chart_relation_sub_groupby:
                                item['ks_chart_relation_sub_groupby'] = ks_chart_relation_sub_groupby.id

                    # Sort by field : Many2one Entery
                    if item['ks_sort_by_field']:
                        ks_group_by = item['ks_sort_by_field']
                        for x in self.env['ir.model'].search([('model', '=', item['ks_model_id'])]).modules.split(", "):
                            field_id = x + '.field_' + ks_model + "__" + ks_group_by
                            ks_sort_by_field = self.env.ref(field_id, False)
                            if ks_sort_by_field:
                                item['ks_sort_by_field'] = ks_sort_by_field.id

                    ks_model_id = self.env['ir.model'].search([('model', '=', item['ks_model_id'])]).id
                    if(item['ks_model_id_2']):
                        ks_model_2 = item['ks_model_id_2'].replace(".", "_")
                        ks_model_id_2 = self.env['ir.model'].search([('model', '=', item['ks_model_id_2'])]).id
                        if item['ks_record_field_2']:
                            ks_record_field = item['ks_record_field_2']
                            for x in self.env['ir.model'].search([('model', '=', item['ks_model_id_2'])]).modules.split(
                                    ", "):
                                record_id = x + '.field_' + ks_model_2 + "__" + ks_record_field
                                ks_record_id = self.env.ref(record_id, False)
                                if ks_record_id:
                                    item['ks_record_field_2'] = ks_record_id.id
                        if item['ks_date_filter_field_2']:
                            ks_date_filter_field = item['ks_date_filter_field_2']
                            for x in self.env['ir.model'].search([('model', '=', item['ks_model_id_2'])]).modules.split(
                                    ", "):
                                record_id = x + '.field_' + ks_model_2 + "__" + ks_date_filter_field
                                ks_record_id = self.env.ref(record_id, False)
                                if ks_record_id:
                                    item['ks_date_filter_field_2'] = ks_record_id.id
                        item['ks_model_id_2'] = ks_model_id_2
                    else:
                        item['ks_date_filter_field_2'] = False
                        item['ks_record_field_2'] = False

                    item['ks_model_id'] = ks_model_id
                    item['ks_dashboard_ninja_board_id'] = dashboard_id.id

                    ks_goal_lines = item['ks_goal_liness'].copy() if item.get('ks_goal_liness', False) else False
                    item['ks_goal_liness'] = False

                    item['ks_item_start_date'] = datetime.datetime.strptime(item['ks_item_start_date'], '%b %d, %Y') if \
                    item[
                        'ks_item_start_date'] else False
                    item['ks_item_end_date'] = datetime.datetime.strptime(item['ks_item_end_date'], '%b %d, %Y') if \
                    item[
                        'ks_item_end_date'] else False
                    item['ks_item_start_date_2'] = datetime.datetime.strptime(item['ks_item_start_date_2'], '%b %d, %Y') if \
                    item[
                        'ks_item_start_date_2'] else False
                    item['ks_item_end_date_2'] = datetime.datetime.strptime(item['ks_item_end_date_2'], '%b %d, %Y') if \
                    item[
                        'ks_item_end_date_2'] else False

                    # Creating dashboard items
                    ks_item = self.env['ks_dashboard_ninja.item'].create(item)
                    if ks_goal_lines and len(ks_goal_lines) != 0:
                        for line in ks_goal_lines:
                            line['ks_goal_date'] = datetime.datetime.strptime(line['ks_goal_date'], '%b %d, %Y')
                            line['ks_dashboard_item'] = ks_item.id
                            self.env['ks_dashboard_ninja.item_goal'].create(line)
                    if data['ks_gridstack_config'] and str(item['ks_id']) in ks_gridstack_config:
                        ks_grid_stack_config[str(ks_item.id)] = ks_gridstack_config[str(item['ks_id'])]

                self.browse(dashboard_id.id).write({
                    'ks_gridstack_config': json.dumps(ks_grid_stack_config)
                })

        return "Success"
示例#4
0
class ReRegistrationResponceStudent(models.Model):
    _name = 're.reg.waiting.responce.student'
    _inherit = ['mail.thread', 'ir.needaction_mixin']
    _description = "Re-Registration Student"

    @api.depends('fees_line_ids')
    def compute_amount(self):
        for record in self:
            amount = 0.00
            for fees_record in record.fees_line_ids:
                amount += fees_record.amount
            record.total_amount = amount

    @api.depends('total_amount', 'total_paid_amount')
    def compute_residual_amount_student(self):
        for record in self:
            record.residual = record.total_amount - record.total_paid_amount

    code = fields.Char('Code')
    name = fields.Many2one('res.partner', 'Name')
    reg_no = fields.Char('Registration')
    batch_id = fields.Many2one("batch", "Current Academic Year")
    course_id = fields.Many2one('course', 'Current Admission To Class')
    next_year_batch_id = fields.Many2one("batch", "Next Academic Year")
    next_year_course_id = fields.Many2one('course',
                                          'Next Year Admission To Class')
    re_reg_parents = fields.Many2one('re.reg.waiting.responce.parents',
                                     'Parent')
    parents_re_reg = fields.Many2one('re.reg.waiting.responce.parents',
                                     'Reg Parent')
    confirm = fields.Boolean("Confirm Student")
    fee_status = fields.Selection([('re_unpaid', 'UnPaid'),
                                   ('re_partially_paid', 'Partially Paid'),
                                   ('re_Paid', 'Paid')],
                                  'Fee Status',
                                  track_visibility='onchange')
    fees_line_ids = fields.Many2many('fees.line', 're_reg_fee_table',
                                     're_reg_sid', 'fee_id', 'Fees Line')
    state = fields.Selection([('awaiting_response', 'Awaiting Response'),
                              ('awaiting_re_registration_fee', 'Awaiting Re-Registration Fee'), \
                              ('re_registration_confirmed', 'Re-Registration Confirmed'),
                              ('tc_expected', 'TC Expected')], track_visibility='onchange')
    total_amount = fields.Float('Total Amount', compute='compute_amount')
    total_paid_amount = fields.Float('Total Paid Amount')
    residual = fields.Float('Balance',
                            compute='compute_residual_amount_student',
                            readonly='1')
    confirmation_date = fields.Date('Confirmed On')
    user_id = fields.Many2one('res.users', 'Confirmed By')
    response = fields.Boolean("Response", default=False)

    @api.multi
    def re_send_payfort_payment_link_student(self):
        if self.residual > 0.00:
            stud_table_data = '<tr><td>%s</td><td>%s</td><td>Yes</td><td>%s</td></tr>' % (
                self.name.name, self.next_year_course_id.name,
                self.total_amount)
            parent_rec = self.re_reg_parents
            parent_rec.send_re_registration_payment_link(
                parent_record=parent_rec, child_data_table=stud_table_data)

    @api.model
    def create(self, vals):
        vals['code'] = self.env['ir.sequence'].get(
            're.reg.student.form') or '/'
        res = super(ReRegistrationResponceStudent, self).create(vals)
        return res

    @api.multi
    def come_tc_expected_to_waiting_fee(self):
        fees_structure_obj = self.env['fees.structure']
        child_data_table = ''
        for re_reg_stud_rec in self:
            fee_line_id_list = []
            fee_record = fees_structure_obj.search(
                [('academic_year_id', '=',
                  re_reg_stud_rec.next_year_batch_id.id),
                 ('course_id', '=', re_reg_stud_rec.next_year_course_id.id),
                 ('type', '=', 're_reg')],
                limit=1)
            if not fee_record.id:
                raise except_orm(_('Warning!'),
                                 _("Re-Registration Fee is Not Define !"))
            for fee_line_rec in fee_record.fee_line_ids:
                fee_line_id_list.append(fee_line_rec.id)
            re_reg_stud_rec.write({
                'confirm':
                True,
                're_reg_parents':
                re_reg_stud_rec.parents_re_reg.id,
                'state':
                'awaiting_re_registration_fee',
                'fee_status':
                're_unpaid',
            })
            for fee_line_id in fee_line_id_list:
                re_reg_stud_rec.fees_line_ids = [(4, fee_line_id)]


#             # if student have already paid advance amount
#             student_advance_paid = re_reg_stud_rec.name.credit
#             if student_advance_paid < 0.00:
#                 student_advance_paid = abs(student_advance_paid)
#                 s_payble_amount = 0.00
#                 is_full_paid = False
#                 if re_reg_stud_rec.residual > student_advance_paid:
#                     s_payble_amount = student_advance_paid
#                 else:
#                     s_payble_amount = re_reg_stud_rec.residual
#                     is_full_paid = True
#                 if s_payble_amount > 0:
# #                     self.re_reg_parents.re_reg_fee_reconcile_stud_advance(re_reg_partner_rec=re_reg_stud_rec,
# #                                                                           amount=s_payble_amount)
#                     re_reg_stud_rec.total_paid_amount = s_payble_amount
#                     if is_full_paid:
#                         re_reg_stud_rec.write({
#                             'fee_status': 're_Paid',
#                             're_reg_next_academic_year': 'yes', })
#                     else:
#                         re_reg_stud_rec.fee_status = 're_partially_paid'
            child_data_table += '<tr><td>%s</td><td>%s</td><td>Yes</td><td>%s</td></tr>' % (
                re_reg_stud_rec.name.name,
                re_reg_stud_rec.next_year_course_id.name,
                re_reg_stud_rec.total_amount)
            # send mail for fee reminder
            re_reg_stud_rec.parents_re_reg.send_re_registration_payment_link(
                parent_record=re_reg_stud_rec.parents_re_reg,
                child_data_table=child_data_table)
示例#5
0
class BlogPost(models.Model):
    _name = "blog.post"
    _description = "Blog Post"
    _inherit = [
        'mail.thread', 'website.seo.metadata', 'website.published.multi.mixin'
    ]
    _order = 'id DESC'
    _mail_post_access = 'read'

    @api.multi
    def _compute_website_url(self):
        super(BlogPost, self)._compute_website_url()
        for blog_post in self:
            blog_post.website_url = "/blog/%s/post/%s" % (slug(
                blog_post.blog_id), slug(blog_post))

    @api.multi
    @api.depends('post_date', 'visits')
    def _compute_ranking(self):
        res = {}
        for blog_post in self:
            if blog_post.id:  # avoid to rank one post not yet saved and so withtout post_date in case of an onchange.
                age = datetime.now() - fields.Datetime.from_string(
                    blog_post.post_date)
                res[blog_post.id] = blog_post.visits * (
                    0.5 + random.random()) / max(3, age.days)
        return res

    def _default_content(self):
        return '''
            <section class="s_text_block">
                <div class="container">
                    <div class="row">
                        <div class="col-lg-12 mb16 mt16">
                            <p class="o_default_snippet_text">''' + _(
            "Start writing here...") + '''</p>
                        </div>
                    </div>
                </div>
            </section>
        '''

    name = fields.Char('Title', required=True, translate=True, default='')
    subtitle = fields.Char('Sub Title', translate=True)
    author_id = fields.Many2one('res.partner',
                                'Author',
                                default=lambda self: self.env.user.partner_id)
    active = fields.Boolean('Active', default=True)
    cover_properties = fields.Text(
        'Cover Properties',
        default=
        '{"background-image": "none", "background-color": "oe_black", "opacity": "0.2", "resize_class": ""}'
    )
    blog_id = fields.Many2one('blog.blog',
                              'Blog',
                              required=True,
                              ondelete='cascade')
    tag_ids = fields.Many2many('blog.tag', string='Tags')
    content = fields.Html('Content',
                          default=_default_content,
                          translate=html_translate,
                          sanitize=False)
    teaser = fields.Text('Teaser',
                         compute='_compute_teaser',
                         inverse='_set_teaser')
    teaser_manual = fields.Text(string='Teaser Content')

    website_message_ids = fields.One2many(
        domain=lambda self: [('model', '=', self._name),
                             ('message_type', '=', 'comment')])

    # creation / update stuff
    create_date = fields.Datetime('Created on', index=True, readonly=True)
    published_date = fields.Datetime('Published Date')
    post_date = fields.Datetime(
        'Publishing date',
        compute='_compute_post_date',
        inverse='_set_post_date',
        store=True,
        help=
        "The blog post will be visible for your visitors as of this date on the website if it is set as published."
    )
    create_uid = fields.Many2one('res.users',
                                 'Created by',
                                 index=True,
                                 readonly=True)
    write_date = fields.Datetime('Last Updated on', index=True, readonly=True)
    write_uid = fields.Many2one('res.users',
                                'Last Contributor',
                                index=True,
                                readonly=True)
    author_avatar = fields.Binary(related='author_id.image_small',
                                  string="Avatar",
                                  readonly=False)
    visits = fields.Integer('No of Views', copy=False)
    ranking = fields.Float(compute='_compute_ranking', string='Ranking')

    website_id = fields.Many2one(related='blog_id.website_id', readonly=True)

    @api.multi
    @api.depends('content', 'teaser_manual')
    def _compute_teaser(self):
        for blog_post in self:
            if blog_post.teaser_manual:
                blog_post.teaser = blog_post.teaser_manual
            else:
                content = html2plaintext(blog_post.content).replace('\n', ' ')
                blog_post.teaser = content[:150] + '...'

    @api.multi
    def _set_teaser(self):
        for blog_post in self:
            blog_post.teaser_manual = blog_post.teaser

    @api.multi
    @api.depends('create_date', 'published_date')
    def _compute_post_date(self):
        for blog_post in self:
            if blog_post.published_date:
                blog_post.post_date = blog_post.published_date
            else:
                blog_post.post_date = blog_post.create_date

    @api.multi
    def _set_post_date(self):
        for blog_post in self:
            blog_post.published_date = blog_post.post_date
            if not blog_post.published_date:
                blog_post._write(dict(post_date=blog_post.create_date)
                                 )  # dont trigger inverse function

    def _check_for_publication(self, vals):
        if vals.get('website_published'):
            for post in self:
                post.blog_id.message_post_with_view(
                    'website_blog.blog_post_template_new_post',
                    subject=post.name,
                    values={'post': post},
                    subtype_id=self.env['ir.model.data'].xmlid_to_res_id(
                        'website_blog.mt_blog_blog_published'))
            return True
        return False

    @api.model
    def create(self, vals):
        post_id = super(BlogPost,
                        self.with_context(mail_create_nolog=True)).create(vals)
        post_id._check_for_publication(vals)
        return post_id

    @api.multi
    def write(self, vals):
        result = True
        for post in self:
            copy_vals = dict(vals)
            if ('website_published' in vals and 'published_date' not in vals
                    and (not post.published_date
                         or post.published_date <= fields.Datetime.now())):
                copy_vals['published_date'] = vals[
                    'website_published'] and fields.Datetime.now() or False
            result &= super(BlogPost, self).write(copy_vals)
        self._check_for_publication(vals)
        return result

    @api.multi
    def get_access_action(self, access_uid=None):
        """ Instead of the classic form view, redirect to the post on website
        directly if user is an employee or if the post is published. """
        self.ensure_one()
        user = access_uid and self.env['res.users'].sudo().browse(
            access_uid) or self.env.user
        if user.share and not self.sudo().website_published:
            return super(BlogPost, self).get_access_action(access_uid)
        return {
            'type': 'ir.actions.act_url',
            'url': self.website_url,
            'target': 'self',
            'target_type': 'public',
            'res_id': self.id,
        }

    @api.multi
    def _notify_get_groups(self, message, groups):
        """ Add access button to everyone if the document is published. """
        groups = super(BlogPost, self)._notify_get_groups(message, groups)

        if self.website_published:
            for group_name, group_method, group_data in groups:
                group_data['has_button_access'] = True

        return groups

    @api.multi
    def _notify_customize_recipients(self, message, msg_vals, recipients_vals):
        """ Override to avoid keeping all notified recipients of a comment.
        We avoid tracking needaction on post comments. Only emails should be
        sufficient. """
        msg_type = msg_vals.get('message_type') or message.message_type
        if msg_type == 'comment':
            return {'needaction_partner_ids': []}
        return {}

    def _default_website_meta(self):
        res = super(BlogPost, self)._default_website_meta()
        res['default_opengraph']['og:description'] = res['default_twitter'][
            'twitter:description'] = self.subtitle
        blog_post_cover_properties = json.loads(self.cover_properties)
        res['default_opengraph']['og:image'] = res['default_twitter'][
            'twitter:image'] = blog_post_cover_properties.get(
                'background-image', 'none')[4:-1]
        res['default_opengraph']['og:title'] = res['default_twitter'][
            'twitter:title'] = self.name
        return res
示例#6
0
class HrEmployeeAttachment(models.Model):
    _inherit = 'ir.attachment'

    doc_attach_rel = fields.Many2many('education.documents', 'doc_attachment_id', 'attach_id3', 'doc_id',
                                      string="Attachment", invisible=1)
示例#7
0
class ResPartner(models.Model):
    _inherit = 'res.partner'

    next_course_ids = fields.One2many(comodel_name='education.course.change',
                                      inverse_name='school_id',
                                      string='Next Courses')
    prev_course_ids = fields.One2many(comodel_name='education.course.change',
                                      inverse_name='next_school_id',
                                      string='Previous Courses')
    alumni_center_id = fields.Many2one(comodel_name='res.partner',
                                       string='Last Education Center',
                                       domain=[('educational_category', '=',
                                                'school')])
    alumni_academic_year_id = fields.Many2one(
        comodel_name='education.academic_year', string='Last Academic Year')
    alumni_member = fields.Boolean(string='Alumni Association Member')
    student_group_ids = fields.Many2many(
        comodel_name='education.group',
        relation='edu_group_student',
        column1='student_id',
        column2='group_id',
        string='Education Groups',
        readonly=True,
        domain="[('group_type_id.type', '=', 'official')]")
    current_group_id = fields.Many2one(comodel_name='education.group',
                                       string='Current Group',
                                       compute='_compute_current_group_id')
    current_center_id = fields.Many2one(
        comodel_name='res.partner',
        string='Current Education Center',
        compute='_compute_current_group_id',
        search='_search_current_center_id',
        domain="[('educational_category', '=', 'school')]")
    current_course_id = fields.Many2one(comodel_name='education.course',
                                        string='Current Course',
                                        compute='_compute_current_group_id',
                                        search='_search_current_course_id')
    childs_current_center_ids = fields.Many2many(
        comodel_name='res.partner',
        string='Children\'s Current Education Centers',
        compute='_compute_child_current_group_ids',
        search='_search_parent_current_center_id')
    childs_current_course_ids = fields.Many2many(
        comodel_name='education.course',
        string='Children\'s Current Courses',
        compute='_compute_child_current_group_ids',
        search='_search_parent_current_course_id')

    @api.depends('student_group_ids')
    def _compute_current_group_id(self):
        today = fields.Date.context_today(self)
        current_year = self.env['education.academic_year'].search(
            [('date_start', '<=', today), ('date_end', '>=', today)], limit=1)
        for partner in self.filtered(
                lambda p: p.educational_category == 'student'):
            groups = partner.student_group_ids.filtered(
                lambda g: g.group_type_id.type == 'official' and g.
                academic_year_id == current_year)
            partner.current_group_id = groups[:1]
            partner.current_center_id = partner.current_group_id.center_id
            partner.current_course_id = partner.current_group_id.course_id

    @api.multi
    def _search_current_center_id(self, operator, value):
        if operator == '=':
            centers = self.browse(value)
        else:
            centers = self.search([
                (self._rec_name, operator, value),
                ('educational_category', '=', 'school'),
            ])
        groups = self._search_current_groups().filtered(
            lambda g: g.center_id in centers)
        return [('id', 'in', groups.mapped('student_ids').ids)]

    @api.multi
    def _search_current_course_id(self, operator, value):
        course_obj = self.env['education.course']
        if operator == '=':
            courses = course_obj.browse(value)
        else:
            courses = course_obj.search([
                (course_obj._rec_name, operator, value),
            ])
        groups = self._search_current_groups().filtered(
            lambda g: g.course_id in courses)
        return [('id', 'in', groups.mapped('student_ids').ids)]

    @api.depends('child_ids', 'child_ids.current_group_id')
    def _compute_child_current_group_ids(self):
        for partner in self.filtered(
                lambda p: p.educational_category == 'family'):
            childs_groups = partner.mapped('child_ids.current_group_id')
            partner.childs_current_center_ids = [
                (6, 0, childs_groups.mapped('center_id').ids)
            ]
            partner.childs_current_course_ids = [
                (6, 0, childs_groups.mapped('course_id').ids)
            ]

    @api.multi
    def _search_parent_current_center_id(self, operator, value):
        if operator == '=':
            centers = self.browse(value)
        else:
            centers = self.search([
                (self._rec_name, operator, value),
                ('educational_category', '=', 'school'),
            ])
        groups = self._search_current_groups().filtered(
            lambda g: g.center_id in centers)
        return [('id', 'in', groups.mapped('student_ids.parent_id').ids)]

    @api.multi
    def _search_parent_current_course_id(self, operator, value):
        course_obj = self.env['education.course']
        if operator == '=':
            courses = course_obj.browse(value)
        else:
            courses = course_obj.search([
                (course_obj._rec_name, operator, value),
            ])
        groups = self._search_current_groups().filtered(
            lambda g: g.course_id in courses)
        return [('id', 'in', groups.mapped('student_ids.parent_id').ids)]

    @api.multi
    def _search_current_groups(self):
        today = fields.Date.context_today(self)
        current_year = self.env['education.academic_year'].search(
            [('date_start', '<=', today), ('date_end', '>=', today)], limit=1)
        official_type = self.env['education.group_type'].search([('type', '=',
                                                                  'official')])
        return self.env['education.group'].search([
            ('academic_year_id', '=', current_year.id),
            ('group_type_id', 'in', official_type.ids)
        ])
class MailComposer(models.TransientModel):
    """ Generic message composition wizard. You may inherit from this wizard
        at model and view levels to provide specific features.

        The behavior of the wizard depends on the composition_mode field:
        - 'comment': post on a record. The wizard is pre-populated via ``get_record_data``
        - 'mass_mail': wizard in mass mailing mode where the mail details can
            contain template placeholders that will be merged with actual data
            before being sent to each recipient.
    """
    _name = 'mail.compose.message'
    _inherit = 'mail.message'
    _description = 'Email composition wizard'
    _log_access = True
    _batch_size = 500

    @api.model
    def default_get(self, fields):
        """ Handle composition mode. Some details about context keys:
            - comment: default mode, model and ID of a record the user comments
                - default_model or active_model
                - default_res_id or active_id
            - reply: active_id of a message the user replies to
                - default_parent_id or message_id or active_id: ID of the
                    mail.message we reply to
                - message.res_model or default_model
                - message.res_id or default_res_id
            - mass_mail: model and IDs of records the user mass-mails
                - active_ids: record IDs
                - default_model or active_model
        """
        result = super(MailComposer, self).default_get(fields)

        # v6.1 compatibility mode
        result['composition_mode'] = result.get(
            'composition_mode',
            self._context.get('mail.compose.message.mode', 'comment'))
        result['model'] = result.get('model',
                                     self._context.get('active_model'))
        result['res_id'] = result.get('res_id', self._context.get('active_id'))
        result['parent_id'] = result.get('parent_id',
                                         self._context.get('message_id'))
        if 'no_auto_thread' not in result and (
                result['model'] not in self.env
                or not hasattr(self.env[result['model']], 'message_post')):
            result['no_auto_thread'] = True

        # default values according to composition mode - NOTE: reply is deprecated, fall back on comment
        if result['composition_mode'] == 'reply':
            result['composition_mode'] = 'comment'
        vals = {}
        if 'active_domain' in self._context:  # not context.get() because we want to keep global [] domains
            vals['active_domain'] = '%s' % self._context.get('active_domain')
        if result['composition_mode'] == 'comment':
            vals.update(self.get_record_data(result))

        for field in vals:
            if field in fields:
                result[field] = vals[field]

        # TDE HACK: as mailboxes used default_model='res.users' and default_res_id=uid
        # (because of lack of an accessible pid), creating a message on its own
        # profile may crash (res_users does not allow writing on it)
        # Posting on its own profile works (res_users redirect to res_partner)
        # but when creating the mail.message to create the mail.compose.message
        # access rights issues may rise
        # We therefore directly change the model and res_id
        if result['model'] == 'res.users' and result['res_id'] == self._uid:
            result['model'] = 'res.partner'
            result['res_id'] = self.env.user.partner_id.id

        if fields is not None:
            [
                result.pop(field, None) for field in list(result)
                if field not in fields
            ]
        return result

    @api.model
    def _get_composition_mode_selection(self):
        return [('comment', 'Post on a document'),
                ('mass_mail', 'Email Mass Mailing'),
                ('mass_post', 'Post on Multiple Documents')]

    composition_mode = fields.Selection(
        selection=_get_composition_mode_selection,
        string='Composition mode',
        default='comment')
    partner_ids = fields.Many2many('res.partner',
                                   'mail_compose_message_res_partner_rel',
                                   'wizard_id', 'partner_id',
                                   'Additional Contacts')
    use_active_domain = fields.Boolean('Use active domain')
    active_domain = fields.Text('Active domain', readonly=True)
    attachment_ids = fields.Many2many(
        'ir.attachment', 'mail_compose_message_ir_attachments_rel',
        'wizard_id', 'attachment_id', 'Attachments')
    is_log = fields.Boolean(
        'Log an Internal Note',
        help='Whether the message is an internal note (comment mode only)')
    subject = fields.Char(default=False)
    # mass mode options
    notify = fields.Boolean(
        'Notify followers',
        help='Notify followers of the document (mass post only)')
    auto_delete = fields.Boolean('Delete Emails',
                                 help='Delete sent emails (mass mailing only)')
    auto_delete_message = fields.Boolean(
        'Delete Message Copy',
        help=
        'Do not keep a copy of the email in the document communication history (mass mailing only)'
    )
    template_id = fields.Many2one('mail.template',
                                  'Use template',
                                  index=True,
                                  domain="[('model', '=', model)]")
    # mail_message updated fields
    message_type = fields.Selection(default="comment")
    subtype_id = fields.Many2one(default=lambda self: self.env['ir.model.data']
                                 .xmlid_to_res_id('mail.mt_comment'))

    @api.multi
    def check_access_rule(self, operation):
        """ Access rules of mail.compose.message:
            - create: if
                - model, no res_id, I create a message in mass mail mode
            - then: fall back on mail.message acces rules
        """
        # Author condition (CREATE (mass_mail))
        if operation == 'create' and self._uid != SUPERUSER_ID:
            # read mail_compose_message.ids to have their values
            message_values = {}
            self._cr.execute(
                'SELECT DISTINCT id, model, res_id FROM "%s" WHERE id = ANY (%%s) AND res_id = 0'
                % self._table, (self.ids, ))
            for mid, rmod, rid in self._cr.fetchall():
                message_values[mid] = {'model': rmod, 'res_id': rid}
            # remove from the set to check the ids that mail_compose_message accepts
            author_ids = [
                mid for mid, message in message_values.items()
                if message.get('model') and not message.get('res_id')
            ]
            self = self.browse(list(set(self.ids) -
                                    set(author_ids)))  # not sure slef = ...

        return super(MailComposer, self).check_access_rule(operation)

    @api.multi
    def _notify(self, **kwargs):
        """ Override specific notify method of mail.message, because we do
            not want that feature in the wizard. """
        return

    @api.model
    def get_record_data(self, values):
        """ Returns a defaults-like dict with initial values for the composition
        wizard when sending an email related a previous email (parent_id) or
        a document (model, res_id). This is based on previously computed default
        values. """
        result, subject = {}, False
        if values.get('parent_id'):
            parent = self.env['mail.message'].browse(values.get('parent_id'))
            result['record_name'] = parent.record_name,
            subject = tools.ustr(parent.subject or parent.record_name or '')
            if not values.get('model'):
                result['model'] = parent.model
            if not values.get('res_id'):
                result['res_id'] = parent.res_id
            partner_ids = values.get('partner_ids', list()) + [
                (4, id) for id in parent.partner_ids.ids
            ]
            if self._context.get(
                    'is_private'
            ) and parent.author_id:  # check message is private then add author also in partner list.
                partner_ids += [(4, parent.author_id.id)]
            result['partner_ids'] = partner_ids
        elif values.get('model') and values.get('res_id'):
            doc_name_get = self.env[values.get('model')].browse(
                values.get('res_id')).name_get()
            result['record_name'] = doc_name_get and doc_name_get[0][1] or ''
            subject = tools.ustr(result['record_name'])

        re_prefix = _('Re:')
        if subject and not (subject.startswith('Re:')
                            or subject.startswith(re_prefix)):
            subject = "%s %s" % (re_prefix, subject)
        result['subject'] = subject

        return result

    #------------------------------------------------------
    # Wizard validation and send
    #------------------------------------------------------
    # action buttons call with positionnal arguments only, so we need an intermediary function
    # to ensure the context is passed correctly
    @api.multi
    def action_send_mail(self):
        self.send_mail()
        return {'type': 'ir.actions.act_window_close', 'infos': 'mail_sent'}

    @api.multi
    def send_mail(self, auto_commit=False):
        """ Process the wizard content and proceed with sending the related
            email(s), rendering any template patterns on the fly if needed. """
        notif_layout = self._context.get('custom_layout')
        # Several custom layouts make use of the model description at rendering, e.g. in the
        # 'View <document>' button. Some models are used for different business concepts, such as
        # 'purchase.order' which is used for a RFQ and and PO. To avoid confusion, we must use a
        # different wording depending on the state of the object.
        # Therefore, we can set the description in the context from the beginning to avoid falling
        # back on the regular display_name retrieved in '_notify_prepare_template_context'.
        model_description = self._context.get('model_description')
        for wizard in self:
            # Duplicate attachments linked to the email.template.
            # Indeed, basic mail.compose.message wizard duplicates attachments in mass
            # mailing mode. But in 'single post' mode, attachments of an email template
            # also have to be duplicated to avoid changing their ownership.
            if wizard.attachment_ids and wizard.composition_mode != 'mass_mail' and wizard.template_id:
                new_attachment_ids = []
                for attachment in wizard.attachment_ids:
                    if attachment in wizard.template_id.attachment_ids:
                        new_attachment_ids.append(
                            attachment.copy({
                                'res_model': 'mail.compose.message',
                                'res_id': wizard.id
                            }).id)
                    else:
                        new_attachment_ids.append(attachment.id)
                wizard.write({'attachment_ids': [(6, 0, new_attachment_ids)]})

            # Mass Mailing
            mass_mode = wizard.composition_mode in ('mass_mail', 'mass_post')

            Mail = self.env['mail.mail']
            ActiveModel = self.env[wizard.model] if wizard.model and hasattr(
                self.env[wizard.model],
                'message_post') else self.env['mail.thread']
            if wizard.composition_mode == 'mass_post':
                # do not send emails directly but use the queue instead
                # add context key to avoid subscribing the author
                ActiveModel = ActiveModel.with_context(
                    mail_notify_force_send=False, mail_create_nosubscribe=True)
            # wizard works in batch mode: [res_id] or active_ids or active_domain
            if mass_mode and wizard.use_active_domain and wizard.model:
                res_ids = self.env[wizard.model].search(
                    safe_eval(wizard.active_domain)).ids
            elif mass_mode and wizard.model and self._context.get(
                    'active_ids'):
                res_ids = self._context['active_ids']
            else:
                res_ids = [wizard.res_id]

            batch_size = int(self.env['ir.config_parameter'].sudo().get_param(
                'mail.batch_size')) or self._batch_size
            sliced_res_ids = [
                res_ids[i:i + batch_size]
                for i in range(0, len(res_ids), batch_size)
            ]

            if wizard.composition_mode == 'mass_mail' or wizard.is_log or (
                    wizard.composition_mode == 'mass_post'
                    and not wizard.notify):  # log a note: subtype is False
                subtype_id = False
            elif wizard.subtype_id:
                subtype_id = wizard.subtype_id.id
            else:
                subtype_id = self.env['ir.model.data'].xmlid_to_res_id(
                    'mail.mt_comment')

            for res_ids in sliced_res_ids:
                batch_mails = Mail
                all_mail_values = wizard.get_mail_values(res_ids)
                for res_id, mail_values in all_mail_values.items():
                    if wizard.composition_mode == 'mass_mail':
                        batch_mails |= Mail.create(mail_values)
                    else:
                        post_params = dict(
                            message_type=wizard.message_type,
                            subtype_id=subtype_id,
                            notif_layout=notif_layout,
                            add_sign=not bool(wizard.template_id),
                            mail_auto_delete=wizard.template_id.auto_delete
                            if wizard.template_id else False,
                            model_description=model_description,
                            **mail_values)
                        if ActiveModel._name == 'mail.thread' and wizard.model:
                            post_params['model'] = wizard.model
                        ActiveModel.browse(res_id).message_post(**post_params)

                if wizard.composition_mode == 'mass_mail':
                    batch_mails.send(auto_commit=auto_commit)

    @api.multi
    def get_mail_values(self, res_ids):
        """Generate the values that will be used by send_mail to create mail_messages
        or mail_mails. """
        self.ensure_one()
        results = dict.fromkeys(res_ids, False)
        rendered_values = {}
        mass_mail_mode = self.composition_mode == 'mass_mail'

        # render all template-based value at once
        if mass_mail_mode and self.model:
            rendered_values = self.render_message(res_ids)
        # compute alias-based reply-to in batch
        reply_to_value = dict.fromkeys(res_ids, None)
        if mass_mail_mode and not self.no_auto_thread:
            records = self.env[self.model].browse(res_ids)
            reply_to_value = self.env[
                'mail.thread']._notify_get_reply_to_on_records(
                    default=self.email_from, records=records)

        blacklisted_rec_ids = []
        if mass_mail_mode and hasattr(self.env[self.model], "_primary_email"):
            BL_sudo = self.env['mail.blacklist'].sudo()
            blacklist = set(BL_sudo.search([]).mapped('email'))
            if blacklist:
                [email_field] = self.env[self.model]._primary_email
                targets = self.env[self.model].browse(res_ids).read(
                    [email_field])
                # First extract email from recipient before comparing with blacklist
                for target in targets:
                    sanitized_email = self.env[
                        'mail.blacklist']._sanitize_email(
                            target.get(email_field))
                    if sanitized_email and sanitized_email in blacklist:
                        blacklisted_rec_ids.append(target['id'])

        for res_id in res_ids:
            # static wizard (mail.message) values
            mail_values = {
                'subject': self.subject,
                'body': self.body or '',
                'parent_id': self.parent_id and self.parent_id.id,
                'partner_ids': [partner.id for partner in self.partner_ids],
                'attachment_ids':
                [attach.id for attach in self.attachment_ids],
                'author_id': self.author_id.id,
                'email_from': self.email_from,
                'reply_to': self.reply_to,
                'record_name': self.record_name,
                'no_auto_thread': self.no_auto_thread,
                'mail_server_id': self.mail_server_id.id,
                'mail_activity_type_id': self.mail_activity_type_id.id,
            }

            # mass mailing: rendering override wizard static values
            if mass_mail_mode and self.model:
                mail_values.update(
                    self.env['mail.thread'].
                    _notify_specific_email_values_on_records(
                        False, records=self.env[self.model].browse(res_id)))
                # keep a copy unless specifically requested, reset record name (avoid browsing records)
                mail_values.update(notification=not self.auto_delete_message,
                                   model=self.model,
                                   res_id=res_id,
                                   record_name=False)
                # auto deletion of mail_mail
                if self.auto_delete or self.template_id.auto_delete:
                    mail_values['auto_delete'] = True
                # rendered values using template
                email_dict = rendered_values[res_id]
                mail_values['partner_ids'] += email_dict.pop('partner_ids', [])
                mail_values.update(email_dict)
                if not self.no_auto_thread:
                    mail_values.pop('reply_to')
                    if reply_to_value.get(res_id):
                        mail_values['reply_to'] = reply_to_value[res_id]
                if self.no_auto_thread and not mail_values.get('reply_to'):
                    mail_values['reply_to'] = mail_values['email_from']
                # mail_mail values: body -> body_html, partner_ids -> recipient_ids
                mail_values['body_html'] = mail_values.get('body', '')
                mail_values['recipient_ids'] = [
                    (4, id) for id in mail_values.pop('partner_ids', [])
                ]

                # process attachments: should not be encoded before being processed by message_post / mail_mail create
                mail_values['attachments'] = [
                    (name, base64.b64decode(enc_cont))
                    for name, enc_cont in email_dict.pop(
                        'attachments', list())
                ]
                attachment_ids = []
                for attach_id in mail_values.pop('attachment_ids'):
                    new_attach_id = self.env['ir.attachment'].browse(
                        attach_id).copy({
                            'res_model': self._name,
                            'res_id': self.id
                        })
                    attachment_ids.append(new_attach_id.id)
                mail_values['attachment_ids'] = self.env[
                    'mail.thread']._message_post_process_attachments(
                        mail_values.pop('attachments', []), attachment_ids, {
                            'model': 'mail.message',
                            'res_id': 0
                        })
                # Filter out the blacklisted records by setting the mail state to cancel -> Used for Mass Mailing stats
                if res_id in blacklisted_rec_ids:
                    mail_values['state'] = 'cancel'
                    # Do not post the mail into the recipient's chatter
                    mail_values['notification'] = False

            results[res_id] = mail_values
        return results

    #------------------------------------------------------
    # Template methods
    #------------------------------------------------------

    @api.multi
    @api.onchange('template_id')
    def onchange_template_id_wrapper(self):
        self.ensure_one()
        values = self.onchange_template_id(self.template_id.id,
                                           self.composition_mode, self.model,
                                           self.res_id)['value']
        for fname, value in values.items():
            setattr(self, fname, value)

    @api.multi
    def onchange_template_id(self, template_id, composition_mode, model,
                             res_id):
        """ - mass_mailing: we cannot render, so return the template values
            - normal mode: return rendered values
            /!\ for x2many field, this onchange return command instead of ids
        """
        if template_id and composition_mode == 'mass_mail':
            template = self.env['mail.template'].browse(template_id)
            fields = [
                'subject', 'body_html', 'email_from', 'reply_to',
                'mail_server_id'
            ]
            values = dict((field, getattr(template, field)) for field in fields
                          if getattr(template, field))
            if template.attachment_ids:
                values['attachment_ids'] = [
                    att.id for att in template.attachment_ids
                ]
            if template.mail_server_id:
                values['mail_server_id'] = template.mail_server_id.id
            if template.user_signature and 'body_html' in values:
                signature = self.env.user.signature
                values['body_html'] = tools.append_content_to_html(
                    values['body_html'], signature, plaintext=False)
        elif template_id:
            values = self.generate_email_for_composer(template_id,
                                                      [res_id])[res_id]
            # transform attachments into attachment_ids; not attached to the document because this will
            # be done further in the posting process, allowing to clean database if email not send
            attachment_ids = []
            Attachment = self.env['ir.attachment']
            for attach_fname, attach_datas in values.pop('attachments', []):
                data_attach = {
                    'name': attach_fname,
                    'datas': attach_datas,
                    'datas_fname': attach_fname,
                    'res_model': 'mail.compose.message',
                    'res_id': 0,
                    'type':
                    'binary',  # override default_type from context, possibly meant for another model!
                }
                attachment_ids.append(Attachment.create(data_attach).id)
            if values.get('attachment_ids', []) or attachment_ids:
                values['attachment_ids'] = [(5, )] + values.get(
                    'attachment_ids', []) + attachment_ids
        else:
            default_values = self.with_context(
                default_composition_mode=composition_mode,
                default_model=model,
                default_res_id=res_id).default_get([
                    'composition_mode', 'model', 'res_id', 'parent_id',
                    'partner_ids', 'subject', 'body', 'email_from', 'reply_to',
                    'attachment_ids', 'mail_server_id'
                ])
            values = dict((key, default_values[key]) for key in [
                'subject', 'body', 'partner_ids', 'email_from', 'reply_to',
                'attachment_ids', 'mail_server_id'
            ] if key in default_values)

        if values.get('body_html'):
            values['body'] = values.pop('body_html')

        # This onchange should return command instead of ids for x2many field.
        # ORM handle the assignation of command list on new onchange (api.v8),
        # this force the complete replacement of x2many field with
        # command and is compatible with onchange api.v7
        values = self._convert_to_write(values)

        return {'value': values}

    @api.multi
    def save_as_template(self):
        """ hit save as template button: current form value will be a new
            template attached to the current document. """
        for record in self:
            model = self.env['ir.model']._get(record.model or 'mail.message')
            model_name = model.name or ''
            template_name = "%s: %s" % (model_name, tools.ustr(record.subject))
            values = {
                'name':
                template_name,
                'subject':
                record.subject or False,
                'body_html':
                record.body or False,
                'model_id':
                model.id or False,
                'attachment_ids':
                [(6, 0, [att.id for att in record.attachment_ids])],
            }
            template = self.env['mail.template'].create(values)
            # generate the saved template
            record.write({'template_id': template.id})
            record.onchange_template_id_wrapper()
            return _reopen(self,
                           record.id,
                           record.model,
                           context=self._context)

    #------------------------------------------------------
    # Template rendering
    #------------------------------------------------------

    @api.multi
    def render_message(self, res_ids):
        """Generate template-based values of wizard, for the document records given
        by res_ids. This method is meant to be inherited by email_template that
        will produce a more complete dictionary, using Jinja2 templates.

        Each template is generated for all res_ids, allowing to parse the template
        once, and render it multiple times. This is useful for mass mailing where
        template rendering represent a significant part of the process.

        Default recipients are also computed, based on mail_thread method
        message_get_default_recipients. This allows to ensure a mass mailing has
        always some recipients specified.

        :param browse wizard: current mail.compose.message browse record
        :param list res_ids: list of record ids

        :return dict results: for each res_id, the generated template values for
                              subject, body, email_from and reply_to
        """
        self.ensure_one()
        multi_mode = True
        if isinstance(res_ids, pycompat.integer_types):
            multi_mode = False
            res_ids = [res_ids]

        subjects = self.env['mail.template']._render_template(
            self.subject, self.model, res_ids)
        bodies = self.env['mail.template']._render_template(self.body,
                                                            self.model,
                                                            res_ids,
                                                            post_process=True)
        emails_from = self.env['mail.template']._render_template(
            self.email_from, self.model, res_ids)
        replies_to = self.env['mail.template']._render_template(
            self.reply_to, self.model, res_ids)
        default_recipients = {}
        if not self.partner_ids:
            default_recipients = self.env[
                'mail.thread'].message_get_default_recipients(
                    res_model=self.model, res_ids=res_ids)

        results = dict.fromkeys(res_ids, False)
        for res_id in res_ids:
            results[res_id] = {
                'subject': subjects[res_id],
                'body': bodies[res_id],
                'email_from': emails_from[res_id],
                'reply_to': replies_to[res_id],
            }
            results[res_id].update(default_recipients.get(res_id, dict()))

        # generate template-based values
        if self.template_id:
            template_values = self.generate_email_for_composer(
                self.template_id.id,
                res_ids,
                fields=[
                    'email_to', 'partner_to', 'email_cc', 'attachment_ids',
                    'mail_server_id'
                ])
        else:
            template_values = {}

        for res_id in res_ids:
            if template_values.get(res_id):
                # recipients are managed by the template
                results[res_id].pop('partner_ids')
                results[res_id].pop('email_to')
                results[res_id].pop('email_cc')
                # remove attachments from template values as they should not be rendered
                template_values[res_id].pop('attachment_ids', None)
            else:
                template_values[res_id] = dict()
            # update template values by composer values
            template_values[res_id].update(results[res_id])

        return multi_mode and template_values or template_values[res_ids[0]]

    @api.model
    def generate_email_for_composer(self, template_id, res_ids, fields=None):
        """ Call email_template.generate_email(), get fields relevant for
            mail.compose.message, transform email_cc and email_to into partner_ids """
        multi_mode = True
        if isinstance(res_ids, pycompat.integer_types):
            multi_mode = False
            res_ids = [res_ids]

        if fields is None:
            fields = [
                'subject', 'body_html', 'email_from', 'email_to', 'partner_to',
                'email_cc', 'reply_to', 'attachment_ids', 'mail_server_id'
            ]
        returned_fields = fields + ['partner_ids', 'attachments']
        values = dict.fromkeys(res_ids, False)

        template_values = self.env['mail.template'].with_context(
            tpl_partners_only=True).browse(template_id).generate_email(
                res_ids, fields=fields)
        for res_id in res_ids:
            res_id_values = dict((field, template_values[res_id][field])
                                 for field in returned_fields
                                 if template_values[res_id].get(field))
            res_id_values['body'] = res_id_values.pop('body_html', '')
            values[res_id] = res_id_values

        return multi_mode and values or values[res_ids[0]]
class MergePartnerAutomatic(models.TransientModel):
    """
        The idea behind this wizard is to create a list of potential partners to
        merge. We use two objects, the first one is the wizard for the end-user.
        And the second will contain the partner list to merge.
    """

    _name = 'base.partner.merge.automatic.wizard'
    _description = 'Merge Partner Wizard'

    @api.model
    def default_get(self, fields):
        res = super(MergePartnerAutomatic, self).default_get(fields)
        active_ids = self.env.context.get('active_ids')
        if self.env.context.get(
                'active_model') == 'res.partner' and active_ids:
            res['state'] = 'selection'
            res['partner_ids'] = [(6, 0, active_ids)]
            res['dst_partner_id'] = self._get_ordered_partner(
                active_ids)[-1].id
        return res

    # Group by
    group_by_email = fields.Boolean('Email')
    group_by_name = fields.Boolean('Name')
    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', 'Option'), ('selection', 'Selection'),
                              ('finished', 'Finished')],
                             readonly=True,
                             required=True,
                             string='State',
                             default='option')

    number_group = fields.Integer('Group of Contacts', readonly=True)
    current_line_id = fields.Many2one('base.partner.merge.line',
                                      string='Current Line')
    line_ids = fields.One2many('base.partner.merge.line',
                               'wizard_id',
                               string='Lines')
    partner_ids = fields.Many2many('res.partner', string='Contacts')
    dst_partner_id = fields.Many2one('res.partner',
                                     string='Destination Contact')

    exclude_contact = fields.Boolean('A user associated to the contact')
    exclude_journal_item = fields.Boolean(
        'Journal Items associated to the contact')
    maximum_group = fields.Integer('Maximum of Group of Contacts')

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

    def _get_fk_on(self, table):
        """ return a list of many2one relation with the given table.
            :param table : the name of the sql table to return relations
            :returns a list of tuple '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_partners, dst_partner):
        """ Update all foreign key from the src_partner to dst_partner. All many2one fields will be updated.
            :param src_partners : merge source res.partner recordset (does not include destination one)
            :param dst_partner : record of destination res.partner
        """
        _logger.debug(
            '_update_foreign_keys for dst_partner: %s for src_partners: %s',
            dst_partner.id, str(src_partners.ids))

        # find the many2one relation to a partner
        Partner = self.env['res.partner']
        relations = self._get_fk_on('res_partner')

        self.flush()

        for table, column in relations:
            if 'base_partner_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 partner in src_partners:
                    self._cr.execute(
                        query, (dst_partner.id, partner.id, dst_partner.id))
            else:
                try:
                    with mute_logger('eagle.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_partner.id,
                            tuple(src_partners.ids),
                        ))

                        # handle the recursivity with parent relation
                        if column == Partner._parent_name and table == 'res_partner':
                            query = """
                                WITH RECURSIVE cycle(id, parent_id) AS (
                                        SELECT id, parent_id FROM res_partner
                                    UNION
                                        SELECT  cycle.id, res_partner.parent_id
                                        FROM    res_partner, cycle
                                        WHERE   res_partner.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_partner.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 partner_id is useless, better delete it
                    query = 'DELETE FROM "%(table)s" WHERE "%(column)s" IN %%s' % query_dic
                    self._cr.execute(query, (tuple(src_partners.ids), ))

        self.invalidate_cache()

    @api.model
    def _update_reference_fields(self, src_partners, dst_partner):
        """ Update all reference fields from the src_partner to dst_partner.
            :param src_partners : merge source res.partner recordset (does not include destination one)
            :param dst_partner : record of destination res.partner
        """
        _logger.debug(
            '_update_reference_fields for dst_partner: %s for src_partners: %r',
            dst_partner.id, src_partners.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.partner'),
                                           (field_id, '=', src.id)])
            try:
                with mute_logger('eagle.sql_db'), self._cr.savepoint(
                ), self.env.clear_upon_failure():
                    records.sudo().write({field_id: dst_partner.id})
                    records.flush()
            except psycopg2.Error:
                # updating fails, most likely due to a violated unique constraint
                # keeping record with nonexistent partner_id is useless, better delete it
                records.sudo().unlink()

        update_records = functools.partial(update_records)

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

        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 partner in src_partners:
                records_ref = Model.sudo().search([
                    (record.name, '=', 'res.partner,%d' % partner.id)
                ])
                values = {
                    record.name: 'res.partner,%d' % dst_partner.id,
                }
                records_ref.sudo().write(values)

        self.flush()

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

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

        model_fields = dst_partner.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:
            field = dst_partner._fields[column]
            if field.type not in ('many2many',
                                  'one2many') and field.compute is None:
                for item in itertools.chain(src_partners, [dst_partner]):
                    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_partner.write(values)
        # try to update the parent_id
        if parent_id and parent_id != dst_partner.id:
            try:
                dst_partner.write({'parent_id': parent_id})
            except ValidationError:
                _logger.info(
                    'Skip recursive partner hierarchies for parent_id %s of partner: %s',
                    parent_id, dst_partner.id)

    def _merge(self, partner_ids, dst_partner=None, extra_checks=True):
        """ private implementation of merge partner
            :param partner_ids : ids of partner to merge
            :param dst_partner : record of destination res.partner
            :param extra_checks: pass False to bypass extra sanity check (e.g. email address)
        """
        # super-admin can be used to bypass extra checks
        if self.env.is_admin():
            extra_checks = False

        Partner = self.env['res.partner']
        partner_ids = Partner.browse(partner_ids).exists()
        if len(partner_ids) < 2:
            return

        if len(partner_ids) > 3:
            raise UserError(
                _("For safety reasons, you cannot merge more than 3 contacts together. You can re-open the wizard several times if needed."
                  ))

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

        if extra_checks and len(set(partner.email
                                    for partner in partner_ids)) > 1:
            raise UserError(
                _("All contacts must have the same email. Only the Administrator can merge contacts with different emails."
                  ))

        # remove dst_partner from partners to merge
        if dst_partner and dst_partner in partner_ids:
            src_partners = partner_ids - dst_partner
        else:
            ordered_partners = self._get_ordered_partner(partner_ids.ids)
            dst_partner = ordered_partners[-1]
            src_partners = ordered_partners[:-1]
        _logger.info("dst_partner: %s", dst_partner.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([('partner_id', 'in', [
                    partner.id for partner in src_partners
                ])]):
            raise UserError(
                _("Only the destination contact may be linked to existing Journal Items. Please ask the Administrator if you need to merge several contacts linked to existing Journal Items."
                  ))

        # Make the company of all related users consistent
        for user in partner_ids.user_ids:
            user.sudo().write({
                'company_ids': [(6, 0, [dst_partner.company_id.id])],
                'company_id':
                dst_partner.company_id.id
            })

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

        self._log_merge_operation(src_partners, dst_partner)

        # delete source partner, since they are merged
        src_partners.unlink()

    def _log_merge_operation(self, src_partners, dst_partner):
        _logger.info('(uid = %s) merged the partners %r with %s', self._uid,
                     src_partners.ids, dst_partner.id)

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

    @api.model
    def _generate_query(self, fields, maximum_group=100):
        """ Build the SQL query on res.partner table to group them according to given criteria
            :param fields : list of column names to group by the partners
            :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_partner",
        ]

        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 partner 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(
                _("You have to specify a filter for your selection."))

        return groups

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

    @api.model
    def _get_ordered_partner(self, partner_ids):
        """ Helper : returns a `res.partner` recordset ordered by create_date/active fields
            :param partner_ids : list of partner ids to sort
        """
        return self.env['res.partner'].browse(partner_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 partners. """
        model_mapping = {}
        if self.exclude_contact:
            model_mapping['res.users'] = 'partner_id'
        if 'account.move.line' in self.env and self.exclude_journal_item:
            model_mapping['account.move.line'] = 'partner_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 partner 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_partner_ids = literal_eval(current_line.aggr_ids)
            values.update({
                'current_line_id':
                current_line.id,
                'partner_ids': [(6, 0, current_partner_ids)],
                'dst_partner_id':
                self._get_ordered_partner(current_partner_ids)[-1].id,
                'state':
                'selection',
            })
        else:
            values.update({
                'current_line_id': False,
                'partner_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 partner query
        self._cr.execute(query)

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

            # exclude partner according to options
            if model_mapping and self._partner_use_in(partners.ids,
                                                      model_mapping):
                continue

            self.env['base.partner.merge.line'].create({
                'wizard_id':
                self.id,
                'min_id':
                min_id,
                'aggr_ids':
                partners.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
            partner to merge, the wizard will be actualized.
                - Compute the selected groups (with duplication)
                - If the user has selected the 'exclude_xxx' fields, avoid the partners
        """
        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 partner 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:
            partner_ids = literal_eval(line.aggr_ids)
            self._merge(partner_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_partner as p1
            INNER join
                res_partner 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:
            partner_ids = literal_eval(line.aggr_ids)
            self._merge(partner_ids)
            line.unlink()
            self._cr.commit()

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

        self._cr.execute("""
            UPDATE
                res_partner
            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_partner
            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 partners, and redirect to
            the end screen (since there is no other wizard line to process.
        """
        if not self.partner_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._merge(self.partner_ids.ids, self.dst_partner_id)

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

        return self._action_next_screen()
示例#10
0
class IrActionsReport(models.Model):
    _name = 'ir.actions.report'
    _description = 'Report Action'
    _inherit = 'ir.actions.actions'
    _table = 'ir_act_report_xml'
    _sequence = 'ir_actions_id_seq'
    _order = 'name'

    name = fields.Char(translate=True)
    type = fields.Char(default='ir.actions.report')
    binding_type = fields.Selection(default='report')
    model = fields.Char(required=True, string='Model Name')
    model_id = fields.Many2one('ir.model',
                               string='Model',
                               compute='_compute_model_id',
                               search='_search_model_id')

    report_type = fields.Selection(
        [
            ('qweb-html', 'HTML'),
            ('qweb-pdf', 'PDF'),
            ('qweb-text', 'Text'),
        ],
        required=True,
        default='qweb-pdf',
        help=
        'The type of the report that will be rendered, each one having its own'
        ' rendering method. HTML means the report will be opened directly in your'
        ' browser PDF means the report will be rendered using Wkhtmltopdf and'
        ' downloaded by the user.')
    report_name = fields.Char(
        string='Template Name',
        required=True,
        help=
        "For QWeb reports, name of the template used in the rendering. The method 'render_html' of the model 'report.template_name' will be called (if any) to give the html. For RML reports, this is the LocalService name."
    )
    report_file = fields.Char(
        string='Report File',
        required=False,
        readonly=False,
        store=True,
        help=
        "The path to the main report file (depending on Report Type) or empty if the content is in another field"
    )
    groups_id = fields.Many2many('res.groups',
                                 'res_groups_report_rel',
                                 'uid',
                                 'gid',
                                 string='Groups')
    multi = fields.Boolean(
        string='On Multiple Doc.',
        help=
        "If set to true, the action will not be displayed on the right toolbar of a form view."
    )

    paperformat_id = fields.Many2one('report.paperformat', 'Paper Format')
    print_report_name = fields.Char(
        'Printed Report Name',
        help=
        "This is the filename of the report going to download. Keep empty to not change the report filename. You can use a python expression with the 'object' and 'time' variables."
    )
    attachment_use = fields.Boolean(
        string='Reload from Attachment',
        help=
        'If you check this, then the second time the user prints with same attachment name, it returns the previous report.'
    )
    attachment = fields.Char(
        string='Save as Attachment Prefix',
        help=
        'This is the filename of the attachment used to store the printing result. Keep empty to not save the printed reports. You can use a python expression with the object and time variables.'
    )

    @api.depends('model')
    def _compute_model_id(self):
        for action in self:
            action.model_id = self.env['ir.model']._get(action.model).id

    def _search_model_id(self, operator, value):
        ir_model_ids = None
        if isinstance(value, str):
            names = self.env['ir.model'].name_search(value, operator=operator)
            ir_model_ids = [n[0] for n in names]

        elif isinstance(value, Iterable):
            ir_model_ids = value

        elif isinstance(value, int) and not isinstance(value, bool):
            ir_model_ids = [value]

        if ir_model_ids:
            operator = 'not in' if operator in NEGATIVE_TERM_OPERATORS else 'in'
            ir_model = self.env['ir.model'].browse(ir_model_ids)
            return [('model', operator, ir_model.mapped('model'))]
        elif isinstance(value, bool) or value is None:
            return [('model', operator, value)]
        else:
            return FALSE_DOMAIN

    @api.multi
    def associated_view(self):
        """Used in the ir.actions.report form view in order to search naively after the view(s)
        used in the rendering.
        """
        self.ensure_one()
        action_ref = self.env.ref('base.action_ui_view')
        if not action_ref or len(self.report_name.split('.')) < 2:
            return False
        action_data = action_ref.read()[0]
        action_data['domain'] = [('name', 'ilike',
                                  self.report_name.split('.')[1]),
                                 ('type', '=', 'qweb')]
        return action_data

    @api.multi
    def create_action(self):
        """ Create a contextual action for each report. """
        for report in self:
            model = self.env['ir.model']._get(report.model)
            report.write({
                'binding_model_id': model.id,
                'binding_type': 'report'
            })
        return True

    @api.multi
    def unlink_action(self):
        """ Remove the contextual actions created for the reports. """
        self.check_access_rights('write', raise_exception=True)
        self.filtered('binding_model_id').write({'binding_model_id': False})
        return True

    #--------------------------------------------------------------------------
    # Main report methods
    #--------------------------------------------------------------------------
    @api.multi
    def retrieve_attachment(self, record):
        '''Retrieve an attachment for a specific record.

        :param record: The record owning of the attachment.
        :param attachment_name: The optional name of the attachment.
        :return: A recordset of length <=1 or None
        '''
        attachment_name = safe_eval(self.attachment, {
            'object': record,
            'time': time
        })
        if not attachment_name:
            return None
        return self.env['ir.attachment'].search(
            [('datas_fname', '=', attachment_name),
             ('res_model', '=', self.model), ('res_id', '=', record.id)],
            limit=1)

    @api.multi
    def postprocess_pdf_report(self, record, buffer):
        '''Hook to handle post processing during the pdf report generation.
        The basic behavior consists to create a new attachment containing the pdf
        base64 encoded.

        :param record_id: The record that will own the attachment.
        :param pdf_content: The optional name content of the file to avoid reading both times.
        :return: A modified buffer if the previous one has been modified, None otherwise.
        '''
        attachment_name = safe_eval(self.attachment, {
            'object': record,
            'time': time
        })
        if not attachment_name:
            return None
        attachment_vals = {
            'name': attachment_name,
            'datas': base64.encodestring(buffer.getvalue()),
            'datas_fname': attachment_name,
            'res_model': self.model,
            'res_id': record.id,
        }
        try:
            self.env['ir.attachment'].create(attachment_vals)
        except AccessError:
            _logger.info("Cannot save PDF report %r as attachment",
                         attachment_vals['name'])
        else:
            _logger.info('The PDF document %s is now saved in the database',
                         attachment_vals['name'])
        return buffer

    @api.model
    def get_wkhtmltopdf_state(self):
        '''Get the current state of wkhtmltopdf: install, ok, upgrade, workers or broken.
        * install: Starting state.
        * upgrade: The binary is an older version (< 0.12.0).
        * ok: A binary was found with a recent version (>= 0.12.0).
        * workers: Not enough workers found to perform the pdf rendering process (< 2 workers).
        * broken: A binary was found but not responding.

        :return: wkhtmltopdf_state
        '''
        return wkhtmltopdf_state

    @api.model
    def get_paperformat(self):
        return self.paperformat_id or self.env.user.company_id.paperformat_id

    @api.model
    def _build_wkhtmltopdf_args(self,
                                paperformat_id,
                                landscape,
                                specific_paperformat_args=None,
                                set_viewport_size=False):
        '''Build arguments understandable by wkhtmltopdf bin.

        :param paperformat_id: A report.paperformat record.
        :param landscape: Force the report orientation to be landscape.
        :param specific_paperformat_args: A dictionary containing prioritized wkhtmltopdf arguments.
        :param set_viewport_size: Enable a viewport sized '1024x1280' or '1280x1024' depending of landscape arg.
        :return: A list of string representing the wkhtmltopdf process command args.
        '''
        if landscape is None and specific_paperformat_args and specific_paperformat_args.get(
                'data-report-landscape'):
            landscape = specific_paperformat_args.get('data-report-landscape')

        command_args = ['--disable-local-file-access']
        if set_viewport_size:
            command_args.extend(
                ['--viewport-size', landscape and '1024x1280' or '1280x1024'])

        # Passing the cookie to wkhtmltopdf in order to resolve internal links.
        try:
            if request:
                command_args.extend(
                    ['--cookie', 'session_id', request.session.sid])
        except AttributeError:
            pass

        # Less verbose error messages
        command_args.extend(['--quiet'])

        # Build paperformat args
        if paperformat_id:
            if paperformat_id.format and paperformat_id.format != 'custom':
                command_args.extend(['--page-size', paperformat_id.format])

            if paperformat_id.page_height and paperformat_id.page_width and paperformat_id.format == 'custom':
                command_args.extend(
                    ['--page-width',
                     str(paperformat_id.page_width) + 'mm'])
                command_args.extend(
                    ['--page-height',
                     str(paperformat_id.page_height) + 'mm'])

            if specific_paperformat_args and specific_paperformat_args.get(
                    'data-report-margin-top'):
                command_args.extend([
                    '--margin-top',
                    str(specific_paperformat_args['data-report-margin-top'])
                ])
            else:
                command_args.extend(
                    ['--margin-top',
                     str(paperformat_id.margin_top)])

            dpi = None
            if specific_paperformat_args and specific_paperformat_args.get(
                    'data-report-dpi'):
                dpi = int(specific_paperformat_args['data-report-dpi'])
            elif paperformat_id.dpi:
                if os.name == 'nt' and int(paperformat_id.dpi) <= 95:
                    _logger.info(
                        "Generating PDF on Windows platform require DPI >= 96. Using 96 instead."
                    )
                    dpi = 96
                else:
                    dpi = paperformat_id.dpi
            if dpi:
                command_args.extend(['--dpi', str(dpi)])
                if wkhtmltopdf_dpi_zoom_ratio:
                    command_args.extend(['--zoom', str(96.0 / dpi)])

            if specific_paperformat_args and specific_paperformat_args.get(
                    'data-report-header-spacing'):
                command_args.extend([
                    '--header-spacing',
                    str(specific_paperformat_args['data-report-header-spacing']
                        )
                ])
            elif paperformat_id.header_spacing:
                command_args.extend(
                    ['--header-spacing',
                     str(paperformat_id.header_spacing)])

            command_args.extend(
                ['--margin-left',
                 str(paperformat_id.margin_left)])
            command_args.extend(
                ['--margin-bottom',
                 str(paperformat_id.margin_bottom)])
            command_args.extend(
                ['--margin-right',
                 str(paperformat_id.margin_right)])
            if not landscape and paperformat_id.orientation:
                command_args.extend(
                    ['--orientation',
                     str(paperformat_id.orientation)])
            if paperformat_id.header_line:
                command_args.extend(['--header-line'])

        if landscape:
            command_args.extend(['--orientation', 'landscape'])

        return command_args

    @api.multi
    def _prepare_html(self, html):
        '''Divide and recreate the header/footer html by merging all found in html.
        The bodies are extracted and added to a list. Then, extract the specific_paperformat_args.
        The idea is to put all headers/footers together. Then, we will use a javascript trick
        (see minimal_layout template) to set the right header/footer during the processing of wkhtmltopdf.
        This allows the computation of multiple reports in a single call to wkhtmltopdf.

        :param html: The html rendered by render_qweb_html.
        :type: bodies: list of string representing each one a html body.
        :type header: string representing the html header.
        :type footer: string representing the html footer.
        :type specific_paperformat_args: dictionary of prioritized paperformat values.
        :return: bodies, header, footer, specific_paperformat_args
        '''
        IrConfig = self.env['ir.config_parameter'].sudo()
        base_url = IrConfig.get_param('report.url') or IrConfig.get_param(
            'web.base.url')

        # Return empty dictionary if 'web.minimal_layout' not found.
        layout = self.env.ref('web.minimal_layout', False)
        if not layout:
            return {}
        layout = self.env['ir.ui.view'].browse(
            self.env['ir.ui.view'].get_view_id('web.minimal_layout'))

        root = lxml.html.fromstring(html)
        match_klass = "//div[contains(concat(' ', normalize-space(@class), ' '), ' {} ')]"

        header_node = etree.Element('div', id='minimal_layout_report_headers')
        footer_node = etree.Element('div', id='minimal_layout_report_footers')
        bodies = []
        res_ids = []

        body_parent = root.xpath('//main')[0]
        # Retrieve headers
        for node in root.xpath(match_klass.format('header')):
            body_parent = node.getparent()
            node.getparent().remove(node)
            header_node.append(node)

        # Retrieve footers
        for node in root.xpath(match_klass.format('footer')):
            body_parent = node.getparent()
            node.getparent().remove(node)
            footer_node.append(node)

        # Retrieve bodies
        for node in root.xpath(match_klass.format('article')):
            layout_with_lang = layout
            # set context language to body language
            if node.get('data-oe-lang'):
                layout_with_lang = layout_with_lang.with_context(
                    lang=node.get('data-oe-lang'))
            body = layout_with_lang.render(
                dict(subst=False,
                     body=lxml.html.tostring(node),
                     base_url=base_url))
            bodies.append(body)
            if node.get('data-oe-model') == self.model:
                res_ids.append(int(node.get('data-oe-id', 0)))
            else:
                res_ids.append(None)

        if not bodies:
            body = bytearray().join(
                [lxml.html.tostring(c) for c in body_parent.getchildren()])
            bodies.append(body)

        # Get paperformat arguments set in the root html tag. They are prioritized over
        # paperformat-record arguments.
        specific_paperformat_args = {}
        for attribute in root.items():
            if attribute[0].startswith('data-report-'):
                specific_paperformat_args[attribute[0]] = attribute[1]

        header = layout.render(
            dict(subst=True,
                 body=lxml.html.tostring(header_node),
                 base_url=base_url))
        footer = layout.render(
            dict(subst=True,
                 body=lxml.html.tostring(footer_node),
                 base_url=base_url))

        return bodies, res_ids, header, footer, specific_paperformat_args

    @api.model
    def _run_wkhtmltopdf(self,
                         bodies,
                         header=None,
                         footer=None,
                         landscape=False,
                         specific_paperformat_args=None,
                         set_viewport_size=False):
        '''Execute wkhtmltopdf as a subprocess in order to convert html given in input into a pdf
        document.

        :param bodies: The html bodies of the report, one per page.
        :param header: The html header of the report containing all headers.
        :param footer: The html footer of the report containing all footers.
        :param landscape: Force the pdf to be rendered under a landscape format.
        :param specific_paperformat_args: dict of prioritized paperformat arguments.
        :param set_viewport_size: Enable a viewport sized '1024x1280' or '1280x1024' depending of landscape arg.
        :return: Content of the pdf as a string
        '''
        paperformat_id = self.get_paperformat()

        # Build the base command args for wkhtmltopdf bin
        command_args = self._build_wkhtmltopdf_args(
            paperformat_id,
            landscape,
            specific_paperformat_args=specific_paperformat_args,
            set_viewport_size=set_viewport_size)

        files_command_args = []
        temporary_files = []
        if header:
            head_file_fd, head_file_path = tempfile.mkstemp(
                suffix='.html', prefix='report.header.tmp.')
            with closing(os.fdopen(head_file_fd, 'wb')) as head_file:
                head_file.write(header)
            temporary_files.append(head_file_path)
            files_command_args.extend(['--header-html', head_file_path])
        if footer:
            foot_file_fd, foot_file_path = tempfile.mkstemp(
                suffix='.html', prefix='report.footer.tmp.')
            with closing(os.fdopen(foot_file_fd, 'wb')) as foot_file:
                foot_file.write(footer)
            temporary_files.append(foot_file_path)
            files_command_args.extend(['--footer-html', foot_file_path])

        paths = []
        for i, body in enumerate(bodies):
            prefix = '%s%d.' % ('report.body.tmp.', i)
            body_file_fd, body_file_path = tempfile.mkstemp(suffix='.html',
                                                            prefix=prefix)
            with closing(os.fdopen(body_file_fd, 'wb')) as body_file:
                body_file.write(body)
            paths.append(body_file_path)
            temporary_files.append(body_file_path)

        pdf_report_fd, pdf_report_path = tempfile.mkstemp(suffix='.pdf',
                                                          prefix='report.tmp.')
        os.close(pdf_report_fd)
        temporary_files.append(pdf_report_path)

        try:
            wkhtmltopdf = [
                _get_wkhtmltopdf_bin()
            ] + command_args + files_command_args + paths + [pdf_report_path]
            process = subprocess.Popen(wkhtmltopdf,
                                       stdout=subprocess.PIPE,
                                       stderr=subprocess.PIPE)
            out, err = process.communicate()

            if process.returncode not in [0, 1]:
                if process.returncode == -11:
                    message = _(
                        'Wkhtmltopdf failed (error code: %s). Memory limit too low or maximum file number of subprocess reached. Message : %s'
                    )
                else:
                    message = _(
                        'Wkhtmltopdf failed (error code: %s). Message: %s')
                raise UserError(message %
                                (str(process.returncode), err[-1000:]))
            else:
                if err:
                    _logger.warning('wkhtmltopdf: %s' % err)
        except:
            raise

        with open(pdf_report_path, 'rb') as pdf_document:
            pdf_content = pdf_document.read()

        # Manual cleanup of the temporary files
        for temporary_file in temporary_files:
            try:
                os.unlink(temporary_file)
            except (OSError, IOError):
                _logger.error('Error when trying to remove file %s' %
                              temporary_file)

        return pdf_content

    @api.model
    def _get_report_from_name(self, report_name):
        """Get the first record of ir.actions.report having the ``report_name`` as value for
        the field report_name.
        """
        report_obj = self.env['ir.actions.report']
        conditions = [('report_name', '=', report_name)]
        context = self.env['res.users'].context_get()
        return report_obj.with_context(context).search(conditions, limit=1)

    @api.model
    def barcode(self,
                barcode_type,
                value,
                width=600,
                height=100,
                humanreadable=0):
        if barcode_type == 'UPCA' and len(value) in (11, 12, 13):
            barcode_type = 'EAN13'
            if len(value) in (11, 12):
                value = '0%s' % value
        try:
            width, height, humanreadable = int(width), int(height), bool(
                int(humanreadable))
            barcode = createBarcodeDrawing(barcode_type,
                                           value=value,
                                           format='png',
                                           width=width,
                                           height=height,
                                           humanReadable=humanreadable)
            return barcode.asString('png')
        except (ValueError, AttributeError):
            if barcode_type == 'Code128':
                raise ValueError("Cannot convert into barcode.")
            else:
                return self.barcode('Code128',
                                    value,
                                    width=width,
                                    height=height,
                                    humanreadable=humanreadable)

    @api.multi
    def render_template(self, template, values=None):
        """Allow to render a QWeb template python-side. This function returns the 'ir.ui.view'
        render but embellish it with some variables/methods used in reports.
        :param values: additionnal methods/variables used in the rendering
        :returns: html representation of the template
        """
        if values is None:
            values = {}

        context = dict(self.env.context,
                       inherit_branding=values.get('enable_editor'))

        # Browse the user instead of using the sudo self.env.user
        user = self.env['res.users'].browse(self.env.uid)
        website = None
        if request and hasattr(request, 'website'):
            if request.website is not None:
                website = request.website
                context = dict(context,
                               translatable=context.get('lang') !=
                               request.env['ir.http']._get_default_lang().code)

        view_obj = self.env['ir.ui.view'].with_context(context)
        values.update(
            time=time,
            context_timestamp=lambda t: fields.Datetime.context_timestamp(
                self.with_context(tz=user.tz), t),
            editable=values.get('enable_editor'),
            user=user,
            res_company=user.company_id,
            website=website,
            web_base_url=self.env['ir.config_parameter'].sudo().get_param(
                'web.base.url', default=''),
        )
        return view_obj.render_template(template, values)

    @api.multi
    def _post_pdf(self, save_in_attachment, pdf_content=None, res_ids=None):
        '''Merge the existing attachments by adding one by one the content of the attachments
        and then, we add the pdf_content if exists. Create the attachments for each record individually
        if required.

        :param save_in_attachment: The retrieved attachments as map record.id -> attachment_id.
        :param pdf_content: The pdf content newly generated by wkhtmltopdf.
        :param res_ids: the ids of record to allow postprocessing.
        :return: The pdf content of the merged pdf.
        '''
        def close_streams(streams):
            for stream in streams:
                try:
                    stream.close()
                except Exception:
                    pass

        # Check special case having only one record with existing attachment.
        if len(save_in_attachment) == 1 and not pdf_content:
            return base64.decodestring(
                list(save_in_attachment.values())[0].datas)

        # Create a list of streams representing all sub-reports part of the final result
        # in order to append the existing attachments and the potentially modified sub-reports
        # by the postprocess_pdf_report calls.
        streams = []

        # In wkhtmltopdf has been called, we need to split the pdf in order to call the postprocess method.
        if pdf_content:
            pdf_content_stream = io.BytesIO(pdf_content)
            # Build a record_map mapping id -> record
            record_map = {
                r.id: r
                for r in self.env[self.model].browse(
                    [res_id for res_id in res_ids if res_id])
            }

            # If no value in attachment or no record specified, only append the whole pdf.
            if not record_map or not self.attachment:
                streams.append(pdf_content_stream)
            else:
                if len(res_ids) == 1:
                    # Only one record, so postprocess directly and append the whole pdf.
                    if res_ids[0] in record_map and not res_ids[
                            0] in save_in_attachment:
                        new_stream = self.postprocess_pdf_report(
                            record_map[res_ids[0]], pdf_content_stream)
                        # If the buffer has been modified, mark the old buffer to be closed as well.
                        if new_stream and new_stream != pdf_content_stream:
                            close_streams([pdf_content_stream])
                            pdf_content_stream = new_stream
                    streams.append(pdf_content_stream)
                else:
                    # In case of multiple docs, we need to split the pdf according the records.
                    # To do so, we split the pdf based on outlines computed by wkhtmltopdf.
                    # An outline is a <h?> html tag found on the document. To retrieve this table,
                    # we look on the pdf structure using pypdf to compute the outlines_pages that is
                    # an array like [0, 3, 5] that means a new document start at page 0, 3 and 5.
                    reader = PdfFileReader(pdf_content_stream)
                    if reader.trailer['/Root'].get('/Dests'):
                        outlines_pages = sorted([
                            outline.getObject()[0] for outline in
                            reader.trailer['/Root']['/Dests'].values()
                        ])
                        assert len(outlines_pages) == len(res_ids)
                        for i, num in enumerate(outlines_pages):
                            to = outlines_pages[i + 1] if i + 1 < len(
                                outlines_pages) else reader.numPages
                            attachment_writer = PdfFileWriter()
                            for j in range(num, to):
                                attachment_writer.addPage(reader.getPage(j))
                            stream = io.BytesIO()
                            attachment_writer.write(stream)
                            if res_ids[i] and res_ids[
                                    i] not in save_in_attachment:
                                new_stream = self.postprocess_pdf_report(
                                    record_map[res_ids[i]], stream)
                                # If the buffer has been modified, mark the old buffer to be closed as well.
                                if new_stream and new_stream != stream:
                                    close_streams([stream])
                                    stream = new_stream
                            streams.append(stream)
                        close_streams([pdf_content_stream])
                    else:
                        # If no outlines available, do not save each record
                        streams.append(pdf_content_stream)

        # If attachment_use is checked, the records already having an existing attachment
        # are not been rendered by wkhtmltopdf. So, create a new stream for each of them.
        if self.attachment_use:
            for attachment_id in save_in_attachment.values():
                content = base64.decodestring(attachment_id.datas)
                streams.append(io.BytesIO(content))

        # Build the final pdf.
        # If only one stream left, no need to merge them (and then, preserve embedded files).
        if len(streams) == 1:
            result = streams[0].getvalue()
        else:
            writer = PdfFileWriter()
            for stream in streams:
                reader = PdfFileReader(stream)
                writer.appendPagesFromReader(reader)
            result_stream = io.BytesIO()
            streams.append(result_stream)
            writer.write(result_stream)
            result = result_stream.getvalue()

        # We have to close the streams after PdfFileWriter's call to write()
        close_streams(streams)
        return result

    @api.multi
    def render_qweb_pdf(self, res_ids=None, data=None):
        if not data:
            data = {}
        data.setdefault('report_type', 'pdf')

        # remove editor feature in pdf generation
        data.update(enable_editor=False)

        # In case of test environment without enough workers to perform calls to wkhtmltopdf,
        # fallback to render_html.
        if (tools.config['test_enable'] or tools.config['test_file']
            ) and not self.env.context.get('force_report_rendering'):
            return self.render_qweb_html(res_ids, data=data)

        # As the assets are generated during the same transaction as the rendering of the
        # templates calling them, there is a scenario where the assets are unreachable: when
        # you make a request to read the assets while the transaction creating them is not done.
        # Indeed, when you make an asset request, the controller has to read the `ir.attachment`
        # table.
        # This scenario happens when you want to print a PDF report for the first time, as the
        # assets are not in cache and must be generated. To workaround this issue, we manually
        # commit the writes in the `ir.attachment` table. It is done thanks to a key in the context.
        context = dict(self.env.context)
        if not config['test_enable']:
            context['commit_assetsbundle'] = True

        # Disable the debug mode in the PDF rendering in order to not split the assets bundle
        # into separated files to load. This is done because of an issue in wkhtmltopdf
        # failing to load the CSS/Javascript resources in time.
        # Without this, the header/footer of the reports randomly disapear
        # because the resources files are not loaded in time.
        # https://github.com/wkhtmltopdf/wkhtmltopdf/issues/2083
        context['debug'] = False

        # The test cursor prevents the use of another environnment while the current
        # transaction is not finished, leading to a deadlock when the report requests
        # an asset bundle during the execution of test scenarios. In this case, return
        # the html version.
        if isinstance(self.env.cr, TestCursor):
            return self.with_context(context).render_qweb_html(res_ids,
                                                               data=data)[0]

        save_in_attachment = OrderedDict()
        if res_ids:
            # Dispatch the records by ones having an attachment and ones requesting a call to
            # wkhtmltopdf.
            Model = self.env[self.model]
            record_ids = Model.browse(res_ids)
            wk_record_ids = Model
            if self.attachment:
                for record_id in record_ids:
                    attachment_id = self.retrieve_attachment(record_id)
                    if attachment_id:
                        save_in_attachment[record_id.id] = attachment_id
                    if not self.attachment_use or not attachment_id:
                        wk_record_ids += record_id
            else:
                wk_record_ids = record_ids
            res_ids = wk_record_ids.ids

        # A call to wkhtmltopdf is mandatory in 2 cases:
        # - The report is not linked to a record.
        # - The report is not fully present in attachments.
        if save_in_attachment and not res_ids:
            _logger.info('The PDF report has been generated from attachments.')
            return self._post_pdf(save_in_attachment), 'pdf'

        if self.get_wkhtmltopdf_state() == 'install':
            # wkhtmltopdf is not installed
            # the call should be catched before (cf /report/check_wkhtmltopdf) but
            # if get_pdf is called manually (email template), the check could be
            # bypassed
            raise UserError(
                _("Unable to find Wkhtmltopdf on this system. The PDF can not be created."
                  ))

        html = self.with_context(context).render_qweb_html(res_ids,
                                                           data=data)[0]

        # Ensure the current document is utf-8 encoded.
        html = html.decode('utf-8')

        bodies, html_ids, header, footer, specific_paperformat_args = self.with_context(
            context)._prepare_html(html)

        if self.attachment and set(res_ids) != set(html_ids):
            raise UserError(
                _("The report's template '%s' is wrong, please contact your administrator. \n\n"
                  "Can not separate file to save as attachment because the report's template does not contains the attributes 'data-oe-model' and 'data-oe-id' on the div with 'article' classname."
                  ) % self.name)

        pdf_content = self._run_wkhtmltopdf(
            bodies,
            header=header,
            footer=footer,
            landscape=context.get('landscape'),
            specific_paperformat_args=specific_paperformat_args,
            set_viewport_size=context.get('set_viewport_size'),
        )
        if res_ids:
            _logger.info('The PDF report has been generated for records %s.' %
                         (str(res_ids)))
            return self._post_pdf(save_in_attachment,
                                  pdf_content=pdf_content,
                                  res_ids=html_ids), 'pdf'
        return pdf_content, 'pdf'

    @api.model
    def render_qweb_text(self, docids, data=None):
        if not data:
            data = {}
        data.setdefault('report_type', 'text')
        data = self._get_rendering_context(docids, data)
        return self.render_template(self.report_name, data), 'text'

    @api.model
    def render_qweb_html(self, docids, data=None):
        """This method generates and returns html version of a report.
        """
        if not data:
            data = {}
        data.setdefault('report_type', 'html')
        data = self._get_rendering_context(docids, data)
        return self.render_template(self.report_name, data), 'html'

    @api.model
    def _get_rendering_context_model(self):
        report_model_name = 'report.%s' % self.report_name
        return self.env.get(report_model_name)

    @api.model
    def _get_rendering_context(self, docids, data):
        # If the report is using a custom model to render its html, we must use it.
        # Otherwise, fallback on the generic html rendering.
        report_model = self._get_rendering_context_model()

        data = data and dict(data) or {}

        if report_model is not None:
            data.update(report_model._get_report_values(docids, data=data))
        else:
            docs = self.env[self.model].browse(docids)
            data.update({
                'doc_ids': docids,
                'doc_model': self.model,
                'docs': docs,
            })
        return data

    @api.multi
    def render(self, res_ids, data=None):
        report_type = self.report_type.lower().replace('-', '_')
        render_func = getattr(self, 'render_' + report_type, None)
        if not render_func:
            return None
        return render_func(res_ids, data=data)

    @api.noguess
    def report_action(self, docids, data=None, config=True):
        """Return an action of type ir.actions.report.

        :param docids: id/ids/browserecord of the records to print (if not used, pass an empty list)
        :param report_name: Name of the template to generate an action for
        """
        discard_logo_check = self.env.context.get('discard_logo_check')
        if (self.env.user._is_admin()) and (
            (not self.env.user.company_id.external_report_layout_id) or
            (not discard_logo_check
             and not self.env.user.company_id.logo)) and config:
            template = self.env.ref('base.view_company_report_form_with_print'
                                    ) if self.env.context.get(
                                        'from_transient_model',
                                        False) else self.env.ref(
                                            'base.view_company_report_form')
            return {
                'name': _('Choose Your Document Layout'),
                'type': 'ir.actions.act_window',
                'context': {
                    'default_report_name': self.report_name,
                    'discard_logo_check': True
                },
                'view_type': 'form',
                'view_mode': 'form',
                'res_id': self.env.user.company_id.id,
                'res_model': 'res.company',
                'views': [(template.id, 'form')],
                'view_id': template.id,
                'target': 'new',
            }

        context = self.env.context
        if docids:
            if isinstance(docids, models.Model):
                active_ids = docids.ids
            elif isinstance(docids, int):
                active_ids = [docids]
            elif isinstance(docids, list):
                active_ids = docids
            context = dict(self.env.context, active_ids=active_ids)

        return {
            'context': context,
            'data': data,
            'type': 'ir.actions.report',
            'report_name': self.report_name,
            'report_type': self.report_type,
            'report_file': self.report_file,
            'name': self.name,
        }
示例#11
0
class SaasPlans(models.Model):
    _name = "saas.plan"
    _order = "id desc"
    _description = 'Class for managing SaaS subscription plans.'

    @api.depends('name')
    def _compute_db_template_name(self):
        for obj in self:
            if obj.name and type(obj.id) != NewId and not obj.db_template:
                template_name = obj.name.lower().replace(" ", "_")
                obj.db_template = "{}_tid_{}".format(template_name, obj.id)

    def _default_saas_server(self):
        saas_servers = self.env['saas.server'].search([])
        if saas_servers:
            return saas_servers[0].id
        return False

    def _get_contract_count(self):
        for obj in self:
            contracts = self.env['saas.contract'].search([('plan_id', '=',
                                                           obj.id)])
            obj.contract_count = len(contracts)

    def action_view_contracts(self):
        contracts = self.env['saas.contract'].search([('plan_id', '=', self.id)
                                                      ])

        action = self.env.ref('eagle_saas_kit.saas_contract_action').read()[0]
        if len(contracts) > 1:
            action['domain'] = [('id', 'in', contracts.ids)]
        elif len(contracts) == 1:
            action['views'] = [
                (self.env.ref('eagle_saas_kit.saas_contract_form_view').id,
                 'form')
            ]
            action['res_id'] = contracts.ids[0]
        else:
            action = {'type': 'ir.actions.act_window_close'}
        return action

    @api.onchange('server_id')
    def server_id_change(self):
        for obj in self:
            obj.saas_base_url = obj.server_id.server_domain

    name = fields.Char(string='Plan', required=True)
    saas_base_url = fields.Char(string="SaaS Domain(Base URL)", required=True)
    image = fields.Binary(string='Image')
    summary = fields.Char(string="Plan Summary")
    expiration = fields.Integer('Expiration (hours)',
                                help='time to delete database. Use for demo')
    grace_period = fields.Integer('Grace period (days)',
                                  help='initial days before expiration')
    product_template_ids = fields.One2many(comodel_name="product.template",
                                           string="Linked Products",
                                           inverse_name="saas_plan_id")
    use_specific_user_template = fields.Boolean(
        string="Use Specific User Template",
        help=
        """Select if you want to provide some specific permissions to your user for acessing its eagle instance which is going to be created by this plan."""
    )
    template_user_id = fields.Char(
        string="Database Template User ID",
        help=
        """Enter the user_id of User which you have created in the DB Template with some specific permissions or whose permission you want to grant to the user of eagle instances which is going to be created by this plan."""
    )
    saas_module_ids = fields.Many2many(comodel_name="saas.module",
                                       relation="saas_plan_module_relation",
                                       column1="plan_id",
                                       column2="module_id",
                                       string="Related Modules")
    description = fields.Text('Plan Description')
    recurring_interval = fields.Integer(default=1,
                                        string='Default Billing Cycle')
    recurring_rule_type = fields.Selection([
        ('daily', 'Day(s)'),
        ('weekly', 'Week(s)'),
        ('monthly', 'Month(s)'),
        ('monthlylastday', 'Month(s) last day'),
        ('yearly', 'Year(s)'),
    ],
                                           default='monthly',
                                           string='Recurrence',
                                           readonly=True)
    # total_cycles = fields.Integer(string="Number of Cycles", default=1)
    trial_period = fields.Integer(string="Complimentary(Free) days", default=0)
    server_id = fields.Many2one(comodel_name="saas.server",
                                string="SaaS Server",
                                default=_default_saas_server,
                                domain=[('state', '=', 'confirm')])
    db_template = fields.Char(
        compute='_compute_db_template_name',
        string="DB Template Name",
        store=True,
        help=
        "Enter a uniquie name to create a DB associated to this plan or leave it blank and let eagle to give it a unique name."
    )
    container_id = fields.Char(string="Instance ID")
    state = fields.Selection(selection=STATE, string="States", default="draft")
    contract_count = fields.Integer(string='Contract Count',
                                    compute='_get_contract_count',
                                    readonly=True)
    billing_criteria = fields.Selection(selection=BILLING_CRITERIA,
                                        string="Default Billing Criteria",
                                        required=True,
                                        default="fixed")
    per_user_pricing = fields.Boolean(
        string="User Based Pricing",
        help="Used to enable the per user costing of end user's instance")
    user_cost = fields.Float(help="PUPC(Per User Per Cycle cost)")
    min_users = fields.Integer(
        string="Min. No. of user",
        help=
        "Minimum number of users whose cost client have to pay either created or not",
        default="1")
    max_users = fields.Integer(
        string="Max. No. of user",
        help=
        "End user is not allowed to create user more than Maximum number of user limit. Enter -1 to allow user to create infinte number of user.",
        default="1")
    due_users_price = fields.Float(string="Due users price", default="1.0")
    user_product = fields.Many2one(
        comodel_name="product.product",
        string="Product for user calculation",
        help="Select a product for calculation costing user pricing.",
        domain="[('is_user_pricing', '=', True)]")

    @api.onchange('max_users')
    def check_max_user(self):
        for obj in self:
            if obj.max_users != -1 and obj.max_users < obj.min_users:
                raise UserError(
                    "Max. No. of users must be greater than or Equal to Min. no. of users"
                )
            else:
                obj.max_users = obj.max_users

    @api.onchange('min_users')
    def check_min_users(self):
        for obj in self:
            if obj.min_users < 1:
                raise UserError("Min. No. of users can't be less than 1")
            if obj.min_users > obj.max_users:
                raise UserError(
                    "Max. No. of users must be greater than or Equal to Min. no. of users"
                )

    def reset_to_draft(self):
        for obj in self:
            contracts = self.env['saas.contract'].search([('plan_id', '=',
                                                           obj.id)])
            if contracts:
                raise UserError(
                    "This plan has some conracts associated with it!")
            obj.state = 'draft'

    def login_to_db_template(self):
        for obj in self:
            host_server, db_server = obj.server_id.get_server_details()
            response = query.get_credentials(obj.db_template,
                                             host_server=host_server,
                                             db_server=db_server)
            if response:
                login = response[0][0]
                password = response[0][1]
                login_url = "http://db13_templates.{}/saas/login?db={}&login={}&passwd={}".format(
                    obj.saas_base_url, obj.db_template, login, password)

                _logger.info("$$$$$$$$$$$$$$%r", login_url)
                return {
                    'type': 'ir.actions.act_url',
                    'url': login_url,
                    'target': 'new',
                }
            else:
                raise UserError("Unknown Error!")

    def restart_db_template(self):
        for obj in self:
            host_server, db_server = obj.server_id.get_server_details()
            response_flag = containers.action(operation="restart",
                                              container_id=obj.container_id,
                                              host_server=host_server,
                                              db_server=db_server)
            if not response_flag:
                raise UserError("Operation Failed! Unknown Error!")

    def force_confirm(self):
        for obj in self:
            response = None
            if not obj.container_id:
                _, db_server = obj.server_id.get_server_details()
                response = query.is_db_exist(obj.db_template,
                                             db_server=db_server)
                if not response:
                    raise Warning("Please create DB Template First!")
            obj.state = 'confirm'

    def create_db_template(self):
        for obj in self:
            if not obj.db_template:
                raise UserError("Please select the DB template name first.")
            if re.match("^template_", obj.db_template):
                raise UserError(
                    "Couldn't Create DB. Please try again with some other Template Name!"
                )
            db_template_name = "template_{}".format(obj.db_template)
            modules = [module.technical_name for module in obj.saas_module_ids]
            config_path = get_module_resource('eagle_saas_kit')
            modules.append('wk_saas_tool')
            try:
                host_server, db_server = obj.server_id.get_server_details()
                response = saas.create_db_template(
                    db_template=db_template_name,
                    modules=modules,
                    config_path=config_path,
                    host_server=host_server,
                    db_server=db_server)
            except Exception as e:
                _logger.info("--------DB-TEMPLATE-CREATION-EXCEPTION-------%r",
                             e)
                raise UserError(e)
            else:
                if response:
                    if response.get('status', False):
                        obj.db_template = db_template_name
                        obj.state = 'confirm'
                        obj.container_id = response.get('container_id', False)
                    else:
                        msg = response.get('msg', False)
                        if msg:
                            raise UserError(msg)
                        else:
                            raise UserError(
                                "Unknown Error. Please try again later with some different Template Name"
                            )
                else:
                    raise UserError(
                        "No Response. Please try again later with some different Template Name"
                    )

    def unlink(self):
        for obj in self:
            if obj.contract_count:
                raise UserError(
                    "Error: You must delete the associated SaaS Contracts first!"
                )
        return super(SaasPlans, self).unlink()

    @api.model
    def create(self, vals):
        if vals.get('recurring_interval', 0) <= 0:
            raise Warning("Default Billing Cycle can't be less than 1")
        res = super(SaasPlans, self).create(vals)
        for obj in res:
            if obj.name and not obj.db_template:
                template_name = obj.name.lower().replace(" ", "_")
                obj.db_template = "{}_tid_{}".format(template_name, res.id)
        return res

    def write(self, vals):
        if vals.get('recurring_interval',
                    False) and vals['recurring_interval'] <= 0:
            raise Warning("Default Billing Cycle can't be less than 1")
        res = super(SaasPlans, self).write(vals)
        return res
示例#12
0
class EducationGroup(models.Model):
    _name = 'education.group'
    _inherit = 'education.data'
    _description = 'Education Group'
    _rec_name = 'description'
    _order = 'academic_year_id, education_code'

    academic_year_id = fields.Many2one(
        comodel_name='education.academic_year', string='Academic Year',
        required=True, copy=False)
    center_id = fields.Many2one(
        comodel_name='res.partner', string='Education Center',
        required=True)
    plan_id = fields.Many2one(
        comodel_name='education.plan', string='Plan')
    level_id = fields.Many2one(
        comodel_name='education.level', string='Level',
        domain="[('plan_id', '=', plan_id)]", required=True)
    field_id = fields.Many2one(
        comodel_name='education.field', string='Study Field')
    classroom_id = fields.Many2one(
        comodel_name='education.classroom', string='Classroom',
        domain="[('center_id', '=', center_id)]")
    shift_id = fields.Many2one(
        comodel_name='education.shift', string='Shift')
    course_id = fields.Many2one(
        comodel_name='education.course', string='Course',
        domain="[('plan_id', '=', plan_id), ('level_id', '=', level_id),"
               "('field_id', '=', field_id), ('shift_id', '=', shift_id)]")
    model_id = fields.Many2one(
        comodel_name='education.model', string='Educational Model')
    group_type_id = fields.Many2one(
        comodel_name='education.group_type', string='Educational Group Type')
    calendar_id = fields.Many2one(
        comodel_name='resource.calendar', string='Calendar',
        domain="[('center_id', '=', center_id)]")
    comments = fields.Text(string='Comments')
    teacher_ids = fields.One2many(
        comodel_name='education.group.teacher',
        inverse_name='group_id', string='Teachers')
    session_ids = fields.One2many(
        comodel_name='education.group.session', inverse_name='group_id',
        string='Sessions')
    student_ids = fields.Many2many(
        comodel_name='res.partner', relation='edu_group_student',
        column1='group_id', column2='student_id', string='Students')
    student_count = fields.Integer(
        string='Student Number', compute='_compute_student_count', store=True)
    parent_id = fields.Many2one(
        comodel_name='education.group', string='Parent Group',
        domain="[('academic_year_id', '=', academic_year_id),"
               "('center_id', '=', center_id),"
               "('course_id', '=', course_id),"
               "('group_type_id.type', '=', 'official')]")
    schedule_ids = fields.Many2many(
        comodel_name='education.schedule', string='Education Schedule',
        relation='edu_schedule_group', column2='schedule_id',
        column1='group_id', readonly=True)
    schedule_count = fields.Integer(
        compute='_compute_schedule_count', string='Schedule Number')

    _sql_constraints = [
        ('education_code_unique',
         'unique(education_code,center_id,academic_year_id)',
         'Education code must be unique per center and academic year!'),
    ]

    @api.constrains('parent_id')
    def _check_group_recursion(self):
        if not self._check_recursion():
            raise ValidationError(_('You cannot create recursive groups.'))

    @api.depends('student_ids')
    def _compute_student_count(self):
        for record in self:
            record.student_count = len(record.student_ids)

    @api.depends('schedule_ids')
    def _compute_schedule_count(self):
        for record in self:
            record.schedule_count = len(record.schedule_ids)

    @api.multi
    def button_open_schedule(self):
        action = self.env.ref('education.action_education_schedule_from_group')
        action_dict = action.read()[0] if action else {}
        domain = expression.AND([
            [('id', 'in', self.mapped('schedule_ids').ids)],
            safe_eval(action.domain or '[]')])
        action_dict.update({
            'domain': domain,
        })
        return action_dict

    @api.multi
    def button_open_students(self):
        action = self.env.ref('base.action_partner_form')
        action_dict = action.read()[0] if action else {}
        domain = expression.AND([
            [('id', 'in', self.mapped('student_ids').ids)],
            safe_eval(action.domain or '[]')
        ])
        context = safe_eval(action.context or '[]')
        context.pop('search_default_customer')
        action_dict.update({
            'display_name': _('Students'),
            'domain': domain,
            'context': context,
        })
        return action_dict
示例#13
0
class EagleeduSyllabus(models.Model):
    _name = 'eagleedu.syllabus'
    _description = "Syllabus "
    _rec_name = 'display'
    _order = 'sequence'

    name = fields.Char(string='Name', help="Enter the Name of the Syllabus")
    # syllabus_code = fields.Char(string='Syllabus Code', compute="_get_code")
    display = fields.Char('Syllabus Display',
                          help="This is printed on the marksheet as Subject")
    class_id = fields.Many2one('eagleedu.class', string='Class ID')
    subject_id = fields.Many2one('eagleedu.subject',
                                 string='Subject',
                                 copy=False)
    academic_year = fields.Many2one('eagleedu.academic.year',
                                    string='Academic Year')
    code = fields.Char('Code', compute="_get_code")
    sequence = fields.Integer(
        default=0, help="Gives the sequence order when displaying a list.")

    #for copy all the value as product template
    # attribute_line_ids = fields.One2many('eagleedu.syllabus', 'class_id', copy=True)

    # has_group=fields.Integer(related='class_id.division_count')
    divisional = fields.Boolean("Grouping ?")
    division_id = fields.Many2one('eagleedu.group_division', string='Group')

    paper = fields.Char(string='Paper')
    active = fields.Boolean('Active?', related='academic_year.active')
    compulsory_for = fields.Many2many('eagleedu.class.history',
                                      'eagleedu_syllabus_class_history_rel',
                                      'compulsory_for', 'compulsory_subjects',
                                      'compulsory for')
    selective_for = fields.Many2many('eagleedu.class.history',
                                     'eagleedu_syllabus_class_history_1_rel',
                                     'selective_for', 'selective_subjects',
                                     'selective for')
    optional_for = fields.Many2many(
        'eagleedu.class.history',
        'eagleedu_syllabus_class_history_optional_rel', 'optional_for',
        'optional_subjects', 'Optional for')

    subject_type = fields.Selection([('theory', 'Theory'),
                                     ('practical', 'Practical'),
                                     ('both', 'Both'), ('other', 'Other')],
                                    'Subject Type',
                                    default="theory",
                                    required=True)
    selection_type = fields.Selection([('compulsory', 'Compulsory'),
                                       ('elective', 'Elective')],
                                      'Selection Type',
                                      default="compulsory",
                                      required=True)
    evaluation_type = fields.Selection([('general', 'General'),
                                        ('extra', 'Extra')],
                                       'Evaluation Type',
                                       default="general",
                                       required=True)

    # total_hours = fields.Float(string='Total Hours')
    total_mark = fields.Integer('Total')
    pass_mark = fields.Integer('Pass')
    tut_mark = fields.Integer('Tutorial')
    tut_pass = fields.Integer('pass')
    subj_mark = fields.Integer('Subjective')
    subj_pass = fields.Integer('pass')
    obj_mark = fields.Integer('Objective')
    obj_pass = fields.Integer('pass')
    prac_mark = fields.Integer('Practical')
    prac_pass = fields.Integer('pass')
    description = fields.Text(string='Syllabus Modules')

    @api.onchange('academic_year', 'class_id', 'division_id', 'subject_id',
                  'paper')
    def _get_code(self):
        for rec in self:
            recname = ''
            reccode = ''
            if rec.paper and rec.subject_id:
                recname = rec.subject_id.name + '-' + rec.paper
                reccode = rec.subject_id.code + '-' + rec.paper
            elif rec.subject_id:
                recname = rec.subject_id.name
                reccode = rec.subject_id.code
            rec.display = recname
            if recname != '':
                if rec.class_id:
                    if rec.academic_year:
                        if rec.divisional == True:
                            recname = recname + '-' + rec.class_id.name + '-' + rec.academic_year.name  # +' ('+rec.division_id.name +')'
                            reccode = reccode + '-' + rec.class_id.code + '-' + rec.academic_year.ay_code  # +' ('+rec.division_id.code +')'
                        else:
                            recname = recname + '-' + rec.class_id.name + '-' + rec.academic_year.name
                            reccode = reccode + '-' + rec.class_id.code + '-' + rec.academic_year.ay_code
                            rec.division_id = False
            rec.name = recname
            rec.code = reccode

    @api.model
    @api.onchange('tut_mark', 'subj_mark', 'obj_mark', 'prac_mark', 'tut_pass',
                  'subj_pass', 'obj_pass', 'prac_pass')
    def calculate_total_mark(self):
        for rec in self:
            rec.total_mark = rec.tut_mark + rec.subj_mark + rec.obj_mark + rec.prac_mark
            rec.pass_mark = rec.tut_pass + rec.subj_pass + rec.obj_pass + rec.prac_pass
class BaseLanguageExport(models.TransientModel):
    _name = "base.language.export"
    _description = 'Language Export'

    @api.model
    def _get_languages(self):
        langs = self.env['res.lang'].search([('translatable', '=', True)])
        return [(NEW_LANG_KEY, _('New Language (Empty translation template)'))] + \
               [(lang.code, lang.name) for lang in langs]

    name = fields.Char('File Name', readonly=True)
    lang = fields.Selection(_get_languages,
                            string='Language',
                            required=True,
                            default=NEW_LANG_KEY)
    format = fields.Selection([('csv', 'CSV File'), ('po', 'PO File'),
                               ('tgz', 'TGZ Archive')],
                              string='File Format',
                              required=True,
                              default='csv')
    modules = fields.Many2many('ir.module.module',
                               'rel_modules_langexport',
                               'wiz_id',
                               'module_id',
                               string='Apps To Export',
                               domain=[('state', '=', 'installed')])
    data = fields.Binary('File', readonly=True)
    state = fields.Selection(
        [('choose', 'choose'),
         ('get', 'get')],  # choose language or get the file
        default='choose')

    @api.multi
    def act_getfile(self):
        this = self[0]
        lang = this.lang if this.lang != NEW_LANG_KEY else False
        mods = sorted(this.mapped('modules.name')) or ['all']

        with contextlib.closing(io.BytesIO()) as buf:
            tools.trans_export(lang, mods, buf, this.format, self._cr)
            out = base64.encodestring(buf.getvalue())

        filename = 'new'
        if lang:
            filename = tools.get_iso_codes(lang)
        elif len(mods) == 1:
            filename = mods[0]
        extension = this.format
        if not lang and extension == 'po':
            extension = 'pot'
        name = "%s.%s" % (filename, extension)
        this.write({'state': 'get', 'data': out, 'name': name})
        return {
            'type': 'ir.actions.act_window',
            'res_model': 'base.language.export',
            'view_mode': 'form',
            'view_type': 'form',
            'res_id': this.id,
            'views': [(False, 'form')],
            'target': 'new',
        }
示例#15
0
class Track(models.Model):
    _name = "event.track"
    _description = 'Event Track'
    _order = 'priority, date'
    _inherit = [
        'mail.thread', 'mail.activity.mixin', 'website.seo.metadata',
        'website.published.mixin'
    ]

    @api.model
    def _get_default_stage_id(self):
        return self.env['event.track.stage'].search([], limit=1).id

    name = fields.Char('Title', required=True, translate=True)
    active = fields.Boolean(default=True)
    user_id = fields.Many2one('res.users',
                              'Responsible',
                              track_visibility='onchange',
                              default=lambda self: self.env.user)
    partner_id = fields.Many2one('res.partner', 'Speaker')
    partner_name = fields.Char('Speaker Name')
    partner_email = fields.Char('Speaker Email')
    partner_phone = fields.Char('Speaker Phone')
    partner_biography = fields.Html('Speaker Biography')
    tag_ids = fields.Many2many('event.track.tag', string='Tags')
    stage_id = fields.Many2one('event.track.stage',
                               string='Stage',
                               ondelete='restrict',
                               index=True,
                               copy=False,
                               default=_get_default_stage_id,
                               group_expand='_read_group_stage_ids',
                               required=True,
                               track_visibility='onchange')
    kanban_state = fields.Selection(
        [('normal', 'Grey'), ('done', 'Green'), ('blocked', 'Red')],
        string='Kanban State',
        copy=False,
        default='normal',
        required=True,
        track_visibility='onchange',
        help=
        "A track's kanban state indicates special situations affecting it:\n"
        " * Grey is the default situation\n"
        " * Red indicates something is preventing the progress of this track\n"
        " * Green indicates the track is ready to be pulled to the next stage")
    description = fields.Html('Track Description',
                              translate=html_translate,
                              sanitize_attributes=False)
    date = fields.Datetime('Track Date')
    duration = fields.Float('Duration', default=1.5)
    location_id = fields.Many2one('event.track.location', 'Room')
    event_id = fields.Many2one('event.event', 'Event', required=True)
    color = fields.Integer('Color Index')
    priority = fields.Selection([('0', 'Low'), ('1', 'Medium'), ('2', 'High'),
                                 ('3', 'Highest')],
                                'Priority',
                                required=True,
                                default='1')
    image = fields.Binary('Image',
                          related='partner_id.image_medium',
                          store=True,
                          attachment=True,
                          readonly=False)

    @api.multi
    @api.depends('name')
    def _compute_website_url(self):
        super(Track, self)._compute_website_url()
        for track in self:
            if not isinstance(track.id, models.NewId):
                track.website_url = '/event/%s/track/%s' % (slug(
                    track.event_id), slug(track))

    @api.onchange('partner_id')
    def _onchange_partner_id(self):
        if self.partner_id:
            self.partner_name = self.partner_id.name
            self.partner_email = self.partner_id.email
            self.partner_phone = self.partner_id.phone
            self.partner_biography = self.partner_id.website_description

    @api.model
    def create(self, vals):
        track = super(Track, self).create(vals)

        track.event_id.message_post_with_view(
            'website_event_track.event_track_template_new',
            values={'track': track},
            subject=track.name,
            subtype_id=self.env.ref('website_event_track.mt_event_track').id,
        )

        return track

    @api.multi
    def write(self, vals):
        if 'stage_id' in vals and 'kanban_state' not in vals:
            vals['kanban_state'] = 'normal'
        res = super(Track, self).write(vals)
        if vals.get('partner_id'):
            self.message_subscribe([vals['partner_id']])
        return res

    @api.model
    def _read_group_stage_ids(self, stages, domain, order):
        """ Always display all stages """
        return stages.search([], order=order)

    @api.multi
    def _track_template(self, tracking):
        res = super(Track, self)._track_template(tracking)
        track = self[0]
        changes, tracking_value_ids = tracking[track.id]
        if 'stage_id' in changes and track.stage_id.mail_template_id:
            res['stage_id'] = (track.stage_id.mail_template_id, {
                'composition_mode':
                'comment',
                'auto_delete_message':
                True,
                'subtype_id':
                self.env['ir.model.data'].xmlid_to_res_id('mail.mt_note'),
                'notif_layout':
                'mail.mail_notification_light'
            })
        return res

    @api.multi
    def _track_subtype(self, init_values):
        self.ensure_one()
        if 'kanban_state' in init_values and self.kanban_state == 'blocked':
            return 'website_event_track.mt_track_blocked'
        elif 'kanban_state' in init_values and self.kanban_state == 'done':
            return 'website_event_track.mt_track_ready'
        return super(Track, self)._track_subtype(init_values)

    @api.multi
    def message_get_suggested_recipients(self):
        recipients = super(Track, self).message_get_suggested_recipients()
        for track in self:
            if track.partner_email != track.partner_id.email:
                track._message_add_suggested_recipient(
                    recipients,
                    email=track.partner_email,
                    reason=_('Speaker Email'))
        return recipients

    def _message_post_after_hook(self, message, *args, **kwargs):
        if self.partner_email and not self.partner_id:
            # we consider that posting a message with a specified recipient (not a follower, a specific one)
            # on a document without customer means that it was created through the chatter using
            # suggested recipients. This heuristic allows to avoid ugly hacks in JS.
            new_partner = message.partner_ids.filtered(
                lambda partner: partner.email == self.partner_email)
            if new_partner:
                self.search([
                    ('partner_id', '=', False),
                    ('partner_email', '=', new_partner.email),
                    ('stage_id.is_cancel', '=', False),
                ]).write({'partner_id': new_partner.id})
        return super(Track,
                     self)._message_post_after_hook(message, *args, **kwargs)

    @api.multi
    def open_track_speakers_list(self):
        return {
            'name': _('Speakers'),
            'domain': [('id', 'in', self.mapped('partner_id').ids)],
            'view_type': 'form',
            'view_mode': 'kanban,form',
            'res_model': 'res.partner',
            'view_id': False,
            'type': 'ir.actions.act_window',
        }
示例#16
0
class EducationFaculty(models.Model):
    _name = 'education.faculty'
    _inherit = ['mail.thread']
    _description = 'Faculty Record'

    @api.multi
    def create_employee(self):
        """Creating the employee for the faculty"""
        for rec in self:
            values = {
                'name': rec.name + rec.last_name,
                'gender': rec.gender,
                'birthday': rec.date_of_birth,
                'image': rec.image,
                'work_phone': rec.phone,
                'work_mobile': rec.mobile,
                'work_email': rec.email,
            }
            emp_id = self.env['hr.employee'].create(values)
            rec.employee_id = emp_id.id

    @api.model
    def create(self, vals):
        """Over riding the create method to assign
        the sequence for newly creating records"""
        vals['faculty_id'] = self.env['ir.sequence'].next_by_code(
            'education.faculty')
        res = super(EducationFaculty, self).create(vals)
        return res

    name = fields.Char(string='Name',
                       required=True,
                       help="Enter the first name")
    faculty_id = fields.Char(string="ID", readonly=True)
    last_name = fields.Char(string='Last Name', help="Enter the last name")
    image = fields.Binary(string="Image")
    email = fields.Char(string="Email",
                        help="Enter the Email for contact purpose")
    phone = fields.Char(string="Phone",
                        help="Enter the Phone for contact purpose")
    mobile = fields.Char(string="Mobile",
                         help="Enter the Mobile for contact purpose")
    date_of_birth = fields.Date(string="Date Of birth", help="Enter the DOB")
    guardian_name = fields.Char(string="Guardian", help="Your guardian is ")
    father_name = fields.Char(string="Father", help="Your Father name is ")
    mother_name = fields.Char(string="Mother", help="Your Mother name is ")
    subject_lines = fields.Many2many('education.subject',
                                     string='Subject Lines')
    employee_id = fields.Many2one('hr.employee', string="Related Employee")
    degree = fields.Many2one('hr.recruitment.degree',
                             string="Degree",
                             Help="Select your Highest degree")
    gender = fields.Selection([('male', 'Male'), ('female', 'Female'),
                               ('other', 'Other')],
                              string='Gender',
                              required=True,
                              default='male',
                              track_visibility='onchange')
    blood_group = fields.Selection([('a+', 'A+'), ('a-', 'A-'), ('b+', 'B+'),
                                    ('o+', 'O+'), ('o-', 'O-'), ('ab-', 'AB-'),
                                    ('ab+', 'AB+')],
                                   string='Blood Group',
                                   required=True,
                                   default='a+',
                                   track_visibility='onchange')
示例#17
0
class EducationDocuments(models.Model):
    _name = 'education.documents'
    _description = "Student Documents"
    _inherit = ['mail.thread']

    @api.model
    def create(self, vals):
        """Over riding the create method to assign
        the sequence for newly creating records"""
        if vals.get('name', _('New')) == _('New'):
            vals['name'] = self.env['ir.sequence'].next_by_code('education.documents') or _('New')
        res = super(EducationDocuments, self).create(vals)
        return res

    @api.multi
    def verify_document(self):
        """Return the state to done if the documents are perfect"""
        for rec in self:
            rec.write({
                'verified_by': self.env.uid,
                'verified_date': datetime.datetime.now().strftime("%Y-%m-%d"),
                'state': 'done'
            })

    @api.multi
    def need_correction(self):
        """Return the state to correction if the documents are not perfect"""
        for rec in self:
            rec.write({
                'state': 'correction'
            })

    @api.multi
    def hard_copy_returned(self):
        """Records who return the documents and when is it returned"""
        for rec in self:
            if rec.state == 'done':
                rec.write({
                    'state': 'returned',
                    'returned_by': self.env.uid,
                    'returned_date': datetime.datetime.now().strftime("%Y-%m-%d")
                })

    name = fields.Char(string='Serial Number', copy=False, default=lambda self: _('New'))
    document_name = fields.Many2one('document.document', string='Document Type', required=True,
                                    help="Choose the type of the Document")
    description = fields.Text(string='Description', copy=False,
                              help="Enter a description about the document")
    has_hard_copy = fields.Boolean(string="Hard copy Received",
                                   help="Tick the field if the hard copy is provided")
    location_id = fields.Many2one('stock.location', 'Location', domain="[('usage', '=', 'internal')]",
                                  help="Location where which the hard copy is stored")
    location_note = fields.Char(string="Location Note", help="Enter some notes about the location")
    submitted_date = fields.Date(string="Submitted Date", default=date.today(),
                                 help="Documents are submitted on")
    received_by = fields.Many2one('hr.employee', string="Received By",
                                  help="The Documents are received by")
    returned_by = fields.Many2one('hr.employee', string="Returned By",
                                  help="The Documents are returned by")
    verified_date = fields.Date(string="Verified Date", help="Date at the verification is done")
    returned_date = fields.Date(string="Returned Date", help="Returning date")
    reference = fields.Char(string='Document Number', required=True, copy=False)
    responsible_verified = fields.Many2one('hr.employee', string="Responsible")
    responsible_returned = fields.Many2one('hr.employee', string="Responsible")

    verified_by = fields.Many2one('res.users', string='Verified by')
    application_ref = fields.Many2one('education.application', invisible=1, copy=False)
    doc_attachment_id = fields.Many2many('ir.attachment', 'doc_attach_rel', 'doc_id', 'attach_id3', string="Attachment",
                                         help='You can attach the copy of your document', copy=False)
    state = fields.Selection([('draft', 'Draft'), ('correction', 'Correction'), ('done', 'Done'),
                              ('returned', 'Returned')],
                             string='State', required=True, default='draft', track_visibility='onchange')
class AccountReconcileModel(models.Model):
    _name = 'account.reconcile.model'
    _description = 'Preset to create journal entries during a invoices and payments matching'
    _order = 'sequence, id'

    # Base fields.
    name = fields.Char(string='Name', required=True)
    sequence = fields.Integer(required=True, default=10)
    company_id = fields.Many2one('res.company',
                                 string='Company',
                                 required=True,
                                 default=lambda self: self.env.user.company_id)

    rule_type = fields.Selection(selection=[
        ('writeoff_button',
         _('Manually create a write-off on clicked button.')),
        ('writeoff_suggestion', _('Suggest counterpart values.')),
        ('invoice_matching', _('Match existing invoices/bills.'))
    ],
                                 string='Type',
                                 default='writeoff_button',
                                 required=True)
    auto_reconcile = fields.Boolean(
        string='Auto-validate',
        help=
        'Validate the statement line automatically (reconciliation based on your rule).'
    )

    # ===== Conditions =====
    match_journal_ids = fields.Many2many(
        'account.journal',
        string='Journals',
        domain="[('type', 'in', ('bank', 'cash'))]",
        help=
        'The reconciliation model will only be available from the selected journals.'
    )
    match_nature = fields.Selection(
        selection=[('amount_received', 'Amount Received'),
                   ('amount_paid', 'Amount Paid'),
                   ('both', 'Amount Paid/Received')],
        string='Amount Nature',
        required=True,
        default='both',
        help=
        '''The reconciliation model will only be applied to the selected transaction type:
        * Amount Received: Only applied when receiving an amount.
        * Amount Paid: Only applied when paying an amount.
        * Amount Paid/Received: Applied in both cases.''')
    match_amount = fields.Selection(
        selection=[
            ('lower', 'Is Lower Than'),
            ('greater', 'Is Greater Than'),
            ('between', 'Is Between'),
        ],
        string='Amount',
        help=
        'The reconciliation model will only be applied when the amount being lower than, greater than or between specified amount(s).'
    )
    match_amount_min = fields.Float(string='Amount Min Parameter')
    match_amount_max = fields.Float(string='Amount Max Parameter')
    match_label = fields.Selection(
        selection=[
            ('contains', 'Contains'),
            ('not_contains', 'Not Contains'),
            ('match_regex', 'Match Regex'),
        ],
        string='Label',
        help='''The reconciliation model will only be applied when the label:
        * Contains: The proposition label must contains this string (case insensitive).
        * Not Contains: Negation of "Contains".
        * Match Regex: Define your own regular expression.''')
    match_label_param = fields.Char(string='Label Parameter')
    match_same_currency = fields.Boolean(
        string='Same Currency Matching',
        default=True,
        help=
        'Restrict to propositions having the same currency as the statement line.'
    )
    match_total_amount = fields.Boolean(
        string='Amount Matching',
        default=True,
        help=
        'The sum of total residual amount propositions matches the statement line amount.'
    )
    match_total_amount_param = fields.Float(
        string='Amount Matching %',
        default=100,
        help=
        'The sum of total residual amount propositions matches the statement line amount under this percentage.'
    )
    match_partner = fields.Boolean(
        string='Partner Is Set',
        help=
        'The reconciliation model will only be applied when a customer/vendor is set.'
    )
    match_partner_ids = fields.Many2many(
        'res.partner',
        string='Restrict Partners to',
        help=
        'The reconciliation model will only be applied to the selected customers/vendors.'
    )
    match_partner_category_ids = fields.Many2many(
        'res.partner.category',
        string='Restrict Partner Categories to',
        help=
        'The reconciliation model will only be applied to the selected customer/vendor categories.'
    )

    # ===== Write-Off =====
    # First part fields.
    account_id = fields.Many2one('account.account',
                                 string='Account',
                                 ondelete='cascade',
                                 domain=[('deprecated', '=', False)])
    journal_id = fields.Many2one(
        'account.journal',
        string='Journal',
        ondelete='cascade',
        help="This field is ignored in a bank statement reconciliation.")
    label = fields.Char(string='Journal Item Label')
    amount_type = fields.Selection([('fixed', 'Fixed'),
                                    ('percentage', 'Percentage of balance')],
                                   required=True,
                                   default='percentage')
    is_tax_price_included = fields.Boolean(
        string='Is Tax Included in Price',
        related='tax_id.price_include',
        help=
        'Technical field used inside the view to make the force_tax_included field readonly if the tax is already price included.'
    )
    tax_amount_type = fields.Selection(
        string='Tax Amount Type',
        related='tax_id.amount_type',
        help=
        'Technical field used inside the view to make the force_tax_included field invisible if the tax is a group.'
    )
    force_tax_included = fields.Boolean(
        string='Tax Included in Price',
        help='Force the tax to be managed as a price included tax.')
    amount = fields.Float(
        string='Write-off Amount',
        digits=0,
        required=True,
        default=100.0,
        help=
        "Fixed amount will count as a debit if it is negative, as a credit if it is positive."
    )
    tax_id = fields.Many2one('account.tax', string='Tax', ondelete='restrict')
    analytic_account_id = fields.Many2one('account.analytic.account',
                                          string='Analytic Account',
                                          ondelete='set null')
    analytic_tag_ids = fields.Many2many('account.analytic.tag',
                                        string='Analytic Tags')

    # Second part fields.
    has_second_line = fields.Boolean(string='Add a second line', default=False)
    second_account_id = fields.Many2one('account.account',
                                        string='Second Account',
                                        ondelete='cascade',
                                        domain=[('deprecated', '=', False)])
    second_journal_id = fields.Many2one(
        'account.journal',
        string='Second Journal',
        ondelete='cascade',
        help="This field is ignored in a bank statement reconciliation.")
    second_label = fields.Char(string='Second Journal Item Label')
    second_amount_type = fields.Selection(
        [('fixed', 'Fixed'), ('percentage', 'Percentage of amount')],
        string="Second Amount type",
        required=True,
        default='percentage')
    is_second_tax_price_included = fields.Boolean(
        string='Is Second Tax Included in Price',
        related='second_tax_id.price_include',
        help=
        'Technical field used inside the view to make the force_second_tax_included field readonly if the tax is already price included.'
    )
    second_tax_amount_type = fields.Selection(
        string='Second Tax Amount Type',
        related='second_tax_id.amount_type',
        help=
        'Technical field used inside the view to make the force_second_tax_included field invisible if the tax is a group.'
    )
    force_second_tax_included = fields.Boolean(
        string='Second Tax Included in Price',
        help='Force the second tax to be managed as a price included tax.')
    second_amount = fields.Float(
        string='Second Write-off Amount',
        digits=0,
        required=True,
        default=100.0,
        help=
        "Fixed amount will count as a debit if it is negative, as a credit if it is positive."
    )
    second_tax_id = fields.Many2one('account.tax',
                                    string='Second Tax',
                                    ondelete='restrict',
                                    domain=[('type_tax_use', '=', 'purchase')])
    second_analytic_account_id = fields.Many2one(
        'account.analytic.account',
        string='Second Analytic Account',
        ondelete='set null')
    second_analytic_tag_ids = fields.Many2many('account.analytic.tag',
                                               string='Second Analytic Tags')

    @api.onchange('name')
    def onchange_name(self):
        self.label = self.name

    @api.onchange('tax_id')
    def _onchange_tax_id(self):
        if self.tax_id:
            self.force_tax_included = self.tax_id.price_include

    @api.onchange('second_tax_id')
    def _onchange_second_tax_id(self):
        if self.second_tax_id:
            self.force_second_tax_included = self.second_tax_id.price_include

    @api.onchange('match_total_amount_param')
    def _onchange_match_total_amount_param(self):
        if self.match_total_amount_param < 0 or self.match_total_amount_param > 100:
            self.match_total_amount_param = min(
                max(0, self.match_total_amount_param), 100)

    ####################################################
    # RECONCILIATION PROCESS
    ####################################################

    @api.model
    def _get_taxes_move_lines_dict(self, tax, base_line_dict):
        ''' Get move.lines dict (to be passed to the create()) corresponding to a tax.
        :param tax:             An account.tax record.
        :param base_line_dict:  A dict representing the move.line containing the base amount.
        :return: A list of dict representing move.lines to be created corresponding to the tax.
        '''
        balance = base_line_dict['debit'] - base_line_dict['credit']
        currency = base_line_dict.get(
            'currency_id') and self.env['res.currency'].browse(
                base_line_dict['currency_id'])

        res = tax.compute_all(balance, currency=currency)

        new_aml_dicts = []
        for tax_res in res['taxes']:
            tax = self.env['account.tax'].browse(tax_res['id'])

            new_aml_dicts.append({
                'account_id':
                tax.account_id and tax.account_id.id
                or base_line_dict['account_id'],
                'name':
                tax.name,
                'partner_id':
                base_line_dict.get('partner_id'),
                'debit':
                tax_res['amount'] > 0 and tax_res['amount'] or 0,
                'credit':
                tax_res['amount'] < 0 and -tax_res['amount'] or 0,
                'analytic_account_id':
                tax.analytic and base_line_dict['analytic_account_id'],
                'analytic_tag_ids':
                tax.analytic and base_line_dict['analytic_tag_ids'],
                'tax_exigible':
                tax.tax_exigibility == 'on_payment',
                'tax_line_id':
                tax.id,
            })

            # Handle price included taxes.
            base_line_dict['debit'] = tax_res['base'] > 0 and tax_res[
                'base'] or base_line_dict['debit']
            base_line_dict['credit'] = tax_res[
                'base'] < 0 and -tax_res['base'] or base_line_dict['credit']
        return new_aml_dicts

    @api.multi
    def _get_write_off_move_lines_dict(self, st_line, move_lines=None):
        ''' Get move.lines dict (to be passed to the create()) corresponding to the reconciliation model's write-off lines.
        :param st_line:     An account.bank.statement.line record.
        :param move_lines:  An account.move.line recordset.
        :return: A list of dict representing move.lines to be created corresponding to the write-off lines.
        '''
        self.ensure_one()

        if self.rule_type == 'invoice_matching' and (
                not self.match_total_amount or
            (self.match_total_amount_param == 100)):
            return []

        line_residual = st_line.currency_id and st_line.amount_currency or st_line.amount
        line_currency = st_line.currency_id or st_line.journal_id.currency_id or st_line.company_id.currency_id
        total_residual = move_lines and sum(
            aml.currency_id and aml.amount_residual_currency
            or aml.amount_residual for aml in move_lines) or 0.0

        balance = total_residual - line_residual

        if not self.account_id or float_is_zero(
                balance, precision_rounding=line_currency.rounding):
            return []

        if self.amount_type == 'percentage':
            line_balance = balance * (self.amount / 100.0)
        else:
            line_balance = self.amount * (1 if balance > 0.0 else -1)

        new_aml_dicts = []

        # First write-off line.
        writeoff_line = {
            'name': self.label or st_line.name,
            'account_id': self.account_id.id,
            'analytic_account_id': self.analytic_account_id.id,
            'analytic_tag_ids': [(6, 0, self.analytic_tag_ids.ids)],
            'debit': line_balance > 0 and line_balance or 0,
            'credit': line_balance < 0 and -line_balance or 0,
        }
        new_aml_dicts.append(writeoff_line)

        if self.tax_id:
            writeoff_line['tax_ids'] = [(6, None, [self.tax_id.id])]
            tax = self.tax_id
            if self.force_tax_included:
                tax = tax.with_context(force_price_include=True)
            new_aml_dicts += self._get_taxes_move_lines_dict(
                tax, writeoff_line)

        # Second write-off line.
        if self.has_second_line and self.second_account_id:
            line_balance = balance - sum(aml['debit'] - aml['credit']
                                         for aml in new_aml_dicts)
            second_writeoff_line = {
                'name': self.second_label or st_line.name,
                'account_id': self.second_account_id.id,
                'analytic_account_id': self.second_analytic_account_id.id,
                'analytic_tag_ids': [(6, 0, self.second_analytic_tag_ids.ids)],
                'debit': line_balance > 0 and line_balance or 0,
                'credit': line_balance < 0 and -line_balance or 0,
            }
            new_aml_dicts.append(second_writeoff_line)

            if self.second_tax_id:
                second_writeoff_line['tax_ids'] = [(6, None,
                                                    [self.second_tax_id.id])]
                tax = self.second_tax_id
                if self.force_second_tax_included:
                    tax = tax.with_context(force_price_include=True)
                new_aml_dicts += self._get_taxes_move_lines_dict(
                    tax, second_writeoff_line)

        return new_aml_dicts

    @api.multi
    def _prepare_reconciliation(self, st_line, move_lines=None, partner=None):
        ''' Reconcile the statement line with some move lines using this reconciliation model.
        :param st_line:     An account.bank.statement.line record.
        :param move_lines:  An account.move.line recordset.
        :param partner_id:  An optional res.partner record. If not set, st_line.partner_id will be used.
        :return:            Counterpart account.moves.
        '''
        self.ensure_one()

        # Create counterpart_aml_dicts + payment_aml_rec.
        counterpart_aml_dicts = []
        payment_aml_rec = self.env['account.move.line']
        if move_lines:
            for aml in move_lines:
                if aml.account_id.internal_type == 'liquidity':
                    payment_aml_rec |= aml
                else:
                    amount = aml.currency_id and aml.amount_residual_currency or aml.amount_residual
                    counterpart_aml_dicts.append({
                        'name':
                        aml.name if aml.name != '/' else aml.move_id.name,
                        'debit':
                        amount < 0 and -amount or 0,
                        'credit':
                        amount > 0 and amount or 0,
                        'move_line':
                        aml,
                    })

        # Create new_aml_dicts.
        new_aml_dicts = self._get_write_off_move_lines_dict(
            st_line, move_lines=move_lines)

        line_residual = st_line.currency_id and st_line.amount_currency or st_line.amount
        line_currency = st_line.currency_id or st_line.journal_id.currency_id or st_line.company_id.currency_id
        total_residual = move_lines and sum(
            aml.currency_id and aml.amount_residual_currency
            or aml.amount_residual for aml in move_lines) or 0.0
        total_residual -= sum(aml['debit'] - aml['credit']
                              for aml in new_aml_dicts)

        # Create open_balance_dict
        open_balance_dict = None
        if float_compare(line_residual,
                         total_residual,
                         precision_rounding=line_currency.rounding) != 0:
            if not partner and not st_line.partner_id:
                open_balance_dict = False
            else:
                balance = total_residual - line_residual
                partner = partner or st_line.partner_id
                open_balance_dict = {
                    'name':
                    '%s : %s' % (st_line.name, _('Open Balance')),
                    'account_id':
                    balance < 0 and partner.property_account_payable_id.id
                    or partner.property_account_receivable_id.id,
                    'debit':
                    balance > 0 and balance or 0,
                    'credit':
                    balance < 0 and -balance or 0,
                }
        return {
            'counterpart_aml_dicts': counterpart_aml_dicts,
            'payment_aml_rec': payment_aml_rec,
            'new_aml_dicts': new_aml_dicts,
            'open_balance_dict': open_balance_dict
        }

    ####################################################
    # RECONCILIATION CRITERIA
    ####################################################

    @api.multi
    def _apply_conditions(self, query, params):
        self.ensure_one()
        rule = self
        # Filter on journals.
        if rule.match_journal_ids:
            query += ' AND st_line.journal_id IN %s'
            params += [tuple(rule.match_journal_ids.ids)]

        # Filter on amount nature.
        if rule.match_nature == 'amount_received':
            query += ' AND st_line.amount >= 0.0'
        elif rule.match_nature == 'amount_paid':
            query += ' AND st_line.amount <= 0.0'

        # Filter on amount.
        if rule.match_amount:
            query += ' AND ROUND(ABS(st_line.amount), jnl_precision.dp) '
            if rule.match_amount == 'lower':
                query += '< %s'
                params += [self.match_amount_max]
            elif rule.match_amount == 'greater':
                query += '> %s'
                params += [self.match_amount_min]
            else:
                # if self.match_amount == 'between'
                query += 'BETWEEN %s AND %s'
                params += [rule.match_amount_min, rule.match_amount_max]

        # Filter on label.
        if rule.match_label == 'contains':
            query += ' AND st_line.name ILIKE %s'
            params += ['%%%s%%' % rule.match_label_param]
        elif rule.match_label == 'not_contains':
            query += ' AND st_line.name NOT ILIKE %s'
            params += ['%%%s%%' % rule.match_label_param]
        elif rule.match_label == 'match_regex':
            query += ' AND st_line.name ~ %s'
            params += [rule.match_label_param]

        # Filter on partners.
        if rule.match_partner:
            query += ' AND line_partner.partner_id != 0'

            if rule.match_partner_ids:
                query += ' AND line_partner.partner_id IN %s'
                params += [tuple(rule.match_partner_ids.ids)]

            if rule.match_partner_category_ids:
                query += '''
                    AND line_partner.partner_id IN (
                        SELECT DISTINCT categ.partner_id FROM res_partner_res_partner_category_rel categ WHERE categ.category_id IN %s
                    )
                '''
                params += [tuple(rule.match_partner_category_ids.ids)]

        return query, params

    @api.multi
    def _get_with_tables(self, st_lines, partner_map=None):
        with_tables = '''
            WITH jnl_precision AS (
                SELECT
                    j.id AS journal_id, currency.decimal_places AS dp
                FROM account_journal j
                LEFT JOIN res_company c ON j.company_id = c.id
                LEFT JOIN res_currency currency ON COALESCE(j.currency_id, c.currency_id) = currency.id
                WHERE j.type IN ('bank', 'cash')
            )'''
        # Compute partners values table.
        # This is required since some statement line's partners could be shadowed in the reconciliation widget.
        partners_list = []
        for line in st_lines:
            partner_id = partner_map and partner_map.get(
                line.id) or line.partner_id.id or 0
            partners_list.append('(%d, %d)' % (line.id, partner_id))
        partners_table = 'SELECT * FROM (VALUES %s) AS line_partner (line_id, partner_id)' % ','.join(
            partners_list)
        with_tables += ', partners_table AS (' + partners_table + ')'
        return with_tables

    @api.multi
    def _get_invoice_matching_query(self,
                                    st_lines,
                                    excluded_ids=None,
                                    partner_map=None):
        ''' Get the query applying all rules trying to match existing entries with the given statement lines.
        :param st_lines:        Account.bank.statement.lines recordset.
        :param excluded_ids:    Account.move.lines to exclude.
        :param partner_map:     Dict mapping each line with new partner eventually.
        :return:                (query, params)
        '''
        if any(m.rule_type != 'invoice_matching' for m in self):
            raise UserError(
                _('Programmation Error: Can\'t call _get_invoice_matching_query() for different rules than \'invoice_matching\''
                  ))

        queries = []
        all_params = []
        for rule in self:
            # N.B: 'communication_flag' is there to distinguish invoice matching through the number/reference
            # (higher priority) from invoice matching using the partner (lower priority).
            query = '''
            SELECT
                %s                                  AS sequence,
                %s                                  AS model_id,
                st_line.id                          AS id,
                aml.id                              AS aml_id,
                aml.currency_id                     AS aml_currency_id,
                aml.date_maturity                   AS aml_date_maturity,
                aml.amount_residual                 AS aml_amount_residual,
                aml.amount_residual_currency        AS aml_amount_residual_currency,
                aml.balance                         AS aml_balance,
                aml.amount_currency                 AS aml_amount_currency,
                account.internal_type               AS account_internal_type,

                -- Determine a matching or not with the statement line communication using the move.name or move.ref.
                regexp_split_to_array(TRIM(REGEXP_REPLACE(move.name, '[^0-9|^\s]', '', 'g')),'\s+')
                && regexp_split_to_array(TRIM(REGEXP_REPLACE(st_line.name, '[^0-9|^\s]', '', 'g')), '\s+')
                OR
                (
                    move.ref IS NOT NULL
                    AND
                        regexp_split_to_array(TRIM(REGEXP_REPLACE(move.ref, '[^0-9|^\s]', '', 'g')),'\s+')
                        && regexp_split_to_array(TRIM(REGEXP_REPLACE(st_line.name, '[^0-9|^\s]', '', 'g')), '\s+')
                )                                   AS communication_flag
            FROM account_bank_statement_line st_line
            LEFT JOIN account_journal journal       ON journal.id = st_line.journal_id
            LEFT JOIN jnl_precision                 ON jnl_precision.journal_id = journal.id
            LEFT JOIN res_company company           ON company.id = st_line.company_id
            LEFT JOIN partners_table line_partner   ON line_partner.line_id = st_line.id
            , account_move_line aml
            LEFT JOIN account_move move             ON move.id = aml.move_id
            LEFT JOIN account_account account       ON account.id = aml.account_id
            WHERE st_line.id IN %s
                AND aml.company_id = st_line.company_id
                AND (
                        -- the field match_partner of the rule might enforce the second part of
                        -- the OR condition, later in _apply_conditions()
                        line_partner.partner_id = 0
                        OR
                        aml.partner_id = line_partner.partner_id
                    )
                AND CASE WHEN st_line.amount > 0.0
                         THEN aml.balance > 0
                         ELSE aml.balance < 0
                    END

                -- if there is a partner, propose all aml of the partner, otherwise propose only the ones
                -- matching the statement line communication
                AND 
                (
                    (
                        line_partner.partner_id != 0
                        AND
                        aml.partner_id = line_partner.partner_id
                    )
                    OR
                    (
                        line_partner.partner_id = 0
                        AND
                        TRIM(REGEXP_REPLACE(st_line.name, '[^0-9|^\s]', '', 'g')) != ''
                        AND
                        (
                            regexp_split_to_array(TRIM(REGEXP_REPLACE(move.name, '[^0-9|^\s]', '', 'g')),'\s+')
                            && regexp_split_to_array(TRIM(REGEXP_REPLACE(st_line.name, '[^0-9|^\s]', '', 'g')), '\s+')
                            OR
                            (
                                move.ref IS NOT NULL
                                AND
                                    regexp_split_to_array(TRIM(REGEXP_REPLACE(move.ref, '[^0-9|^\s]', '', 'g')),'\s+')
                                    && regexp_split_to_array(TRIM(REGEXP_REPLACE(st_line.name, '[^0-9|^\s]', '', 'g')), '\s+')
                            )
                        )
                    )
                )
                AND
                (
                    (
                    -- blue lines appearance conditions
                    aml.account_id IN (journal.default_credit_account_id, journal.default_debit_account_id)
                    AND aml.statement_id IS NULL
                    AND (
                        company.account_bank_reconciliation_start IS NULL
                        OR
                        aml.date > company.account_bank_reconciliation_start
                        )
                    )
                    OR
                    (
                    -- black lines appearance conditions
                    account.reconcile IS TRUE
                    AND aml.reconciled IS FALSE
                    )
                )
            '''
            # Filter on the same currency.
            if rule.match_same_currency:
                query += '''
                    AND COALESCE(st_line.currency_id, journal.currency_id, company.currency_id) = COALESCE(aml.currency_id, company.currency_id)
                '''

            params = [rule.sequence, rule.id, tuple(st_lines.ids)]
            # Filter out excluded account.move.line.
            if excluded_ids:
                query += 'AND aml.id NOT IN %s'
                params += [tuple(excluded_ids)]
            query, params = rule._apply_conditions(query, params)
            queries.append(query)
            all_params += params
        full_query = self._get_with_tables(st_lines, partner_map=partner_map)
        full_query += ' UNION ALL '.join(queries)
        # Oldest due dates come first.
        full_query += ' ORDER BY aml_date_maturity, aml_id'
        return full_query, all_params

    @api.multi
    def _get_writeoff_suggestion_query(self,
                                       st_lines,
                                       excluded_ids=None,
                                       partner_map=None):
        ''' Get the query applying all reconciliation rules.
        :param st_lines:        Account.bank.statement.lines recordset.
        :param excluded_ids:    Account.move.lines to exclude.
        :param partner_map:     Dict mapping each line with new partner eventually.
        :return:                (query, params)
        '''
        if any(m.rule_type != 'writeoff_suggestion' for m in self):
            raise UserError(
                _('Programmation Error: Can\'t call _get_wo_suggestion_query() for different rules than \'writeoff_suggestion\''
                  ))

        queries = []
        all_params = []
        for rule in self:
            query = '''
                SELECT
                    %s                                  AS sequence,
                    %s                                  AS model_id,
                    st_line.id                          AS id
                FROM account_bank_statement_line st_line
                LEFT JOIN account_journal journal       ON journal.id = st_line.journal_id
                LEFT JOIN jnl_precision                 ON jnl_precision.journal_id = journal.id
                LEFT JOIN res_company company           ON company.id = st_line.company_id
                LEFT JOIN partners_table line_partner   ON line_partner.line_id = st_line.id
                WHERE st_line.id IN %s
            '''
            params = [rule.sequence, rule.id, tuple(st_lines.ids)]

            query, params = rule._apply_conditions(query, params)
            queries.append(query)
            all_params += params

        full_query = self._get_with_tables(st_lines, partner_map=partner_map)
        full_query += ' UNION ALL '.join(queries)
        return full_query, all_params

    @api.multi
    def _check_rule_propositions(self, statement_line, candidates):
        ''' Check restrictions that can't be handled for each move.line separately.
        /!\ Only used by models having a type equals to 'invoice_matching'.
        :param statement_line:  An account.bank.statement.line record.
        :param candidates:      Fetched account.move.lines from query (dict).
        :return:                True if the reconciliation propositions are accepted. False otherwise.
        '''
        if not self.match_total_amount:
            return True

        # Match total residual amount.
        total_residual = 0.0
        for aml in candidates:
            if aml['account_internal_type'] == 'liquidity':
                total_residual += aml['aml_currency_id'] and aml[
                    'aml_amount_currency'] or aml['aml_balance']
            else:
                total_residual += aml['aml_currency_id'] and aml[
                    'aml_amount_residual_currency'] or aml[
                        'aml_amount_residual']
        line_residual = statement_line.currency_id and statement_line.amount_currency or statement_line.amount
        line_currency = statement_line.currency_id or statement_line.journal_id.currency_id or statement_line.company_id.currency_id

        # Statement line amount is equal to the total residual.
        if float_is_zero(total_residual - line_residual,
                         precision_rounding=line_currency.rounding):
            return True

        if line_residual > total_residual:
            amount_percentage = (total_residual / line_residual) * 100
        else:
            amount_percentage = (line_residual / total_residual) * 100
        return amount_percentage >= self.match_total_amount_param

    @api.multi
    def _apply_rules(self, st_lines, excluded_ids=None, partner_map=None):
        ''' Apply criteria to get candidates for all reconciliation models.
        :param st_lines:        Account.bank.statement.lines recordset.
        :param excluded_ids:    Account.move.lines to exclude.
        :param partner_map:     Dict mapping each line with new partner eventually.
        :return:                A dict mapping each statement line id with:
            * aml_ids:      A list of account.move.line ids.
            * model:        An account.reconcile.model record (optional).
            * status:       'reconciled' if the lines has been already reconciled, 'write_off' if the write-off must be
                            applied on the statement line.
        '''
        available_models = self.filtered(
            lambda m: m.rule_type != 'writeoff_button')

        results = dict((r.id, {'aml_ids': []}) for r in st_lines)

        if not available_models:
            return results

        ordered_models = available_models.sorted(
            key=lambda m: (m.sequence, m.id))

        grouped_candidates = {}

        # Type == 'invoice_matching'.
        # Map each (st_line.id, model_id) with matching amls.
        invoices_models = ordered_models.filtered(
            lambda m: m.rule_type == 'invoice_matching')
        if invoices_models:
            query, params = invoices_models._get_invoice_matching_query(
                st_lines, excluded_ids=excluded_ids, partner_map=partner_map)
            self._cr.execute(query, params)
            query_res = self._cr.dictfetchall()

            for res in query_res:
                grouped_candidates.setdefault(res['id'], {})
                grouped_candidates[res['id']].setdefault(res['model_id'], [])
                grouped_candidates[res['id']][res['model_id']].append(res)

        # Type == 'writeoff_suggestion'.
        # Map each (st_line.id, model_id) with a flag indicating the st_line matches the criteria.
        write_off_models = ordered_models.filtered(
            lambda m: m.rule_type == 'writeoff_suggestion')
        if write_off_models:
            query, params = write_off_models._get_writeoff_suggestion_query(
                st_lines, excluded_ids=excluded_ids, partner_map=partner_map)
            self._cr.execute(query, params)
            query_res = self._cr.dictfetchall()

            for res in query_res:
                grouped_candidates.setdefault(res['id'], {})
                grouped_candidates[res['id']].setdefault(res['model_id'], True)

        # Keep track of already processed amls.
        amls_ids_to_exclude = set()

        # Keep track of already reconciled amls.
        reconciled_amls_ids = set()

        # Iterate all and create results.
        for line in st_lines:
            line_currency = line.currency_id or line.journal_id.currency_id or line.company_id.currency_id
            line_residual = line.currency_id and line.amount_currency or line.amount

            # Search for applicable rule.
            # /!\ BREAK are very important here to avoid applying multiple rules on the same line.
            for model in ordered_models:
                # No result found.
                if not grouped_candidates.get(
                        line.id) or not grouped_candidates[line.id].get(
                            model.id):
                    continue

                excluded_lines_found = False

                if model.rule_type == 'invoice_matching':
                    candidates = grouped_candidates[line.id][model.id]

                    # If some invoices match on the communication, suggest them.
                    # Otherwise, suggest all invoices having the same partner.
                    # N.B: The only way to match a line without a partner is through the communication.
                    first_batch_candidates = []
                    second_batch_candidates = []
                    for c in candidates:
                        # Don't take into account already reconciled lines.
                        if c['aml_id'] in reconciled_amls_ids:
                            continue

                        # Dispatch candidates between lines matching invoices with the communication or only the partner.
                        if c['communication_flag']:
                            first_batch_candidates.append(c)
                        elif not first_batch_candidates:
                            second_batch_candidates.append(c)
                    available_candidates = first_batch_candidates or second_batch_candidates

                    # Special case: the amount are the same, submit the line directly.
                    for c in available_candidates:
                        residual_amount = c['aml_currency_id'] and c[
                            'aml_amount_residual_currency'] or c[
                                'aml_amount_residual']

                        if float_is_zero(
                                residual_amount - line_residual,
                                precision_rounding=line_currency.rounding):
                            available_candidates = [c]
                            break

                    # Needed to handle check on total residual amounts.
                    if first_batch_candidates or model._check_rule_propositions(
                            line, available_candidates):
                        results[line.id]['model'] = model

                        # Add candidates to the result.
                        for candidate in available_candidates:

                            # Special case: the propositions match the rule but some of them are already consumed by
                            # another one. Then, suggest the remaining propositions to the user but don't make any
                            # automatic reconciliation.
                            if candidate['aml_id'] in amls_ids_to_exclude:
                                excluded_lines_found = True
                                continue

                            results[line.id]['aml_ids'].append(
                                candidate['aml_id'])
                            amls_ids_to_exclude.add(candidate['aml_id'])

                        if excluded_lines_found:
                            break

                        # Create write-off lines.
                        move_lines = self.env['account.move.line'].browse(
                            results[line.id]['aml_ids'])
                        partner = partner_map and partner_map.get(
                            line.id) and self.env['res.partner'].browse(
                                partner_map[line.id])
                        reconciliation_results = model._prepare_reconciliation(
                            line, move_lines, partner=partner)

                        # A write-off must be applied.
                        if reconciliation_results['new_aml_dicts']:
                            results[line.id]['status'] = 'write_off'

                        # Process auto-reconciliation.
                        if model.auto_reconcile:
                            # An open balance is needed but no partner has been found.
                            if reconciliation_results[
                                    'open_balance_dict'] is False:
                                break

                            new_aml_dicts = reconciliation_results[
                                'new_aml_dicts']
                            if reconciliation_results['open_balance_dict']:
                                new_aml_dicts.append(
                                    reconciliation_results['open_balance_dict']
                                )
                            if not line.partner_id and partner:
                                line.partner_id = partner
                            counterpart_moves = line.process_reconciliation(
                                counterpart_aml_dicts=reconciliation_results[
                                    'counterpart_aml_dicts'],
                                payment_aml_rec=reconciliation_results[
                                    'payment_aml_rec'],
                                new_aml_dicts=new_aml_dicts,
                            )
                            results[line.id]['status'] = 'reconciled'
                            results[line.id][
                                'reconciled_lines'] = counterpart_moves.mapped(
                                    'line_ids')

                            # The reconciled move lines are no longer candidates for another rule.
                            reconciled_amls_ids.update(move_lines.ids)

                        # Break models loop.
                        break

                elif model.rule_type == 'writeoff_suggestion' and grouped_candidates[
                        line.id][model.id]:
                    results[line.id]['model'] = model
                    results[line.id]['status'] = 'write_off'

                    # Create write-off lines.
                    partner = partner_map and partner_map.get(
                        line.id) and self.env['res.partner'].browse(
                            partner_map[line.id])
                    reconciliation_results = model._prepare_reconciliation(
                        line, partner=partner)

                    # An open balance is needed but no partner has been found.
                    if reconciliation_results['open_balance_dict'] is False:
                        break

                    # Process auto-reconciliation.
                    if model.auto_reconcile:
                        new_aml_dicts = reconciliation_results['new_aml_dicts']
                        if reconciliation_results['open_balance_dict']:
                            new_aml_dicts.append(
                                reconciliation_results['open_balance_dict'])
                        if not line.partner_id and partner:
                            line.partner_id = partner
                        counterpart_moves = line.process_reconciliation(
                            counterpart_aml_dicts=reconciliation_results[
                                'counterpart_aml_dicts'],
                            payment_aml_rec=reconciliation_results[
                                'payment_aml_rec'],
                            new_aml_dicts=new_aml_dicts,
                        )
                        results[line.id]['status'] = 'reconciled'
                        results[line.id][
                            'reconciled_lines'] = counterpart_moves.mapped(
                                'line_ids')

                    # Break models loop.
                    break
        return results
示例#19
0
class PaperLessRegistration(models.Model):

    _inherit = 'registration'

    health_form_signed = fields.Boolean('Health form signed')
    postal_address = fields.Text('Postal Address')
    student_is_living_with = fields.Char('Student is living with')
    lang_spoken_at_home = fields.Many2many('res.lang',
                                               string='Language(s) spoken at home')
    english_is_spoken_at_home = fields.Selection([('yes','YES'),('no','NO')],
                                                 string="English is spoken at home (Yes / No)")
    english_written = fields.Selection([('none','None'),('some','Some'),
                                        ('satisfactory','Satisfactory'),
                                        ('proficient','Proficient')],string="Written")
    english_spoken = fields.Selection([('none','None'),('some','Some'),
                                       ('satisfactory','Satisfactory'),
                                       ('proficient','Proficient')],string="Spoken")
    english_reading = fields.Selection([('none','None'),('some','Some'),
                                        ('satisfactory','Satisfactory'),
                                        ('proficient','Proficient')],string="Reading")
    father_nationality = fields.Many2one('res.country',string="Father Nationality")
    father_passport = fields.Char('Father Passport')
    father_emirates_id = fields.Char('Father Emirates Id')
    father_designation = fields.Char('Father Designation')
    mother_nationality = fields.Many2one('res.country',string="Mother Nationality")
    mother_passport = fields.Char('Mother Passport')
    mother_emirates_id = fields.Char('Mother Emirates Id')
    mother_designation = fields.Char('Mother Designation')
    medium_of_instruction = fields.Char('Medium of Instruction')
    identified_gifted_or_talented = fields.Selection([('yes','YES'),('no','NO')],
                                                     string="Has your child ever been identified as gifted or talented ?")
    has_child_detained = fields.Selection([('yes','YES'),('no','NO')],
                                          string="Has your child ever been detained ?")
    has_child_detained_grade = fields.Many2one('course','Grade')
    child_received_academic_distinction = fields.Selection([('yes','YES'),('no','NO')],
                                                           string="Has your child received any academic distinction ?")
    child_received_academic_distinction_details = fields.Char(string='If yes, please indicate details')
    has_suspended_expelled_by_school = fields.Selection([('yes','YES'),('no','NO')],
                                                        string="Has your child ever been suspended/ expelled by any school in the past ?")
    has_suspended_expelled_by_school_details = fields.Char(string='If yes, please indicate details')
    child_associated_with_awareness = fields.Selection([('yes','YES'),('no','NO')],
                                                       string="Has your child been associated with any social awareness programme?")
    child_associated_with_awareness_details = fields.Char(string='If yes, please indicate details')
    member_of_environment_protection = fields.Selection([('yes','YES'),('no','NO')],
                                                        string="Has your child been a member of environment protection group ?")
    member_of_environment_protection_details = fields.Char(string='If yes, please indicate details')
    leadership_positions_in_school = fields.Selection([('yes','YES'),('no','NO')],
                                                      string="Has your child held any leadership positions in School? (Prefectorial Board/ Student Council)")
    leadership_positions_in_school_details = fields.Char(string='If yes, please indicate details')
    special_education_programme = fields.Selection([('yes','YES'),('no','NO')],
                                                   string="Has your child ever been in a speech therapy, remedial reading support, special education programme?")
    special_education_programme_details = fields.Char(string='If yes, please indicate details')
    special_learning_disability = fields.Selection([('yes','YES'),('no','NO')],
                                                   string="Has your child ever been identified as having a special learning disability?")
    special_learning_disability_details = fields.Selection([('reading','Reading'),('language','Language'),('mathematics','Mathematics')],
                                                           string="If yes, please indicate learning disability area")
    has_other_than_english_languages = fields.Selection([('yes','YES'),('no','NO')],
                                                        string="Has your child ever studied any languages other than English ?")
    other_than_english_languages = fields.Many2one('res.lang', 'If yes, please mention the other languages')
    hobbies_interests = fields.Text("Please list your child's hobbies / interests")
    has_play_any_musical_instrument = fields.Selection([('yes','YES'),('no','NO')],
                                                       string="Does your child play any musical instrument ?")
    musical_instrument_details = fields.Char(string='If yes, please specify')
    has_formal_training_in_music = fields.Selection([('yes','YES'),('no','NO')],
                                                    string="Has your child had any formal training in music ?")
    training_in_music_details = fields.Char(string='If yes, please give details')
    sport_child_play = fields.Char(string='Which sport does your child play ?')
    has_training_or_interest_art = fields.Selection([('yes','YES'),('no','NO')],
                                                    string="Has your child had any training / shown interest in fine arts ?")
    has_training_or_interest_art_details = fields.Char(string='If yes, please give details')
    inter_school_competitions = fields.Selection([('yes','YES'),('no','NO')],
                                                 string="Has your child participated in inter/ intra school competitions?")
    inter_school_competitions_details = fields.Char(string='If yes, please give details')
    special_activity_interested = fields.Char(string='Any other special activity your child is interested in?')

    adjusts_new_situations_with_ease = fields.Boolean('Adjusts to new situations with ease')
    has_small_group_of_friends = fields.Boolean('Has a small group of friends')
    has_never_adjust_new_situation = fields.Boolean('Has never had to adjust to a new situation')
    has_many_friends = fields.Boolean('Has many friends')
    likes_be_active_in_school = fields.Boolean('Likes to be active in school')
    expressions_describe_your_child = fields.Selection([('very_active','Very Active'),('very_quiet','Very Quiet'),
                                                        ('average','Average'),('above_average','Above Average'),
                                                        ('shy','Shy'),('sociable','Sociable'),
                                                        ('aggressive','Aggressive'),('other','Other')],
                                                       string='Please Select the expression that describe your child.')
    social_emotional_behavioural_difficulties = fields.Selection([('yes','YES'),('no','NO')],
                                                                 string="Has your child ever experienced social, emotional or behavioural difficulties?")
    useful_information_for_educating = fields.Char(string='Is there any other information you feel would be useful for those educating your child?')
    person_to_call = fields.Char('Person to call')
    emergency_relationship = fields.Char(string='Relationship')
    # emergency_tel_no = fields.Char(string='Tel. Nos. to call')
    has_use_bus_facility = fields.Selection([('yes', 'YES'), ('no', 'NO')],
                                            string="Would your child be using bus facility?")
    normal_delivery = fields.Char('Normal delivery')
    caesarean = fields.Char('Caesarean')
    premature = fields.Char('Premature')
    developmental_milestones = fields.Char('Developmental Milestones')
    age_your_child_talk = fields.Char('At what age did your child talk? (14 months +)')
    hand_preference = fields.Selection([('left', 'Left'), ('right', 'Right'),('both', 'Both')],
                                       string="Hand Preference")
    can_button_his_shirt = fields.Selection([('yes', 'YES'), ('no', 'NO')],
                                            string='Can the child button his shirt?')
    can_zip_his_pant = fields.Selection([('yes', 'YES'), ('no', 'NO')],
                                        string='Can the child zip his pant ?')
    can_child_indicate_his_toilet_needs = fields.Selection([('yes', 'YES'), ('no', 'NO')],
                                                           string = 'Can the child indicate his toilet needs?')
    child_indicate_his_toilet_needs_details = fields.Char('If yes, how?')
    child_know_his_phone_number = fields.Selection([('yes', 'YES'), ('no', 'NO')],
                                                   string='Does your child know his phone number?')
    toys_likes_to_play_with = fields.Char('What are the toys he / she likes to play with?')
    special_interest = fields.Char('Any special interest that your child has?')
    child_like_to_play_with = fields.Char(string='Does your child like to play: alone / with friends / with family members')
    child_like_to_look_at_picture = fields.Selection([('yes', 'YES'), ('no', 'NO')],
                                                     string='Does your child like to look at picture books?')
    child_like_to_watch_tv_programmes = fields.Selection([('yes', 'YES'), ('no', 'NO')],
                                                         string='Does your child like to watch TV programmes?')
    channels_like_to_watch = fields.Char('What channels does he / she watch?')
    child_have_any_health_problem = fields.Selection([('yes', 'YES'), ('no', 'NO')],
                                                     string='Does your child have any health problem?')
    health_problem_details = fields.Char('If yes, what?')
    health_card_no = fields.Char('Health Card No')
    diphtheria = fields.Selection([('yes', 'YES'), ('no', 'NO')], string='Diphtheria')
    accident = fields.Selection([('yes', 'YES'), ('no', 'NO')], string='Accident')

    @api.multi
    def reminder_for_additional_form(self):
        if self.fee_structure_confirm != True:
             raise except_orm(_("Warning !"), _('Please Confirm the fee Structure before send reminder For Additional form'))
        return super(PaperLessRegistration,self).reminder_for_additional_form()
class OpRoomDistribution(models.TransientModel):
    """ Exam Room Distribution """
    _name = "op.room.distribution"
    _description = "Room Distribution"

    @api.depends('student_ids')
    def _compute_get_total_student(self):
        for record in self:
            total_student = 0
            if record.student_ids:
                total_student = len(record.student_ids)
            record.total_student = total_student

    @api.depends('room_ids', 'room_ids.capacity')
    def _compute_get_room_capacity(self):
        for record in self:
            room_capacity = 0
            if record.room_ids:
                for room in record.room_ids:
                    room_capacity += (room.capacity or 0)
            record.room_capacity = room_capacity

    exam_id = fields.Many2one('op.exam', 'Exam(s)')
    subject_id = fields.Many2one('op.subject', 'Subject',
                                 related="exam_id.subject_id")
    name = fields.Char("Exam")
    start_time = fields.Datetime("Start Time")
    end_time = fields.Datetime("End Time")
    exam_session = fields.Many2one("op.exam.session", 'Exam Session')
    course_id = fields.Many2one("op.course", 'Course')
    batch_id = fields.Many2one("op.batch", 'Batch')
    total_student = fields.Integer(
        "Total Student", compute="_compute_get_total_student")
    room_capacity = fields.Integer(
        "Room Capacity", compute="_compute_get_room_capacity")
    room_ids = fields.Many2many("op.exam.room", string="Exam Rooms")
    student_ids = fields.Many2many("op.student", String='Student')

    @api.model
    def default_get(self, fields):
        res = super(OpRoomDistribution, self).default_get(fields)
        active_id = self.env.context.get('active_id', False)
        exam = self.env['op.exam'].browse(active_id)
        session = exam.session_id
        reg_ids = self.env['op.subject.registration'].search(
            [('course_id', '=', session.course_id.id)])
        student_ids = []
        for reg in reg_ids:
            if exam.subject_id.subject_type == 'compulsory':
                student_ids.append(reg.student_id.id)
            else:
                for sub in reg.elective_subject_ids:
                    if sub.id == exam.subject_id.id:
                        student_ids.append(reg.student_id.id)
        student_ids = list(set(student_ids))
        total_student = len(student_ids)
        res.update({
            'exam_id': active_id,
            'name': exam.name,
            'start_time': exam.start_time,
            'end_time': exam.end_time,
            'exam_session': session.id,
            'course_id': session.course_id.id,
            'batch_id': session.batch_id.id,
            'total_student': total_student,
            'student_ids': [(6, 0, student_ids)],
        })
        return res

    def schedule_exam(self):
        attendance = self.env['op.exam.attendees']
        for exam in self:
            if exam.total_student > exam.room_capacity:
                raise exceptions.AccessError(
                    _("Room capacity must be greater than total number \
                      of student"))
            student_ids = exam.student_ids.ids
            for room in exam.room_ids:
                for i in range(room.capacity):
                    if not student_ids:
                        continue
                    attendance.create({
                        'exam_id': exam.exam_id.id,
                        'student_id': student_ids[0],
                        'status': 'present',
                        'course_id': exam.course_id.id,
                        'batch_id': exam.batch_id.id,
                        'room_id': room.id
                    })
                    student_ids.remove(student_ids[0])
            exam.exam_id.state = 'schedule'
            return True
示例#21
0
class PosPaymentMethod(models.Model):
    """ Used to classify pos.payment.

    Generic characteristics of a pos.payment is described in this model.
    E.g. A cash payment can be described by a pos.payment.method with
    fields: is_cash_count = True and a cash_journal_id set to an
    `account.journal` (type='cash') record.

    When a pos.payment.method is cash, cash_journal_id is required as
    it will be the journal where the account.bank.statement.line records
    will be created.
    """

    _name = "pos.payment.method"
    _description = "Point of Sale Payment Methods"
    _order = "id asc"

    def _get_payment_terminal_selection(self):
        return []

    name = fields.Char(string="Payment Method", required=True)
    receivable_account_id = fields.Many2one(
        'account.account',
        string='Intermediary Account',
        required=True,
        domain=[('reconcile', '=', True),
                ('user_type_id.type', '=', 'receivable')],
        default=lambda self: self.env.company.
        account_default_pos_receivable_account_id,
        ondelete='restrict',
        help=
        'Account used as counterpart of the income account in the accounting entry representing the pos sales.'
    )
    is_cash_count = fields.Boolean(string='Cash')
    cash_journal_id = fields.Many2one(
        'account.journal',
        string='Cash Journal',
        domain=[('type', '=', 'cash')],
        ondelete='restrict',
        help=
        'The payment method is of type cash. A cash statement will be automatically generated.'
    )
    split_transactions = fields.Boolean(
        string='Split Transactions',
        default=False,
        help=
        'If ticked, each payment will generate a separated journal item. Ticking that option will slow the closing of the PoS.'
    )
    open_session_ids = fields.Many2many(
        'pos.session',
        string='Pos Sessions',
        compute='_compute_open_session_ids',
        help='Open PoS sessions that are using this payment method.')
    config_ids = fields.Many2many('pos.config',
                                  string='Point of Sale Configurations')
    company_id = fields.Many2one('res.company',
                                 string='Company',
                                 default=lambda self: self.env.company)
    use_payment_terminal = fields.Selection(
        selection=lambda self: self._get_payment_terminal_selection(),
        string='Use a Payment Terminal',
        help='Record payments with a terminal on this journal.')
    hide_use_payment_terminal = fields.Boolean(
        compute='_compute_hide_use_payment_terminal',
        help='Technical field which is used to '
        'hide use_payment_terminal when no payment interfaces are installed.')

    @api.depends('is_cash_count')
    def _compute_hide_use_payment_terminal(self):
        no_terminals = not bool(
            self._fields['use_payment_terminal'].selection(self))
        for payment_method in self:
            payment_method.hide_use_payment_terminal = no_terminals or payment_method.is_cash_count

    @api.onchange('use_payment_terminal')
    def _onchange_use_payment_terminal(self):
        """Used by inheriting model to unset the value of the field related to the unselected payment terminal."""
        pass

    @api.depends('config_ids')
    def _compute_open_session_ids(self):
        for payment_method in self:
            payment_method.open_session_ids = self.env['pos.session'].search([
                ('config_id', 'in', payment_method.config_ids.ids),
                ('state', '!=', 'closed')
            ])

    @api.onchange('is_cash_count')
    def _onchange_is_cash_count(self):
        if not self.is_cash_count:
            self.cash_journal_id = False
        else:
            self.use_payment_terminal = False

    def _is_write_forbidden(self, fields):
        return bool(fields and self.open_session_ids)

    def write(self, vals):
        if self._is_write_forbidden(set(vals.keys())):
            raise UserError(
                'Kindly close and validate the following open PoS Sessions before modifying this payment method.\n'
                'Open sessions: %s' %
                (' '.join(self.open_session_ids.mapped('name')), ))
        return super(PosPaymentMethod, self).write(vals)
示例#22
0
class educationExamResultWizard(models.TransientModel):
    _name = 'education.exam.result.wizard'
    _description = 'print academic transcript for selected exams'
    academic_year = fields.Many2one('education.academic.year', "Academic Year")
    level = fields.Many2one('education.class', "Level")
    exams = fields.Many2many('education.exam')
    specific_section = fields.Boolean('For a specific section', default="True")
    section = fields.Many2one('education.class.division', required="True")
    specific_student = fields.Boolean('For a specific Student')
    student = fields.Many2one('education.student', 'Student')
    report_type = fields.Selection([('1', 'Regular'), ('2', 'Converted')],
                                   string="Report Type",
                                   default='1',
                                   required='True')
    state = fields.Selection([('draft', 'Draft'), ('done', 'Done')],
                             compute='calculate_state')
    show_paper = fields.Boolean("Show Papers")
    show_tut = fields.Boolean("Show Monthly")
    show_subjective = fields.Boolean("Show Subjective")
    show_merit_class = fields.Boolean("Show Merit Class Position",
                                      default="True")
    show_merit_group = fields.Boolean("Show Merit Group Position")
    show_merit_section = fields.Boolean("Show Merit Section Position")
    show_objective = fields.Boolean("show objective")
    show_prac = fields.Boolean("Show Practical")
    show_total = fields.Boolean("Show Total")
    show_average = fields.Boolean("Show average", default="True")
    show_average_only = fields.Boolean("Show average Only")
    record_per_page = fields.Integer(string="Student Per Page", default="6")

    @api.model
    def del_generated_results(self):
        for exam in self.exams:
            records = self.env['education.exam.results.new'].search([
                ('exam_id', '=', exam.id)
            ]).unlink()
            records = self.env['exam.subject.pass.rules'].search([
                ('exam_id', '=', exam.id)
            ]).unlink()
            records = self.env['exam.paper.pass.rules'].search([
                ('subject_rule_id.exam_id', '=', exam.id)
            ]).unlink()
            result_lines = self.env['education.exam.result.exam.line'].search([
                ('exam_count', '=', '1'), ('exam_ids', '=', exam.id)
            ])
            result_lines.unlink()
            exam_lines = self.env['education.exam.result.exam.line'].search([
                ('exam_ids', 'in', [exa.id for exa in exam])
            ])
            exam_lines.unlink()
            print('ok')

    @api.model
    def render_html(self, docids, data=None):
        return {
            'type': 'ir.actions.report',
            'report_name': 'education_exam.report_exam_marksheet',
            'model': 'education.exam.result.wizard',
            'report_type': "qweb-pdf",
            'paperformat': "legal_landscape",
        }

    @api.model
    def calculate_state(self):
        results = self.env[('education.exam.results')].search([
            ('academic_year', '=', self.academic_year.id),
            ('class_id', '=', 'level')
        ])
        for exam in self.exams:
            rec = results.search([('exam_id', '=', exam.id)])
            for line in rec:
                if line.state != 'done':
                    self.state = 'draft'
                    return True
        self.state = 'done'

    @api.model
    def get_merit_list(self):
        exam_lines = []
        for exam in self.exams:
            result_exam_lines = self.env[
                'education.exam.result.exam.line'].search([
                    ('exam_count', '=', '1'), ('exam_ids', '=', exam.id)
                ])
            exam_lines.append(result_exam_lines)
        if len(self.exams) > 1:
            exam_no = len(self.exams)
            result_exam_lines = self.env[
                'education.exam.result.exam.line'].search([
                    ('exam_count', '=', exam_no),
                    ('exam_ids', 'in', [exa.id for exa in self.exams])
                ])
            exam_lines.append(result_exam_lines)

        self.env['education.exam.results.new'].calculate_merit_list(
            exam_lines, self.level)

    @api.model
    @api.onchange('level', 'section')
    def get_student_domain(self):
        for rec in self:
            domain = []
            if rec.section:
                domain.append(('class_id', '=', rec.section.id))
            else:
                domain.append(('class_id.class_id.id', '=', rec.level.id))

        return {'domain': {'student': domain}}

    @api.model
    @api.onchange('specific_section')
    def onchange_specific_section(self):
        for rec in self:
            if rec.specific_section == False:
                rec.specific_student = False
                rec.section = False
                rec.student = False

    @api.model
    @api.onchange('specific_student')
    def onchange_specific_student(self):
        for rec in self:
            if rec.specific_student == False:
                rec.student = False

    @api.model
    def generate_results(self):
        for exam in self.exams:
            result_exam_lines = self.env[
                'education.exam.result.exam.line'].search([
                    ('exam_count', '=', '1'), ('exam_ids', '=', exam.id)
                ])
            if len(result_exam_lines) == 0:
                data = {
                    'exam_ids': [(6, 0, [exam.id])],
                    'academic_year': exam.academic_year.id,
                    'total_working_days': exam.total_working_days,
                    'name': exam.name,
                    # 'exam_result_line':result_exam_lines.id,
                    'exam_count': 1,
                }
                result_exam_lines = result_exam_lines.create(data)
            # todo this line is hashed for average line test fast
            for line in result_exam_lines:
                result_exam_lines.calculate_exam_results(line)
        if len(self.exams) > 1:
            t_working_days = 0
            exam_list = []
            for exam in self.exams:
                result_exam_lines = self.env[
                    'education.exam.result.exam.line'].search([
                        ('exam_count', '=', '1'), ('exam_ids', '=', exam.id)
                    ])
                t_working_days = t_working_days + result_exam_lines.total_working_days
                exam_list.append(exam.id)
            exam_no = len(self.exams)
            search_str = [('exam_count', '=', exam_no)]
            result_exam_lines = self.env[
                'education.exam.result.exam.line'].search([
                    ('exam_count', '=', exam_no),
                    ('exam_ids', 'in', [exa for exa in exam_list])
                ])

            if len(result_exam_lines) == 0:
                data = {
                    'exam_ids': [(6, 0, exam_list)],
                    'academic_year': self.academic_year.id,
                    'total_working_days': t_working_days,
                    'name': 'Average & Final',
                    'exam_count': exam_no,
                }
                result_exam_lines = result_exam_lines.create(data)
            else:
                result_exam_lines.total_working_days = t_working_days
            result_exam_lines.process_average_results(result_exam_lines)

    @api.model
    def calculate_subject_rules(self, subject_list, exam):
        for subjects in subject_list:
            subjectRules = self.env['exam.subject.pass.rules'].search([
                ('exam_id', '=', exam.id), ('subject_id', '=', subjects.id)
            ])
            for line in subjectRules:
                for paper_rule in line.paper_ids:
                    paper_rule.name = paper_rule.paper_id.paper
                    paper_rule.paper_marks = paper_rule.tut_mark + paper_rule.subj_mark + paper_rule.obj_mark + paper_rule.prac_mark
                line.academic_year = line.exam_id.academic_year.id
                line.name = line.subject_id.name + " for " + line.class_id.name + "-" + line.academic_year.name
                subject_full_marks = 0
                subjective_mark = 0
                objective_mark = 0
                tutorial_mark = 0
                practical_mark = 0
                for paper in line.paper_ids:
                    subject_full_marks = subject_full_marks + paper.paper_marks
                    subjective_mark = subjective_mark + paper.subj_mark
                    objective_mark = objective_mark + paper.obj_mark
                    tutorial_mark = tutorial_mark + paper.tut_mark
                    practical_mark = practical_mark + paper.prac_mark
                line.subject_marks = subject_full_marks
                line.prac_mark = practical_mark
                line.obj_mark = objective_mark
                line.subj_mark = subjective_mark
                line.tut_mark = tutorial_mark

    @api.model
    def calculate_subjects_results(self, exam):
        student_lines = self.env['education.exam.results.new'].search([
            ('exam_id', '=', exam.id)
        ])
        for student in student_lines:
            obtained_general = 0
            obtained_general_converted = 0
            count_general_subjects = 0
            count_general_paper = 0
            count_general_fail = 0
            student.general_fail_count = 0
            full_general_mark = 0
            full_general_mark_converted = 0
            gp_general = 0
            obtained_optional = 0
            obtained_optional_converted = 0
            count_optional_subjects = 0
            count_optional_paper = 0
            count_optional_fail = 0
            optional_full_mark = 0
            optional_full_mark_converted = 0
            gp_optional = 0
            obtained_extra = 0
            obtained_extra_converted = 0
            count_extra_subjects = 0
            count_extra_paper = 0
            count_extra_fail = 0
            extra_full_mark = 0
            extra_full_mark_converted = 0
            gp_extra = 0
            res_type_count = 0
            hide_tut = True
            hide_subj = True
            hide_obj = True
            hide_prac = True
            hide_paper = True
            for subject in student.subject_line:
                paper_count = 0
                PassFail = True
                optional = False
                extra = False
                obt_tut = 0
                obt_prac = 0
                obt_subj = 0
                obt_obj = 0
                mark_tut = 0
                mark_prac = 0
                mark_subj = 0
                mark_obj = 0
                subject_obtained = 0
                subject_obtained_converted = 0
                subject_full = 0
                subject_full_converted = 0
                count_fail = 0
                for paper in subject.paper_ids:
                    paper_obtained = 0
                    paper_obtained_converted = 0
                    paper_full = 0
                    paper_full_converted = 0
                    paper_count = paper_count + 1
                    if paper.paper_id in student.student_history.optional_subjects:
                        optional = True
                    elif paper.paper_id.evaluation_type == 'extra':
                        extra = True
                    if paper.pass_rule_id.tut_mark > 0:
                        hide_tut = False
                        if paper.tut_pr == True:
                            paper_obtained = paper_obtained + paper.tut_obt
                            obt_tut = obt_tut + paper.tut_obt
                            paper_full = paper_full + paper.pass_rule_id.tut_mark
                            mark_tut = mark_tut + paper.pass_rule_id.tut_mark
                        else:
                            PassFail = False
                    if paper.pass_rule_id.subj_mark > 0:
                        hide_subj = False
                        if paper.subj_pr == True:
                            paper_obtained = paper_obtained + paper.subj_obt
                            paper_full = paper_full + paper.pass_rule_id.subj_mark
                            obt_subj = obt_subj + paper.subj_obt
                            mark_subj = mark_subj + paper.pass_rule_id.subj_mark
                        else:
                            PassFail = False
                    if paper.pass_rule_id.obj_mark > 0:
                        hide_obj = False
                        if paper.obj_pr == True:
                            paper_obtained = paper_obtained + paper.obj_obt
                            paper_full = paper_full + paper.pass_rule_id.obj_mark
                            obt_obj = obt_obj + paper.obj_obt
                            mark_obj = mark_obj + paper.pass_rule_id.obj_mark
                        else:
                            PassFail = False
                    if paper.pass_rule_id.prac_mark > 0:
                        hide_prac = False
                        if paper.prac_pr == True:
                            paper_obtained = paper_obtained + paper.prac_obt
                            paper_full = paper_full + paper.pass_rule_id.prac_mark
                            obt_prac = obt_prac + paper.prac_obt
                            mark_prac = mark_prac + paper.pass_rule_id.prac_mark
                        else:
                            PassFail = False

                    if paper.pass_rule_id.tut_pass > paper.tut_obt:
                        PassFail = False
                    elif paper.pass_rule_id.subj_pass > paper.subj_obt:
                        PassFail = False
                    elif paper.pass_rule_id.obj_pass > paper.obj_obt:
                        PassFail = False
                    elif paper.pass_rule_id.prac_pass > paper.prac_obt:
                        PassFail = False

                    paper.paper_obt = paper_obtained
                    paper.passed = PassFail
                    paper.paper_marks = paper_full
                    if paper_full >= 100:
                        paper.paper_marks_converted = 100
                    else:
                        paper.paper_marks_converted = 50
                    paper.paper_obt_converted = self.env[
                        'report.education_exam.report_dsblsc_marksheet'].half_round_up(
                            (paper_obtained / paper_full) *
                            paper.paper_marks_converted)
                    subject_obtained = subject_obtained + paper.paper_obt
                    subject_obtained_converted = subject_obtained_converted + paper.paper_obt_converted
                    subject_full = subject_full + paper_full
                    subject_full_converted = subject_full_converted + paper.paper_marks_converted
                subject.obj_obt = obt_obj
                subject.tut_obt = obt_tut
                subject.subj_obt = obt_subj
                subject.prac_obt = obt_prac
                subject.subject_obt = subject_obtained
                subject.subject_obt_converted = self.env[
                    'report.education_exam.report_dsblsc_marksheet'].half_round_up(
                        (subject_obtained / subject_full) *
                        subject_full_converted)  #subject_obtained_converted
                subject.subject_marks = subject_full
                subject.subject_marks_converted = subject_full_converted
                if subject.pass_rule_id.tut_pass > subject.tut_obt:
                    PassFail = False
                elif subject.pass_rule_id.subj_pass > subject.subj_obt:
                    PassFail = False
                elif subject.pass_rule_id.obj_pass > subject.obj_obt:
                    PassFail = False
                elif subject.pass_rule_id.prac_pass > subject.prac_obt:
                    PassFail = False
                subject.pass_or_fail = PassFail
                if PassFail == False:
                    count_fail = 1
                    subject_grade_point = 0
                    subject_letter_grade = 'F'
                else:
                    count_fail = 0
                    subject_grade_point = self.env[
                        'education.result.grading'].get_grade_point(
                            subject_full, subject_obtained)
                    subject_letter_grade = self.env[
                        'education.result.grading'].get_letter_grade(
                            subject_full, subject_obtained)
                if subject_letter_grade == 'F':
                    count_fail = 1
                subject.grade_point = subject_grade_point
                subject.letter_grade = subject_letter_grade
                if extra == True:
                    subject.extra_for = student.id
                    obtained_extra = obtained_extra + subject.subject_obt
                    obtained_extra_converted = obtained_extra_converted + subject.subject_obt_converted
                    count_extra_subjects = count_extra_subjects + 1
                    count_extra_paper = count_extra_paper + paper_count
                    extra_full_mark = extra_full_mark + subject_full
                    extra_full_mark_converted = extra_full_mark_converted + subject_full_converted
                    gp_extra = gp_extra + subject_grade_point
                    count_extra_fail = count_extra_fail + count_fail
                elif optional == True:
                    subject.optional_for = student.id
                    obtained_optional = obtained_optional + subject.subject_obt
                    obtained_optional_converted = obtained_optional_converted + subject.subject_obt_converted
                    count_optional_subjects = count_optional_subjects + 1
                    count_optional_paper = count_optional_paper + paper_count
                    optional_full_mark = optional_full_mark + subject.pass_rule_id.subject_marks
                    optional_full_mark_converted = optional_full_mark_converted + subject_full_converted
                    gp_optional = gp_optional + subject_grade_point
                    count_optional_fail = count_optional_fail + count_fail
                else:
                    full_general_mark = full_general_mark + subject_full
                    full_general_mark_converted = full_general_mark_converted + subject_full_converted
                    subject.general_for = student.id
                    count_general_subjects = count_general_subjects + 1
                    obtained_general = obtained_general + subject.subject_obt
                    obtained_general_converted = obtained_general_converted + subject.subject_obt_converted
                    count_general_paper = count_general_paper + paper_count
                    gp_general = gp_general + subject_grade_point
                    count_general_fail = count_general_fail + count_fail
                subject.paper_count = paper_count
                if paper_count > 1:
                    hide_paper = False
            if hide_tut == True:
                student.show_tut = False
            else:
                student.show_tut = True
            if hide_subj == True:
                student.show_subj = False
            else:
                student.show_subj = True
            if hide_obj == True:
                student.show_obj = False
            else:
                student.show_obj = True
            if hide_prac == True:
                student.show_prac = False
            else:
                student.show_prac = True
            if hide_paper == True:
                student.show_paper = False
            else:
                student.show_paper = True

            if student.show_tut == True:
                res_type_count = res_type_count + 1
            if student.show_subj == True:
                res_type_count = res_type_count + 1
            if student.show_obj == True:
                res_type_count = res_type_count + 1
            if student.show_prac == True:
                res_type_count = res_type_count + 1
            student.result_type_count = res_type_count
            student.extra_row_count = count_extra_paper
            student.extra_count = count_extra_subjects
            student.extra_obtained = obtained_extra
            student.extra_obtained_converted = obtained_extra_converted
            student.extra_fail_count = count_extra_fail
            student.extra_full_mark = extra_full_mark
            student.extra_full_mark_converted = extra_full_mark_converted

            student.general_row_count = count_general_paper
            student.general_count = count_general_subjects
            student.general_obtained = obtained_general
            student.general_obtained_converted = obtained_general_converted
            student.general_fail_count = count_general_fail
            student.general_gp = gp_general
            student.general_full_mark = full_general_mark
            student.general_full_mark_converted = full_general_mark_converted

            student.optional_row_count = count_optional_paper
            student.optional_count = count_optional_subjects
            student.optional_obtained = obtained_optional
            student.optional_obtained_converted = obtained_optional_converted
            student.optional_fail_count = count_optional_fail
            student.optional_gp = gp_optional
            student.optional_full_mark = optional_full_mark
            student.optional_full_converted = optional_full_mark_converted
            if student.general_count > 0:
                student.general_gpa = student.general_gp / student.general_count
            else:
                student.general_gpa = 0

            if student.optional_count > 0:
                student.optional_gpa = student.optional_gp / student.optional_count
                if student.optional_gpa > 2:
                    student.optional_gpa_above_2 = student.optional_gpa - 2
                else:
                    student.optional_gpa = 0
            if student.optional_gpa > 0:
                optional_40_perc = student.optional_full_mark * 40 / 100
                optional_40_perc_converted = student.optional_full_converted * 40 / 100
                student.optional_obtained_above_40_perc = student.optional_obtained - optional_40_perc
                student.optional_obtained_above_40_perc_converted = student.optional_obtained_converted - optional_40_perc_converted
            student.net_obtained = student.general_obtained + student.optional_obtained_above_40_perc
            student.net_obtained_converted = student.general_obtained_converted + student.optional_obtained_above_40_perc_converted
            if student.general_count > 0:
                if student.optional_gpa_above_2 < 0:
                    student.optional_gpa_above_2 = 0
                netGPA = student.general_gpa + (student.optional_gpa_above_2 /
                                                student.general_count)
                if netGPA < 5:
                    student.net_gpa = round(netGPA, 2)
                else:
                    student.net_gpa = 5
                student.net_lg = self.env['education.result.grading'].get_lg(
                    student.net_gpa)
            if student.extra_count > 0:
                if student.extra_fail_count < 1:
                    student.extra_gpa = student.extra_gp / student.extra_count

        #   TODO   Here to genrate Merit List
        # result_lines=self.env['education.exam.results.new'].sorted(key=lambda r: (r.name, r.country_id.name))
        #

        # ############# TODO get subject Highest

        subject_rule_lines = self.env['exam.subject.pass.rules'].search([
            ('exam_id', '=', exam.id)
        ])
        for subject_rule_line in subject_rule_lines:
            subject_result_lines = self.env['results.subject.line.new'].search(
                [('pass_rule_id', '=', subject_rule_line.id)],
                limit=1,
                order='subject_obt DESC')
            subject_rule_line.subject_highest = subject_result_lines.subject_obt
            for paper_rule_line in subject_rule_line.paper_ids:
                paper_result_line = self.env['results.paper.line'].search(
                    [('pass_rule_id', '=', paper_rule_line.id)],
                    limit=1,
                    order='paper_obt DESC')
                paper_rule_line.paper_highest = paper_result_line.paper_obt

    # subjectLines=self.env['results.subject.line.new'].search([('result_id.exam_id','=',exam.id)])
    # ##### distinct values search
    # subject=subjectLines.mapped('subject_id')
    # for value in set(subject):
    #     lines=subjectLines.search([('subject_id','=',value.id)],  order='subject_mark DESC')
    #     highest_set=False
    #     for line in lines:
    #         if highest_set==False:
    #             highest=line.subject_mark
    #             highest_set=True
    #         line.subject_highest=highest

    @api.model
    def calculate_result_paper_lines(self, result_paper_lines):
        for rec in result_paper_lines:
            passFail = True
            if rec.pass_rule_id.tut_pass > rec.tut_obt:
                passFail = False
            elif rec.pass_rule_id.subj_pass > rec.subj_obt:
                passFail = False
            elif rec.pass_rule_id.obj_pass > rec.obj_obt:
                passFail = False
            elif rec.pass_rule_id.prac_pass > rec.prac_obt:
                passFail = False
            elif rec.pass_rule_id.tut_mark > 0:
                if rec.tut_pr == False:
                    passFail = False
            elif rec.pass_rule_id.subj_mark > 0:
                if rec.subj_pr == False:
                    passFail = False
            elif rec.pass_rule_id.obj_mark > 0:
                if rec.obj_pr == False:
                    passFail = False
            elif rec.pass_rule_id.prac_mark > 0:
                if rec.prac_pr == False:
                    passFail = False
            paper_obtained = 0
            if rec.pass_rule_id.tut_mark > 0:
                paper_obtained = paper_obtained + rec.tut_obt
            if rec.pass_rule_id.subj_mark > 0:
                paper_obtained = paper_obtained + rec.subj_obt
            if rec.pass_rule_id.obj_mark > 0:
                paper_obtained = paper_obtained + rec.obj_obt
            if rec.pass_rule_id.prac_mark > 0:
                paper_obtained = paper_obtained + rec.prac_obt
            rec.paper_obt = paper_obtained
            rec.passed = passFail
            if passFail == True:
                rec.gp = self.env['education.result.grading'].get_grade_point(
                    rec.pass_rule_id.paper_marks, rec.paper_obt)
                rec.lg = self.env['education.result.grading'].get_letter_grade(
                    rec.pass_rule_id.paper_marks, rec.paper_obt)
            else:
                rec.gp = 0
                rec.lg = 'F'

    @api.model
    def calculate_result_subject_lines(self, result_subject_lines):
        for rec in result_subject_lines:
            practical_obt = 0
            subjective_obt = 0
            objective_obt = 0
            tutorial_obt = 0
            practical_mark = 0
            subjective_mark = 0
            objective_mark = 0
            tutorial_mark = 0
            PassFail = True
            for line in rec.paper_ids:
                practical_obt = practical_obt + line.prac_obt
                subjective_obt = subjective_obt + line.subj_obt
                objective_obt = objective_obt + line.obj_obt
                tutorial_obt = tutorial_obt + line.tut_obt
                practical_mark = practical_mark + line.pass_rule_id.tut_mark
                subjective_mark = subjective_mark + line.pass_rule_id.subj_mark
                objective_mark = objective_mark + line.pass_rule_id.obj_mark
                tutorial_mark = tutorial_mark + line.pass_rule_id.tut_mark
                if line.passed == False:
                    PassFail = False
            rec.tut_obt = tutorial_obt
            rec.prac_obt = practical_obt
            rec.subj_obt = subjective_obt
            rec.obj_obt = objective_obt
            rec.tut_mark = tutorial_mark
            rec.prac_mark = practical_mark
            rec.subj_mark = subjective_mark
            rec.obj_mark = objective_mark

            if PassFail == False:
                PassFail = False
            elif rec.pass_rule_id.tut_pass > rec.tut_obt:
                PassFail = False
            elif rec.pass_rule_id.subj_pass > rec.subj_obt:
                PassFail = False
            elif rec.pass_rule_id.obj_pass > rec.obj_obt:
                PassFail = False
            elif rec.pass_rule_id.prac_pass > rec.prac_obt:
                PassFail = False

            rec.mark_scored = 0
            if rec.pass_rule_id.tut_mark > 0:
                rec.mark_scored = rec.mark_scored + rec.tut_obt
            if rec.pass_rule_id.subj_mark > 0:
                rec.mark_scored = rec.mark_scored + rec.subj_obt
            if rec.pass_rule_id.obj_mark > 0:
                rec.mark_scored = rec.mark_scored + rec.obj_obt
            if rec.pass_rule_id.prac_mark > 0:
                rec.mark_scored = rec.mark_scored + rec.prac_obt
            if PassFail == True:
                rec.grade_point = rec.env[
                    'education.result.grading'].get_grade_point(
                        rec.pass_rule_id.subject_marks, rec.mark_scored)
                rec.letter_grade = rec.env[
                    'education.result.grading'].get_letter_grade(
                        rec.pass_rule_id.subject_marks, rec.mark_scored)
            else:
                rec.grade_point = 0
                rec.letter_grade = 'F'

    @api.model
    def get_result_type_count(self, exam):
        result_lines = self.env['education.exam.results.new'].search([
            ('exam_id', '=', exam.id)
        ])
        for rec in result_lines:
            res_type_count = 0
            if rec.show_tut == True:
                res_type_count = res_type_count + 1
            if rec.show_subj == True:
                res_type_count = res_type_count + 1
            if rec.show_obj == True:
                res_type_count = res_type_count + 1
            if rec.show_prac == True:
                res_type_count = res_type_count + 1
            rec.result_type_count = res_type_count
示例#23
0
class DeliveryCarrier(models.Model):
    _name = 'delivery.carrier'
    _description = "Delivery Methods"
    _order = 'sequence, id'

    ''' A Shipping Provider

    In order to add your own external provider, follow these steps:

    1. Create your model MyProvider that _inherit 'delivery.carrier'
    2. Extend the selection of the field "delivery_type" with a pair
       ('<my_provider>', 'My Provider')
    3. Add your methods:
       <my_provider>_rate_shipment
       <my_provider>_send_shipping
       <my_provider>_get_tracking_link
       <my_provider>_cancel_shipment
       _<my_provider>_get_default_custom_package_code
       (they are documented hereunder)
    '''

    # -------------------------------- #
    # Internals for shipping providers #
    # -------------------------------- #

    name = fields.Char('Delivery Method', required=True, translate=True)
    active = fields.Boolean(default=True)
    sequence = fields.Integer(help="Determine the display order", default=10)
    # This field will be overwritten by internal shipping providers by adding their own type (ex: 'fedex')
    delivery_type = fields.Selection([('fixed', 'Fixed Price')], string='Provider', default='fixed', required=True)
    integration_level = fields.Selection([('rate', 'Get Rate'), ('rate_and_ship', 'Get Rate and Create Shipment')], string="Integration Level", default='rate_and_ship', help="Action while validating Delivery Orders")
    prod_environment = fields.Boolean("Environment", help="Set to True if your credentials are certified for production.")
    debug_logging = fields.Boolean('Debug logging', help="Log requests in order to ease debugging")
    company_id = fields.Many2one('res.company', string='Company', related='product_id.company_id', store=True, readonly=False)
    product_id = fields.Many2one('product.product', string='Delivery Product', required=True, ondelete='restrict')

    country_ids = fields.Many2many('res.country', 'delivery_carrier_country_rel', 'carrier_id', 'country_id', 'Countries')
    state_ids = fields.Many2many('res.country.state', 'delivery_carrier_state_rel', 'carrier_id', 'state_id', 'States')
    zip_from = fields.Char('Zip From')
    zip_to = fields.Char('Zip To')

    margin = fields.Integer(help='This percentage will be added to the shipping price.')
    free_over = fields.Boolean('Free if order amount is above', help="If the order total amount (shipping excluded) is above or equal to this value, the customer benefits from a free shipping", default=False, oldname='free_if_more_than')
    amount = fields.Float(string='Amount', help="Amount of the order to benefit from a free shipping, expressed in the company currency")

    _sql_constraints = [
        ('margin_not_under_100_percent', 'CHECK (margin >= -100)', 'Margin cannot be lower than -100%'),
    ]

    def toggle_prod_environment(self):
        for c in self:
            c.prod_environment = not c.prod_environment

    def toggle_debug(self):
        for c in self:
            c.debug_logging = not c.debug_logging

    @api.multi
    def install_more_provider(self):
        return {
            'name': 'New Providers',
            'view_mode': 'kanban,form',
            'res_model': 'ir.module.module',
            'domain': [['name', 'ilike', 'delivery_']],
            'type': 'ir.actions.act_window',
            'help': _('''<p class="o_view_nocontent">
                    Buy Eagle Enterprise now to get more providers.
                </p>'''),
        }

    def available_carriers(self, partner):
        return self.filtered(lambda c: c._match_address(partner))

    def _match_address(self, partner):
        self.ensure_one()
        if self.country_ids and partner.country_id not in self.country_ids:
            return False
        if self.state_ids and partner.state_id not in self.state_ids:
            return False
        if self.zip_from and (partner.zip or '').upper() < self.zip_from.upper():
            return False
        if self.zip_to and (partner.zip or '').upper() > self.zip_to.upper():
            return False
        return True

    @api.onchange('state_ids')
    def onchange_states(self):
        self.country_ids = [(6, 0, self.country_ids.ids + self.state_ids.mapped('country_id.id'))]

    @api.onchange('country_ids')
    def onchange_countries(self):
        self.state_ids = [(6, 0, self.state_ids.filtered(lambda state: state.id in self.country_ids.mapped('state_ids').ids).ids)]

    # -------------------------- #
    # API for external providers #
    # -------------------------- #

    def rate_shipment(self, order):
        ''' Compute the price of the order shipment

        :param order: record of sale.order
        :return dict: {'success': boolean,
                       'price': a float,
                       'error_message': a string containing an error message,
                       'warning_message': a string containing a warning message}
                       # TODO maybe the currency code?
        '''
        self.ensure_one()
        if hasattr(self, '%s_rate_shipment' % self.delivery_type):
            res = getattr(self, '%s_rate_shipment' % self.delivery_type)(order)
            # apply margin on computed price
            res['price'] = float(res['price']) * (1.0 + (float(self.margin) / 100.0))
            # free when order is large enough
            if res['success'] and self.free_over and order._compute_amount_total_without_delivery() >= self.amount:
                res['warning_message'] = _('Info:\nThe shipping is free because the order amount exceeds %.2f.\n(The actual shipping cost is: %.2f)') % (self.amount, res['price'])
                res['price'] = 0.0
            return res

    def send_shipping(self, pickings):
        ''' Send the package to the service provider

        :param pickings: A recordset of pickings
        :return list: A list of dictionaries (one per picking) containing of the form::
                         { 'exact_price': price,
                           'tracking_number': number }
                           # TODO missing labels per package
                           # TODO missing currency
                           # TODO missing success, error, warnings
        '''
        self.ensure_one()
        if hasattr(self, '%s_send_shipping' % self.delivery_type):
            return getattr(self, '%s_send_shipping' % self.delivery_type)(pickings)

    def get_tracking_link(self, picking):
        ''' Ask the tracking link to the service provider

        :param picking: record of stock.picking
        :return str: an URL containing the tracking link or False
        '''
        self.ensure_one()
        if hasattr(self, '%s_get_tracking_link' % self.delivery_type):
            return getattr(self, '%s_get_tracking_link' % self.delivery_type)(picking)

    def cancel_shipment(self, pickings):
        ''' Cancel a shipment

        :param pickings: A recordset of pickings
        '''
        self.ensure_one()
        if hasattr(self, '%s_cancel_shipment' % self.delivery_type):
            return getattr(self, '%s_cancel_shipment' % self.delivery_type)(pickings)

    def log_xml(self, xml_string, func):
        self.ensure_one()

        if self.debug_logging:
            db_name = self._cr.dbname

            # Use a new cursor to avoid rollback that could be caused by an upper method
            try:
                db_registry = registry(db_name)
                with db_registry.cursor() as cr:
                    env = api.Environment(cr, SUPERUSER_ID, {})
                    IrLogging = env['ir.logging']
                    IrLogging.sudo().create({'name': 'delivery.carrier',
                              'type': 'server',
                              'dbname': db_name,
                              'level': 'DEBUG',
                              'message': xml_string,
                              'path': self.delivery_type,
                              'func': func,
                              'line': 1})
            except psycopg2.Error:
                pass

    def _get_default_custom_package_code(self):
        """ Some delivery carriers require a prefix to be sent in order to use custom
        packages (ie not official ones). This optional method will return it as a string.
        """
        self.ensure_one()
        if hasattr(self, '_%s_get_default_custom_package_code' % self.delivery_type):
            return getattr(self, '_%s_get_default_custom_package_code' % self.delivery_type)()
        else:
            return False

    # ------------------------------------------------ #
    # Fixed price shipping, aka a very simple provider #
    # ------------------------------------------------ #

    fixed_price = fields.Float(compute='_compute_fixed_price', inverse='_set_product_fixed_price', store=True, string='Fixed Price')

    @api.depends('product_id.list_price', 'product_id.product_tmpl_id.list_price')
    def _compute_fixed_price(self):
        for carrier in self:
            carrier.fixed_price = carrier.product_id.list_price

    def _set_product_fixed_price(self):
        for carrier in self:
            carrier.product_id.list_price = carrier.fixed_price

    def fixed_rate_shipment(self, order):
        carrier = self._match_address(order.partner_shipping_id)
        if not carrier:
            return {'success': False,
                    'price': 0.0,
                    'error_message': _('Error: this delivery method is not available for this address.'),
                    'warning_message': False}
        price = self.fixed_price
        if self.company_id and self.company_id.currency_id.id != order.currency_id.id:
            price = self.env['res.currency']._compute(self.company_id.currency_id, order.currency_id, price)
        return {'success': True,
                'price': price,
                'error_message': False,
                'warning_message': False}

    def fixed_send_shipping(self, pickings):
        res = []
        for p in pickings:
            res = res + [{'exact_price': p.carrier_id.fixed_price,
                          'tracking_number': False}]
        return res

    def fixed_get_tracking_link(self, picking):
        return False

    def fixed_cancel_shipment(self, pickings):
        raise NotImplementedError()
示例#24
0
class Partner(models.Model):

    _inherit = 'res.partner'

    website_tag_ids = fields.Many2many('res.partner.tag', 'res_partner_res_partner_tag_rel', 'partner_id', 'tag_id', string='Website tags')
class CRMLeadMiningRequest(models.Model):
    _name = 'crm.iap.lead.mining.request'
    _description = 'CRM Lead Mining Request'

    def _default_lead_type(self):
        if self.env.user.has_group('crm.group_use_lead'):
            return 'lead'
        else:
            return 'opportunity'

    name = fields.Char(string='Request Number', required=True, readonly=True, default=lambda self: _('New'), copy=False)
    state = fields.Selection([('draft', 'Draft'), ('done', 'Done'), ('error', 'Error')], string='Status', required=True, default='draft')

    # Request Data
    lead_number = fields.Integer(string='Number of Leads', required=True, default=10)
    search_type = fields.Selection([('companies', 'Companies'), ('people', 'Companies and their Contacts')], string='Target', required=True, default='companies')
    error = fields.Text(string='Error', readonly=True)

    # Lead / Opportunity Data
    lead_type = fields.Selection([('lead', 'Lead'), ('opportunity', 'Opportunity')], string='Type', required=True, default=_default_lead_type)
    team_id = fields.Many2one('crm.team', string='Sales Team', domain="[('use_opportunities', '=', True)]")
    user_id = fields.Many2one('res.users', string='Salesperson')
    tag_ids = fields.Many2many('crm.lead.tag', string='Tags')
    lead_ids = fields.One2many('crm.lead', 'lead_mining_request_id', string='Generated Lead / Opportunity')
    leads_count = fields.Integer(compute='_compute_leads_count', string='Number of Generated Leads')

    # Company Criteria Filter
    filter_on_size = fields.Boolean(string='Filter on Size', default=False)
    company_size_min = fields.Integer(string='Size', default=1)
    company_size_max = fields.Integer(default=1000)
    country_ids = fields.Many2many('res.country', string='Countries')
    state_ids = fields.Many2many('res.country.state', string='States')
    industry_ids = fields.Many2many('crm.iap.lead.industry', string='Industries')

    # Contact Generation Filter
    contact_number = fields.Integer(string='Number of Contacts', default=1)
    contact_filter_type = fields.Selection([('role', 'Role'), ('seniority', 'Seniority')], string='Filter on', default='role')
    preferred_role_id = fields.Many2one('crm.iap.lead.role', string='Preferred Role')
    role_ids = fields.Many2many('crm.iap.lead.role', string='Other Roles')
    seniority_id = fields.Many2one('crm.iap.lead.seniority', string='Seniority')

    # Fields for the blue tooltip
    lead_credits = fields.Char(compute='_compute_tooltip', readonly=True)
    lead_contacts_credits = fields.Char(compute='_compute_tooltip', readonly=True)
    lead_total_credits = fields.Char(compute='_compute_tooltip', readonly=True)

    @api.onchange('lead_number', 'contact_number')
    def _compute_tooltip(self):
        for record in self:
            company_credits = CREDIT_PER_COMPANY * record.lead_number
            contact_credits = CREDIT_PER_CONTACT * record.contact_number
            total_contact_credits = contact_credits * record.lead_number
            record.lead_contacts_credits = _("Up to %d additional credits will be consumed to identify %d contacts per company.") % (contact_credits*company_credits, record.contact_number)
            record.lead_credits = _('%d credits will be consumed to find %d companies.') % (company_credits, record.lead_number)
            record.lead_total_credits = _("This makes a total of %d credits for this request.") % (total_contact_credits + company_credits)

    @api.depends('lead_ids')
    def _compute_leads_count(self):
        for req in self:
            req.leads_count = len(req.lead_ids)

    @api.onchange('lead_number')
    def _onchange_lead_number(self):
        if self.lead_number <= 0:
            self.lead_number = 1
        elif self.lead_number > MAX_LEAD:
            self.lead_number = MAX_LEAD

    @api.onchange('contact_number')
    def _onchange_contact_number(self):
        if self.contact_number <= 0:
            self.contact_number = 1
        elif self.contact_number > MAX_CONTACT:
            self.contact_number = MAX_CONTACT

    @api.onchange('country_ids')
    def _onchange_country_ids(self):
        self.state_ids = []

    @api.onchange('company_size_min')
    def _onchange_company_size_min(self):
        if self.company_size_min <= 0:
            self.company_size_min = 1
        elif self.company_size_min > self.company_size_max:
            self.company_size_min = self.company_size_max

    @api.onchange('company_size_max')
    def _onchange_company_size_max(self):
        if self.company_size_max < self.company_size_min:
            self.company_size_max = self.company_size_min
    
    def _prepare_iap_payload(self):
        """
        This will prepare the data to send to the server
        """
        self.ensure_one()
        payload = {'lead_number': self.lead_number,
                   'search_type': self.search_type,
                   'countries': self.country_ids.mapped('code')}
        if self.state_ids:
            payload['states'] = self.state_ids.mapped('code')
        if self.filter_on_size:
            payload.update({'company_size_min': self.company_size_min,
                            'company_size_max': self.company_size_max})
        if self.industry_ids:
            payload['industry_ids'] = self.industry_ids.mapped('reveal_id')
        if self.search_type == 'people':
            payload.update({'contact_number': self.contact_number,
                            'contact_filter_type': self.contact_filter_type})
            if self.contact_filter_type == 'role':
                payload.update({'preferred_role': self.preferred_role_id.reveal_id,
                                'other_roles': self.role_ids.mapped('reveal_id')})
            elif self.contact_filter_type == 'seniority':
                payload['seniority'] = self.seniority_id.reveal_id
        return payload

    def _perform_request(self):
        """
        This will perform the request and create the corresponding leads.
        The user will be notified if he hasn't enough credits.
        """
        server_payload = self._prepare_iap_payload()
        reveal_account = self.env['iap.account'].get('reveal')
        dbuuid = self.env['ir.config_parameter'].sudo().get_param('database.uuid')
        endpoint = self.env['ir.config_parameter'].sudo().get_param('reveal.endpoint', DEFAULT_ENDPOINT) + '/iap/clearbit/1/lead_mining_request'
        params = {
            'account_token': reveal_account.account_token,
            'dbuuid': dbuuid,
            'data': server_payload
        }
        try:
            response = jsonrpc(endpoint, params=params, timeout=300)
            return response['data']
        except InsufficientCreditError as e:
            self.error = 'Insufficient credits. Recharge your account and retry.'
            self.state = 'error'
            self._cr.commit()
            raise e

    def _create_leads_from_response(self, result):
        """ This method will get the response from the service and create the leads accordingly """
        self.ensure_one()
        lead_vals = []
        messages_to_post = {}
        for data in result:
            lead_vals.append(self._lead_vals_from_response(data))
            messages_to_post[data['company_data']['clearbit_id']] = self.env['crm.iap.lead.helpers'].format_data_for_message_post(data['company_data'], data.get('people_data'))
        leads = self.env['crm.lead'].create(lead_vals)
        for lead in leads:
            if messages_to_post.get(lead.reveal_id):
                lead.message_post_with_view('crm_iap_lead.lead_message_template', values=messages_to_post[lead.reveal_id], subtype_id=self.env.ref('mail.mt_note').id)

    # Methods responsible for format response data into valid eagle lead data
    @api.model
    def _lead_vals_from_response(self, data):
        self.ensure_one()
        company_data = data.get('company_data')
        people_data = data.get('people_data')
        lead_vals = self.env['crm.iap.lead.helpers'].lead_vals_from_response(self.lead_type, self.team_id.id, self.tag_ids.ids, self.user_id.id, company_data, people_data)
        lead_vals['lead_mining_request_id'] = self.id
        return lead_vals

    @api.model
    def get_empty_list_help(self, help):
        help_title = _('Create a Lead Mining Request')
        sub_title = _('Generate new leads based on their country, industry, size, etc.')
        return '<p class="o_view_nocontent_smiling_face">%s</p><p class="oe_view_nocontent_alias">%s</p>' % (help_title, sub_title)

    def action_draft(self):
        self.ensure_one()
        self.name = _('New')
        self.state = 'draft'

    def action_submit(self):
        self.ensure_one()
        if self.name == _('New'):
            self.name = self.env['ir.sequence'].next_by_code('crm.iap.lead.mining.request') or _('New')
        results = self._perform_request()
        if results:
            self._create_leads_from_response(results)
            self.state = 'done'
        if self.lead_type == 'lead':
            return self.action_get_lead_action()
        elif self.lead_type == 'opportunity':
            return self.action_get_opportunity_action()

    def action_get_lead_action(self):
        self.ensure_one()
        action = self.env.ref('crm.crm_lead_all_leads').read()[0]
        action['domain'] = [('id', 'in', self.lead_ids.ids), ('type', '=', 'lead')]
        action['help'] = _("""<p class="o_view_nocontent_empty_folder">
            No leads found
        </p><p>
            No leads could be generated according to your search criteria
        </p>""")
        return action

    def action_get_opportunity_action(self):
        self.ensure_one()
        action = self.env.ref('crm.crm_lead_opportunities').read()[0]
        action['domain'] = [('id', 'in', self.lead_ids.ids), ('type', '=', 'opportunity')]
        action['help'] = _("""<p class="o_view_nocontent_empty_folder">
            No opportunities found
        </p><p>
            No opportunities could be generated according to your search criteria
        </p>""")
        return action
示例#26
0
class Event(models.Model):
    _inherit = "event.event"

    track_ids = fields.One2many('event.track', 'event_id', 'Tracks')
    track_count = fields.Integer('Track Count', compute='_compute_track_count')

    sponsor_ids = fields.One2many('event.sponsor', 'event_id', 'Sponsors')
    sponsor_count = fields.Integer('Sponsor Count',
                                   compute='_compute_sponsor_count')

    website_track = fields.Boolean('Tracks on Website')
    website_track_proposal = fields.Boolean('Proposals on Website')

    track_menu_ids = fields.One2many('website.event.menu',
                                     'event_id',
                                     string='Event Tracks Menus',
                                     domain=[('menu_type', '=', 'track')])
    track_proposal_menu_ids = fields.One2many('website.event.menu',
                                              'event_id',
                                              string='Event Proposals Menus',
                                              domain=[('menu_type', '=',
                                                       'track_proposal')])

    allowed_track_tag_ids = fields.Many2many(
        'event.track.tag',
        relation='event_allowed_track_tags_rel',
        string='Available Track Tags')
    tracks_tag_ids = fields.Many2many('event.track.tag',
                                      relation='event_track_tags_rel',
                                      string='Track Tags',
                                      compute='_compute_tracks_tag_ids',
                                      store=True)

    @api.multi
    def _compute_track_count(self):
        data = self.env['event.track'].read_group(
            [('stage_id.is_cancel', '!=', True)], ['event_id'], ['event_id'])
        result = dict(
            (data['event_id'][0], data['event_id_count']) for data in data)
        for event in self:
            event.track_count = result.get(event.id, 0)

    @api.multi
    def _compute_sponsor_count(self):
        data = self.env['event.sponsor'].read_group([], ['event_id'],
                                                    ['event_id'])
        result = dict(
            (data['event_id'][0], data['event_id_count']) for data in data)
        for event in self:
            event.sponsor_count = result.get(event.id, 0)

    def _toggle_create_website_menus(self, vals):
        super(Event, self)._toggle_create_website_menus(vals)
        for event in self:
            if 'website_track' in vals:
                if vals['website_track']:
                    for sequence, (name, url, xml_id, menu_type) in enumerate(
                            event._get_track_menu_entries()):
                        menu = super(Event, event)._create_menu(
                            sequence, name, url, xml_id)
                        event.env['website.event.menu'].create({
                            'menu_id':
                            menu.id,
                            'event_id':
                            event.id,
                            'menu_type':
                            menu_type,
                        })
                else:
                    event.track_menu_ids.mapped('menu_id').unlink()
            if 'website_track_proposal' in vals:
                if vals['website_track_proposal']:
                    for sequence, (name, url, xml_id, menu_type) in enumerate(
                            event._get_track_proposal_menu_entries()):
                        menu = super(Event, event)._create_menu(
                            sequence, name, url, xml_id)
                        event.env['website.event.menu'].create({
                            'menu_id':
                            menu.id,
                            'event_id':
                            event.id,
                            'menu_type':
                            menu_type,
                        })
                else:
                    event.track_proposal_menu_ids.mapped('menu_id').unlink()

    def _get_track_menu_entries(self):
        self.ensure_one()
        res = [(_('Talks'), '/event/%s/track' % slug(self), False, 'track'),
               (_('Agenda'), '/event/%s/agenda' % slug(self), False, 'track')]
        return res

    def _get_track_proposal_menu_entries(self):
        self.ensure_one()
        res = [(_('Talk Proposals'), '/event/%s/track_proposal' % slug(self),
                False, 'track_proposal')]
        return res

    @api.multi
    @api.depends('track_ids.tag_ids')
    def _compute_tracks_tag_ids(self):
        for event in self:
            event.tracks_tag_ids = event.track_ids.mapped('tag_ids').ids

    @api.onchange('event_type_id')
    def _onchange_type(self):
        super(Event, self)._onchange_type()
        if self.event_type_id and self.website_menu:
            self.website_track = self.event_type_id.website_track
            self.website_track_proposal = self.event_type_id.website_track_proposal

    @api.onchange('website_menu')
    def _onchange_website_menu(self):
        if not self.website_menu:
            self.website_track = False
            self.website_track_proposal = False

    @api.onchange('website_track')
    def _onchange_website_track(self):
        if not self.website_track:
            self.website_track_proposal = False

    @api.onchange('website_track_proposal')
    def _onchange_website_track_proposal(self):
        if self.website_track_proposal:
            self.website_track = True
示例#27
0
class CrmTeam(models.Model):
    _name = "crm.team"
    _inherit = ['mail.thread']
    _description = "Sales Team"
    _order = "sequence"
    _check_company_auto = True

    @api.model
    @api.returns('self', lambda value: value.id if value else False)
    def _get_default_team_id(self, user_id=None, domain=None):
        if not user_id:
            user_id = self.env.uid
        team_id = self.env['crm.team'].search([
            '|', ('user_id', '=', user_id), ('member_ids', '=', user_id), '|',
            ('company_id', '=', False),
            ('company_id', '=', self.env.company.id)
        ],
                                              limit=1)
        if not team_id and 'default_team_id' in self.env.context:
            team_id = self.env['crm.team'].browse(
                self.env.context.get('default_team_id'))
        if not team_id:
            team_domain = domain or []
            default_team_id = self.env['crm.team'].search(team_domain, limit=1)
            return default_team_id or self.env['crm.team']
        return team_id

    def _get_default_favorite_user_ids(self):
        return [(6, 0, [self.env.uid])]

    name = fields.Char('Sales Team', required=True, translate=True)
    sequence = fields.Integer('Sequence', default=10)
    active = fields.Boolean(
        default=True,
        help=
        "If the active field is set to false, it will allow you to hide the Sales Team without removing it."
    )
    company_id = fields.Many2one('res.company',
                                 string='Company',
                                 default=lambda self: self.env.company,
                                 index=True)
    currency_id = fields.Many2one("res.currency",
                                  related='company_id.currency_id',
                                  string="Currency",
                                  readonly=True)
    user_id = fields.Many2one('res.users',
                              string='Team Leader',
                              check_company=True)
    member_ids = fields.One2many(
        'res.users',
        'sale_team_id',
        string='Channel Members',
        check_company=True,
        domain=lambda self: [('groups_id', 'in', self.env.ref('base.group_user'
                                                              ).id)],
        help=
        "Add members to automatically assign their documents to this sales team. You can only be member of one team."
    )
    favorite_user_ids = fields.Many2many(
        'res.users',
        'team_favorite_user_rel',
        'team_id',
        'user_id',
        string='Favorite Members',
        default=_get_default_favorite_user_ids)
    is_favorite = fields.Boolean(
        string='Show on dashboard',
        compute='_compute_is_favorite',
        inverse='_inverse_is_favorite',
        help=
        "Favorite teams to display them in the dashboard and access them easily."
    )
    color = fields.Integer(string='Color Index',
                           help="The color of the channel")
    dashboard_button_name = fields.Char(
        string="Dashboard Button", compute='_compute_dashboard_button_name')
    dashboard_graph_data = fields.Text(compute='_compute_dashboard_graph')

    def _compute_dashboard_graph(self):
        for team in self:
            team.dashboard_graph_data = json.dumps(team._get_graph())

    def _compute_is_favorite(self):
        for team in self:
            team.is_favorite = self.env.user in team.favorite_user_ids

    def _inverse_is_favorite(self):
        sudoed_self = self.sudo()
        to_fav = sudoed_self.filtered(
            lambda team: self.env.user not in team.favorite_user_ids)
        to_fav.write({'favorite_user_ids': [(4, self.env.uid)]})
        (sudoed_self - to_fav).write(
            {'favorite_user_ids': [(3, self.env.uid)]})
        return True

    def _graph_get_model(self):
        """ skeleton function defined here because it'll be called by crm and/or sale
        """
        raise UserError(
            _('Undefined graph model for Sales Team: %s') % self.name)

    def _graph_get_dates(self, today):
        """ return a coherent start and end date for the dashboard graph covering a month period grouped by week.
        """
        start_date = today - relativedelta(months=1)
        # we take the start of the following week if we group by week
        # (to avoid having twice the same week from different month)
        start_date += relativedelta(days=8 - start_date.isocalendar()[2])
        return [start_date, today]

    def _graph_date_column(self):
        return 'create_date'

    def _graph_x_query(self):
        return 'EXTRACT(WEEK FROM %s)' % self._graph_date_column()

    def _graph_y_query(self):
        raise UserError(
            _('Undefined graph model for Sales Team: %s') % self.name)

    def _extra_sql_conditions(self):
        return ''

    def _graph_title_and_key(self):
        """ Returns an array containing the appropriate graph title and key respectively.

            The key is for lineCharts, to have the on-hover label.
        """
        return ['', '']

    def _graph_data(self, start_date, end_date):
        """ return format should be an iterable of dicts that contain {'x_value': ..., 'y_value': ...}
            x_values should be weeks.
            y_values are floats.
        """
        query = """SELECT %(x_query)s as x_value, %(y_query)s as y_value
                     FROM %(table)s
                    WHERE team_id = %(team_id)s
                      AND DATE(%(date_column)s) >= %(start_date)s
                      AND DATE(%(date_column)s) <= %(end_date)s
                      %(extra_conditions)s
                    GROUP BY x_value;"""

        # apply rules
        dashboard_graph_model = self._graph_get_model()
        GraphModel = self.env[dashboard_graph_model]
        graph_table = GraphModel._table
        extra_conditions = self._extra_sql_conditions()
        where_query = GraphModel._where_calc([])
        GraphModel._apply_ir_rules(where_query, 'read')
        from_clause, where_clause, where_clause_params = where_query.get_sql()
        if where_clause:
            extra_conditions += " AND " + where_clause

        query = query % {
            'x_query': self._graph_x_query(),
            'y_query': self._graph_y_query(),
            'table': graph_table,
            'team_id': "%s",
            'date_column': self._graph_date_column(),
            'start_date': "%s",
            'end_date': "%s",
            'extra_conditions': extra_conditions
        }

        self._cr.execute(query,
                         [self.id, start_date, end_date] + where_clause_params)
        return self.env.cr.dictfetchall()

    def _get_graph(self):
        def get_week_name(start_date, locale):
            """ Generates a week name (string) from a datetime according to the locale:
                E.g.: locale    start_date (datetime)      return string
                      "en_US"      November 16th           "16-22 Nov"
                      "en_US"      December 28th           "28 Dec-3 Jan"
            """
            if (start_date + relativedelta(days=6)).month == start_date.month:
                short_name_from = format_date(start_date, 'd', locale=locale)
            else:
                short_name_from = format_date(start_date,
                                              'd MMM',
                                              locale=locale)
            short_name_to = format_date(start_date + relativedelta(days=6),
                                        'd MMM',
                                        locale=locale)
            return short_name_from + '-' + short_name_to

        self.ensure_one()
        values = []
        today = fields.Date.from_string(fields.Date.context_today(self))
        start_date, end_date = self._graph_get_dates(today)
        graph_data = self._graph_data(start_date, end_date)
        x_field = 'label'
        y_field = 'value'

        # generate all required x_fields and update the y_values where we have data for them
        locale = self._context.get('lang') or 'en_US'

        weeks_in_start_year = int(
            date(start_date.year, 12, 28).isocalendar()
            [1])  # This date is always in the last week of ISO years
        for week in range(
                0, (end_date.isocalendar()[1] - start_date.isocalendar()[1]) %
                weeks_in_start_year + 1):
            short_name = get_week_name(
                start_date + relativedelta(days=7 * week), locale)
            values.append({x_field: short_name, y_field: 0})

        for data_item in graph_data:
            index = int(
                (data_item.get('x_value') - start_date.isocalendar()[1]) %
                weeks_in_start_year)
            values[index][y_field] = data_item.get('y_value')

        [graph_title, graph_key] = self._graph_title_and_key()
        color = '#875A7B' if '+e' in version else '#7c7bad'
        return [{
            'values': values,
            'area': True,
            'title': graph_title,
            'key': graph_key,
            'color': color
        }]

    def _compute_dashboard_button_name(self):
        """ Sets the adequate dashboard button name depending on the Sales Team's options
        """
        for team in self:
            team.dashboard_button_name = _(
                "Big Pretty Button :)")  # placeholder

    def action_primary_channel_button(self):
        """ skeleton function to be overloaded
            It will return the adequate action depending on the Sales Team's options
        """
        return False

    @api.model
    def create(self, values):
        team = super(
            CrmTeam,
            self.with_context(mail_create_nosubscribe=True)).create(values)
        if values.get('member_ids'):
            team._add_members_to_favorites()
        return team

    def write(self, values):
        res = super(CrmTeam, self).write(values)
        if values.get('member_ids'):
            self._add_members_to_favorites()
        return res

    def unlink(self):
        default_teams = [
            self.env.ref('sales_team.salesteam_website_sales'),
            self.env.ref('sales_team.pos_sales_team'),
            self.env.ref('sales_team.ebay_sales_team')
        ]
        for team in self:
            if team in default_teams:
                raise UserError(
                    _('Cannot delete default team "%s"' % (team.name)))
        return super(CrmTeam, self).unlink()

    def _add_members_to_favorites(self):
        for team in self:
            team.favorite_user_ids = [(4, member.id)
                                      for member in team.member_ids]
示例#28
0
class PurchaseRequisitionLine(models.Model):
    _name = "purchase.requisition.line"
    _description = "Purchase Requisition Line"
    _rec_name = 'product_id'

    product_id = fields.Many2one('product.product',
                                 string='Product',
                                 domain=[('purchase_ok', '=', True)],
                                 required=True)
    product_uom_id = fields.Many2one('uom.uom',
                                     string='Product Unit of Measure')
    product_qty = fields.Float(
        string='Quantity', digits=dp.get_precision('Product Unit of Measure'))
    price_unit = fields.Float(string='Unit Price',
                              digits=dp.get_precision('Product Price'))
    qty_ordered = fields.Float(compute='_compute_ordered_qty',
                               string='Ordered Quantities')
    requisition_id = fields.Many2one('purchase.requisition',
                                     required=True,
                                     string='Purchase Agreement',
                                     ondelete='cascade')
    company_id = fields.Many2one(
        'res.company',
        related='requisition_id.company_id',
        string='Company',
        store=True,
        readonly=True,
        default=lambda self: self.env['res.company']._company_default_get(
            'purchase.requisition.line'))
    account_analytic_id = fields.Many2one('account.analytic.account',
                                          string='Analytic Account')
    analytic_tag_ids = fields.Many2many('account.analytic.tag',
                                        string='Analytic Tags')
    schedule_date = fields.Date(string='Scheduled Date')
    move_dest_id = fields.Many2one('stock.move', 'Downstream Move')
    supplier_info_ids = fields.One2many('product.supplierinfo',
                                        'purchase_requisition_line_id')

    @api.model
    def create(self, vals):
        res = super(PurchaseRequisitionLine, self).create(vals)
        if res.requisition_id.state not in [
                'draft', 'cancel', 'done'
        ] and res.requisition_id.is_quantity_copy == 'none':
            supplier_infos = self.env['product.supplierinfo'].search([
                ('product_id', '=', vals.get('product_id')),
                ('name', '=', res.requisition_id.vendor_id.id),
            ])
            if not any([s.purchase_requisition_id for s in supplier_infos]):
                res.create_supplier_info()
            if vals['price_unit'] <= 0.0:
                raise UserError(
                    _('You cannot confirm the blanket order without price.'))
        return res

    @api.multi
    def write(self, vals):
        res = super(PurchaseRequisitionLine, self).write(vals)
        if 'price_unit' in vals:
            if vals['price_unit'] <= 0.0:
                raise UserError(
                    _('You cannot confirm the blanket order without price.'))
            # If the price is updated, we have to update the related SupplierInfo
            self.supplier_info_ids.write({'price': vals['price_unit']})
        return res

    def unlink(self):
        to_unlink = self.filtered(lambda r: r.requisition_id.state not in
                                  ['draft', 'cancel', 'done'])
        to_unlink.mapped('supplier_info_ids').unlink()
        return super(PurchaseRequisitionLine, self).unlink()

    def create_supplier_info(self):
        purchase_requisition = self.requisition_id
        if purchase_requisition.type_id.quantity_copy == 'none' and purchase_requisition.vendor_id:
            # create a supplier_info only in case of blanket order
            self.env['product.supplierinfo'].create({
                'name':
                purchase_requisition.vendor_id.id,
                'product_id':
                self.product_id.id,
                'product_tmpl_id':
                self.product_id.product_tmpl_id.id,
                'price':
                self.price_unit,
                'currency_id':
                self.requisition_id.currency_id.id,
                'purchase_requisition_id':
                purchase_requisition.id,
                'purchase_requisition_line_id':
                self.id,
            })

    @api.multi
    @api.depends('requisition_id.purchase_ids.state')
    def _compute_ordered_qty(self):
        for line in self:
            total = 0.0
            for po in line.requisition_id.purchase_ids.filtered(
                    lambda purchase_order: purchase_order.state in
                ['purchase', 'done']):
                for po_line in po.order_line.filtered(
                        lambda order_line: order_line.product_id == line.
                        product_id):
                    if po_line.product_uom != line.product_uom_id:
                        total += po_line.product_uom._compute_quantity(
                            po_line.product_qty, line.product_uom_id)
                    else:
                        total += po_line.product_qty
            line.qty_ordered = total

    @api.onchange('product_id')
    def _onchange_product_id(self):
        if self.product_id:
            self.product_uom_id = self.product_id.uom_po_id
            self.product_qty = 1.0
        if not self.schedule_date:
            self.schedule_date = self.requisition_id.schedule_date

    @api.multi
    def _prepare_purchase_order_line(self,
                                     name,
                                     product_qty=0.0,
                                     price_unit=0.0,
                                     taxes_ids=False):
        self.ensure_one()
        requisition = self.requisition_id
        if requisition.schedule_date:
            date_planned = datetime.combine(requisition.schedule_date,
                                            time.min)
        else:
            date_planned = datetime.now()
        return {
            'name':
            name,
            'product_id':
            self.product_id.id,
            'product_uom':
            self.product_id.uom_po_id.id,
            'product_qty':
            product_qty,
            'price_unit':
            price_unit,
            'taxes_id': [(6, 0, taxes_ids)],
            'date_planned':
            date_planned,
            'account_analytic_id':
            self.account_analytic_id.id,
            'analytic_tag_ids':
            self.analytic_tag_ids.ids,
            'move_dest_ids':
            self.move_dest_id and [(4, self.move_dest_id.id)] or []
        }
示例#29
0
class ServerActions(models.Model):
    """ Add email option in server actions. """
    _name = 'ir.actions.server'
    _description = 'Server Action'
    _inherit = ['ir.actions.server']

    state = fields.Selection(selection_add=[
        ('email', 'Send Email'),
        ('followers', 'Add Followers'),
        ('next_activity', 'Create Next Activity'),
    ])
    # Followers
    partner_ids = fields.Many2many('res.partner', string='Add Followers')
    channel_ids = fields.Many2many('mail.channel', string='Add Channels')
    # Template
    template_id = fields.Many2one(
        'mail.template',
        'Email Template',
        ondelete='set null',
        domain="[('model_id', '=', model_id)]",
    )
    # Next Activity
    activity_type_id = fields.Many2one(
        'mail.activity.type',
        string='Activity',
        domain=
        "['|', ('res_model_id', '=', False), ('res_model_id', '=', model_id)]")
    activity_summary = fields.Char('Summary')
    activity_note = fields.Html('Note')
    activity_date_deadline_range = fields.Integer(string='Due Date In')
    activity_date_deadline_range_type = fields.Selection([
        ('days', 'Days'),
        ('weeks', 'Weeks'),
        ('months', 'Months'),
    ],
                                                         string='Due type',
                                                         default='days')
    activity_user_type = fields.Selection(
        [('specific', 'Specific User'),
         ('generic', 'Generic User From Record')],
        default="specific",
        required=True,
        help=
        "Use 'Specific User' to always assign the same user on the next activity. Use 'Generic User From Record' to specify the field name of the user to choose on the record."
    )
    activity_user_id = fields.Many2one('res.users', string='Responsible')
    activity_user_field_name = fields.Char(
        'User field name',
        help="Technical name of the user on the record",
        default="user_id")

    @api.onchange('activity_date_deadline_range')
    def _onchange_activity_date_deadline_range(self):
        if self.activity_date_deadline_range < 0:
            raise UserError(_("The 'Due Date In' value can't be negative."))

    @api.onchange('template_id')
    def on_change_template_id(self):
        """ Render the raw template in the server action fields. """
        if self.template_id and not self.template_id.email_from:
            raise UserError(_('Your template should define email_from'))

    @api.constrains('state', 'model_id')
    def _check_mail_thread(self):
        for action in self:
            if action.state == 'followers' and not action.model_id.is_mail_thread:
                raise ValidationError(
                    _("Add Followers can only be done on a mail thread model"))

    @api.constrains('state', 'model_id')
    def _check_activity_mixin(self):
        for action in self:
            if action.state == 'next_activity' and not action.model_id.is_mail_thread:
                raise ValidationError(
                    _("A next activity can only be planned on models that use the chatter"
                      ))

    @api.model
    def run_action_followers_multi(self, action, eval_context=None):
        Model = self.env[action.model_name]
        if self.partner_ids or self.channel_ids and hasattr(
                Model, 'message_subscribe'):
            records = Model.browse(
                self._context.get('active_ids',
                                  self._context.get('active_id')))
            records.message_subscribe(self.partner_ids.ids,
                                      self.channel_ids.ids)
        return False

    @api.model
    def _is_recompute(self, action):
        """When an activity is set on update of a record,
        update might be triggered many times by recomputes.
        When need to know it to skip these steps.
        Except if the computed field is supposed to trigger the action
        """
        records = self.env[action.model_name].browse(
            self._context.get('active_ids', self._context.get('active_id')))
        old_values = action._context.get('old_values')
        if old_values:
            domain_post = action._context.get('domain_post')
            tracked_fields = []
            if domain_post:
                for leaf in domain_post:
                    if isinstance(leaf, (tuple, list)):
                        tracked_fields.append(leaf[0])
            fields_to_check = [
                field for record, field_names in old_values.items()
                for field in field_names if field not in tracked_fields
            ]
            if fields_to_check:
                field = records._fields[fields_to_check[0]]
                # Pick an arbitrary field; if it is marked to be recomputed,
                # it means we are in an extraneous write triggered by the recompute.
                # In this case, we should not create a new activity.
                if records & self.env.records_to_compute(field):
                    return True
        return False

    @api.model
    def run_action_email(self, action, eval_context=None):
        # TDE CLEANME: when going to new api with server action, remove action
        if not action.template_id or not self._context.get(
                'active_id') or self._is_recompute(action):
            return False
        # Clean context from default_type to avoid making attachment
        # with wrong values in subsequent operations
        cleaned_ctx = dict(self.env.context)
        cleaned_ctx.pop('default_type', None)
        cleaned_ctx.pop('default_parent_id', None)
        action.template_id.with_context(cleaned_ctx).send_mail(
            self._context.get('active_id'),
            force_send=False,
            raise_exception=False)
        return False

    @api.model
    def run_action_next_activity(self, action, eval_context=None):
        if not action.activity_type_id or not self._context.get(
                'active_id') or self._is_recompute(action):
            return False

        records = self.env[action.model_name].browse(
            self._context.get('active_ids', self._context.get('active_id')))

        vals = {
            'summary': action.activity_summary or '',
            'note': action.activity_note or '',
            'activity_type_id': action.activity_type_id.id,
        }
        if action.activity_date_deadline_range > 0:
            vals['date_deadline'] = fields.Date.context_today(
                action) + relativedelta(
                    **{
                        action.activity_date_deadline_range_type:
                        action.activity_date_deadline_range
                    })
        for record in records:
            if action.activity_user_type == 'specific':
                user = action.activity_user_id
            elif action.activity_user_type == 'generic' and action.activity_user_field_name in record:
                user = record[action.activity_user_field_name]
            if user:
                vals['user_id'] = user.id
            record.activity_schedule(**vals)
        return False

    @api.model
    def _get_eval_context(self, action=None):
        """ Override the method giving the evaluation context but also the
        context used in all subsequent calls. Add the mail_notify_force_send
        key set to False in the context. This way all notification emails linked
        to the currently executed action will be set in the queue instead of
        sent directly. This will avoid possible break in transactions. """
        eval_context = super(ServerActions,
                             self)._get_eval_context(action=action)
        ctx = dict(eval_context['env'].context)
        ctx['mail_notify_force_send'] = False
        eval_context['env'].context = ctx
        return eval_context
示例#30
0
class M2MReadonly(models.Model):
    _name = 'test_testing_utilities.g'
    _description = 'Testing Utilities G'

    m2m = fields.Many2many('test_testing_utilities.sub3', readonly=True)