141 lines
5.9 KiB
Python

# -*- coding: utf-8 -*-
import logging
import re
from odoo import models, fields, api, _
_logger = logging.getLogger(__name__)
class SocialWhatsAppMessage(models.Model):
_name = 'social.whatsapp.message'
_description = 'Customer WhatsApp Message & Communication History'
_order = 'timestamp desc, id desc'
meta_message_id = fields.Char(string='WhatsApp Message ID (wamid)', index=True, copy=False)
direction = fields.Selection([
('inbound', 'Inbound (Customer to Business)'),
('outbound', 'Outbound (Business to Customer)'),
], string='Direction', default='inbound', required=True, index=True)
from_phone = fields.Char(string='Sender Phone', required=True, index=True)
to_phone = fields.Char(string='Recipient Phone')
partner_id = fields.Many2one('res.partner', string='Customer / Partner', index=True)
sale_order_id = fields.Many2one('sale.order', string='Identified Order', index=True)
body = fields.Text(string='Message Text')
message_type = fields.Char(string='Message Type', default='text')
timestamp = fields.Datetime(string='Timestamp', default=fields.Datetime.now, required=True)
raw_payload = fields.Text(string='Raw Payload', groups='dine360_meta_social.group_social_manager')
status = fields.Selection([
('received', 'Received'),
('processed', 'Processed'),
('replied', 'Replied'),
], string='Status', default='received', index=True)
company_id = fields.Many2one('res.company', string='Company', default=lambda self: self.env.company)
_sql_constraints = [
('meta_msg_id_unique', 'unique(meta_message_id)',
'WhatsApp message ID must be unique to prevent duplicate processing.')
]
@api.model
def process_incoming_message(self, message_data, company=None):
"""
Processes an inbound message from WhatsApp Cloud API webhook:
- Deduplicates using meta_message_id
- Associates phone number with res.partner
- Identifies existing order via text analysis or recent orders
- Stores in communication history
"""
company = company or self.env.company
msg_id = message_data.get('id')
from_phone = message_data.get('from', '')
text_body = message_data.get('text', {}).get('body', '') if message_data.get('type') == 'text' else ''
msg_type = message_data.get('type', 'text')
if not msg_id or not from_phone:
_logger.warning("Invalid incoming WhatsApp message format: %s", message_data)
return False
# 1. Deduplication check
existing = self.search([('meta_message_id', '=', msg_id)], limit=1)
if existing:
_logger.info("WhatsApp message %s already processed. Skipping duplicate.", msg_id)
return existing
# 2. Partner association (match by mobile or phone)
wa_api = self.env['social.whatsapp.api']
cleaned_phone = wa_api.format_phone(from_phone)
partner = self._find_partner_by_phone(cleaned_phone)
# 3. Order identification (look for SO\d+ or check most recent order)
order = self._identify_order(text_body, partner)
record = self.create({
'meta_message_id': msg_id,
'direction': 'inbound',
'from_phone': from_phone,
'to_phone': company.whatsapp_business_phone_number or '',
'partner_id': partner.id if partner else False,
'sale_order_id': order.id if order else False,
'body': text_body,
'message_type': msg_type,
'timestamp': fields.Datetime.now(),
'company_id': company.id,
'status': 'received',
})
_logger.info("Processed inbound WhatsApp message from %s (Partner: %s, Order: %s)",
from_phone, partner.name if partner else "Unknown", order.name if order else "None")
return record
@api.model
def _find_partner_by_phone(self, phone):
if not phone:
return False
digits = re.sub(r'\D', '', phone)
if not digits:
return False
# 1. Search by phone_sanitized
partner = self.env['res.partner'].search([
'|', '|',
('phone_sanitized', '=', f"+{digits}"),
('phone_sanitized', '=', digits),
('phone_sanitized', 'like', digits[-10:] if len(digits) >= 10 else digits)
], limit=1)
if partner:
return partner
# 2. Search partners with phone/mobile and compare normalized digits
potential_partners = self.env['res.partner'].search([
'|', ('mobile', '!=', False), ('phone', '!=', False)
], limit=200)
for p in potential_partners:
p_mob = re.sub(r'\D', '', p.mobile or '')
p_pho = re.sub(r'\D', '', p.phone or '')
target = digits[-8:] if len(digits) >= 8 else digits
if (p_mob and target in p_mob) or (p_pho and target in p_pho):
return p
return False
@api.model
def _identify_order(self, text, partner):
if not text:
return False
# Try matching order reference like SO1234 or S01234 or order #1234
match = re.search(r'\b(SO\d+|S\d{4,})\b', text, re.IGNORECASE)
if match:
order_name = match.group(1).upper()
order = self.env['sale.order'].search([('name', '=ilike', order_name)], limit=1)
if order:
return order
# If customer asks about order status and has a recent order
order_keywords = ['where is my order', 'order status', 'my order', 'track order', 'delivery status']
if partner and any(kw in text.lower() for kw in order_keywords):
recent_order = self.env['sale.order'].search([
('partner_id', '=', partner.id)
], order='date_order desc', limit=1)
if recent_order:
return recent_order
return False