Source code for odoo.addons.g2p_programs.models.managers.cycle_manager
# Part of OpenG2P. See LICENSE file for full copyright and licensing details.importloggingfromdatetimeimportdatetime,timedeltafromodooimport_,api,fields,modelsfromodoo.exceptionsimportValidationErrorfromodoo.addons.queue_job.delayimportgroupfrom..importconstants_logger=logging.getLogger(__name__)classCycleManager(models.Model):_name="g2p.cycle.manager"_description="Cycle Manager"_inherit="g2p.manager.mixin"program_id=fields.Many2one("g2p.program","Program")@api.modeldef_selection_manager_ref_id(self):selection=super()._selection_manager_ref_id()new_manager=("g2p.cycle.manager.default","Default")ifnew_managernotinselection:selection.append(new_manager)returnselection
[docs]defcheck_eligibility(self,cycle,beneficiaries=None):""" Validate the eligibility of each beneficiary for the cycle """raiseNotImplementedError()
[docs]defprepare_entitlements(self,cycle):""" Prepare the entitlements for the cycle """raiseNotImplementedError()
[docs]defissue_payments(self,cycle):""" Issue the payments based on entitlements for the cycle """raiseNotImplementedError()
[docs]defvalidate_entitlements(self,cycle,cycle_memberships):""" Validate the entitlements for the cycle """raiseNotImplementedError()
[docs]defnew_cycle(self,name,new_start_date,sequence):""" Create a new cycle for the program """raiseNotImplementedError()
[docs]defmark_distributed(self,cycle):""" Mark the cycle as distributed """raiseNotImplementedError()
[docs]defmark_ended(self,cycle):""" Mark the cycle as ended """raiseNotImplementedError()
[docs]defmark_cancelled(self,cycle):""" Mark the cycle as cancelled """raiseNotImplementedError()
[docs]defadd_beneficiaries(self,cycle,beneficiaries,state="draft"):""" Add beneficiaries to the cycle """raiseNotImplementedError()
[docs]defon_start_date_change(self,cycle):""" Hook for when the start date change """raiseNotImplementedError()
[docs]defapprove_cycle(self,cycle,auto_approve=False,entitlement_manager=None):""" :param cycle: :param auto_approve: :param entitlement_manager: :return: """# Check if user is allowed to approve the cycleifcycle.state=="to_approve":cycle.update({"state":"approved"})# Running on_state_change because it is not triggered automatically with rec.update aboveself.on_state_change(cycle)else:message=_("Only 'to approve' cycles can be approved.")kind="danger"return{"type":"ir.actions.client","tag":"display_notification","params":{"title":_("Cycle"),"message":message,"sticky":True,"type":kind,"next":{"type":"ir.actions.act_window_close",},},}# Approve entitlementsifauto_approve:ifentitlement_manager.IS_CASH_ENTITLEMENT:entitlement_mdl="g2p.entitlement"else:entitlement_mdl="g2p.entitlement.inkind"entitlements=cycle.get_entitlements(["draft","pending_validation"],entitlement_model=entitlement_mdl)ifentitlements:returnentitlement_manager.validate_entitlements(cycle)else:message=_("Auto-approve entitlements is set but there are no entitlements to process.")kind="warning"return{"type":"ir.actions.client","tag":"display_notification","params":{"title":_("Entitlements"),"message":message,"sticky":True,"type":kind,"next":{"type":"ir.actions.act_window_close",},},}
[docs]defon_state_change(self,cycle):""" :param cycle: :return: """ifcycle.state==cycle.STATE_APPROVED:ifnotself.approver_group_id:raiseValidationError(_("The cycle approver group is not specified!"))else:if(self.env.user.id!=1andself.env.user.idnotinself.approver_group_id.users.ids):raiseValidationError(_("You are not allowed to approve this cycle!"))
def_ensure_can_edit_cycle(self,cycle):"""Base :meth:'_ensure_can_edit_cycle` Check if the cycle can be editted :param cycle: A recordset of cycle :return: """ifcycle.statenotin[cycle.STATE_DRAFT]:raiseValidationError(_("The Cycle is not in draft mode"))
[docs]defmark_import_as_done(self,cycle,msg):"""Complete the import of beneficiaries. Base :meth:`mark_import_as_done`. This is executed when all the jobs are completed. Post a message in the chatter. :param cycle: A recordset of cycle :param msg: A string to be posted in the chatter :return: """self.ensure_one()cycle.locked=Falsecycle.locked_reason=Nonecycle.message_post(body=msg)# Update Statisticscycle._compute_members_count()
[docs]defmark_prepare_entitlement_as_done(self,cycle,msg):"""Complete the preparation of entitlements. Base :meth:`mark_prepare_entitlement_as_done`. This is executed when all the jobs are completed. Post a message in the chatter. :param cycle: A recordset of cycle :param msg: A string to be posted in the chatter :return: """self.ensure_one()cycle.locked=Falsecycle.locked_reason=Nonecycle.message_post(body=msg)# Update Statisticscycle._compute_entitlements_count()
[docs]defcheck_eligibility(self,cycle,beneficiaries=None):""" Default Cycle Manager eligibility checker :param cycle: The cycle that is being verified :type cycle: :class:`g2p_programs.models.cycle.G2PCycle` :param beneficiaries: the beneficiaries that need to be verified. By Default the one with the state ``draft`` or ``enrolled`` are verified. :type beneficiaries: list or None :return: The list of eligible beneficiaries :rtype: list Validate the eligibility of each beneficiary for the cycle using the configured manager(s) :class:`g2p_programs.models.managers.eligibility_manager.BaseEligibilityManager`. If there is multiple managers for eligibility, each of them are run using the filtered list of eligible beneficiaries from the previous one. The ``state`` of beneficiaries is updated to either ``enrolled`` if they match the enrollment criteria or ``not_eligible`` in case they do not match them. """forrecinself:rec._ensure_can_edit_cycle(cycle)# Get all the draft and enrolled beneficiariesifbeneficiariesisNone:beneficiaries=cycle.get_beneficiaries(["draft","enrolled"])eligibility_managers=rec.program_id.get_managers(constants.MANAGER_ELIGIBILITY)filtered_beneficiaries=beneficiariesformanagerineligibility_managers:filtered_beneficiaries=manager.verify_cycle_eligibility(cycle,filtered_beneficiaries)filtered_beneficiaries.write({"state":"enrolled"})beneficiaries_ids=beneficiaries.idsfiltered_beneficiaries_ids=filtered_beneficiaries.idsids_to_remove=list(set(beneficiaries_ids)-set(filtered_beneficiaries_ids))# Mark the beneficiaries as not eligiblememberships_to_remove=self.env["g2p.cycle.membership"].browse(ids_to_remove)memberships_to_remove.write({"state":"not_eligible"})# Update the members_count fieldcycle._compute_members_count()# TODO: Move this to the entitlement manager# Disable the entitlements of the beneficiariesentitlements=self.env["g2p.entitlement"].search([("cycle_id","=",cycle.id),("partner_id","in",memberships_to_remove.mapped("partner_id.id")),])entitlements.write({"state":"cancelled"})returnfiltered_beneficiaries
[docs]defprepare_entitlements(self,cycle):forrecinself:rec._ensure_can_edit_cycle(cycle)# Get all the enrolled beneficiariesbeneficiaries_count=cycle.get_beneficiaries(["enrolled"],count=True)rec.program_id.get_manager(constants.MANAGER_ENTITLEMENT)ifbeneficiaries_count<self.MIN_ROW_JOB_QUEUE:self._prepare_entitlements(cycle,do_count=True)else:self._prepare_entitlements_async(cycle,beneficiaries_count)
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=[]foriinrange(0,beneficiaries_count,self.MAX_ROW_JOB_QUEUE):jobs.append(self.delayable()._prepare_entitlements(cycle,i,self.MAX_ROW_JOB_QUEUE))main_job=group(*jobs)main_job.on_done(self.delayable().mark_prepare_entitlement_as_done(cycle,_("Entitlement Ready.")))main_job.delay()def_prepare_entitlements(self,cycle,offset=0,limit=None,do_count=False):"""Prepare Entitlements Get the beneficiaries and generate their entitlements. :param cycle: The cycle :param offset: Optional integer value for the ORM search offset :param limit: Optional integer value for the ORM search limit :param do_count: Boolean - set to False to not run compute function :return: """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)ifdo_count:# Update Statisticscycle._compute_entitlements_count()
[docs]defnew_cycle(self,name,new_start_date,sequence):_logger.debug("Creating new cycle for program %s",self.program_id.name)_logger.debug("New start date: %s",new_start_date)# convert date to datetimenew_start_date=datetime.combine(new_start_date,datetime.min.time())# get start date and end date# Note:# second argument is irrelevant but make sure it is in timedelta class# and do not exceed to 24 hoursoccurences=self._get_ranges(new_start_date,timedelta(seconds=1))prev_occurence=next(occurences)current_occurence=next(occurences)start_date=Noneend_date=None# This prevents getting an end date that is less than the start datewhileTrue:# get the date of occurencesstart_date=prev_occurence[0]end_date=current_occurence[0]-timedelta(days=1)# To handle DST (Daylight Saving Time) changesstart_date=start_date+timedelta(hours=11)end_date=end_date+timedelta(hours=11)ifstart_date>=new_start_date:break# move current occurence to previous then get a new current occurenceprev_occurence=current_occurencecurrent_occurence=next(occurences)forrecinself:cycle=self.env["g2p.cycle"].create({"program_id":rec.program_id.id,"name":name,"state":"draft","sequence":sequence,"start_date":start_date,"end_date":end_date,"auto_approve_entitlements":rec.auto_approve_entitlements,})_logger.debug("New cycle created: %s",cycle.name)returncycle
[docs]defadd_beneficiaries(self,cycle,beneficiaries,state="draft"):""" Add beneficiaries to the cycle """self.ensure_one()self._ensure_can_edit_cycle(cycle)_logger.debug("Adding beneficiaries to the cycle %s",cycle.name)_logger.debug("Beneficiaries: %s",beneficiaries)# Only add beneficiaries not added yetexisting_ids=cycle.cycle_membership_ids.mapped("partner_id.id")beneficiaries=list(set(beneficiaries)-set(existing_ids))iflen(beneficiaries)==0:message=_("No beneficiaries to import.")kind="warning"eliflen(beneficiaries)<self.MIN_ROW_JOB_QUEUE:self._add_beneficiaries(cycle,beneficiaries,state,do_count=True)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.debug("Adding beneficiaries asynchronously")cycle.message_post(body="Import of %s beneficiaries started."%len(beneficiaries))cycle.write({"locked":True,"locked_reason":_("Importing beneficiaries.")})beneficiaries_count=len(beneficiaries)jobs=[]foriinrange(0,beneficiaries_count,self.MAX_ROW_JOB_QUEUE):jobs.append(self.delayable()._add_beneficiaries(cycle,beneficiaries[i:i+self.MAX_ROW_JOB_QUEUE],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",do_count=False):"""Add Beneficiaries :param cycle: Recordset of cycle :param beneficiaries: Recordset of beneficiaries :param state: String state to be set to beneficiary :param do_count: Boolean - set to False to not run compute functions :return: Integer - count of not enrolled members """new_beneficiaries=[]forrinbeneficiaries:new_beneficiaries.append([0,0,{"partner_id":r,"enrollment_date":fields.Date.today(),"state":state,},])cycle.update({"cycle_membership_ids":new_beneficiaries})ifdo_count:# Update Statisticscycle._compute_members_count()@api.depends("cycle_duration")def_compute_interval(self):forrecinself:rec.interval=rec.cycle_duration