Alaguraj0361 0f462ad21a
Some checks failed
CI / Brand-leak check (push) Has been cancelled
CI / flake8 / pylint-odoo / manifest completeness (push) Has been cancelled
CI / Install all modules with --test-enable (push) Has been cancelled
add TNCSC deployment module, community add-ons, role guides, and website navigation overrides
2026-09-28 19:43:48 +05:30

118 lines
4.2 KiB
Python

from dateutil.relativedelta import relativedelta
from odoo import api, fields, models
PERIOD_DURATION_DAYS = {
'annual': 365,
'monthly': 30,
'one_time': None, # never expires
}
class CommunityMembershipTier(models.Model):
_name = 'community.membership.tier'
_description = 'Membership Tier'
_order = 'sequence, id'
name = fields.Char(required=True, translate=True)
code = fields.Char(required=True, help="Short internal code, e.g. 'IND', 'FAM'.")
price = fields.Monetary(currency_field='currency_id', required=True)
currency_id = fields.Many2one(
'res.currency', required=True,
default=lambda self: self.env.company.currency_id,
)
period = fields.Selection(
[
('annual', 'Annual'),
('one_time', 'One-time (lifetime)'),
('monthly', 'Monthly'),
('fixed_term', 'Fixed term (years)'),
],
default='annual', required=True,
)
term_years = fields.Integer(
string='Term (Years)', default=1,
help="Length of the membership in years, for fixed-term tiers (e.g. 10 for a long-term membership).",
)
description = fields.Text(
translate=True,
help="Who the tier is for and what it covers, shown to applicants on the signup page.",
)
min_age = fields.Integer(
string='Minimum Age',
help="Applicants must be at least this old to choose this tier. 0 means no age limit.",
)
requires_proof = fields.Boolean(
string='Requires Proof Document',
help="Applicants may be asked to upload proof of eligibility (e.g. age or student status).",
)
allows_family_members = fields.Boolean(
string='Includes Family Members',
help="The membership also covers family members, who can be listed on the member's record.",
)
member_state_logic = fields.Text(
string='State Notes',
help="Free-text notes for admins on any special membership-state handling "
"this tier needs (e.g. lifetime tiers never move to 'expired').",
)
sequence = fields.Integer(default=10)
product_id = fields.Many2one(
'product.product', string='Membership Product', readonly=True, copy=False,
help="Auto-created service product used to invoice this tier's dues.",
)
active = fields.Boolean(default=True)
_code_uniq = models.Constraint(
'unique(code)', 'A membership tier with this code already exists.',
)
_term_years_positive = models.Constraint(
"CHECK(period != 'fixed_term' OR term_years > 0)",
'A fixed-term tier needs a term of at least one year.',
)
@api.model_create_multi
def create(self, vals_list):
tiers = super().create(vals_list)
for tier in tiers:
if not tier.product_id:
tier.product_id = tier._create_membership_product()
return tiers
def write(self, vals):
res = super().write(vals)
if 'price' in vals or 'name' in vals:
for tier in self:
if tier.product_id:
tier.product_id.write(tier._membership_product_values())
return res
def _membership_product_values(self):
self.ensure_one()
vals = {
'name': self.name,
'list_price': self.price,
'type': 'service',
'sale_ok': True,
'purchase_ok': False,
}
if 'invoice_policy' in self.env['product.product']._fields:
vals['invoice_policy'] = 'order'
return vals
def _create_membership_product(self):
self.ensure_one()
product = self.env['product.product'].create(self._membership_product_values())
return product
def duration_days(self):
self.ensure_one()
return PERIOD_DURATION_DAYS.get(self.period)
def get_expiry_date(self, start):
"""Expiry for a membership of this tier starting on `start`, or False if it never expires."""
self.ensure_one()
if self.period == 'fixed_term':
return start + relativedelta(years=self.term_years)
duration_days = self.duration_days()
return start + relativedelta(days=duration_days) if duration_days else False