1044 lines
50 KiB
Python
Executable File
1044 lines
50 KiB
Python
Executable File
import ast
|
|
import datetime
|
|
import json
|
|
import re
|
|
import uuid
|
|
import logging
|
|
import base64
|
|
import subprocess
|
|
import tempfile
|
|
import easywebdav
|
|
import os
|
|
import os.path
|
|
import requests
|
|
|
|
|
|
from odoo import api, fields, models, _
|
|
from odoo import tools
|
|
from odoo.exceptions import ValidationError
|
|
from datetime import date
|
|
from datetime import datetime
|
|
from dateutil.relativedelta import relativedelta
|
|
from pyffmpeg import FFmpeg
|
|
from tuya_iot import TuyaOpenAPI, TUYA_LOGGER
|
|
from tuya_connector import TuyaOpenAPI, TUYA_LOGGER
|
|
import sys
|
|
|
|
TUYA_LOGGER.setLevel(logging.DEBUG)
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
class dssSettings(models.Model):
|
|
|
|
_name = "dss.settings"
|
|
_description = "DigitalSignage Einstellungen"
|
|
_inherit = ['mail.thread','mail.activity.mixin']
|
|
uuid = fields.Char(default=lambda self: self._default_uuid(), required=True, readonly=True, copy=False, string='UUID')
|
|
def_cloud_url_base = fields.Char('Cloud URL Basis',help='https://xyxxxxxx.de/cccccccc/ bis zum vorkommen des des Grundsystem URL Teils',tracking=True)
|
|
def_project_cloudpath = fields.Char('Standard Projekt Pfad',tracking=True)
|
|
def_project_cloudpath_sample = fields.Char('Projekt Beispiel :',store=True,tracking=True)
|
|
def_contract_cloudpath = fields.Char('Standard Kunden Pfad',tracking=True)
|
|
def_contract_cloudpath_sample = fields.Char('Vertrags Beispiel :',tracking=True)
|
|
def_contract_cloudpath_V2 = fields.Char('Standard Kunden Pfad V2',tracking=True)
|
|
def_contract_cloudpath_sample_V2 = fields.Char('Projekt Beispiel V2 :',tracking=True)
|
|
def_ad_cloudpath = fields.Char('Standard Kampagnen Pfad',tracking=True)
|
|
def_ad_cloudpath_sample = fields.Char('Kampagnen Beispiel :',tracking=True)
|
|
marker_1_color_used = fields.Char('Marker Farbe (benutzt)',default='#00FF00',tracking=True)
|
|
marker_1_color_unused = fields.Char('Marker Farbe (unbenutzt)',default='#FF0000',tracking=True)
|
|
marker_2_color_used = fields.Char('Marker 2 Farbe (benutzt)',default='#00FF00',tracking=True)
|
|
marker_2_color_unused = fields.Char('Marker 2 Farbe (unbenutzt)',default='#FF0000',tracking=True)
|
|
marker_3_color_used = fields.Char('Marker 3 Farbe (benutzt)',default='#00FF00',tracking=True)
|
|
marker_3_color_unused = fields.Char('Marker 3 Farbe (unbenutzt)',default='#FF0000',tracking=True)
|
|
marker_3_color_value = fields.Boolean('Marker 3 Farbe aus wert ?',tracking=True)
|
|
marker_3_1_color_used = fields.Char('Marker 3.1 Farbe (benutzt)',default='#00FF00',tracking=True)
|
|
marker_3_1_color_unused = fields.Char('Marker 3.1 Farbe (unbenutzt)',default='#FF0000',tracking=True)
|
|
marker_3_1_color_value = fields.Boolean('Marker 3.1 Farbe aus wert ?',tracking=True)
|
|
marker_3_2_color_used = fields.Char('Marker 3.2 Farbe (benutzt)',default='#00FF00',tracking=True)
|
|
marker_3_2_color_unused = fields.Char('Marker 3.2 Farbe (unbenutzt)',default='#FF0000',tracking=True)
|
|
marker_3_2_color_value = fields.Boolean('Marker 3.2 Farbe aus wert ?',tracking=True)
|
|
marker_4_color_used = fields.Char('Marker 4 Farbe (benutzt)',default='#00FF00',tracking=True)
|
|
marker_4_color_unused = fields.Char ('Marker 4 Farbe (unbenutzt)',default='#FF0000',tracking=True)
|
|
marker_4_color_value = fields.Boolean('Marker 4 Farbe aus wert ?',tracking=True)
|
|
marker_4_1_color_used = fields.Char('Marker 4.1 Farbe (benutzt)',default='#00FF00',tracking=True)
|
|
marker_4_1_color_unused = fields.Char('Marker 4.1 Farbe (unbenutzt)',default='#FF0000',tracking=True)
|
|
marker_4_1_color_value = fields.Boolean('Marker 4.1 Farbe aus wert ?',tracking=True)
|
|
marker_4_2_color_used = fields.Char('Marker 4.2 Farbe (benutzt)',default='#00FF00',tracking=True)
|
|
marker_4_2_color_unused = fields.Char('Marker 4.2 Farbe (unbenutzt)',default='#FF0000',tracking=True)
|
|
marker_4_2_color_value = fields.Boolean('Marker 4.2 Farbe aus wert ?',tracking=True)
|
|
|
|
tuya_access_id = fields.Char('Tuya Access ID',tracking=True)
|
|
tuya_access_key = fields.Char('Tuya Access KEY',tracking=True)
|
|
tuya_endpoint = fields.Char('Tuya Access Endpoint',tracking=True)
|
|
google_maps_key = fields.Char('Google API Key (Maps)',tracking=True)
|
|
os_webimg_path = fields.Char('Verzeichnis für Bilder im Odooweb',tracking=True)
|
|
contact_runtime_user = fields.Many2one('res.users',string="Kalenderkontact",help="Kalenderkontakt für Ablauf von Verträgen")
|
|
runtime_auto_calandar_entry = fields.Boolean('Autom. Vertragsende Kalendereintrag ?',help='Jeden berechneten Vertrag-Ablaufzeitpunkt automatisch in Kalender eintragen ?',tracking=True)
|
|
show_expand_contract_group_ad_state = fields.Boolean('Jeden Kampagnen-Status anzeigen ?',help='im Groupierungsmodus der Verträge nach Kampagnenstatus alle (auch unbenutzte) Statusspalten anzeigen ?',tracking=True)
|
|
show_expand_contract_group_ad_work_state = fields.Boolean('Jeden Kampagnen-Arbeitsstatus anzeigen ?',help='im Groupierungsmodus der Verträge nach Kampagnen-Arbeitsstatus alle (auch unbenutzte) Statusspalten anzeigen ?',tracking=True)
|
|
show_expand_contract_group_ad_todo_state = fields.Boolean('Jeden Kampagnen-Aufgabenschritt anzeigen ?',help='im Groupierungsmodus der Verträge nach Kampagnen-Aufgabenschritt alle (auch unbenutzte) Aufgabenspalten anzeigen ?',tracking=True)
|
|
adportal_ranking_show_foreign_names = fields.Boolean('Ranking Fremdnamen anzeigen', default=True, help='Wenn deaktiviert, werden im AD Portal Ranking fremde Namen als ***** angezeigt. Der eigene Name bleibt sichtbar.', tracking=True)
|
|
adportal_graphics_base_prompt = fields.Text('AD Portal Grafik Prompt', compute='_compute_adportal_graphics_config', inverse='_inverse_adportal_graphics_config', tracking=True)
|
|
adportal_graphics_use_chatgpt = fields.Boolean('AD Portal: Benutze ChatGPT', compute='_compute_adportal_graphics_config', inverse='_inverse_adportal_graphics_config', tracking=True)
|
|
adportal_graphics_chatgpt_model = fields.Selection(
|
|
[
|
|
('gpt-image-1', 'gpt-image-1'),
|
|
('dall-e-3', 'dall-e-3'),
|
|
('dall-e-2', 'dall-e-2'),
|
|
],
|
|
string='AD Portal ChatGPT Modell',
|
|
compute='_compute_adportal_graphics_config',
|
|
inverse='_inverse_adportal_graphics_config',
|
|
tracking=True,
|
|
)
|
|
adportal_graphics_api_url = fields.Char('AD Portal Grafik API URL', compute='_compute_adportal_graphics_config', inverse='_inverse_adportal_graphics_config', tracking=True)
|
|
adportal_graphics_api_key = fields.Char('AD Portal Grafik API Key', compute='_compute_adportal_graphics_config', inverse='_inverse_adportal_graphics_config', tracking=True)
|
|
adportal_graphics_api_timeout = fields.Integer('AD Portal Grafik API Timeout (Sekunden)', compute='_compute_adportal_graphics_config', inverse='_inverse_adportal_graphics_config', tracking=True)
|
|
adportal_google_company_check = fields.Boolean('AD Portal Firmenpruefung via Google', compute='_compute_adportal_graphics_config', inverse='_inverse_adportal_graphics_config', tracking=True)
|
|
adportal_google_api_key = fields.Char('AD Portal Google API Key', compute='_compute_adportal_graphics_config', inverse='_inverse_adportal_graphics_config', tracking=True)
|
|
adportal_google_cse_id = fields.Char('AD Portal Google CSE ID', compute='_compute_adportal_graphics_config', inverse='_inverse_adportal_graphics_config', tracking=True)
|
|
freigabe_auto_time = fields.Integer('Auto Freigabetage',help='Automatische Freigabe nach x Tagen',tracking=True)
|
|
tv_vertragsankuendigung_actionID = fields.Integer('TV Vertragsankündigung ActionID',help="Aktion die ausgeführt wird, wenn im Web-Vertragseingang der Button Vertragsankündigung geklickt wird.",tracking=True)
|
|
systemonline_color = fields.Char('Farbe Online',tracking=True)
|
|
systemoffline_color = fields.Char('Farbe Offline',tracking=True)
|
|
vnnoxurl = fields.Char('VNNox URL',tracking=True)
|
|
vnnoxusername = fields.Char('VNNox Username',tracking=True)
|
|
vnnoxpassword = fields.Char('VNNox Password',tracking=True)
|
|
rporturl = fields.Char('RPort URL',tracking=True)
|
|
rportusername = fields.Char('RPort Username',tracking=True)
|
|
rporttoken = fields.Char('RPort API Token',tracking=True)
|
|
ai_agent_enabled = fields.Boolean('AI Agent aktiv', default=True, tracking=True)
|
|
ai_agent_base_url = fields.Char('AI Base URL', default='https://api.openai.com/v1', tracking=True)
|
|
ai_agent_model = fields.Char('AI Modell', default='gpt-4o-mini', tracking=True)
|
|
ai_agent_api_key = fields.Char('AI API Key', tracking=True)
|
|
ai_agent_system_prompt = fields.Text(
|
|
'AI System Prompt',
|
|
default='Du bist ein hilfreicher virtueller Mitarbeiter im Odoo Backend.',
|
|
tracking=True,
|
|
)
|
|
ai_agent_callback_prompt = fields.Text(
|
|
'AI Callback Prompt',
|
|
default=(
|
|
'Verwende Callback-Funktionen fuer Datenzugriffe. '
|
|
'Nutze get_model_records fuer gezielte Modell/Feld-Abfragen, '
|
|
'get_all_contracts fuer Vertragslisten, get_contract_by_number fuer Vertragsdetails '
|
|
'und get_projects fuer Projektlisten. Nutze create_model_record nur fuer explizite '
|
|
'Anlagevorgaenge und nur mit freigegebenen Schreibfeldern. Formuliere Antworten kompakt '
|
|
'und nachvollziehbar.'
|
|
),
|
|
tracking=True,
|
|
)
|
|
ai_agent_callback_models = fields.Char(
|
|
'AI Callback Modelle',
|
|
default='dss.contracts,dss.projects',
|
|
help='Kommagetrennte Modellnamen, die per get_model_records abgefragt werden duerfen.',
|
|
tracking=True,
|
|
)
|
|
ai_agent_callback_fields_json = fields.Text(
|
|
'AI Callback Felder (JSON)',
|
|
default='{"dss.contracts": ["contract_auto_id", "contract_name", "client_short_company", "contract_state", "project", "vertragssumme"], "dss.projects": ["projectid", "projektname", "name", "standort_strasse", "standort_plz", "standort_ort", "maps_visitors", "aktstatus"]}',
|
|
help='JSON-Objekt mit erlaubten Feldern pro Modell fuer get_model_records.',
|
|
tracking=True,
|
|
)
|
|
ai_agent_callback_aliases_json = fields.Text(
|
|
'AI Callback Feld-Aliase (JSON)',
|
|
default='{"dss.contracts": {"kundennummer": "contract_auto_id", "vertragsnummer": "contract_auto_id", "vertragsname": "contract_name", "vertragskennung": "contract_auto_name", "kunde": "client_short_company", "firma": "client_short_company", "vorname": "client_short_vorname", "nachname": "client_short_name", "autoverlaengerung": "contract_auto_extend", "auto_verlaengerung": "contract_auto_extend", "verlaengerung_auto": "contract_auto_extend"}}',
|
|
help='JSON-Objekt mit Aliasen pro Modell, z.B. {"dss.contracts": {"kundennummer": "contract_auto_id"}}.',
|
|
tracking=True,
|
|
)
|
|
ai_agent_callback_write_fields = fields.Text(
|
|
'AI Callback Schreibfelder',
|
|
default='',
|
|
help='Exakte Erlaubnisliste fuer Schreibzugriffe im Muster model.field, z.B. dss.projects.name oder dss.contracts.remark. Mehrere Eintraege per Komma oder Zeilenumbruch trennen.',
|
|
tracking=True,
|
|
)
|
|
ai_agent_timeout_seconds = fields.Integer('AI Timeout (Sekunden)', default=60, tracking=True)
|
|
ai_agent_temperature = fields.Float('AI Temperatur', default=0.3, tracking=True)
|
|
ai_agent_max_context_messages = fields.Integer('AI Kontext Nachrichten', default=20, tracking=True)
|
|
ai_agent_rag_enabled = fields.Boolean('AI RAG aktiv (LangChain)', default=True, tracking=True)
|
|
ai_agent_rag_top_k = fields.Integer('AI RAG Treffer (Top-K)', default=8, tracking=True)
|
|
ai_agent_contracts_access = fields.Boolean('AI Zugriff auf Vertraege', default=True, tracking=True)
|
|
ai_agent_contracts_limit = fields.Integer('AI Vertragskontext Anzahl (0 = alle)', default=0, tracking=True)
|
|
ai_agent_projects_access = fields.Boolean('AI Zugriff auf Projekte', default=True, tracking=True)
|
|
ai_agent_projects_limit = fields.Integer('AI Projektkontext Anzahl (0 = alle)', default=0, tracking=True)
|
|
phonecenter_company_provider = fields.Selection([
|
|
('google_places', 'Google Places'),
|
|
('custom_api', 'Custom Firmen-API'),
|
|
('mock', 'Mock/Fallback'),
|
|
], string='Telefoniecenter Firmenprovider', default='google_places', tracking=True)
|
|
phonecenter_company_api_url = fields.Char('Telefoniecenter Firmen-API URL', tracking=True)
|
|
phonecenter_company_api_key = fields.Char('Telefoniecenter Firmen-API Key', tracking=True)
|
|
phonecenter_company_timeout = fields.Integer('Telefoniecenter Firmen-Timeout (Sekunden)', default=20, tracking=True)
|
|
phonecenter_ai_provider = fields.Selection([
|
|
('none', 'Keine KI'),
|
|
('openai', 'OpenAI kompatibel'),
|
|
('custom_api', 'Custom KI-API'),
|
|
], string='Telefoniecenter KI-Provider', default='openai', tracking=True)
|
|
phonecenter_ai_api_url = fields.Char('Telefoniecenter KI-API URL', tracking=True)
|
|
phonecenter_ai_api_key = fields.Char('Telefoniecenter KI-API Key', tracking=True)
|
|
phonecenter_ai_model = fields.Char('Telefoniecenter KI Modell', default='gpt-4o-mini', tracking=True)
|
|
phonecenter_search_min_entries = fields.Integer('Telefoniecenter Min. Suchergebnisse', default=8, tracking=True)
|
|
phonecenter_search_max_results = fields.Integer('Telefoniecenter Max. Ergebnisse', default=30, tracking=True)
|
|
phonecenter_search_cache_minutes = fields.Integer('Telefoniecenter Cache (Minuten)', default=30, tracking=True)
|
|
showdebug = fields.Boolean('Debug',help='Debug Informationen aktivieren ',tracking=True)
|
|
m2mcron = fields.Many2one('ir.cron',string='M2M Cron',tracking=True)
|
|
|
|
variables = fields.Many2many('dss.settings.vars',string='Einstellungs Variablen',tracking=True)
|
|
|
|
def _as_bool(self, value):
|
|
return str(value or '').strip().lower() in ('1', 'true', 'yes', 'y', 'on')
|
|
|
|
def _normalize_adportal_graphics_chatgpt_model(self, value):
|
|
allowed_models = {'gpt-image-1', 'dall-e-3', 'dall-e-2'}
|
|
normalized_value = (value or '').strip()
|
|
return normalized_value if normalized_value in allowed_models else 'gpt-image-1'
|
|
|
|
def _compute_adportal_graphics_config(self):
|
|
config = self.env['ir.config_parameter'].sudo()
|
|
for record in self:
|
|
record.adportal_graphics_base_prompt = config.get_param(
|
|
'digitalsignage.adportal_graphics_base_prompt',
|
|
default='Erstelle eine hochwertige Werbegrafik im Corporate-Stil fuer die Firma.',
|
|
)
|
|
record.adportal_graphics_use_chatgpt = self._as_bool(config.get_param('digitalsignage.adportal_graphics_use_chatgpt', default='False'))
|
|
record.adportal_graphics_chatgpt_model = self._normalize_adportal_graphics_chatgpt_model(
|
|
config.get_param('digitalsignage.adportal_graphics_chatgpt_model', default='gpt-image-1')
|
|
)
|
|
record.adportal_graphics_api_url = config.get_param('digitalsignage.adportal_graphics_api_url', default='')
|
|
record.adportal_graphics_api_key = config.get_param('digitalsignage.adportal_graphics_api_key', default='')
|
|
record.adportal_graphics_api_timeout = int(config.get_param('digitalsignage.adportal_graphics_api_timeout', default='90') or 90)
|
|
record.adportal_google_company_check = self._as_bool(config.get_param('digitalsignage.adportal_google_company_check', default='False'))
|
|
record.adportal_google_api_key = config.get_param('digitalsignage.adportal_google_api_key', default='')
|
|
record.adportal_google_cse_id = config.get_param('digitalsignage.adportal_google_cse_id', default='')
|
|
|
|
def _inverse_adportal_graphics_config(self):
|
|
config = self.env['ir.config_parameter'].sudo()
|
|
for record in self:
|
|
config.set_param('digitalsignage.adportal_graphics_base_prompt', record.adportal_graphics_base_prompt or '')
|
|
config.set_param('digitalsignage.adportal_graphics_use_chatgpt', 'True' if record.adportal_graphics_use_chatgpt else 'False')
|
|
config.set_param(
|
|
'digitalsignage.adportal_graphics_chatgpt_model',
|
|
self._normalize_adportal_graphics_chatgpt_model(record.adportal_graphics_chatgpt_model),
|
|
)
|
|
config.set_param('digitalsignage.adportal_graphics_api_url', record.adportal_graphics_api_url or '')
|
|
config.set_param('digitalsignage.adportal_graphics_api_key', record.adportal_graphics_api_key or '')
|
|
config.set_param('digitalsignage.adportal_graphics_api_timeout', int(record.adportal_graphics_api_timeout or 90))
|
|
config.set_param('digitalsignage.adportal_google_company_check', 'True' if record.adportal_google_company_check else 'False')
|
|
config.set_param('digitalsignage.adportal_google_api_key', record.adportal_google_api_key or '')
|
|
config.set_param('digitalsignage.adportal_google_cse_id', record.adportal_google_cse_id or '')
|
|
|
|
def _get_settingvalue(self,valuename):
|
|
settings = (self.env['dss.settings'].search([],limit=1))
|
|
wert = settings._origin.read([valuename])[0][valuename]
|
|
return wert
|
|
@api.model
|
|
def _get_path_converted(self,path,record):
|
|
alls = ''
|
|
_logger.info("GetPath with " + str(path)+ " and Record : "+str(record.id))
|
|
if path:
|
|
for parts in path.split(','):
|
|
if parts[0] == '$':
|
|
fels = parts[1:]
|
|
if fels == 'shortwerbe_feld_selected':
|
|
part= ''
|
|
for wf in record.werbe_feld_selected:
|
|
if wf.Field_notinCloud: # AChtung NOtinCloud = True ist Name anzeigen
|
|
part += wf.feldname + '_'
|
|
part = part if parts else ''
|
|
else:
|
|
part = record._origin.read([fels])[0][fels]
|
|
_logger.info("Get Path - Anfrage-Teil gefunden : " + str(part))
|
|
if parts:
|
|
value = str(part)
|
|
else:
|
|
value = ''
|
|
_logger.info("Get Path - Anfrage-Teil gefunden - setze : " + str(value))
|
|
alls = alls + value
|
|
else:
|
|
alls = alls + str(parts)
|
|
return alls
|
|
|
|
@api.onchange('def_project_cloudpath')
|
|
def _onchange_def_project_cloudpath(self):
|
|
_logger.info("Default Project Cloudpath Setzen")
|
|
project_1 = self.env['dss.projects'].search([('uuid','=ilike','%')],limit=1)
|
|
if not project_1:
|
|
_logger.info("Default Cloudpath Setzen - kein MusterProjekt gefunden")
|
|
else:
|
|
_logger.info("Default Cloudpath Setzen - MusterProjekt gefunden "+str(project_1.id))
|
|
alls = self._get_path_converted(self.def_project_cloudpath,project_1)
|
|
self.def_project_cloudpath_sample = alls
|
|
|
|
@api.onchange('def_contract_cloudpath')
|
|
def _onchange_def_contract_cloudpath(self):
|
|
_logger.info("Default Contract Cloudpath Setzen")
|
|
contract_1 = self.env['dss.contracts'].search([('uuid','=ilike','%')],limit=1)
|
|
if not contract_1:
|
|
_logger.info("Default Cloudpath Setzen - kein MusterProjekt gefunden")
|
|
else:
|
|
_logger.info("Default Cloudpath Setzen - MusterProjekt gefunden "+str(contract_1.id))
|
|
alls = self._get_path_converted(self.def_contract_cloudpath,contract_1)
|
|
self.def_contract_cloudpath_sample = alls
|
|
|
|
@api.onchange('def_ad_cloudpath')
|
|
def _onchange_def_ad_cloudpath(self):
|
|
_logger.info("Default ad Cloudpath Setzen")
|
|
ad_1 = self.env['dss.ads'].search([('uuid','=ilike','%')],limit=1)
|
|
if not ad_1:
|
|
_logger.info("Default Cloudpath Setzen - kein MusterProjekt gefunden")
|
|
else:
|
|
_logger.info("Default Cloudpath Setzen - MusterProjekt gefunden "+str(ad_1.id))
|
|
alls = self._get_path_converted(self.def_ad_cloudpath,ad_1)
|
|
self.def_ad_cloudpath_sample = alls
|
|
|
|
def _default_uuid(self):
|
|
return str(uuid.uuid4())
|
|
|
|
@api.model
|
|
def saveConfig(self,tableInfo):
|
|
_logger.info("Config speichern "+str(tableInfo))
|
|
activefields = tableInfo[3]
|
|
for field in activefields:
|
|
_logger.info("Config speichern : "+str(field)+" / "+str(tableInfo[0])+" -> "+str(tableInfo[1])+" "+str(tableInfo[2]))
|
|
myview = self.env['dss.viewsettings'].search(['&',("user","=",self.env.user.name),("model_view","=",tableInfo[1]),("model_datensatz","=",tableInfo[0]),("viewtype","=",tableInfo[2]),("column","=",field[0])])
|
|
if not myview:
|
|
_logger.info("Config speichern : noch keine gespeicherten Daten")
|
|
else:
|
|
myview.unlink()
|
|
self.env['dss.viewsettings'].create({
|
|
'model_view':tableInfo[1],
|
|
'model_datensatz':tableInfo[0],
|
|
'viewtype':tableInfo[2],
|
|
'user':self.env.user.name,
|
|
'user_id':self.env.user.id,
|
|
'column':field[0],
|
|
'column_size':field[1],
|
|
'state_visible':field[2],
|
|
'state_collapse':field[3]
|
|
})
|
|
return 0
|
|
|
|
@api.model
|
|
def resetConfig(self,tableInfo):
|
|
_logger.info("Config reset "+str(tableInfo))
|
|
myview = self.env['dss.viewsettings'].search(['&',("user","=",self.env.user.name),("model_view","=",tableInfo[1]),("model_datensatz","=",tableInfo[0]),("viewtype","=",tableInfo[2])])
|
|
if not myview:
|
|
_logger.info("Config Reset : noch keine gespeicherten Daten")
|
|
else:
|
|
myview.unlink()
|
|
return 0
|
|
|
|
@api.model
|
|
def getprojectpath(self,project):
|
|
_logger.info("Get Project Path for "+str(project.id))
|
|
if project:
|
|
settings = (self.env['dss.settings'].search([],limit=1))
|
|
alls = dssSettings._get_path_converted(self,settings.def_project_cloudpath,project)
|
|
_logger.info("Get Project Path - Ergebnis : " + str(alls))
|
|
return alls
|
|
else:
|
|
_logger.info("Get Project Path - kein Projekt gefunden")
|
|
return ''
|
|
|
|
@api.model
|
|
def getClientpath(self,client):
|
|
_logger.info("Get Client Path for "+str(client.id))
|
|
if client:
|
|
settings = (self.env['dss.settings'].search([],limit=1))
|
|
alls = dssSettings._get_path_converted(self,settings.def_contract_cloudpath,client)
|
|
_logger.info("Get Client Path - Ergebnis : " + str(alls))
|
|
return alls
|
|
else:
|
|
_logger.info("Get Client Path - kein Client gefunden")
|
|
return ''
|
|
|
|
@api.model
|
|
def readFields(self,tablename):
|
|
_logger.info("Project read Fields : "+str(tablename))
|
|
model = self.env['ir.model'].search([('model', '=', tablename)])
|
|
fields = self.env['ir.model.fields'].search([('model_id', '=', model.id)],order = 'field_description')
|
|
_logger.info("Project read Fields - Suche nach Feldern abgeschlossen - "+str(model)+" - "+str(fields))
|
|
records = []
|
|
for field in fields:
|
|
fieldrecord=[]
|
|
fieldrecord.append(field.id)
|
|
fieldrecord.append(field.field_description)
|
|
fieldrecord.append(field.name)
|
|
fieldrecord.append(str(field.field_description+' ('+field.name+')'))
|
|
records.append(fieldrecord)
|
|
return records
|
|
|
|
@api.model
|
|
def _phonecenter_get_settings(self):
|
|
settings = self.search([], limit=1)
|
|
if not settings:
|
|
return {}
|
|
|
|
ai_api_url = settings.phonecenter_ai_api_url or settings.ai_agent_base_url or 'https://api.openai.com/v1'
|
|
ai_api_key = settings.phonecenter_ai_api_key or settings.ai_agent_api_key or ''
|
|
ai_model = settings.phonecenter_ai_model or settings.ai_agent_model or 'gpt-4o-mini'
|
|
|
|
return {
|
|
'google_maps_key': settings.google_maps_key or '',
|
|
'company_provider': settings.phonecenter_company_provider or 'google_places',
|
|
'company_api_url': settings.phonecenter_company_api_url or '',
|
|
'company_api_key': settings.phonecenter_company_api_key or '',
|
|
'company_timeout': max(5, int(settings.phonecenter_company_timeout or 20)),
|
|
'ai_provider': settings.phonecenter_ai_provider or 'openai',
|
|
'ai_api_url': ai_api_url,
|
|
'ai_api_key': ai_api_key,
|
|
'ai_model': ai_model,
|
|
'min_entries': max(1, int(settings.phonecenter_search_min_entries or 8)),
|
|
'max_results': max(5, int(settings.phonecenter_search_max_results or 30)),
|
|
}
|
|
|
|
@api.model
|
|
def phonecenter_get_search_config(self):
|
|
cfg = self._phonecenter_get_settings()
|
|
return {
|
|
'company_provider': cfg.get('company_provider', 'google_places'),
|
|
'ai_provider': cfg.get('ai_provider', 'openai'),
|
|
'min_entries': cfg.get('min_entries', 8),
|
|
'max_results': cfg.get('max_results', 30),
|
|
'radius_max': 2000,
|
|
}
|
|
|
|
@api.model
|
|
def phonecenter_check_location(self, query):
|
|
query = (query or '').strip()
|
|
if not query:
|
|
return {'found': False, 'message': 'Kein Ort angegeben.'}
|
|
|
|
cfg = self._phonecenter_get_settings()
|
|
google_key = cfg.get('google_maps_key')
|
|
|
|
if google_key:
|
|
try:
|
|
response = requests.get(
|
|
'https://maps.googleapis.com/maps/api/geocode/json',
|
|
params={
|
|
'address': query,
|
|
'key': google_key,
|
|
'language': 'de',
|
|
},
|
|
timeout=cfg.get('company_timeout', 20),
|
|
)
|
|
payload = response.json()
|
|
if payload.get('status') == 'OK' and payload.get('results'):
|
|
first = payload['results'][0]
|
|
location = first.get('geometry', {}).get('location', {})
|
|
return {
|
|
'found': True,
|
|
'provider': 'google_geocode',
|
|
'query': query,
|
|
'lat': location.get('lat'),
|
|
'lng': location.get('lng'),
|
|
'address': first.get('formatted_address') or query,
|
|
}
|
|
except Exception as ex:
|
|
_logger.warning('Phonecenter location check via Google failed: %s', ex)
|
|
|
|
try:
|
|
response = requests.get(
|
|
'https://nominatim.openstreetmap.org/search',
|
|
params={
|
|
'q': query,
|
|
'format': 'json',
|
|
'limit': 1,
|
|
'addressdetails': 1,
|
|
},
|
|
headers={
|
|
'User-Agent': 'DigitalSignagePhoneCenter/1.0',
|
|
},
|
|
timeout=cfg.get('company_timeout', 20),
|
|
)
|
|
payload = response.json()
|
|
if isinstance(payload, list) and payload:
|
|
first = payload[0]
|
|
return {
|
|
'found': True,
|
|
'provider': 'nominatim',
|
|
'query': query,
|
|
'lat': float(first.get('lat') or 0.0),
|
|
'lng': float(first.get('lon') or 0.0),
|
|
'address': first.get('display_name') or query,
|
|
}
|
|
except Exception as ex:
|
|
_logger.warning('Phonecenter location check via Nominatim failed: %s', ex)
|
|
|
|
return {'found': False, 'query': query, 'message': 'Ort nicht gefunden.'}
|
|
|
|
@api.model
|
|
def _phonecenter_normalize_company_item(self, item):
|
|
if not isinstance(item, dict):
|
|
return None
|
|
name = (item.get('name') or '').strip()
|
|
if not name:
|
|
return None
|
|
return {
|
|
'id': str(item.get('id') or item.get('place_id') or item.get('external_id') or name),
|
|
'place_id': (item.get('place_id') or '').strip(),
|
|
'name': name,
|
|
'address': (item.get('address') or item.get('formatted_address') or '').strip(),
|
|
'city': (item.get('city') or '').strip(),
|
|
'website': (item.get('website') or '').strip(),
|
|
'phone': (item.get('phone') or '').strip(),
|
|
'email': (item.get('email') or '').strip(),
|
|
'rating': item.get('rating') or 0,
|
|
'source': (item.get('source') or '').strip(),
|
|
'google_maps_url': (item.get('google_maps_url') or '').strip(),
|
|
'types': item.get('types') or [],
|
|
'lat': item.get('lat'),
|
|
'lng': item.get('lng'),
|
|
'verified': bool(item.get('verified', False)),
|
|
'verified_at': item.get('verified_at') or '',
|
|
}
|
|
|
|
@api.model
|
|
def _phonecenter_analyze_company(self, company, cfg):
|
|
company = company or {}
|
|
name = company.get('name') or ''
|
|
website = company.get('website') or ''
|
|
address = company.get('address') or ''
|
|
phone = company.get('phone') or ''
|
|
rating = company.get('rating') or ''
|
|
|
|
fallback = 'Firma: %s | Telefon: %s | Webseite: %s | Adresse: %s | Rating: %s' % (
|
|
name or '-',
|
|
phone or '-',
|
|
website or '-',
|
|
address or '-',
|
|
rating or '-',
|
|
)
|
|
|
|
ai_provider = (cfg.get('ai_provider') or 'openai').strip()
|
|
if ai_provider not in ('openai', 'custom_api'):
|
|
return fallback
|
|
|
|
api_key = cfg.get('ai_api_key') or ''
|
|
api_url = (cfg.get('ai_api_url') or '').strip()
|
|
if not api_url:
|
|
if ai_provider == 'openai':
|
|
api_url = 'https://api.openai.com/v1'
|
|
else:
|
|
return fallback
|
|
if not api_key:
|
|
return fallback
|
|
|
|
try:
|
|
if ai_provider == 'openai':
|
|
response = requests.post(
|
|
'%s/chat/completions' % api_url.rstrip('/'),
|
|
headers={
|
|
'Authorization': 'Bearer %s' % api_key,
|
|
'Content-Type': 'application/json',
|
|
},
|
|
json={
|
|
'model': cfg.get('ai_model') or 'gpt-4o-mini',
|
|
'temperature': 0.2,
|
|
'messages': [
|
|
{'role': 'system', 'content': 'Du analysierst Firmenkontakte fuer ein deutsches Callcenter. Antworte kurz in maximal 4 Saetzen.'},
|
|
{'role': 'user', 'content': json.dumps({
|
|
'task': 'Analysiere den Firmenkontakt fuer Erstansprache, nenne Nutzenargument, Risiko und naechsten Schritt.',
|
|
'company': company,
|
|
}, ensure_ascii=False)},
|
|
],
|
|
},
|
|
timeout=cfg.get('company_timeout', 20),
|
|
)
|
|
payload = response.json() if response else {}
|
|
content = (((payload.get('choices') or [{}])[0].get('message') or {}).get('content') or '').strip()
|
|
return content or fallback
|
|
|
|
response = requests.post(
|
|
api_url,
|
|
headers={
|
|
'Authorization': 'Bearer %s' % api_key,
|
|
'Content-Type': 'application/json',
|
|
},
|
|
json={
|
|
'task': 'company_analysis',
|
|
'company': company,
|
|
},
|
|
timeout=cfg.get('company_timeout', 20),
|
|
)
|
|
payload = response.json() if response else {}
|
|
if isinstance(payload, dict):
|
|
return (payload.get('analysis') or payload.get('summary') or '').strip() or fallback
|
|
return fallback
|
|
except Exception as ex:
|
|
_logger.warning('Phonecenter company analysis failed: %s', ex)
|
|
return fallback
|
|
|
|
@api.model
|
|
def phonecenter_review_bad_contact(self, payload):
|
|
payload = payload or {}
|
|
cfg = self._phonecenter_get_settings()
|
|
|
|
reason = (payload.get('reason') or '').strip()
|
|
contact = payload.get('contact') or {}
|
|
note = (payload.get('note') or '').strip()
|
|
if not isinstance(contact, dict):
|
|
contact = {}
|
|
|
|
if not reason:
|
|
return {
|
|
'ok': False,
|
|
'message': 'Bitte Grund fuer die Einschaetzung angeben.',
|
|
}
|
|
|
|
fallback_summary = (
|
|
'Einschaetzung: %s. Kontakt: %s (%s). Telefon: %s. Nächster Schritt: Wiedervorlage oder weitere Recherche pruefen.'
|
|
% (
|
|
reason,
|
|
contact.get('company') or '-',
|
|
contact.get('city') or '-',
|
|
contact.get('phone') or '-',
|
|
)
|
|
)
|
|
fallback_actions = ['reschedule', 'research', 'keep', 'discard']
|
|
|
|
ai_provider = (cfg.get('ai_provider') or 'openai').strip()
|
|
api_key = cfg.get('ai_api_key') or ''
|
|
api_url = (cfg.get('ai_api_url') or '').strip()
|
|
model = cfg.get('ai_model') or 'gpt-4o-mini'
|
|
|
|
if ai_provider == 'none' or not api_key:
|
|
return {
|
|
'ok': True,
|
|
'summary': fallback_summary,
|
|
'actions': fallback_actions,
|
|
'source': 'fallback',
|
|
}
|
|
|
|
if not api_url and ai_provider == 'openai':
|
|
api_url = 'https://api.openai.com/v1'
|
|
|
|
try:
|
|
if ai_provider == 'openai':
|
|
response = requests.post(
|
|
'%s/chat/completions' % api_url.rstrip('/'),
|
|
headers={
|
|
'Authorization': 'Bearer %s' % api_key,
|
|
'Content-Type': 'application/json',
|
|
},
|
|
json={
|
|
'model': model,
|
|
'temperature': 0.2,
|
|
'messages': [
|
|
{
|
|
'role': 'system',
|
|
'content': (
|
|
'Du analysierst schlechte Callcenter-Kontakte. '
|
|
'Antworte nur als JSON mit keys "summary" und "actions". '
|
|
'actions muss eine Teilmenge aus ["reschedule", "discard", "research", "keep"] sein.'
|
|
),
|
|
},
|
|
{
|
|
'role': 'user',
|
|
'content': json.dumps({
|
|
'reason': reason,
|
|
'contact': contact,
|
|
'classification_note': note,
|
|
'task': 'Erzeuge eine kurze Zusammenfassung und passende naechste Schritte.',
|
|
}, ensure_ascii=False),
|
|
},
|
|
],
|
|
'response_format': {'type': 'json_object'},
|
|
},
|
|
timeout=cfg.get('company_timeout', 20),
|
|
)
|
|
data = response.json() if response else {}
|
|
content = (((data.get('choices') or [{}])[0].get('message') or {}).get('content') or '').strip()
|
|
parsed = json.loads(content) if content else {}
|
|
actions = parsed.get('actions') if isinstance(parsed, dict) else []
|
|
if not isinstance(actions, list):
|
|
actions = []
|
|
cleaned = [a for a in actions if a in fallback_actions]
|
|
return {
|
|
'ok': True,
|
|
'summary': (parsed.get('summary') if isinstance(parsed, dict) else '') or fallback_summary,
|
|
'actions': cleaned or fallback_actions,
|
|
'source': 'llm',
|
|
}
|
|
|
|
if ai_provider == 'custom_api' and api_url:
|
|
response = requests.post(
|
|
api_url,
|
|
headers={
|
|
'Authorization': 'Bearer %s' % api_key,
|
|
'Content-Type': 'application/json',
|
|
},
|
|
json={
|
|
'task': 'bad_contact_review',
|
|
'reason': reason,
|
|
'contact': contact,
|
|
'classification_note': note,
|
|
},
|
|
timeout=cfg.get('company_timeout', 20),
|
|
)
|
|
data = response.json() if response else {}
|
|
actions = data.get('actions', []) if isinstance(data, dict) else []
|
|
if not isinstance(actions, list):
|
|
actions = []
|
|
cleaned = [a for a in actions if a in fallback_actions]
|
|
return {
|
|
'ok': True,
|
|
'summary': (data.get('summary') if isinstance(data, dict) else '') or fallback_summary,
|
|
'actions': cleaned or fallback_actions,
|
|
'source': 'custom_api',
|
|
}
|
|
except Exception as ex:
|
|
_logger.warning('Phonecenter bad contact review failed: %s', ex)
|
|
|
|
return {
|
|
'ok': True,
|
|
'summary': fallback_summary,
|
|
'actions': fallback_actions,
|
|
'source': 'fallback',
|
|
}
|
|
|
|
@api.model
|
|
def _phonecenter_dedupe_companies(self, items):
|
|
deduped = []
|
|
seen = set()
|
|
for raw in items or []:
|
|
item = self._phonecenter_normalize_company_item(raw)
|
|
if not item:
|
|
continue
|
|
key = (item.get('name', '').lower().strip(), item.get('address', '').lower().strip())
|
|
if key in seen:
|
|
continue
|
|
seen.add(key)
|
|
deduped.append(item)
|
|
return deduped
|
|
|
|
@api.model
|
|
def _phonecenter_search_google_places(self, industry, lat, lng, radius_km, cfg):
|
|
key = cfg.get('company_api_key') or cfg.get('google_maps_key')
|
|
if not key:
|
|
return []
|
|
|
|
query = (industry or '').strip() or 'unternehmen'
|
|
radius_m = max(1000, int((radius_km or 10) * 1000))
|
|
params = {
|
|
'query': query,
|
|
'location': f'{lat},{lng}',
|
|
'radius': radius_m,
|
|
'key': key,
|
|
'language': 'de',
|
|
}
|
|
items = []
|
|
|
|
try:
|
|
response = requests.get(
|
|
'https://maps.googleapis.com/maps/api/place/textsearch/json',
|
|
params=params,
|
|
timeout=cfg.get('company_timeout', 20),
|
|
)
|
|
payload = response.json()
|
|
for row in payload.get('results', [])[:cfg.get('max_results', 30)]:
|
|
location = row.get('geometry', {}).get('location', {})
|
|
items.append({
|
|
'id': row.get('place_id'),
|
|
'place_id': row.get('place_id'),
|
|
'name': row.get('name'),
|
|
'address': row.get('formatted_address') or row.get('vicinity') or '',
|
|
'rating': row.get('rating') or 0,
|
|
'city': '',
|
|
'source': 'google_places',
|
|
'lat': location.get('lat'),
|
|
'lng': location.get('lng'),
|
|
})
|
|
except Exception as ex:
|
|
_logger.warning('Phonecenter Google Places search failed: %s', ex)
|
|
|
|
return items
|
|
|
|
@api.model
|
|
def _phonecenter_search_custom_company_api(self, industry, lat, lng, radius_km, cfg):
|
|
api_url = (cfg.get('company_api_url') or '').strip()
|
|
if not api_url:
|
|
return []
|
|
|
|
headers = {'Content-Type': 'application/json'}
|
|
if cfg.get('company_api_key'):
|
|
headers['Authorization'] = 'Bearer %s' % cfg.get('company_api_key')
|
|
|
|
body = {
|
|
'industry': industry,
|
|
'lat': lat,
|
|
'lng': lng,
|
|
'radius_km': radius_km,
|
|
'limit': cfg.get('max_results', 30),
|
|
}
|
|
|
|
try:
|
|
response = requests.post(api_url, headers=headers, json=body, timeout=cfg.get('company_timeout', 20))
|
|
payload = response.json()
|
|
if isinstance(payload, dict):
|
|
return payload.get('companies', []) or []
|
|
except Exception as ex:
|
|
_logger.warning('Phonecenter custom company API search failed: %s', ex)
|
|
return []
|
|
|
|
@api.model
|
|
def _phonecenter_enrich_companies_openai(self, companies, industry, location_query, cfg):
|
|
api_url = (cfg.get('ai_api_url') or '').rstrip('/')
|
|
api_key = cfg.get('ai_api_key') or ''
|
|
model = cfg.get('ai_model') or 'gpt-4o-mini'
|
|
if not api_url or not api_key:
|
|
return companies
|
|
|
|
prompt = {
|
|
'industry': industry,
|
|
'location': location_query,
|
|
'companies': companies,
|
|
'task': 'Normalisiere, entferne Dubletten und bewerte Relevanz fuer Branchen- und Ortsbezug. Antworte als JSON-Objekt mit key "companies".',
|
|
}
|
|
|
|
try:
|
|
response = requests.post(
|
|
'%s/chat/completions' % api_url,
|
|
headers={
|
|
'Authorization': 'Bearer %s' % api_key,
|
|
'Content-Type': 'application/json',
|
|
},
|
|
json={
|
|
'model': model,
|
|
'temperature': 0.1,
|
|
'messages': [
|
|
{'role': 'system', 'content': 'Du bist ein Daten-Normalisierer fuer Firmenlisten. Gib nur gueltiges JSON zurueck.'},
|
|
{'role': 'user', 'content': json.dumps(prompt, ensure_ascii=False)},
|
|
],
|
|
'response_format': {'type': 'json_object'},
|
|
},
|
|
timeout=cfg.get('company_timeout', 20),
|
|
)
|
|
data = response.json()
|
|
content = (((data.get('choices') or [{}])[0].get('message') or {}).get('content') or '').strip()
|
|
parsed = json.loads(content) if content else {}
|
|
enriched = parsed.get('companies', []) if isinstance(parsed, dict) else []
|
|
return self._phonecenter_dedupe_companies(enriched) or companies
|
|
except Exception as ex:
|
|
_logger.warning('Phonecenter OpenAI enrichment failed: %s', ex)
|
|
return companies
|
|
|
|
@api.model
|
|
def _phonecenter_enrich_companies_custom(self, companies, industry, location_query, cfg):
|
|
api_url = (cfg.get('ai_api_url') or '').strip()
|
|
if not api_url:
|
|
return companies
|
|
|
|
headers = {'Content-Type': 'application/json'}
|
|
if cfg.get('ai_api_key'):
|
|
headers['Authorization'] = 'Bearer %s' % cfg.get('ai_api_key')
|
|
|
|
body = {
|
|
'industry': industry,
|
|
'location': location_query,
|
|
'companies': companies,
|
|
}
|
|
|
|
try:
|
|
response = requests.post(api_url, headers=headers, json=body, timeout=cfg.get('company_timeout', 20))
|
|
payload = response.json()
|
|
if isinstance(payload, dict):
|
|
enriched = payload.get('companies', []) or []
|
|
normalized = self._phonecenter_dedupe_companies(enriched)
|
|
return normalized or companies
|
|
except Exception as ex:
|
|
_logger.warning('Phonecenter custom AI enrichment failed: %s', ex)
|
|
return companies
|
|
|
|
@api.model
|
|
def _phonecenter_get_google_place_details(self, place_id, cfg):
|
|
key = cfg.get('company_api_key') or cfg.get('google_maps_key')
|
|
if not key or not place_id:
|
|
return {}
|
|
|
|
detail_fields = ','.join([
|
|
'name',
|
|
'formatted_address',
|
|
'formatted_phone_number',
|
|
'international_phone_number',
|
|
'website',
|
|
'rating',
|
|
'user_ratings_total',
|
|
'business_status',
|
|
'types',
|
|
'url',
|
|
'geometry',
|
|
])
|
|
try:
|
|
response = requests.get(
|
|
'https://maps.googleapis.com/maps/api/place/details/json',
|
|
params={
|
|
'place_id': place_id,
|
|
'fields': detail_fields,
|
|
'key': key,
|
|
'language': 'de',
|
|
},
|
|
timeout=cfg.get('company_timeout', 20),
|
|
)
|
|
payload = response.json()
|
|
if payload.get('status') != 'OK':
|
|
return {}
|
|
details = payload.get('result') or {}
|
|
return {
|
|
'id': place_id,
|
|
'place_id': place_id,
|
|
'name': details.get('name') or '',
|
|
'address': details.get('formatted_address') or '',
|
|
'phone': details.get('formatted_phone_number') or details.get('international_phone_number') or '',
|
|
'website': details.get('website') or '',
|
|
'email': '',
|
|
'rating': details.get('rating') or 0,
|
|
'source': 'google_places',
|
|
'google_maps_url': details.get('url') or '',
|
|
'types': details.get('types') or [],
|
|
'verified': True,
|
|
'verified_at': fields.Datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
|
|
'business_status': details.get('business_status') or '',
|
|
'lat': (details.get('geometry') or {}).get('location', {}).get('lat'),
|
|
'lng': (details.get('geometry') or {}).get('location', {}).get('lng'),
|
|
}
|
|
except Exception as ex:
|
|
_logger.warning('Phonecenter Google Place details failed: %s', ex)
|
|
return {}
|
|
|
|
@api.model
|
|
def phonecenter_get_company_details(self, payload):
|
|
payload = payload or {}
|
|
cfg = self._phonecenter_get_settings()
|
|
company = payload.get('company') or {}
|
|
if not isinstance(company, dict):
|
|
company = {}
|
|
|
|
provider = (company.get('source') or cfg.get('company_provider') or 'google_places').strip()
|
|
place_id = (company.get('place_id') or company.get('id') or '').strip()
|
|
|
|
details = {}
|
|
if provider == 'google_places' and place_id:
|
|
details = self._phonecenter_get_google_place_details(place_id, cfg)
|
|
|
|
if not details:
|
|
details = self._phonecenter_normalize_company_item(company) or {}
|
|
if details:
|
|
details['verified'] = bool(details.get('verified', False))
|
|
details['verified_at'] = details.get('verified_at') or ''
|
|
|
|
if not details:
|
|
return {
|
|
'ok': False,
|
|
'message': 'Keine Unternehmensdetails gefunden.',
|
|
}
|
|
|
|
details.setdefault('source', provider)
|
|
details['analysis'] = self._phonecenter_analyze_company(details, cfg)
|
|
return {
|
|
'ok': True,
|
|
'company': details,
|
|
}
|
|
|
|
@api.model
|
|
def phonecenter_search_companies(self, payload):
|
|
payload = payload or {}
|
|
cfg = self._phonecenter_get_settings()
|
|
|
|
current_options = payload.get('current_options') or []
|
|
current_list = self._phonecenter_dedupe_companies(current_options)
|
|
|
|
min_entries = cfg.get('min_entries', 8)
|
|
if current_list and len(current_list) < min_entries:
|
|
return {
|
|
'companies': current_list,
|
|
'added_count': 0,
|
|
'search_blocked': True,
|
|
'min_entries': min_entries,
|
|
'message': 'Naechste Suche erst ab Mindestanzahl moeglich.',
|
|
}
|
|
|
|
industry = (payload.get('industry') or '').strip()
|
|
location_query = (payload.get('location_query') or '').strip()
|
|
radius_km = int(payload.get('radius_km') or 20)
|
|
|
|
lat = payload.get('lat')
|
|
lng = payload.get('lng')
|
|
if (lat is None or lng is None) and location_query:
|
|
location_info = self.phonecenter_check_location(location_query)
|
|
lat = location_info.get('lat')
|
|
lng = location_info.get('lng')
|
|
|
|
if lat is None or lng is None:
|
|
return {
|
|
'companies': current_list,
|
|
'added_count': 0,
|
|
'search_blocked': bool(current_list and len(current_list) < min_entries),
|
|
'min_entries': min_entries,
|
|
'message': 'Kein gueltiger Ort fuer die Suche vorhanden.',
|
|
}
|
|
|
|
provider = cfg.get('company_provider', 'google_places')
|
|
found = []
|
|
if provider == 'google_places':
|
|
found = self._phonecenter_search_google_places(industry, lat, lng, radius_km, cfg)
|
|
elif provider == 'custom_api':
|
|
found = self._phonecenter_search_custom_company_api(industry, lat, lng, radius_km, cfg)
|
|
|
|
if not found:
|
|
found = [{
|
|
'id': 'fallback-%s-1' % (industry or 'unternehmen'),
|
|
'name': '%s %s 1' % (industry or 'Unternehmen', location_query or ''),
|
|
'city': location_query,
|
|
'source': 'mock',
|
|
}]
|
|
|
|
normalized_new = self._phonecenter_dedupe_companies(found)
|
|
ai_provider = cfg.get('ai_provider', 'openai')
|
|
if ai_provider == 'openai':
|
|
normalized_new = self._phonecenter_enrich_companies_openai(normalized_new, industry, location_query, cfg)
|
|
elif ai_provider == 'custom_api':
|
|
normalized_new = self._phonecenter_enrich_companies_custom(normalized_new, industry, location_query, cfg)
|
|
|
|
combined = self._phonecenter_dedupe_companies(current_list + normalized_new)
|
|
combined = combined[:cfg.get('max_results', 30)]
|
|
|
|
return {
|
|
'companies': combined,
|
|
'added_count': max(0, len(combined) - len(current_list)),
|
|
'search_blocked': bool(combined and len(combined) < min_entries),
|
|
'min_entries': min_entries,
|
|
'radius_km': radius_km,
|
|
'provider': provider,
|
|
'ai_provider': ai_provider,
|
|
}
|
|
|
|
|
|
class dssSettingsSave(models.Model):
|
|
|
|
_name = "dss.viewsettings"
|
|
_description = "DigitalSignage Ansicht Einstellungen"
|
|
_inherit = []
|
|
uuid = fields.Char(default=lambda self: self._default_uuid(), required=True, readonly=True, copy=False, string='UUID')
|
|
user = fields.Char('User')
|
|
user_id = fields.Many2one('res.users',string='User_id')
|
|
model_view = fields.Char('Model')
|
|
model_datensatz = fields.Char('Datensatz')
|
|
viewtype = fields.Char('Ansichtstyp')
|
|
column = fields.Char('Spalte')
|
|
column_size = fields.Char('Spalten Grösse')
|
|
state_visible = fields.Char('Status sichtbar')
|
|
state_collapse = fields.Char('Status zusammengeklappt')
|
|
|
|
|
|
@api.model
|
|
def _default_uuid(self):
|
|
return str(uuid.uuid4())
|
|
|
|
def _default_create_user(self):
|
|
return str(self.env.user.name)
|
|
|
|
def _default_create_user_id(self):
|
|
return self.env.user.id
|
|
|
|
|