xmlodooodoo-viewodoo-17

How to show archived records in Odoo 17


I need some help.

I made a functionality that allows finding duplicate records in the system and these duplicates are displayed in a drop-down list on the pop-up. But I can't display archive records that are duplicates. Please help.

This is what the parts of the functionality that currently display duplicates of only active records look like.

hr_applicant model

selected_duplicate_id = fields.Many2one('hr.applicant', string="Selected Duplicate", domain="[('id', 'in', duplicate_ids)]")

 duplicate_ids = fields.Many2many(
        'hr.applicant',
        string="Дубликаты",
        compute='_compute_duplicates',
        store=False
    )
    show_duplicates_warning = fields.Boolean(
        string="Показывать предупреждение",
        compute='_compute_duplicates'
    )

@api.model
    def name_search(self, name='', args=None, operator='ilike', limit=100):
        args = list(args or [])
        _logger.info("name_search called with name=%s, args=%s, context=%s", name, args, self._context)

        if self._context.get('active_test') is False:
            args = [arg for arg in args if arg[0] != 'active']
            args = expression.AND([args, [('active', 'in', [True, False])]])

        duplicate_ids = self._context.get('duplicate_ids', [])
        if duplicate_ids:
            args = expression.AND([args, [('id', 'in', duplicate_ids)]])
        else:
            for arg in args:
                if arg[0] == 'id' and arg[1] == 'in' and not arg[2]:
                    _logger.warning("Empty id list in name_search args: %s", args)
                    return []

        result = super(Applicant, self).name_search(name=name, args=args, operator=operator, limit=limit)
        _logger.info("name_search result: %s", result)
        return result

    @api.depends('email_from', 'partner_mobile_sanitized')
    def _compute_duplicates(self):
        for applicant in self:
            duplicates = self.env['hr.applicant']
            if applicant.email_normalized or applicant.partner_mobile_sanitized:
                domain = applicant._get_similar_applicants_domain()
                _logger.info("Domain for applicant %s: %s", applicant.id, domain)
                if domain:
                    duplicates = self.env['hr.applicant'].with_context(active_test=False).search(domain)
                _logger.info("Duplicates for applicant %s: %s (Active: %s)",
                             applicant.id, duplicates.ids, [d.active for d in duplicates])
            applicant.duplicate_ids = duplicates
            applicant.show_duplicates_warning = bool(duplicates)
            if applicant.selected_duplicate_id and applicant.selected_duplicate_id.id not in duplicates.ids:
                applicant.selected_duplicate_id = False

    @api.depends('email_from', 'partner_mobile_sanitized')
    def _compute_application_count(self):
        if not any(self._ids):
            for applicant in self:
                duplicates = self.env['hr.applicant'].with_context(
                    active_test=False
                )
                if applicant.email_normalized or applicant.partner_mobile_sanitized:
                    domain = applicant._get_similar_applicants_domain()
                    if domain:
                        duplicates = self.env['hr.applicant'].with_context(
                            active_test=False
                        ).search(domain)
                applicant.application_count = len(duplicates)
                _logger.info("Found duplicates (active/inactive): %s",
                             [(d.id, d.partner_name, d.active) for d in applicant.duplicate_ids])
            return

        self.flush_recordset(['email_normalized', 'partner_mobile_sanitized'])
        self.env.cr.execute("""
                SELECT
                    a.id,
                    (
                        SELECT COUNT(*)
                        FROM hr_applicant AS sub
                        WHERE a.id != sub.id
                        AND sub.create_date < a.create_date
                        AND ((a.email_normalized <> '' AND a.email_normalized = sub.email_normalized)
                            OR (a.partner_mobile_sanitized <> '' AND a.partner_mobile_sanitized = sub.partner_mobile_sanitized))
                        AND (sub.active = TRUE OR sub.active = FALSE)
                    ) AS similar_applicants
                FROM hr_applicant AS a
                WHERE id IN %(ids)s
            """, {'ids': tuple(self._origin.ids)})
        query_results = self.env.cr.dictfetchall()
        mapped_data = {result['id']: result['similar_applicants'] for result in query_results}
        for applicant in self:
            applicant.application_count = mapped_data.get(applicant.id, 0)

    def action_show_duplicates_dialog(self):
        self.ensure_one()
        self._compute_duplicates()
        _logger.info("Opening duplicates dialog for applicant %s with duplicates: %s",
                     self.id, self.duplicate_ids.ids)
        return {
            'name': 'Обнаружены дубликаты',
            'type': 'ir.actions.act_window',
            'res_model': 'hr.applicant',
            'view_mode': 'form',
            'views': [(self.env.ref('hr_recruitment.hr_applicant_view_form').id, 'form')],
            'target': 'new',
            'res_id': self.id,
            'context': {
                'dialog_size': 'large',
                'create': False,
                'edit': False,
                'active_test': False,
                'default_duplicate_ids': self.duplicate_ids.ids,
                'duplicate_ids': self.duplicate_ids.ids,
            }
        }

xml form

<h4><i class="fa fa-warning me-2"/>Обнаружены дубликаты!</h4>
                        <field name="duplicate_ids" widget="many2many_tags" options="{'no_create': True}"/>

                        <div class="row mt-2">
                            <div class="col-md-12">
                                <div class="o_form_label">Выберите основную запись для объединения:</div>
                                <field name="selected_duplicate_id"
                                       widget="many2one"
                                       options="{'no_create': True, 'no_open': True}"
                                       context="{'active_test': False, 'hr_force_show_archived': True, 'search_default_duplicates': True}"
                                       domain="[('id', 'in', duplicate_ids)]"
                                       class="oe_inline w-100"/>
                        </div>
                        </div>

logs

odoo-1  | 2025-07-18 15:13:31,399 1 INFO Test odoo.tools.translate: Domain for applicant 9: ['&', '&', '|', ('email_normalized', '=ilike', 'test@gmail.com'), ('partner_mobile_sanitized', '=', '958604986'), ('id', '!=', 9), ('active', 'in', [True, False])] 
odoo-1  | 2025-07-18 15:13:31,401 1 INFO Test odoo.tools.translate: Duplicates for applicant 9: [8] (Active: [False]) 

Solution

  • domain="[('id', 'in', duplicate_ids)]"

    duplicates = self.env['hr.applicant'].with_context(active_test=False).search(domain)

    Ensure the widget context does not override active_test=False implicitly. Currently, your field looks like this<field name="selected_duplicate_id" widget="many2one" options="{'no_create': True, 'no_open': True}" context="{'active_test': False, 'hr_force_show_archived': True, 'search_default_duplicates': True}" domain="[('id', 'in', duplicate_ids)]" class="oe_inline w-100"/>

    You are very close — your backend logic is correct. The final fix likely lies in ensuring the context is properly passed into name_search() from the form, and confirming that duplicate_ids truly includes inactive records at runtime.

    Would you like help checking your _get_similar_applicants_domain() method too? Sometimes the filter (‘active’, ‘in’, [True, False]) might be missing there.