feat: Major WhatsApp integration update with Odoo and pause/resume
## Frontend - Add media display (images, audio, video, docs) in Inbox - Add pause/resume functionality for WhatsApp accounts - Fix media URLs to use nginx proxy (relative URLs) ## API Gateway - Add /accounts/:id/pause and /accounts/:id/resume endpoints - Fix media URL handling for browser access ## WhatsApp Core - Add pauseSession() - disconnect without logout - Add resumeSession() - reconnect using saved credentials - Add media download and storage for incoming messages - Serve media files via /media/ static route ## Odoo Module (odoo_whatsapp_hub) - Add Chat Hub interface with DOLLARS theme (dark, 3-column layout) - Add WhatsApp/DRRR theme switcher for chat view - Add "ABRIR CHAT" button in conversation form - Add send_message_from_chat() method - Add security/ir.model.access.csv - Fix CSS scoping to avoid breaking Odoo UI - Update webhook to handle message events properly ## Documentation - Add docs/CONTEXTO_DESARROLLO.md with complete project context ## Infrastructure - Add whatsapp_media Docker volume - Configure nginx proxy for /media/ route - Update .gitignore to track src/sessions/ source files Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -59,9 +59,9 @@ class WhatsAppAccount(models.Model):
|
||||
raise UserError('Esta cuenta no está vinculada a WhatsApp Central')
|
||||
|
||||
try:
|
||||
# Use internal endpoint (no auth required)
|
||||
response = requests.get(
|
||||
f'{self.api_url}/api/whatsapp/accounts/{self.external_id}',
|
||||
headers=self._get_headers(),
|
||||
f'{self.api_url}/api/whatsapp/internal/odoo/accounts/{self.external_id}',
|
||||
timeout=10,
|
||||
)
|
||||
if response.status_code == 200:
|
||||
@@ -71,7 +71,9 @@ class WhatsAppAccount(models.Model):
|
||||
'phone_number': data.get('phone_number'),
|
||||
'qr_code': data.get('qr_code'),
|
||||
})
|
||||
except Exception as e:
|
||||
else:
|
||||
raise UserError(f'Error del servidor: {response.status_code}')
|
||||
except requests.exceptions.RequestException as e:
|
||||
_logger.error(f'Error syncing WhatsApp account: {e}')
|
||||
raise UserError(f'Error de conexión: {e}')
|
||||
|
||||
|
||||
@@ -115,6 +115,79 @@ class WhatsAppConversation(models.Model):
|
||||
'status': 'active',
|
||||
})
|
||||
|
||||
def action_open_send_wizard(self):
|
||||
"""Open wizard to send WhatsApp message"""
|
||||
self.ensure_one()
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': 'Enviar WhatsApp',
|
||||
'res_model': 'whatsapp.send.wizard',
|
||||
'view_mode': 'form',
|
||||
'target': 'new',
|
||||
'context': {
|
||||
'default_phone': self.phone_number,
|
||||
'default_account_id': self.account_id.id,
|
||||
'default_partner_id': self.partner_id.id if self.partner_id else False,
|
||||
},
|
||||
}
|
||||
|
||||
def action_open_chat(self):
|
||||
"""Open fullscreen chat interface"""
|
||||
self.ensure_one()
|
||||
return {
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'whatsapp_chat',
|
||||
'name': self.display_name,
|
||||
'context': {
|
||||
'active_id': self.id,
|
||||
},
|
||||
}
|
||||
|
||||
def send_message_from_chat(self, message):
|
||||
"""Send a message from the chat interface"""
|
||||
self.ensure_one()
|
||||
import requests
|
||||
|
||||
account = self.account_id
|
||||
if not account or not account.api_url:
|
||||
raise Exception("Cuenta WhatsApp no configurada")
|
||||
|
||||
# Send via API
|
||||
api_url = account.api_url.rstrip('/')
|
||||
response = requests.post(
|
||||
f"{api_url}/whatsapp/send",
|
||||
json={
|
||||
'account_id': account.external_id,
|
||||
'phone': self.phone_number,
|
||||
'message': message,
|
||||
},
|
||||
timeout=30,
|
||||
)
|
||||
|
||||
if response.status_code != 200:
|
||||
raise Exception(f"Error al enviar: {response.text}")
|
||||
|
||||
result = response.json()
|
||||
|
||||
# Create local message record
|
||||
self.env['whatsapp.message'].create({
|
||||
'conversation_id': self.id,
|
||||
'direction': 'outbound',
|
||||
'message_type': 'text',
|
||||
'content': message,
|
||||
'status': 'sent',
|
||||
'sent_by_id': self.env.user.id,
|
||||
'external_id': result.get('message_id'),
|
||||
})
|
||||
|
||||
# Update conversation
|
||||
self.write({
|
||||
'last_message_at': fields.Datetime.now(),
|
||||
'status': 'active' if self.status == 'bot' else self.status,
|
||||
})
|
||||
|
||||
return True
|
||||
|
||||
@api.model
|
||||
def find_or_create_by_phone(self, phone, account_id, contact_name=None):
|
||||
"""Find or create conversation by phone number"""
|
||||
|
||||
@@ -68,15 +68,23 @@ class WhatsAppMessage(models.Model):
|
||||
"""Send message via WhatsApp Central API"""
|
||||
self.ensure_one()
|
||||
account = self.conversation_id.account_id
|
||||
phone_number = self.conversation_id.phone_number
|
||||
|
||||
if not account.external_id:
|
||||
self.write({
|
||||
'status': 'failed',
|
||||
'error_message': 'La cuenta no está vinculada a WhatsApp Central',
|
||||
})
|
||||
return
|
||||
|
||||
try:
|
||||
# Use internal endpoint (no auth required)
|
||||
response = requests.post(
|
||||
f'{account.api_url}/api/whatsapp/conversations/{self.conversation_id.external_id}/messages',
|
||||
headers=account._get_headers(),
|
||||
f'{account.api_url}/api/whatsapp/internal/odoo/send',
|
||||
json={
|
||||
'type': self.message_type,
|
||||
'content': self.content,
|
||||
'media_url': self.media_url,
|
||||
'phone_number': phone_number,
|
||||
'message': self.content,
|
||||
'account_id': account.external_id,
|
||||
},
|
||||
timeout=30,
|
||||
)
|
||||
@@ -84,7 +92,7 @@ class WhatsAppMessage(models.Model):
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
self.write({
|
||||
'external_id': data.get('id'),
|
||||
'external_id': data.get('message_id'),
|
||||
'status': 'sent',
|
||||
'error_message': False,
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user