Files
WhatsAppCentralizado/odoo_whatsapp_hub/models/whatsapp_account.py
Claude AI 5dd3499097 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>
2026-01-30 20:48:56 +00:00

111 lines
3.8 KiB
Python

from odoo import models, fields, api
from odoo.exceptions import UserError
import requests
import logging
_logger = logging.getLogger(__name__)
class WhatsAppAccount(models.Model):
_name = 'whatsapp.account'
_description = 'WhatsApp Account'
_order = 'name'
name = fields.Char(string='Nombre', required=True)
phone_number = fields.Char(string='Número de Teléfono')
status = fields.Selection([
('disconnected', 'Desconectado'),
('connecting', 'Conectando'),
('connected', 'Conectado'),
], string='Estado', default='disconnected', readonly=True)
qr_code = fields.Text(string='Código QR')
external_id = fields.Char(string='ID Externo', help='ID en WhatsApp Central')
api_url = fields.Char(
string='URL API',
default='http://localhost:8000',
required=True,
)
api_key = fields.Char(string='API Key')
is_default = fields.Boolean(string='Cuenta por Defecto')
company_id = fields.Many2one(
'res.company',
string='Compañía',
default=lambda self: self.env.company,
)
conversation_count = fields.Integer(
string='Conversaciones',
compute='_compute_conversation_count',
)
@api.depends()
def _compute_conversation_count(self):
for account in self:
account.conversation_count = self.env['whatsapp.conversation'].search_count([
('account_id', '=', account.id)
])
@api.model
def get_default_account(self):
"""Get default WhatsApp account"""
account = self.search([('is_default', '=', True)], limit=1)
if not account:
account = self.search([], limit=1)
return account
def action_sync_status(self):
"""Sync status from WhatsApp Central"""
self.ensure_one()
if not self.external_id:
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/internal/odoo/accounts/{self.external_id}',
timeout=10,
)
if response.status_code == 200:
data = response.json()
self.write({
'status': data.get('status', 'disconnected'),
'phone_number': data.get('phone_number'),
'qr_code': data.get('qr_code'),
})
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}')
def action_view_conversations(self):
"""Open conversations for this account"""
self.ensure_one()
return {
'type': 'ir.actions.act_window',
'name': 'Conversaciones',
'res_model': 'whatsapp.conversation',
'view_mode': 'tree,form',
'domain': [('account_id', '=', self.id)],
'context': {'default_account_id': self.id},
}
def _get_headers(self):
"""Get API headers"""
headers = {'Content-Type': 'application/json'}
if self.api_key:
headers['Authorization'] = f'Bearer {self.api_key}'
return headers
@api.model_create_multi
def create(self, vals_list):
for vals in vals_list:
if vals.get('is_default'):
self.search([('is_default', '=', True)]).write({'is_default': False})
break
return super().create(vals_list)
def write(self, vals):
if vals.get('is_default'):
self.search([('is_default', '=', True), ('id', 'not in', self.ids)]).write({'is_default': False})
return super().write(vals)