diff --git a/membership_group/data/ir_cron_data.xml b/membership_group/data/ir_cron_data.xml new file mode 100644 index 0000000..e90a806 --- /dev/null +++ b/membership_group/data/ir_cron_data.xml @@ -0,0 +1,17 @@ + + + + + Membership: Revoke expired memberships + 1 + days + + -1 + + code + model._cron_revoke_membership() + + + + diff --git a/membership_group/menuitems.xml b/membership_group/menuitems.xml index eb46ccc..21b1e32 100644 --- a/membership_group/menuitems.xml +++ b/membership_group/menuitems.xml @@ -25,9 +25,30 @@ + + + + + + diff --git a/membership_group/models/membership_group.py b/membership_group/models/membership_group.py index 5474aa6..b59536e 100644 --- a/membership_group/models/membership_group.py +++ b/membership_group/models/membership_group.py @@ -28,25 +28,37 @@ class MembershipGroup(models.Model): ) partner_ids_count = fields.Integer("# of Members", compute="_compute_partner_ids") + termination_cycle = fields.Boolean( + help=""" + Members from a group with a termination cycle will be + removed from the group on the termination date", + """, + ) + next_termination_date = fields.Date( + help="Next termination date for members of this group", + ) + @api.depends("name", "parent_id.complete_name") def _compute_complete_name(self): for group in self: if group.parent_id: - group.complete_name = "%s / %s" % ( - group.parent_id.complete_name, - group.name, - ) + group.complete_name = f"{group.parent_id.complete_name} / {group.name}" else: group.complete_name = group.name @api.depends( - "membership_group_member_ids", "membership_group_member_ids.partner_id" + "membership_group_member_ids", + "membership_group_member_ids.partner_id", ) def _compute_partner_ids(self): for group in self: group.partner_ids = group.membership_group_member_ids.mapped("partner_id") group.partner_ids_count = len(group.partner_ids) + def _has_termination_cycle(self): + self.ensure_one() + return bool(self.termination_cycle and self.next_termination_date) + def action_open_partner_view(self): action_name = "membership.action_membership_members" action_vals = self.env["ir.actions.act_window"]._for_xml_id(action_name) diff --git a/membership_group/models/membership_group_member.py b/membership_group/models/membership_group_member.py index 1f83d79..3e0ace2 100644 --- a/membership_group/models/membership_group_member.py +++ b/membership_group/models/membership_group_member.py @@ -1,13 +1,15 @@ -from odoo import fields, models +from odoo import api, fields, models class MembershipGroupMember(models.Model): _name = "membership.group.member" _description = "Membership Group Member" + active = fields.Boolean(default=True) partner_id = fields.Many2one("res.partner", required=True, ondelete="cascade") group_id = fields.Many2one("membership.group", required=True, ondelete="cascade") wants_to_collaborate = fields.Boolean() + can_vote = fields.Boolean() type = fields.Selection( [ ("follower", "Follower"), @@ -18,11 +20,55 @@ class MembershipGroupMember(models.Model): ("committee", "Committee"), ], ) + date_from = fields.Date( + default=fields.Date.context_today, + help="Start date of the membership", + ) + date_to = fields.Date( + compute="_compute_date_to", + store=True, + readonly=False, + precompute=True, + help="Planned end date of the membership", + ) + date_end = fields.Date( + help="End date of the membership", + ) + can_revoke_membership = fields.Boolean(compute="_compute_can_revoke_membership") _sql_constraints = [ ( "partner_group_uniq", - "unique(partner_id, group_id)", + "unique(active, partner_id, group_id)", "Member already exists for this group!", ) ] + + @api.depends("group_id") + def _compute_date_to(self): + for record in self: + if ( + not record.date_to + and record.group_id + and record.group_id._has_termination_cycle() + ): + record.date_to = record.group_id.next_termination_date + + @api.depends("date_to") + def _compute_can_revoke_membership(self): + for record in self: + record.can_revoke_membership = record._can_revoke_membership() + + def _can_revoke_membership(self): + self.ensure_one() + return self.date_to < fields.Date.today() if self.date_to else True + + def action_revoke_membership(self): + if active_records := self.filtered(lambda x: x.active): + active_records.active = False + active_records.date_end = fields.Date.today() + return True + + @api.model + def _cron_revoke_membership(self): + self.search([("date_to", "<=", fields.date.today())]).action_revoke_membership() diff --git a/membership_group/security/ir.model.access.csv b/membership_group/security/ir.model.access.csv index 9c90d41..75337e0 100644 --- a/membership_group/security/ir.model.access.csv +++ b/membership_group/security/ir.model.access.csv @@ -4,4 +4,4 @@ access_membership_group_portal,access_membership_group_portal,model_membership_g access_membership_group,access_membership_group,model_membership_group,base.group_user,1,1,1,1 access_membership_group_member_public,access_membership_group_member_public,model_membership_group_member,base.group_public,1,0,0,0 access_membership_group_member_portal,access_membership_group_member_portal,model_membership_group_member,base.group_portal,1,0,0,0 -access_membership_group_member,access_membership_group_member,model_membership_group_member,base.group_user,1,1,1,1 +access_membership_group_member,access_membership_group_member,model_membership_group_member,base.group_user,1,1,1,0 diff --git a/membership_group/tests/test_membership_group.py b/membership_group/tests/test_membership_group.py index e951568..8c9988c 100644 --- a/membership_group/tests/test_membership_group.py +++ b/membership_group/tests/test_membership_group.py @@ -1,3 +1,4 @@ +import freezegun from odoo.tests import common @@ -84,3 +85,38 @@ def test_04_action_open_membership_group_view(self): ], ) self.assertEqual(res["res_id"], self.group_1.id) + + def test_05_membership_group_with_revoke_date(self): + group_1_with_termination = self.env["membership.group"].create( + { + "name": "Test Group 1 with termination", + "termination_cycle": True, + "next_termination_date": "2025-06-01", + } + ) + member_group_termination = self.env["membership.group.member"].create( + { + "partner_id": self.partner_1.id, + "group_id": group_1_with_termination.id, + } + ) + + self.assertEqual( + member_group_termination.date_to, + group_1_with_termination.next_termination_date, + ) + self.assertTrue(member_group_termination.active) + + with freezegun.freeze_time("2025-05-01"): + self.env["membership.group.member"]._cron_revoke_membership() + + self.assertTrue(member_group_termination.active) + + with freezegun.freeze_time("2025-06-01"): + self.env["membership.group.member"]._cron_revoke_membership() + + self.assertFalse(member_group_termination.active) + self.assertEqual( + str(member_group_termination.date_end), + "2025-06-01", + ) diff --git a/membership_group/views/membership_group_member_view.xml b/membership_group/views/membership_group_member_view.xml index f8ff6b2..01e4096 100644 --- a/membership_group/views/membership_group_member_view.xml +++ b/membership_group/views/membership_group_member_view.xml @@ -1,14 +1,22 @@ - + membership.group.member + - - + + + + + + +