# -*- 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)