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])
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.