from odoo import fields, models, api from datetime import datetime, timedelta import logging import json import requests _logger = logging.getLogger(__name__) class WatiNotification(models.Model): _name = 'wati.notification' lead_id = fields.Many2one('crm.lead', string='Lead Id') ticket_id = fields.Char(string='Ticked Id') mobile = fields.Char(string='Mobile') sender_name = fields.Char(string='Sender Name') text = fields.Char(string='Text Message') text_time = fields.Datetime(string='Text Time') text_type = fields.Char(string='Text Type') json_raw = fields.Char(string='JSON Raw Text') is_parsed = fields.Boolean(string='Is Parsed', default=False) is_lead = fields.Boolean(string='To Leads', help='apakah sudah ter-convert jadi leads') def _cleanup(self): current_time = datetime.now() delta_time = current_time - timedelta(days=15) delta_time = delta_time.strftime('%Y-%m-%d %H:%M:%S') self.env['wati.notification'].search([ ('create_date', '<', delta_time), ('is_lead', '=', True), ]).unlink() _logger.info('Success Cleanup WATI Notification') def _parse_notification(self, limit=0): domain = [('is_parsed', '=', False)] notifications = self.search(domain, order='id', limit=limit) notification_not_parsed_count = self.search_count(domain) i = 0 for notification in notifications: i += 1 _logger.info('[Parse Notification][%s] Process: %s/%s | Not Parsed: %s' % (notification.id, i, str(limit), str(notification_not_parsed_count))) notification_json = json.loads(notification.json_raw) sender_name = 'Indoteknik' if 'senderName' in notification_json: sender_name = notification_json['senderName'] ticket_id = notification_json.get('ticketId') timestamp = notification_json.get('timestamp') if not timestamp: _logger.warning('[Parse Notification][%s] Missing timestamp in notification JSON: %s' % (notification.id, notification.json_raw)) continue # Skip this notification try: date_wati = datetime.fromtimestamp(float(timestamp)) except ValueError as e: _logger.error('[Parse Notification][%s] Invalid timestamp format: %s. Error: %s' % (notification.id, timestamp, str(e))) continue wati_history = self.env['wati.history'].search([('ticket_id', '=', ticket_id)], limit=1) if wati_history: self._create_wati_history_line(wati_history, ticket_id, sender_name, notification_json, date_wati) else: new_header = self._create_wati_history_header(ticket_id, sender_name, notification_json, date_wati) self._create_wati_history_line(new_header, ticket_id, sender_name, notification_json, date_wati) notification.is_parsed = True return def _create_wati_history_header(self, ticket_id, sender_name, notification_json, date_wati): # Helper function to remove NUL characters def remove_null_characters(value): if isinstance(value, str): return value.replace('\0', '') return value # Sanitize the input data param_header = { 'ticket_id': ticket_id, 'conversation_id': remove_null_characters(notification_json.get('conversationId', '')), 'sender_name': remove_null_characters(sender_name), 'wa_id': remove_null_characters(notification_json.get('waId', '')), 'text': remove_null_characters(notification_json.get('text', '')), 'date_wati': remove_null_characters(date_wati or ''), } # Create the record new_header = self.env['wati.history'].create([param_header]) return new_header def _create_wati_history_line(self, new_header, ticket_id, sender_name, notification_json, date_wati): # Helper function to remove NUL characters def remove_null_characters(value): if isinstance(value, str): return value.replace('\0', '') return value # Sanitize the input data param_line = { "wati_history_id": new_header.id, "conversation_id": remove_null_characters(notification_json.get('conversationId', '')), "data": remove_null_characters(notification_json.get('data', '')), "event_type": remove_null_characters(notification_json.get('eventType', '')), "operator_email": remove_null_characters(notification_json.get('operatorEmail', '')), "operator_name": remove_null_characters(notification_json.get('operatorName', '')), "sender_name": remove_null_characters(sender_name or ''), "status_string": remove_null_characters(notification_json.get('statusString', '')), "text": remove_null_characters(notification_json.get('text', '')), "ticket_id": ticket_id, "type": remove_null_characters(notification_json.get('type', '')), "wa_id": remove_null_characters(notification_json.get('waId', '')), "date_wati": remove_null_characters(date_wati or ''), } # Create the record safely without NUL characters self.env['wati.history.line'].create([param_line]) self._update_header_after_create_line(new_header, sender_name, date_wati, param_line['text']) return def _update_header_after_create_line(self, new_header, sender_name, date_wati, text_body): new_header.last_reply_by = sender_name new_header.last_reply_date = date_wati new_header.last_reply_text = text_body if sender_name == 'Indoteknik': current_time = date_wati delta_time = current_time + timedelta(days=1) new_header.expired_date = delta_time # this commented code not run smoothly, need more time # change to scheduler # if not new_header.perusahaan or not new_header.email: # self._get_attribute_wati(new_header) return def _convert_to_leads(self): query = [ ('is_lead', '=', False) ] watis = self.env['wati.notification'].search(query, order='id') for wati in watis: _logger.info('Convert to Lead WATI Notification ID %s' % wati.id) ticket_id = json.loads(wati.json_raw)['ticketId'] text = json.loads(wati.json_raw)['text'] current_lead = self.env['crm.lead'].search([('ticket_id', '=', ticket_id)], limit=1) operator_email = json.loads(wati.json_raw)['operatorEmail'] operator_name = json.loads(wati.json_raw)['operatorName'] event_type = json.loads(wati.json_raw)['eventType'] if event_type == 'sessionMessageSent': if 'Saya *Eko*' in str(text): sales = 11 elif 'Saya *Nabila*' in str(text): sales = 20 elif 'Saya *Novita*' in str(text): sales = 377 elif 'Saya *Putri*' in str(text): sales = 10 elif 'Saya *Heriyanto*' in str(text): sales = 375 elif 'Saya *Ade*' in str(text): sales = 9 elif 'Saya *Adela*' in str(text): sales = 8 elif 'Saya *Jananto*' in str(text): sales = 376 elif 'Saya *Dwi*' in str(text): sales = 24 else: sales = 25 #System current_lead.description = str(current_lead.description)+ "| i:" + str(text) current_lead.operator_email = operator_email current_lead.operator_name = operator_name current_lead.user_id = sales elif current_lead: # must append internal notes as a reply here current_lead.description = str(current_lead.description) + " | c:" +str(text) current_lead.operator_email = operator_email current_lead.operator_name = operator_name else: # create new leads contact_name = json.loads(wati.json_raw)['senderName'] phone = json.loads(wati.json_raw)['waId'] name = 'Ada pesan dari WATI '+str(phone)+' '+str(contact_name) current_lead = self.env['crm.lead'].create([{ 'name': name, 'ticket_id': ticket_id, 'operator_email': operator_email, 'operator_name': operator_name, 'contact_name': contact_name, 'phone': phone, 'description': "c:" +str(text) }]) wati.is_lead = True wati.lead_id = current_lead.id class WatiHistory(models.Model): _name = 'wati.history' _order = 'id desc' ticket_id = fields.Char(string='Ticket ID') conversation_id = fields.Char(string='Conversation ID') sender_name = fields.Char(string='Sender Name') wa_id = fields.Char(string='WA ID') text = fields.Char(string='Text') date_wati = fields.Datetime(string='Date WATI') wati_lines = fields.One2many('wati.history.line', 'wati_history_id', string='Lines', auto_join=True) last_reply_by = fields.Char(string='Last Reply by') last_reply_text = fields.Char(string='Last Reply Text') last_reply_date = fields.Datetime(string='Last Reply Date') expired_date = fields.Datetime(string='Expired Date') email = fields.Char(string='Email') perusahaan = fields.Char(string='Perusahaan') is_get_attribute = fields.Boolean(string='Get Attribute', default=False) def _get_attribute_wati(self): domain = [ '&', ('is_get_attribute', '=', False), '|', ('perusahaan', '=', False), ('email', '=', False), ] limit = 50 wati_histories = self.env['wati.history'].search(domain, limit=limit) count = 0 for wati_history in wati_histories: count += 1 _logger.info('[Parse Notification] Processing: %s/%s', count, limit) wati_api = self.env['wati.api'] # Perbaikan pada parameter JSON params = { 'pageSize': 1, 'pageNumber': 1, 'attribute': json.dumps([ {'name': "phone", 'operator': "contain", 'value': wati_history.wa_id} ]), } try: wati_contacts = wati_api.http_get('/api/v1/getContacts', params) except Exception as e: _logger.error('Error while calling WATI API: %s', str(e)) continue # Validasi respons dari API if not isinstance(wati_contacts, dict): _logger.error('Invalid response format from WATI API: %s', wati_contacts) continue if wati_contacts.get('result') != 'success': _logger.warning('WATI API request failed with result: %s', wati_contacts.get('result')) continue contact_list = wati_contacts.get('contact_list', []) if not contact_list: _logger.info('No contacts found for WA ID: %s', wati_history.wa_id) continue perusahaan = email = '' for data in contact_list: custom_params = data.get('customParams', []) for custom_param in custom_params: name = custom_param.get('name') value = custom_param.get('value') if name == 'perusahaan': perusahaan = value elif name == 'email': email = value # Update wati_history fields wati_history.write({ 'perusahaan': perusahaan, 'email': email, 'is_get_attribute': True, }) _logger.info('Wati history updated: %s', wati_history.id) # @api.onchange('last_reply_date') # def _compute_expired_date(self): # if self.last_reply_by == 'Indoteknik': # return # else: # print(1) # current_time = self.last_reply_date # # current_time_str = current_time.strftime('%Y-%m-%d %H:%M:%S') # delta_time = current_time + timedelta(days=1) # # delta_time_str = delta_time.strftime('%Y-%m-%d %H:%M:%S') # self.expired_date = delta_time # return class WatiHistoryLine(models.Model): _name = 'wati.history.line' #sender wati_history_id = fields.Many2one('ref', required=True, ondelete='cascade', index=True, copy=False) conversation_id = fields.Char(string='Conversation ID') data = fields.Char(string='data') event_type = fields.Char(string='Event Type') list_reply = fields.Char(string='List Reply') message_contact = fields.Char(string='Message Contact') operator_email = fields.Char(string='Operator Email') operator_name = fields.Char(string='Operator Name') sender_name = fields.Char(string='Sender Name') source_url = fields.Char(string='Source URL') status_string = fields.Char(string='Status String') text = fields.Char(string='Text') ticket_id = fields.Char(string='Ticket ID') type = fields.Char(string='Type') wa_id = fields.Char(string='WA ID') date_wati = fields.Datetime(string='Date WATI')