Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve performance #6

Merged
merged 17 commits into from
Dec 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 36 additions & 23 deletions g2p_programs/models/cycle.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,32 +60,31 @@ class G2PCycle(models.Model):
string="# Payments", compute="_compute_payments_count"
)

# This is used to prevent any issue while some background tasks are happening such as importing beneficiaries
locked = fields.Boolean(default=False)
locked_reason = fields.Char()

@api.depends("cycle_membership_ids")
def _compute_members_count(self):
for rec in self:
members_count = 0
if rec.cycle_membership_ids:
members_count = len(
rec.cycle_membership_ids.filtered(lambda mb: mb.state == "enrolled")
)
domain = rec._get_beneficiaries_domain(["enrolled"])
members_count = self.env["g2p.cycle.membership"].search_count(domain)
rec.update({"members_count": members_count})

@api.depends("entitlement_ids")
def _compute_entitlements_count(self):
for rec in self:
entitlements_count = 0
if rec.entitlement_ids:
entitlements_count = len(rec.entitlement_ids)
entitlements_count = self.env["g2p.entitlement"].search_count(
[("cycle_id", "=", rec.id)]
)
rec.update({"entitlements_count": entitlements_count})

@api.depends("entitlement_ids")
def _compute_payments_count(self):
for rec in self:
payments_count = 0
if rec.entitlement_ids:
payments_count = self.env["g2p.payment"].search_count(
[("entitlement_id", "in", rec.entitlement_ids.ids)]
)
payments_count = self.env["g2p.payment"].search_count(
[("cycle_id", "=", rec.id)]
)
rec.update({"payments_count": payments_count})

@api.onchange("start_date")
Expand All @@ -94,26 +93,29 @@ def on_start_date_change(self):

@api.onchange("state")
def on_state_change(self):
# _logger.info("DEBUG! state change: %s", self.state)
self.program_id.get_manager(constants.MANAGER_CYCLE).on_state_change(self)

def _get_beneficiaries_domain(self, states=None):
domain = [("cycle_id", "=", self.id)]
if states:
domain.append(("state", "in", states))
return domain

@api.model
def get_beneficiaries(self, state):
def get_beneficiaries(self, state, offset=0, limit=None, order=None, count=False):
if isinstance(state, str):
state = [state]
for rec in self:
domain = [("state", "in", state), ("cycle_id", "=", rec.id)]
return self.env["g2p.cycle.membership"].search(domain)

# TODO: JJ - Add a way to link reports/Dashboard about this cycle.

# TODO: Implement the method that will call the different managers
domain = rec._get_beneficiaries_domain(state)
return self.env["g2p.cycle.membership"].search(
domain, offset=offset, limit=limit, order=order, count=count
)

# @api.model
def copy_beneficiaries_from_program(self):
# _logger.info("Copying beneficiaries from program, cycles: %s", cycles)
self.ensure_one()
self.program_id.get_manager(
return self.program_id.get_manager(
constants.MANAGER_CYCLE
).copy_beneficiaries_from_program(self)

Expand All @@ -138,6 +140,9 @@ def to_approve(self):
"message": message,
"sticky": True,
"type": kind,
"next": {
"type": "ir.actions.act_window_close",
},
},
}

Expand All @@ -157,6 +162,9 @@ def reset_draft(self):
"message": message,
"sticky": True,
"type": kind,
"next": {
"type": "ir.actions.act_window_close",
},
},
}

Expand Down Expand Up @@ -198,6 +206,9 @@ def approve(self):
"message": message,
"sticky": True,
"type": kind,
"next": {
"type": "ir.actions.act_window_close",
},
},
}

Expand Down Expand Up @@ -230,7 +241,9 @@ def mark_cancelled(self):
def validate_entitlement(self):
# 1. Make sure the user has the right to do this
# 2. Validate the entitlement of the beneficiaries using entitlement_manager.validate_entitlements()
pass
self.program_id.get_manager(constants.MANAGER_CYCLE).validate_entitlements(
self, self.entitlement_ids
)

def export_distribution_list(self):
# Not sure if this should be here.
Expand Down
4 changes: 2 additions & 2 deletions g2p_programs/models/cycle_membership.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ class G2PCycleMembership(models.Model):
_order = "id desc"

partner_id = fields.Many2one(
"res.partner", "Registrant", help="A beneficiary", required=True, auto_join=True
"res.partner", "Registrant", help="A beneficiary", required=True, index=True
)
cycle_id = fields.Many2one(
"g2p.cycle", "Cycle", help="A cycle", required=True, auto_join=True
"g2p.cycle", "Cycle", help="A cycle", required=True, index=True
)
enrollment_date = fields.Date(default=lambda self: fields.Datetime.now())
state = fields.Selection(
Expand Down
185 changes: 155 additions & 30 deletions g2p_programs/models/managers/cycle_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError

from odoo.addons.queue_job.delay import group

from .. import constants

_logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -143,7 +145,7 @@ def check_eligibility(self, cycle, beneficiaries=None):
for rec in self:
rec._ensure_can_edit_cycle(cycle)

# Get all the enrolled beneficiaries
# Get all the draft and enrolled beneficiaries
if beneficiaries is None:
beneficiaries = cycle.get_beneficiaries(["draft", "enrolled"])

Expand All @@ -160,8 +162,6 @@ def check_eligibility(self, cycle, beneficiaries=None):

beneficiaries_ids = beneficiaries.ids
filtered_beneficiaries_ids = filtered_beneficiaries.ids
_logger.info("Beneficiaries: %s", beneficiaries_ids)
_logger.info("Filtered beneficiaries: %s", filtered_beneficiaries_ids)
ids_to_remove = list(
set(beneficiaries_ids) - set(filtered_beneficiaries_ids)
)
Expand All @@ -187,11 +187,42 @@ def prepare_entitlements(self, cycle):
for rec in self:
rec._ensure_can_edit_cycle(cycle)
# Get all the enrolled beneficiaries
beneficiaries = cycle.get_beneficiaries(["enrolled"])
beneficiaries_count = cycle.get_beneficiaries(["enrolled"], count=True)
rec.program_id.get_manager(constants.MANAGER_ENTITLEMENT)
if beneficiaries_count < 200:
self._prepare_entitlements(cycle)
else:
self._prepare_entitlements_async(cycle, beneficiaries_count)

rec.program_id.get_manager(
constants.MANAGER_ENTITLEMENT
).prepare_entitlements(cycle, beneficiaries)
def _prepare_entitlements_async(self, cycle, beneficiaries_count):
_logger.debug("Prepare entitlement asynchronously")
cycle.message_post(
body=_(
"Prepare entitlement for %s beneficiaries started.", beneficiaries_count
)
)
cycle.write(
{
"locked": True,
"locked_reason": _("Prepare entitlement for beneficiaries."),
}
)

jobs = []
for i in range(0, beneficiaries_count, 2000):
jobs.append(self.delayable()._prepare_entitlements(cycle, i, 2000))
main_job = group(*jobs)
main_job.on_done(
self.delayable().mark_import_as_done(cycle, _("Entitlement Ready."))
)
main_job.delay()

def _prepare_entitlements(self, cycle, offset=0, limit=None):
beneficiaries = cycle.get_beneficiaries(
["enrolled"], offset=offset, limit=limit, order="id"
)
entitlement_manager = self.program_id.get_manager(constants.MANAGER_ENTITLEMENT)
entitlement_manager.prepare_entitlements(cycle, beneficiaries)

def mark_distributed(self, cycle):
cycle.update({"state": constants.STATE_DISTRIBUTED})
Expand All @@ -202,14 +233,54 @@ def mark_ended(self, cycle):
def mark_cancelled(self, cycle):
cycle.update({"state": constants.STATE_CANCELLED})

def validate_entitlements(self, cycle, cycle_memberships):
def validate_entitlements(self, cycle, entitlement_ids):
# TODO: call the program's entitlement manager and validate the entitlements
# TODO: Use a Job attached to the cycle
# TODO: Implement validation workflow
for rec in self:
rec.program_id.get_manager(
constants.MANAGER_ENTITLEMENT
).validate_entitlements(cycle_memberships)
rec._ensure_can_edit_cycle(cycle)
rec.program_id.get_manager(constants.MANAGER_ENTITLEMENT)
if len(entitlement_ids) < 200:
self._validate_entitlements(entitlement_ids)
else:
self._validate_entitlements_async(cycle, entitlement_ids)

def _validate_entitlements_async(self, cycle, entitlement_ids):
_logger.debug("Validate entitlement asynchronously")
cycle.message_post(
body=_("Validation for %s entitlements started.", len(entitlement_ids))
)
cycle.write(
{
"locked": True,
"locked_reason": _("Validate entitlement for beneficiaries."),
}
)

jobs = []
max_jobs_per_batch = 100
entitlements = []
max_rec = len(entitlement_ids)
for ctr_entitlements, entitlement in enumerate(entitlement_ids, 1):
entitlements.append(entitlement.id)
if (
ctr_entitlements % max_jobs_per_batch == 0
) or ctr_entitlements == max_rec:
entitlements_ids = self.env["g2p.entitlement"].search(
[("id", "in", entitlements)]
)
jobs.append(self.delayable()._validate_entitlements(entitlements_ids))
entitlements = []

main_job = group(*jobs)
main_job.on_done(
self.delayable().mark_import_as_done(cycle, _("Entitlement approved."))
)
main_job.delay()

def _validate_entitlements(self, entitlements):
entitlement_manager = self.program_id.get_manager(constants.MANAGER_ENTITLEMENT)
entitlement_manager.approve_entitlements(entitlements)

def new_cycle(self, name, new_start_date, sequence):
_logger.info("Creating new cycle for program %s", self.program_id.name)
Expand All @@ -230,45 +301,99 @@ def new_cycle(self, name, new_start_date, sequence):

def copy_beneficiaries_from_program(self, cycle, state="enrolled"):
self._ensure_can_edit_cycle(cycle)
self.ensure_one()

for rec in self:
beneficiary_ids = rec.program_id.get_beneficiaries(["enrolled"]).mapped(
"partner_id.id"
)
rec.add_beneficiaries(cycle, beneficiary_ids, state)
return rec.add_beneficiaries(cycle, beneficiary_ids, state)

def add_beneficiaries(self, cycle, beneficiaries, state="draft"):
"""
Add beneficiaries to the cycle
"""
self.ensure_one()
self._ensure_can_edit_cycle(cycle)
_logger.info("Adding beneficiaries to the cycle %s", cycle.name)
_logger.info("Beneficiaries: %s", beneficiaries)

# Only add beneficiaries not added yet
existing_ids = cycle.cycle_membership_ids.mapped("partner_id.id")
beneficiaries = list(set(beneficiaries) - set(existing_ids))
if len(beneficiaries) == 0:
message = _("No beneficiaries to import.")
kind = "warning"
elif len(beneficiaries) < 1000:
self._add_beneficiaries(cycle, beneficiaries, state)
message = _("%s beneficiaries imported.", len(beneficiaries))
kind = "success"
else:
self._add_beneficiaries_async(cycle, beneficiaries, state)
message = _("Import of %s beneficiaries started.", len(beneficiaries))
kind = "warning"

return {
"type": "ir.actions.client",
"tag": "display_notification",
"params": {
"title": _("Enrollment"),
"message": message,
"sticky": True,
"type": kind,
"next": {
"type": "ir.actions.act_window_close",
},
},
}

def _add_beneficiaries_async(self, cycle, beneficiaries, state):
_logger.info("Adding beneficiaries asynchronously")
cycle.message_post(
body="Import of %s beneficiaries started." % len(beneficiaries)
)
cycle.write({"locked": True, "locked_reason": _("Importing beneficiaries.")})

jobs = []
for i in range(0, len(beneficiaries), 2000):
jobs.append(
self.delayable()._add_beneficiaries(
cycle, beneficiaries[i : i + 2000], state
)
)
main_job = group(*jobs)
main_job.on_done(
self.delayable().mark_import_as_done(
cycle, _("Beneficiary import finished.")
)
)
main_job.delay()

def _add_beneficiaries(self, cycle, beneficiaries, state="draft"):
new_beneficiaries = []
for r in beneficiaries:
if r not in existing_ids:
new_beneficiaries.append(
[
0,
0,
{
"partner_id": r,
"enrollment_date": fields.Date.today(),
"state": state,
},
]
)
if new_beneficiaries:
cycle.update({"cycle_membership_ids": new_beneficiaries})
return True
else:
return False
new_beneficiaries.append(
[
0,
0,
{
"partner_id": r,
"enrollment_date": fields.Date.today(),
"state": state,
},
]
)
cycle.update({"cycle_membership_ids": new_beneficiaries})

def mark_import_as_done(self, cycle, msg):
self.ensure_one()
cycle.locked = False
cycle.locked_reason = None
cycle.message_post(body=msg)

def _ensure_can_edit_cycle(self, cycle):
if cycle.state not in [cycle.STATE_DRAFT]:
raise ValidationError(_("The Cycle is not in Draft Mode"))
raise ValidationError(_("The Cycle is not in draft mode"))

def on_state_change(self, cycle):
if cycle.state == cycle.STATE_APPROVED:
Expand Down
Loading