237 lines
9.4 KiB
Python
237 lines
9.4 KiB
Python
# -*- coding: utf-8 -*-
|
|
from odoo import http
|
|
from odoo.http import request
|
|
|
|
def normalize_phone_search(phone):
|
|
if not phone:
|
|
return []
|
|
digits = ''.join(c for c in phone if c.isdigit())
|
|
if not digits:
|
|
return []
|
|
candidates = [digits]
|
|
if digits.startswith('62'):
|
|
candidates.append('0' + digits[2:])
|
|
candidates.append(digits[2:])
|
|
elif digits.startswith('0'):
|
|
candidates.append('62' + digits[1:])
|
|
candidates.append(digits[1:])
|
|
else:
|
|
candidates.append('0' + digits)
|
|
candidates.append('62' + digits)
|
|
return list(set(candidates))
|
|
|
|
class AppNotificationController(http.Controller):
|
|
|
|
@http.route('/api/loyalty/fetch_notifications', type='jsonrpc', auth='user', methods=['POST'], csrf=False)
|
|
def fetch_notifications(self, **kw):
|
|
"""
|
|
Endpoint for the Flutter app Background Task and In-App notification center.
|
|
"""
|
|
user = request.env.user
|
|
partner = user.partner_id
|
|
|
|
last_id = kw.get('last_id', 0)
|
|
|
|
# Give them global notifications OR notifications tagged explicitly to them
|
|
domain = [
|
|
('id', '>', last_id),
|
|
'|',
|
|
('partner_ids', 'in', [partner.id]),
|
|
('is_global', '=', True)
|
|
]
|
|
|
|
notifications = request.env['mapan.app.notification'].sudo().search_read(
|
|
domain,
|
|
['id', 'title', 'body', 'create_date'],
|
|
order='create_date desc',
|
|
limit=50
|
|
)
|
|
|
|
return {
|
|
'status': 'success',
|
|
'data': notifications
|
|
}
|
|
|
|
@http.route('/api/loyalty/branches', type='jsonrpc', auth='public', methods=['POST'], csrf=False)
|
|
def fetch_branches(self, **kw):
|
|
"""
|
|
Public endpoint for the Flutter app to get branches without exposing API keys.
|
|
"""
|
|
try:
|
|
branches = request.env['res.company'].sudo().search_read(
|
|
[('parent_id', '!=', False)],
|
|
['name', 'street', 'city', 'phone'],
|
|
limit=50
|
|
)
|
|
return {'status': 'success', 'data': branches}
|
|
except Exception as e:
|
|
return {'status': 'error', 'message': str(e)}
|
|
|
|
@http.route('/api/loyalty/activate_account', type='jsonrpc', auth='public', methods=['POST'], csrf=False)
|
|
def activate_account(self, **kw):
|
|
"""
|
|
Public endpoint to activate/set password for an existing partner.
|
|
"""
|
|
phone = kw.get('phone')
|
|
password = kw.get('password')
|
|
birth_date = kw.get('birth_date')
|
|
|
|
if not phone or not password:
|
|
return {'status': 'error', 'message': 'Phone and Password are required.'}
|
|
|
|
candidates = normalize_phone_search(phone)
|
|
partner = request.env['res.partner'].sudo().search([
|
|
'|', ('phone', 'in', candidates), ('phone_sanitized', 'in', candidates)
|
|
], limit=1)
|
|
|
|
if not partner:
|
|
return {'status': 'error', 'message': 'Member with this phone number not found. Please contact support or register.'}
|
|
|
|
# Verify birth date if set on the partner
|
|
if partner.birth_date:
|
|
if not birth_date:
|
|
return {'status': 'error', 'message': 'Birth Date is required for verification.'}
|
|
if str(partner.birth_date) != str(birth_date):
|
|
return {'status': 'error', 'message': 'Birth Date does not match our records.'}
|
|
|
|
# Find or create user
|
|
user = request.env['res.users'].sudo().with_context(active_test=False).search([('partner_id', '=', partner.id)], limit=1)
|
|
if user:
|
|
user.sudo().write({
|
|
'company_id': request.env.company.id,
|
|
'company_ids': [(6, 0, [request.env.company.id])],
|
|
'password': password,
|
|
'active': True
|
|
})
|
|
portal_group = request.env.ref('base.group_portal')
|
|
if portal_group not in user.group_ids:
|
|
user.sudo().write({'group_ids': [(4, portal_group.id)]})
|
|
else:
|
|
portal_template = request.env.ref('base.template_portal_user_id')
|
|
existing_user = request.env['res.users'].sudo().with_context(active_test=False).search([('login', '=', phone)], limit=1)
|
|
if existing_user:
|
|
return {'status': 'error', 'message': 'A user with this phone number login already exists.'}
|
|
|
|
try:
|
|
user = portal_template.sudo().with_context(no_reset_password=True).copy({
|
|
'name': partner.name,
|
|
'login': phone,
|
|
'email': partner.email or f"{phone}@miemapan.com",
|
|
'partner_id': partner.id,
|
|
'active': False,
|
|
})
|
|
# Write company, password and activate
|
|
user.sudo().write({
|
|
'company_id': request.env.company.id,
|
|
'company_ids': [(6, 0, [request.env.company.id])],
|
|
'password': password,
|
|
'active': True,
|
|
})
|
|
# Ensure portal group
|
|
portal_group = request.env.ref('base.group_portal')
|
|
if portal_group not in user.group_ids:
|
|
user.sudo().write({'group_ids': [(4, portal_group.id)]})
|
|
except Exception as e:
|
|
return {'status': 'error', 'message': f'Failed to create user: {str(e)}'}
|
|
|
|
return {'status': 'success', 'message': 'Account activated successfully. You can now login.'}
|
|
|
|
@http.route('/api/loyalty/signup_member', type='jsonrpc', auth='public', methods=['POST'], csrf=False)
|
|
def signup_member(self, **kw):
|
|
"""
|
|
Public endpoint to sign up a new member.
|
|
"""
|
|
name = kw.get('name')
|
|
phone = kw.get('phone')
|
|
birth_date = kw.get('birth_date')
|
|
gender = kw.get('gender')
|
|
password = kw.get('password')
|
|
|
|
if not name or not phone or not password:
|
|
return {'status': 'error', 'message': 'Name, Phone, and Password are required.'}
|
|
|
|
candidates = normalize_phone_search(phone)
|
|
|
|
existing_partner = request.env['res.partner'].sudo().search([
|
|
'|', ('phone', 'in', candidates), ('phone_sanitized', 'in', candidates)
|
|
], limit=1)
|
|
if existing_partner:
|
|
return {'status': 'error', 'message': 'A member with this phone number is already registered.'}
|
|
|
|
existing_user = request.env['res.users'].sudo().with_context(active_test=False).search([
|
|
'|', ('login', '=', phone), ('login', 'in', candidates)
|
|
], limit=1)
|
|
if existing_user:
|
|
return {'status': 'error', 'message': 'A user with this phone number login already exists.'}
|
|
|
|
try:
|
|
partner_vals = {
|
|
'name': name,
|
|
'phone': phone,
|
|
'birth_date': birth_date,
|
|
'gender': gender,
|
|
'company_id': request.env.company.id,
|
|
}
|
|
partner = request.env['res.partner'].sudo().create(partner_vals)
|
|
|
|
portal_template = request.env.ref('base.template_portal_user_id')
|
|
user = portal_template.sudo().with_context(no_reset_password=True).copy({
|
|
'name': partner.name,
|
|
'login': phone,
|
|
'email': f"{phone}@miemapan.com",
|
|
'partner_id': partner.id,
|
|
'active': False,
|
|
})
|
|
# Write company, password and activate
|
|
user.sudo().write({
|
|
'company_id': request.env.company.id,
|
|
'company_ids': [(6, 0, [request.env.company.id])],
|
|
'password': password,
|
|
'active': True,
|
|
'active': True,
|
|
})
|
|
# Ensure portal group
|
|
portal_group = request.env.ref('base.group_portal')
|
|
if portal_group not in user.group_ids:
|
|
user.sudo().write({'group_ids': [(4, portal_group.id)]})
|
|
|
|
return {'status': 'success', 'message': 'Account registered successfully.'}
|
|
except Exception as e:
|
|
return {'status': 'error', 'message': f'Registration failed: {str(e)}'}
|
|
|
|
@http.route('/api/loyalty/delete_account', type='jsonrpc', auth='user', methods=['POST'], csrf=False)
|
|
def delete_account(self, **kw):
|
|
"""
|
|
Authenticated endpoint to permanently delete/anonymize a member account.
|
|
"""
|
|
user = request.env.user
|
|
password = kw.get('password')
|
|
|
|
if not password:
|
|
return {'status': 'error', 'message': 'Password is required.'}
|
|
|
|
try:
|
|
user.with_user(user)._check_credentials({'type': 'password', 'password': password}, {'interactive': True})
|
|
except Exception:
|
|
return {'status': 'error', 'message': 'Incorrect password.'}
|
|
|
|
partner = user.partner_id
|
|
|
|
user.sudo().write({'active': False})
|
|
|
|
partner.sudo().write({
|
|
'name': f'Deleted Member {partner.id}',
|
|
'phone': False,
|
|
'email': False,
|
|
'birth_date': False,
|
|
'gender': False,
|
|
'active': False,
|
|
})
|
|
|
|
partner.loyalty_card_ids.sudo().write({
|
|
'active': False,
|
|
'points': 0.0
|
|
})
|
|
|
|
return {'status': 'success', 'message': 'Account deleted successfully.'}
|