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

296 lines
12 KiB
Python

# -*- coding: utf-8 -*-
import base64
import json
import logging
from datetime import date
from odoo import fields, http
from odoo.http import request
_logger = logging.getLogger(__name__)
ELIGIBILITY_KEYS = (
'tncsc_born_tamil_nadu',
'tncsc_pio_oci',
'tncsc_tamil_speaker_resident',
'tncsc_family_tamil_indian',
'tncsc_other_citizenship',
)
MONTH_OPTIONS = [
('1', 'January'), ('2', 'February'), ('3', 'March'), ('4', 'April'),
('5', 'May'), ('6', 'June'), ('7', 'July'), ('8', 'August'),
('9', 'September'), ('10', 'October'), ('11', 'November'), ('12', 'December'),
]
class TNCSCMembershipController(http.Controller):
@http.route('/membership/check_username', type='jsonrpc', auth='public', methods=['POST'], website=True)
def check_username(self, username=None, **kwargs):
if not username:
return {'available': False, 'message': 'Username is required.'}
username = username.strip()
if len(username) < 3:
return {'available': False, 'message': 'Username must be at least 3 characters.'}
count = request.env['res.users'].sudo().search_count([('login', '=', username)])
if count > 0:
return {'available': False, 'message': 'Username is already taken.'}
return {'available': True, 'message': 'Username is available.'}
@http.route('/membership/check_email', type='jsonrpc', auth='public', methods=['POST'], website=True)
def check_email(self, email=None, **kwargs):
if not email:
return {'available': False, 'message': 'Email is required.'}
email = email.strip()
if '@' not in email or '.' not in email:
return {'available': False, 'message': 'Please enter a valid email address.'}
user_count = request.env['res.users'].sudo().search_count([('email', '=', email)])
partner_count = request.env['res.partner'].sudo().search_count([
('email', '=', email),
('membership_tier_id', '!=', False),
])
if user_count > 0 or partner_count > 0:
return {'available': False, 'message': 'An account with this email already exists.'}
return {'available': True, 'message': 'Email is available.'}
def _get_form_context(self, selected_code=None, errors=None, form_data=None):
Tier = request.env['community.membership.tier'].sudo()
tiers = Tier.search([('active', '=', True)], order='sequence, id')
selected_tier = None
if selected_code:
selected_tier = tiers.filtered(lambda t: t.code == selected_code)
if not selected_tier and selected_code.isdigit():
selected_tier = tiers.filtered(lambda t: t.id == int(selected_code))
if selected_tier:
selected_tier = selected_tier[0]
if not selected_tier and tiers:
selected_tier = tiers[0]
countries = request.env['res.country'].sudo().search([], order='name asc')
ca_country = request.env.ref('base.ca', raise_if_not_found=False)
if not ca_country:
ca_country = request.env['res.country'].sudo().search([('code', '=', 'CA')], limit=1)
current_year = fields.Date.today().year
birth_years = list(range(current_year - 14, current_year - 100, -1))
return {
'tiers': tiers,
'selected_tier': selected_tier,
'countries': countries,
'default_country_id': ca_country.id if ca_country else False,
'birth_months': MONTH_OPTIONS,
'birth_years': birth_years,
'errors': errors or [],
'form_data': form_data or {},
}
@http.route(['/membership/join'], type='http', auth='public', methods=['GET', 'POST'], website=True, sitemap=True)
def membership_join(self, **post):
if request.httprequest.method == 'GET':
tier_code = post.get('tier') or 'FAM'
values = self._get_form_context(selected_code=tier_code)
return request.render('tncsc_deployment.page_membership_join', values)
# Handle POST submission
errors = []
tier_id = post.get('tier_id')
username = (post.get('username') or '').strip()
email = (post.get('email') or '').strip().lower()
password = post.get('password') or ''
confirm_password = post.get('confirm_password') or ''
first_name = (post.get('first_name') or '').strip()
last_name = (post.get('last_name') or '').strip()
phone = (post.get('phone') or '').strip()
street = (post.get('street') or '').strip()
city = (post.get('city') or '').strip()
zip_code = (post.get('zip') or '').strip()
country_id = post.get('country_id')
birth_month = post.get('birth_month') or ''
birth_year = post.get('birth_year') or ''
terms_accepted = bool(post.get('membership_terms_accepted'))
coupon_code = (post.get('coupon_code') or '').strip()
tier = None
if tier_id:
tier = request.env['community.membership.tier'].sudo().browse(int(tier_id))
if not tier.exists():
tier = None
if not tier:
errors.append('Please select a valid membership plan.')
if not username or len(username) < 3:
errors.append('Username must be at least 3 characters long.')
elif request.env['res.users'].sudo().search_count([('login', '=', username)]) > 0:
errors.append(f'The username "{username}" is already taken. Please choose another username.')
if not email or '@' not in email:
errors.append('Please provide a valid email address.')
else:
existing_user = request.env['res.users'].sudo().search_count([('email', '=', email)])
existing_partner = request.env['res.partner'].sudo().search_count([
('email', '=', email),
('membership_tier_id', '!=', False),
])
if existing_user > 0 or existing_partner > 0:
errors.append(f'An account or membership application with email "{email}" already exists.')
if not password or len(password) < 6:
errors.append('Password must be at least 6 characters long.')
elif password != confirm_password:
errors.append('Passwords do not match.')
if not first_name:
errors.append('First Name is required.')
if not last_name:
errors.append('Last Name is required.')
if not terms_accepted:
errors.append('You must accept the TNCSC Constitution and Membership Terms to proceed.')
# Minimum age validation if tier requires it
today = date.today()
calculated_age = None
if birth_year and birth_year.isdigit():
b_year = int(birth_year)
b_month = int(birth_month) if birth_month and birth_month.isdigit() else 1
calculated_age = today.year - b_year
if today.month < b_month:
calculated_age -= 1
if tier and tier.min_age and calculated_age < tier.min_age:
errors.append(
f'The {tier.name} plan requires a minimum age of {tier.min_age} years '
f'(calculated age: {calculated_age}).'
)
# File uploads
avatar_file = request.httprequest.files.get('avatar_file')
proof_file = request.httprequest.files.get('proof_file')
if tier and tier.requires_proof and not proof_file:
# Alert requirement if mandatory
pass
if errors:
values = self._get_form_context(
selected_code=tier.code if tier else post.get('tier_id'),
errors=errors,
form_data=post,
)
return request.render('tncsc_deployment.page_membership_join', values)
# Create Member Partner
partner_vals = {
'first_name': first_name,
'last_name': last_name,
'name': f"{first_name} {last_name}".strip(),
'email': email,
'phone': phone,
'street': street,
'city': city,
'zip': zip_code,
'country_id': int(country_id) if country_id and country_id.isdigit() else False,
'birth_month': str(birth_month) if birth_month else False,
'birth_year': int(birth_year) if birth_year and birth_year.isdigit() else False,
'membership_tier_id': tier.id,
'membership_approval_state': 'pending',
'membership_terms_accepted': True,
'company_id': request.env.company.id,
}
# TNCSC Eligibility Answers
for k in ELIGIBILITY_KEYS:
ans = post.get(k)
if ans in ('yes', 'no'):
partner_vals[k] = ans
if avatar_file and avatar_file.filename:
try:
partner_vals['image_1920'] = base64.b64encode(avatar_file.read())
except Exception as e:
_logger.warning("Failed to read uploaded avatar: %s", e)
if proof_file and proof_file.filename:
try:
partner_vals['membership_proof_document'] = base64.b64encode(proof_file.read())
partner_vals['membership_proof_filename'] = proof_file.filename
except Exception as e:
_logger.warning("Failed to read proof document: %s", e)
partner = request.env['res.partner'].sudo().create(partner_vals)
# Create Portal User
try:
portal_group = request.env.ref('base.group_portal')
user_vals = {
'name': partner.name,
'login': username,
'email': email,
'partner_id': partner.id,
'groups_id': [(6, 0, [portal_group.id])],
}
user = request.env['res.users'].sudo().create(user_vals)
user.sudo().password = password
except Exception as e:
_logger.exception("Failed to create portal user for new member: %s", e)
# Process Family Members if applicable
if tier.allows_family_members:
fam_first_names = request.httprequest.form.getlist('family_first_name[]')
fam_last_names = request.httprequest.form.getlist('family_last_name[]')
fam_emails = request.httprequest.form.getlist('family_email[]')
fam_phones = request.httprequest.form.getlist('family_phone[]')
for idx in range(len(fam_first_names)):
f_first = (fam_first_names[idx] or '').strip()
f_last = (fam_last_names[idx] or '').strip()
if f_first or f_last:
f_email = (fam_emails[idx] or '').strip() if idx < len(fam_emails) else ''
f_phone = (fam_phones[idx] or '').strip() if idx < len(fam_phones) else ''
request.env['res.partner'].sudo().create({
'first_name': f_first,
'last_name': f_last,
'name': f"{f_first} {f_last}".strip(),
'email': f_email or False,
'phone': f_phone or False,
'family_head_id': partner.id,
'company_id': request.env.company.id,
})
# Post signup note on partner chatter
coupon_note = f"<br/><b>Coupon Applied:</b> {coupon_code}" if coupon_code else ""
partner.sudo().message_post(
body=(
f"<b>Online Membership Application Submitted</b><br/>"
f"<b>Tier:</b> {tier.name} ({tier.code}) - ${tier.price}<br/>"
f"<b>Username:</b> {username}<br/>"
f"<b>Status:</b> Pending Board Approval"
f"{coupon_note}"
),
subtype_xmlid='mail.mt_note',
)
# Send application received confirmation email
try:
template = request.env.ref('community_membership.mail_template_membership_application_received', raise_if_not_found=False)
if template:
template.sudo().send_mail(partner.id, force_send=True)
except Exception as e:
_logger.warning("Failed to send application confirmation email: %s", e)
return request.render('tncsc_deployment.page_membership_thank_you', {
'partner': partner,
'tier': tier,
'username': username,
})
@http.route('/membership/thank-you', type='http', auth='public', website=True)
def membership_thank_you(self, **kw):
return request.render('tncsc_deployment.page_membership_thank_you', {})