Skip to content

Commit

Permalink
Merge pull request #19 from mkumar-02/17.0-id-dedup
Browse files Browse the repository at this point in the history
Added Registry ID Deduplication Module
  • Loading branch information
shibu-narayanan authored Jul 12, 2024
2 parents 5c9b304 + 47a0a2e commit f16ebd7
Show file tree
Hide file tree
Showing 23 changed files with 1,146 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ addon | version | maintainers | summary
--- | --- | --- | ---
[g2p_lock_unlock](g2p_lock_unlock/) | 17.0.1.0.0 | | OpenG2P Lock Unlock
[g2p_registry_g2p_connect_rest_api](g2p_registry_g2p_connect_rest_api/) | 17.0.1.0.0 | | OpenG2P Registry: G2P Connect REST API
[g2p_registry_id_deduplication](g2p_registry_id_deduplication/) | 17.0.1.0.0 | | OpenG2P Registry ID Deduplication
[g2p_social_registry](g2p_social_registry/) | 17.0.1.0.0 | | OpenG2P Social Registry
[g2p_social_registry_theme](g2p_social_registry_theme/) | 17.0.1.0.0 | | OpenG2P Social Registry: Theme

Expand Down
62 changes: 62 additions & 0 deletions g2p_registry_id_deduplication/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
=================================
OpenG2P Registry ID Deduplication
=================================

..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:f31518db783800f8cac739f6f38ddd3ac1ea35c3c9a16426ae3e58d5a79a5b6f
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Alpha-red.png
:target: https://odoo-community.org/page/development-status
:alt: Alpha
.. |badge2| image:: https://img.shields.io/badge/github-OpenG2P%2Fopeng2p--social--registry-lightgray.png?logo=github
:target: https://github.com/OpenG2P/openg2p-social-registry/tree/17.0-develop/g2p_registry_id_deduplication
:alt: OpenG2P/openg2p-social-registry

|badge1| |badge2|

G2P Registry ID Deduplication

.. IMPORTANT::
This is an alpha version, the data model and design can change at any time without warning.
Only for development or testing purpose, do not use in production.
`More details on development status <https://odoo-community.org/page/development-status>`_

**Table of contents**

.. contents::
:local:

Bug Tracker
===========

Bugs are tracked on `GitHub Issues <https://github.com/OpenG2P/openg2p-social-registry/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
`feedback <https://github.com/OpenG2P/openg2p-social-registry/issues/new?body=module:%20g2p_registry_id_deduplication%0Aversion:%2017.0-develop%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.

Do not contact contributors directly about support or help with technical issues.

Credits
=======

Authors
~~~~~~~

* OpenG2P

Contributors
~~~~~~~~~~~~

`Manoj Kumar <[email protected]>`

Maintainers
~~~~~~~~~~~

This module is part of the `OpenG2P/openg2p-social-registry <https://github.com/OpenG2P/openg2p-social-registry/tree/17.0-develop/g2p_registry_id_deduplication>`_ project on GitHub.

You are welcome to contribute.
3 changes: 3 additions & 0 deletions g2p_registry_id_deduplication/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Part of OpenG2P Social Registry. See LICENSE file for full copyright and licensing details.

from . import models
36 changes: 36 additions & 0 deletions g2p_registry_id_deduplication/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Part of OpenG2P Social Registry. See LICENSE file for full copyright and licensing details.
{
"name": "OpenG2P Registry ID Deduplication",
"category": "G2P",
"version": "17.0.1.0.0",
"sequence": 1,
"author": "OpenG2P",
"website": "https://openg2p.org",
"license": "Other OSI approved licence",
"development_status": "Alpha",
"depends": [
"g2p_registry_group",
"g2p_registry_individual",
"g2p_registry_membership",
],
"external_dependencies": {},
"data": [
"security/ir.model.access.csv",
"views/deduplication_criteria_view.xml",
"views/deduplication_view.xml",
"views/group_view.xml",
"views/individual_view.xml",
"views/res_config_view.xml",
],
"assets": {
"web.assets_backend": [
"/g2p_registry_id_deduplication/static/src/js/duplicate_button.js",
"/g2p_registry_id_deduplication/static/src/xml/duplicate_template.xml",
],
},
"demo": [],
"images": [],
"application": True,
"installable": True,
"auto_install": False,
}
5 changes: 5 additions & 0 deletions g2p_registry_id_deduplication/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Part of OpenG2P. See LICENSE file for full copyright and licensing details.

from . import deduplication
from . import registrant
from . import res_config_settings
11 changes: 11 additions & 0 deletions g2p_registry_id_deduplication/models/deduplication.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Part of OpenG2P. See LICENSE file for full copyright and licensing details.

from odoo import fields, models


class G2PDeduplicationCriteria(models.Model):
_name = "g2p.registry.id.deduplication_criteria"

name = fields.Char(string="Criteria Name", required=True)
grp_id_types = fields.Many2many("g2p.id.type", "g2p_registry_id_dedup_grp", string="ID Types")
ind_id_types = fields.Many2many("g2p.id.type", "g2p_registry_id_dedup_ind", string="ID Types")
206 changes: 206 additions & 0 deletions g2p_registry_id_deduplication/models/registrant.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
# Part of OpenG2P. See LICENSE file for full copyright and licensing details.

import logging

from odoo import _, fields, models
from odoo.exceptions import UserError

_logger = logging.getLogger(__name__)


class ResPartner(models.Model):
_inherit = "res.partner"

is_duplicate = fields.Boolean(default=False)

def deduplicate_registrants(self):
is_group = self._context.get("default_is_group")

ind_id_types = self.get_id_types(id_field="ind_id_types")

self.reset_duplicate_flag(is_group)

if not is_group:
ind_duplicate_ids = []
duplicates = self.get_duplicate_registrants(
is_group, ind_id_types, group_condition="group_kind.name IS NULL"
)

for duplicate in duplicates:
duplicate_partner_ids_str = duplicate.get("partner_ids")
ind_duplicate_ids += duplicate_partner_ids_str.split(",")

updated_ind_duplicate_ids = list(set(ind_duplicate_ids))
self.mark_registrant_as_duplicated(updated_ind_duplicate_ids)

message = f"{len(updated_ind_duplicate_ids)} individuals"

else:
grp_id_types = self.get_id_types(id_field="grp_id_types")
group_duplicate_ids = []
member_duplicate_ids = []
grouped_kinds = self.get_grouped_kinds()
for kind in grouped_kinds:
group_kind_name = kind.get("kind")
group_ids_str = kind.get("group_ids")

group_duplicates = self.get_duplicate_registrants(
is_group,
grp_id_types,
group_condition=(
f"group_kind.name = '{group_kind_name}'"
if group_kind_name is not None
else "group_kind.name IS NULL"
),
)

# Group Duplicate
for duplicate in group_duplicates:
duplicate_partner_ids_str = duplicate.get("partner_ids")
group_duplicate_ids += duplicate_partner_ids_str.split(",")

updated_grp_duplicate_ids = list(set(group_duplicate_ids))
self.mark_registrant_as_duplicated(updated_grp_duplicate_ids)

# Group Member Duplicate
group_member_duplicates = self.get_duplicate_group_members(group_ids_str, grp_id_types)
for member_duplicate in group_member_duplicates:
duplicate_partner_ids_str = member_duplicate.get("partner_ids")
member_duplicate_ids += duplicate_partner_ids_str.split(",")

updated_member_duplicate_ids = list(set(member_duplicate_ids))
self.mark_registrant_as_duplicated(updated_member_duplicate_ids)

if len(updated_grp_duplicate_ids) > 0 and len(updated_member_duplicate_ids) > 0:
message = f"{len(updated_grp_duplicate_ids)} groups and \
{len(updated_member_duplicate_ids)} group members"
elif len(updated_member_duplicate_ids) > 0:
message = f"{len(updated_member_duplicate_ids)} group members"
else:
message = f"{len(updated_grp_duplicate_ids)} groups"

return {
"type": "ir.actions.client",
"tag": "display_notification",
"params": {
"title": _("Duplicate"),
"message": f"{message} are duplicated.",
"sticky": False,
"type": "success",
"next": {
"type": "ir.actions.act_window_close",
},
},
}

def get_id_types(self, id_field):
id_types = []

dedup_criteria_id = (
self.env["ir.config_parameter"]
.sudo()
.get_param("g2p_registry_id_deduplication.dedup_criteria_id", default=None)
)

if not dedup_criteria_id:
raise UserError(_("No deduplication criteria configured. Please configure it in the settings."))

dedup_criteria = self.env["g2p.registry.id.deduplication_criteria"].search(
[("id", "=", dedup_criteria_id)], limit=1
)

if not dedup_criteria:
raise UserError(_("Deduplication configuration not found."))

for id_type in dedup_criteria[id_field]:
id_types.append(id_type.name)

if len(id_types) < 1:
raise UserError(_("No ID Types found in the Deduplication Configuration."))

return tuple(id_types) if len(id_types) != 1 else f"('{id_types[0]}')"

def mark_registrant_as_duplicated(self, partner_ids):
for partner in partner_ids:
registrant = self.browse(int(partner))
if registrant:
registrant.update({"is_duplicate": True})

def reset_duplicate_flag(self, is_group):
query = f"""
UPDATE res_partner
SET is_duplicate = FALSE
WHERE is_registrant = TRUE AND is_group = {is_group}
"""
_logger.debug("DB Query: %s" % query)

try:
self._cr.execute(query) # pylint: disable=sql-injection
except Exception as e:
_logger.error("Database Query Error: %s", e)
raise UserError(_("Database Query Error: %s") % e) from None

def get_grouped_kinds(self):
query = """
SELECT
group_kind.name AS kind, STRING_AGG(partner.id::text, ',')
AS group_ids
FROM res_partner AS partner
LEFT JOIN g2p_group_kind AS group_kind ON group_kind.id = partner.kind
WHERE is_registrant = TRUE AND is_group = TRUE
GROUP BY group_kind.name
"""

try:
self._cr.execute(query) # pylint: disable=sql-injection
grouped_kinds = self._cr.dictfetchall()
return grouped_kinds
except Exception as e:
_logger.error("Database Query Error: %s", e)
raise UserError(_("Database Query Error: %s") % e) from None

def get_duplicate_registrants(self, is_group, id_types, group_condition):
query = f"""
SELECT
reg_id.value AS id_value, STRING_AGG(partner.id::text, ',')
AS partner_ids
FROM res_partner AS partner
INNER JOIN g2p_reg_id AS reg_id ON reg_id.partner_id = partner.id
JOIN g2p_id_type AS id_type ON id_type.id = reg_id.id_type
LEFT JOIN g2p_group_kind AS group_kind ON group_kind.id = partner.kind
WHERE is_registrant = TRUE AND id_type.name IN {id_types} AND is_group = {is_group}
AND {group_condition}
GROUP BY reg_id.value
HAVING COUNT(partner.id) > 1
"""

try:
self._cr.execute(query) # pylint: disable=sql-injection
individual_duplicates = self._cr.dictfetchall()
return individual_duplicates
except Exception as e:
_logger.error("Database Query Error: %s", e)
raise UserError(_("Database Query Error: %s") % e) from None

def get_duplicate_group_members(self, group_ids, id_types):
query = f"""
SELECT
reg_id.value AS id_value, STRING_AGG(group_member.group::text, ',')
AS partner_ids
FROM res_partner AS partner
JOIN g2p_group_membership AS group_member ON partner.id = group_member.individual
INNER JOIN g2p_reg_id AS reg_id ON reg_id.partner_id = partner.id
JOIN g2p_id_type AS id_type ON id_type.id = reg_id.id_type
WHERE partner.is_registrant = TRUE AND id_type.name IN {id_types}
AND group_member.group IN ({group_ids})
GROUP BY reg_id.value
HAVING COUNT(partner.id) > 1
"""

try:
self._cr.execute(query) # pylint: disable=sql-injection
group_duplicates = self._cr.dictfetchall()
return group_duplicates
except Exception as e:
_logger.error("Database Query Error: %s", e)
raise UserError(_("Database Query Error: %s") % e) from None
12 changes: 12 additions & 0 deletions g2p_registry_id_deduplication/models/res_config_settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Part of OpenG2P. See LICENSE file for full copyright and licensing details.

from odoo import fields, models


class RegistryConfig(models.TransientModel):
_inherit = "res.config.settings"

dedup_criteria_id = fields.Many2one(
"g2p.registry.id.deduplication_criteria",
config_parameter="g2p_registry_id_deduplication.dedup_criteria_id",
)
3 changes: 3 additions & 0 deletions g2p_registry_id_deduplication/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[build-system]
requires = ["whool"]
build-backend = "whool.buildapi"
1 change: 1 addition & 0 deletions g2p_registry_id_deduplication/readme/CONTRIBUTORS.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
`Manoj Kumar <[email protected]>`
1 change: 1 addition & 0 deletions g2p_registry_id_deduplication/readme/DESCRIPTION.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
G2P Registry ID Deduplication
3 changes: 3 additions & 0 deletions g2p_registry_id_deduplication/security/ir.model.access.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
g2p_registry_id_deduplication_criteria_admin,Registry Deduplication Criteria Admin Access,model_g2p_registry_id_deduplication_criteria,g2p_registry_base.group_g2p_admin,1,1,1,1
g2p_registry_id_deduplication_criteria_registrar,Registry Deduplication Criteria Registrar Access,model_g2p_registry_id_deduplication_criteria,,1,0,0,0
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit f16ebd7

Please sign in to comment.