# -*- 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.'}