333 lines
13 KiB
Python
333 lines
13 KiB
Python
# -*- coding: utf-8 -*-
|
||
|
||
from odoo import models, fields, api
|
||
|
||
|
||
class Contact(models.Model):
|
||
_name = 'dsrpt.contact'
|
||
_description = 'Contact'
|
||
_inherit = ['mail.thread', 'mail.activity.mixin']
|
||
_rec_name = 'name'
|
||
_order = 'create_date desc, name'
|
||
|
||
name = fields.Char(string='Name', required=True, tracking=True)
|
||
display_name = fields.Char(string='Display Name') # Will be computed in realty_sales module
|
||
active = fields.Boolean(string='Active', default=True, tracking=True)
|
||
status = fields.Selection([
|
||
('awaiting_qualification', 'Awaiting Qualification'),
|
||
('not_customer', 'Not a Customer'),
|
||
('supplier', 'Supplier'),
|
||
('has_request', 'Has Request'),
|
||
('has_contract', 'Has Contract'),
|
||
('potential_investor', 'Potential Investor'),
|
||
('cancelled', 'Cancelled')
|
||
], string='Status', default='awaiting_qualification', tracking=True, help='Contact processing status for AI analysis')
|
||
user_id = fields.Many2one('res.users', string='Responsible', tracking=True)
|
||
source_id = fields.Many2one('contact.source', string='Source', tracking=True)
|
||
qualification_date = fields.Datetime(string='Qualification Date', tracking=True, help='When contact was qualified')
|
||
communication_ids = fields.One2many(
|
||
'dsrpt.contact.communication',
|
||
'contact_id',
|
||
string='Communications'
|
||
)
|
||
note = fields.Text(string='Note')
|
||
|
||
# Relations
|
||
event_ids = fields.One2many(
|
||
'contact.event',
|
||
'contact_id',
|
||
string='Events'
|
||
)
|
||
# call_ids moved to dsrpt_calls module to avoid circular dependencies
|
||
|
||
# Computed fields
|
||
event_count = fields.Integer(
|
||
string='Events',
|
||
compute='_compute_event_count'
|
||
)
|
||
phone_numbers = fields.Char(
|
||
string='Phone Numbers',
|
||
compute='_compute_phone_numbers',
|
||
store=True
|
||
)
|
||
next_contact = fields.Datetime(
|
||
string='Next Contact',
|
||
tracking=True,
|
||
help='Date of the nearest planned event'
|
||
)
|
||
# call_count moved to dsrpt_calls module
|
||
|
||
@api.depends('event_ids')
|
||
def _compute_event_count(self):
|
||
"""Counts the number of events"""
|
||
for record in self:
|
||
record.event_count = len(record.event_ids)
|
||
|
||
@api.depends('communication_ids.value', 'communication_ids.communication_type_id')
|
||
def _compute_phone_numbers(self):
|
||
"""Extracts all phone numbers for search functionality"""
|
||
for record in self:
|
||
phones = record.communication_ids.filtered(
|
||
lambda c: c.communication_type_id.code == 'phone'
|
||
).mapped('value')
|
||
record.phone_numbers = ', '.join(phones) if phones else ''
|
||
|
||
def _update_next_contact(self, date_value=None):
|
||
"""Update next_contact field with provided date"""
|
||
for record in self:
|
||
if date_value is not None:
|
||
record.next_contact = date_value
|
||
|
||
# _compute_call_count moved to dsrpt_calls module
|
||
|
||
def action_view_events(self):
|
||
"""Opens contact events"""
|
||
self.ensure_one()
|
||
return {
|
||
'name': 'Events',
|
||
'type': 'ir.actions.act_window',
|
||
'res_model': 'contact.event',
|
||
'view_mode': 'list,form',
|
||
'domain': [('contact_id', '=', self.id)],
|
||
'context': {'default_contact_id': self.id}
|
||
}
|
||
|
||
def action_qualify(self):
|
||
"""Action to trigger qualification for selected contacts"""
|
||
qualified_count = 0
|
||
no_calls_count = 0
|
||
already_qualified_count = 0
|
||
|
||
for contact in self:
|
||
# Skip if already qualified (status not 'awaiting_qualification')
|
||
if contact.status != 'awaiting_qualification':
|
||
already_qualified_count += 1
|
||
continue
|
||
|
||
# Find last call with transcription for this contact
|
||
last_call = self.env['dsrpt_calls.call'].search([
|
||
('contact_id', '=', contact.id),
|
||
('transcription', '!=', False)
|
||
], order='date_start desc', limit=1)
|
||
|
||
if last_call:
|
||
# Send on_need_qualification event directly to contact
|
||
contact._event('on_need_qualification').notify(contact)
|
||
qualified_count += 1
|
||
else:
|
||
no_calls_count += 1
|
||
|
||
# Build notification message
|
||
message_parts = []
|
||
if qualified_count:
|
||
message_parts.append(f"{qualified_count} contacts queued for qualification")
|
||
if no_calls_count:
|
||
message_parts.append(f"{no_calls_count} contacts have no calls with transcription")
|
||
if already_qualified_count:
|
||
message_parts.append(f"{already_qualified_count} contacts already qualified")
|
||
|
||
return {
|
||
'type': 'ir.actions.client',
|
||
'tag': 'display_notification',
|
||
'params': {
|
||
'title': 'Qualification Process',
|
||
'message': '. '.join(message_parts) if message_parts else 'No contacts to qualify',
|
||
'type': 'success' if qualified_count else 'info',
|
||
'sticky': False,
|
||
}
|
||
}
|
||
|
||
def action_view_calls(self):
|
||
"""Opens contact calls"""
|
||
self.ensure_one()
|
||
return {
|
||
'name': 'Calls',
|
||
'type': 'ir.actions.act_window',
|
||
'res_model': 'dsrpt_calls.call',
|
||
'view_mode': 'list,form',
|
||
'domain': [('contact_id', '=', self.id)],
|
||
'context': {'default_contact_id': self.id}
|
||
}
|
||
|
||
def name_get(self):
|
||
result = []
|
||
for record in self:
|
||
# Найти предпочитаемый способ связи
|
||
preferred = record.communication_ids.filtered('is_preferred')
|
||
if preferred:
|
||
name = f"{record.name} ({preferred[0].value})"
|
||
else:
|
||
name = record.name
|
||
result.append((record.id, name))
|
||
return result
|
||
|
||
def action_view_telegram_card(self):
|
||
"""Open telegram card editing form"""
|
||
self.ensure_one()
|
||
|
||
base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url', 'http://localhost:8069')
|
||
card_url = f"{base_url}/telegram/contact-card/{self.id}"
|
||
|
||
return {
|
||
'type': 'ir.actions.act_url',
|
||
'url': card_url,
|
||
'target': 'new',
|
||
}
|
||
|
||
def _process_name_suggestion_from_call(self, call_record):
|
||
"""Queue job method to process name suggestion from call transcription"""
|
||
import logging
|
||
_logger = logging.getLogger(__name__)
|
||
|
||
if not call_record.transcription:
|
||
_logger.warning(f"No transcription available for call {call_record.id}")
|
||
return
|
||
|
||
# Get AI collection and component
|
||
collection = self.env['dsrpt.ai.collection'].search([], limit=1)
|
||
if not collection:
|
||
_logger.error("No AI collection found")
|
||
return
|
||
|
||
work = collection.work_on(model_name='field.change.suggestion')
|
||
ai_component = work.component(usage='field.change.suggestion')
|
||
|
||
# Get suggested name from AI
|
||
suggested_name = ai_component.suggest_name_from_transcription(call_record.transcription)
|
||
|
||
# Only create suggestion if AI returned a valid name
|
||
if suggested_name and suggested_name not in ['Не определено', 'Undefined', '', False, 'Unknown']:
|
||
# Find telegram user for notification
|
||
telegram_user = self._find_telegram_user_for_suggestions()
|
||
|
||
# Create field suggestion with AI result
|
||
suggestion_vals = {
|
||
'res_model': 'dsrpt.contact',
|
||
'res_id': self.id,
|
||
'field_name': 'name',
|
||
'current_value': self.name,
|
||
'suggested_value': suggested_name,
|
||
'telegram_user_id': telegram_user.id if telegram_user else None
|
||
}
|
||
|
||
suggestion = self.env['field.change.suggestion'].create(suggestion_vals)
|
||
_logger.info(f"Created name suggestion {suggestion.id} with value '{suggested_name}' for contact {self.id}")
|
||
|
||
else:
|
||
_logger.info(f"AI did not return valid name suggestion for contact {self.id}, skipping")
|
||
|
||
def _find_telegram_user_for_suggestions(self):
|
||
"""Find telegram user for notifications"""
|
||
telegram_user = None
|
||
|
||
# Priority: contact owner > first admin user
|
||
if hasattr(self, 'user_id') and self.user_id:
|
||
telegram_user = self.env['telegram.user'].search([
|
||
('odoo_user_id', '=', self.user_id.id),
|
||
('active', '=', True)
|
||
], limit=1)
|
||
|
||
if not telegram_user:
|
||
# Find first active telegram admin user
|
||
telegram_user = self.env['telegram.user'].search([
|
||
('active', '=', True)
|
||
], limit=1)
|
||
|
||
return telegram_user
|
||
|
||
def _process_note_suggestion_from_context(self, context_record):
|
||
"""Queue job method to process note suggestion from contact history"""
|
||
import logging
|
||
_logger = logging.getLogger(__name__)
|
||
|
||
# Get AI collection and component
|
||
collection = self.env['dsrpt.ai.collection'].search([], limit=1)
|
||
if not collection:
|
||
_logger.error("No AI collection found")
|
||
return
|
||
|
||
work = collection.work_on(model_name='field.change.suggestion')
|
||
ai_component = work.component(usage='field.change.suggestion')
|
||
|
||
# Gather contact history for AI
|
||
history = self._gather_contact_history_for_ai()
|
||
|
||
# Get suggested summary from AI
|
||
suggested_summary = ai_component.suggest_summary_from_history(history)
|
||
|
||
# Only create suggestion if AI returned a valid summary
|
||
if suggested_summary and suggested_summary not in ['Не определено', 'Undefined', '', False]:
|
||
# Find telegram user for notification
|
||
telegram_user = self._find_telegram_user_for_suggestions()
|
||
|
||
# Create field suggestion with AI result
|
||
suggestion_vals = {
|
||
'res_model': 'dsrpt.contact',
|
||
'res_id': self.id,
|
||
'field_name': 'note',
|
||
'current_value': self.note or '',
|
||
'suggested_value': suggested_summary,
|
||
'telegram_user_id': telegram_user.id if telegram_user else None
|
||
}
|
||
|
||
suggestion = self.env['field.change.suggestion'].create(suggestion_vals)
|
||
_logger.info(f"Created note suggestion {suggestion.id} with summary for contact {self.id}")
|
||
|
||
else:
|
||
_logger.info(f"AI did not return valid note suggestion for contact {self.id}, skipping")
|
||
|
||
def _gather_contact_history_for_ai(self):
|
||
"""Gather contact history for summary generation"""
|
||
history_parts = []
|
||
|
||
# Add contact basic info
|
||
history_parts.append(f"Contact: {self.name}")
|
||
|
||
# Add communications
|
||
for comm in self.communication_ids:
|
||
history_parts.append(f"{comm.communication_type_id.name}: {comm.value}")
|
||
|
||
# Add recent calls if module is installed
|
||
if 'dsrpt_calls.call' in self.env:
|
||
calls = self.env['dsrpt_calls.call'].search([
|
||
('contact_id', '=', self.id)
|
||
], limit=5, order='date_start desc')
|
||
for call in calls:
|
||
history_parts.append(f"Call {call.date_start}: {call.duration} sec")
|
||
if call.transcription:
|
||
history_parts.append(f"Summary: {call.transcription[:200]}...")
|
||
|
||
return "\n".join(history_parts)
|
||
|
||
def recompute_next_contact_all(self):
|
||
"""Clear next_contact for all contacts (simplified logic)"""
|
||
all_contacts = self.env['dsrpt.contact'].search([])
|
||
all_contacts._update_next_contact(None)
|
||
return {
|
||
'type': 'ir.actions.client',
|
||
'tag': 'display_notification',
|
||
'params': {
|
||
'title': 'Reset Completed',
|
||
'message': f'"Next Contact" cleared for {len(all_contacts)} contacts',
|
||
'type': 'success',
|
||
'sticky': False,
|
||
}
|
||
}
|
||
|
||
def write(self, vals):
|
||
"""Override write to automatically set qualification_date when status changes"""
|
||
# Check if status is being changed
|
||
if 'status' in vals:
|
||
for record in self:
|
||
old_status = record.status
|
||
new_status = vals['status']
|
||
|
||
# If changing FROM awaiting_qualification TO any other status
|
||
if (old_status == 'awaiting_qualification' and
|
||
new_status != 'awaiting_qualification' and
|
||
not record.qualification_date):
|
||
# Set qualification date automatically
|
||
vals['qualification_date'] = fields.Datetime.now()
|
||
|
||
return super().write(vals)
|