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
+
-
-
+
+
+
+
+
+
+
@@ -17,32 +25,67 @@
membership.group.member
-
-
-
+
+
+
-
+
membership.group.member
+
+
+
-
-
-
+
+
+
- Membership Group Members
+ Current Members
+ membership.group.member
+ tree,pivot
+
+
+
+ Future Members
+ membership.group.member
+ tree,pivot
+ {'active_test': False, 'create': False, 'edit': False}
+ [('active', '=', False), ('date_from', '>',
+ context_today().strftime('%Y-%m-%d'))]
+
+
+
+ Past Members
membership.group.member
tree,pivot
+ {'active_test': False, 'create': False, 'edit': False,
+ 'membership_remove_options': True}
+ [('active', '=', False), ('date_end', '!=', False)]
+
+
+ Voting Members
+ membership.group.member
+ tree,pivot
+ {'create': False, 'edit': False, 'membership_remove_options': True,
+ 'search_default_groupby_group_id': 1}
+ [('can_vote', '=', True)]
+
+
diff --git a/membership_group/views/membership_group_view.xml b/membership_group/views/membership_group_view.xml
index 8d8d63c..3f82270 100644
--- a/membership_group/views/membership_group_view.xml
+++ b/membership_group/views/membership_group_view.xml
@@ -1,4 +1,4 @@
-
+
@@ -48,20 +48,19 @@
-
-
-
-
-
-
+
-
+
+
+
+
+
+
+
diff --git a/membership_group/views/res_partner_view.xml b/membership_group/views/res_partner_view.xml
index 959674d..dfc6ccf 100644
--- a/membership_group/views/res_partner_view.xml
+++ b/membership_group/views/res_partner_view.xml
@@ -1,4 +1,4 @@
-
+
@@ -22,13 +22,7 @@
-
-
-
-
-
-
-
+