DigitalSignage/models/dss_phonecenter_query.py

473 lines
18 KiB
Python
Executable File

# -*- coding: utf-8 -*-
from datetime import timedelta
from odoo import api, fields, models
class DssPhonecenterQuery(models.Model):
_name = "dss.phonecenter.query"
_description = "Telefoniecenter Suchanfrage"
_order = "id desc"
name = fields.Char("Anfrage", required=True)
project_id = fields.Many2one("dss.projects", string="Projekt", required=True, index=True)
user_id = fields.Many2one("res.users", string="Benutzer", required=True, default=lambda self: self.env.user)
location_query = fields.Char("Ort", required=True)
radius_km = fields.Integer("Umkreis (km)", default=20)
industry_query = fields.Char("Branche", required=True)
state = fields.Selection([
("draft", "Neu"),
("active", "Aktiv"),
("done", "Abgeschlossen"),
], default="draft", string="Status")
search_count = fields.Integer("Suchen", default=0)
line_ids = fields.One2many("dss.phonecenter.query.line", "query_id", string="Ergebnisse")
open_line_count = fields.Integer("Offene Eintraege", compute="_compute_line_counts")
done_line_count = fields.Integer("Abarbeitet", compute="_compute_line_counts")
@api.depends("line_ids.processed", "line_ids.rejected", "line_ids.reschedule_at")
def _compute_line_counts(self):
now = fields.Datetime.now()
for rec in self:
total = len(rec.line_ids)
done = len(rec.line_ids.filtered(
lambda line: line.processed or line.rejected or (
line.reschedule_at and line.reschedule_at > now and not line.rejected and not line.processed
)
))
rec.done_line_count = done
rec.open_line_count = max(0, total - done)
@api.model
def get_project_queries(self, project_id):
project_id = int(project_id or 0)
if not project_id:
return []
records = self.search([
("project_id", "=", project_id),
("user_id", "=", self.env.user.id),
], order="id desc", limit=100)
payload = []
for rec in records:
payload.append({
"id": rec.id,
"name": rec.name,
"location_query": rec.location_query or "",
"radius_km": rec.radius_km or 20,
"industry_query": rec.industry_query or "",
"state": rec.state,
"search_count": rec.search_count,
"open_line_count": rec.open_line_count,
"done_line_count": rec.done_line_count,
"can_repeat": (rec.open_line_count == 0),
})
now = fields.Datetime.now()
reschedule_lines = self.env["dss.phonecenter.query.line"].search([
("query_id.project_id", "=", project_id),
("query_id.user_id", "=", self.env.user.id),
("reschedule_at", "!=", False),
("rejected", "=", False),
("processed", "=", False),
])
if reschedule_lines:
due_count = len(reschedule_lines.filtered(lambda l: l.reschedule_at and l.reschedule_at <= now))
total_count = len(reschedule_lines)
payload.append({
"id": "reschedule:%s" % project_id,
"name": "Wiedervorlagen",
"location_query": "",
"radius_km": 0,
"industry_query": "",
"state": "active",
"search_count": 0,
"open_line_count": due_count,
"done_line_count": max(0, total_count - due_count),
"can_repeat": True,
"is_reschedule_bucket": True,
})
return payload
@api.model
def get_reschedule_details(self, project_id):
project_id = int(project_id or 0)
if not project_id:
return {"ok": False, "message": "Projekt fehlt."}
now = fields.Datetime.now()
lines = self.env["dss.phonecenter.query.line"].search([
("query_id.project_id", "=", project_id),
("query_id.user_id", "=", self.env.user.id),
("reschedule_at", "!=", False),
("rejected", "=", False),
("processed", "=", False),
], order="reschedule_at asc, id desc")
due_lines = lines.filtered(lambda l: l.reschedule_at and l.reschedule_at <= now)
companies = []
for line in due_lines:
companies.append({
"id": str(line.external_id or line.id),
"line_id": line.id,
"name": line.name,
"address": line.address or "",
"city": line.city or "",
"website": line.website or "",
"phone": line.phone or "",
"email": line.email or "",
"rating": line.rating or 0,
"source": line.source or "",
"verified": bool(line.verified),
"verified_at": line.verified_at or "",
"place_id": line.place_id or "",
"google_maps_url": line.google_maps_url or "",
"lat": line.lat,
"lng": line.lng,
"types": line.types_json or [],
"processed": bool(line.processed),
"rejected": bool(line.rejected),
"reschedule_at": fields.Datetime.to_string(line.reschedule_at) if line.reschedule_at else "",
"reschedule_note": line.reschedule_note or "",
})
return {
"ok": True,
"query": {
"id": "reschedule:%s" % project_id,
"name": "Wiedervorlagen",
"location_query": "",
"radius_km": 0,
"industry_query": "",
"state": "active",
"search_count": 0,
"open_line_count": len(companies),
"done_line_count": max(0, len(lines) - len(companies)),
"can_repeat": True,
"is_reschedule_bucket": True,
},
"companies": companies,
}
@api.model
def create_query(self, payload):
payload = payload or {}
project_id = int(payload.get("project_id") or 0)
if not project_id:
return {"ok": False, "message": "Projekt fehlt."}
location_query = (payload.get("location_query") or "").strip()
industry_query = (payload.get("industry_query") or "").strip()
radius_km = int(payload.get("radius_km") or 20)
if not location_query or not industry_query:
return {"ok": False, "message": "Ort und Branche sind erforderlich."}
record = self.create({
"project_id": project_id,
"user_id": self.env.user.id,
"location_query": location_query,
"industry_query": industry_query,
"radius_km": radius_km,
"name": "%s | %s | %s km" % (industry_query, location_query, radius_km),
"state": "active",
})
return {
"ok": True,
"query": {
"id": record.id,
"name": record.name,
"location_query": record.location_query,
"radius_km": record.radius_km,
"industry_query": record.industry_query,
"state": record.state,
"search_count": record.search_count,
"open_line_count": record.open_line_count,
"done_line_count": record.done_line_count,
"can_repeat": (record.open_line_count == 0),
},
}
@api.model
def get_query_details(self, query_id):
query_id = int(query_id or 0)
record = self.browse(query_id)
if not record.exists() or record.user_id.id != self.env.user.id:
return {"ok": False, "message": "Anfrage nicht gefunden."}
now = fields.Datetime.now()
companies = []
visible_lines = record.line_ids.filtered(
lambda l: not l.rejected and not (
l.reschedule_at and l.reschedule_at > now and not l.processed
)
)
for line in visible_lines.sorted(key=lambda l: (l.processed, -l.id)):
companies.append({
"id": str(line.external_id or line.id),
"line_id": line.id,
"name": line.name,
"address": line.address or "",
"city": line.city or "",
"website": line.website or "",
"phone": line.phone or "",
"email": line.email or "",
"rating": line.rating or 0,
"source": line.source or "",
"verified": bool(line.verified),
"verified_at": line.verified_at or "",
"place_id": line.place_id or "",
"google_maps_url": line.google_maps_url or "",
"lat": line.lat,
"lng": line.lng,
"types": line.types_json or [],
"processed": bool(line.processed),
"rejected": bool(line.rejected),
"reschedule_at": fields.Datetime.to_string(line.reschedule_at) if line.reschedule_at else "",
"reschedule_note": line.reschedule_note or "",
})
return {
"ok": True,
"query": {
"id": record.id,
"name": record.name,
"location_query": record.location_query or "",
"radius_km": record.radius_km or 20,
"industry_query": record.industry_query or "",
"state": record.state,
"search_count": record.search_count,
"open_line_count": record.open_line_count,
"done_line_count": record.done_line_count,
"can_repeat": (record.open_line_count == 0),
},
"companies": companies,
}
@api.model
def can_repeat_query(self, query_id):
query_id = int(query_id or 0)
record = self.browse(query_id)
if not record.exists() or record.user_id.id != self.env.user.id:
return {"ok": False, "can_repeat": False}
return {
"ok": True,
"can_repeat": bool(record.open_line_count == 0),
"open_line_count": record.open_line_count,
}
@api.model
def save_query_results(self, query_id, companies):
query_id = int(query_id or 0)
record = self.browse(query_id)
if not record.exists() or record.user_id.id != self.env.user.id:
return {"ok": False, "message": "Anfrage nicht gefunden."}
if not isinstance(companies, list):
companies = []
line_model = self.env["dss.phonecenter.query.line"]
added = 0
for company in companies:
if not isinstance(company, dict):
continue
name = (company.get("name") or "").strip()
if not name:
continue
external_id = str(company.get("id") or company.get("place_id") or name)
existing = line_model.search([
("query_id", "=", record.id),
("external_id", "=", external_id),
], limit=1)
vals = {
"query_id": record.id,
"external_id": external_id,
"place_id": (company.get("place_id") or "").strip(),
"name": name,
"address": (company.get("address") or "").strip(),
"city": (company.get("city") or "").strip(),
"website": (company.get("website") or "").strip(),
"phone": (company.get("phone") or "").strip(),
"email": (company.get("email") or "").strip(),
"rating": company.get("rating") or 0,
"source": (company.get("source") or "").strip(),
"verified": bool(company.get("verified", False)),
"verified_at": (company.get("verified_at") or "").strip(),
"google_maps_url": (company.get("google_maps_url") or "").strip(),
"lat": company.get("lat"),
"lng": company.get("lng"),
"types_json": company.get("types") if isinstance(company.get("types"), list) else [],
"processed": bool(existing.processed) if existing else False,
"rejected": bool(existing.rejected) if existing else False,
"reject_reason": existing.reject_reason if existing else "",
"decision_summary": existing.decision_summary if existing else "",
"reschedule_at": existing.reschedule_at if existing else False,
"reschedule_note": existing.reschedule_note if existing else "",
}
if existing:
existing.write(vals)
else:
line_model.create(vals)
added += 1
record.search_count = (record.search_count or 0) + 1
record.state = "active"
details = self.get_query_details(record.id)
details.update({"ok": True, "added": added})
return details
@api.model
def mark_result_processed(self, line_id, processed=True):
line_id = int(line_id or 0)
line = self.env["dss.phonecenter.query.line"].browse(line_id)
if not line.exists() or line.query_id.user_id.id != self.env.user.id:
return {"ok": False, "message": "Eintrag nicht gefunden."}
line.processed = bool(processed)
if processed:
line.rejected = False
line.reschedule_at = False
line.reschedule_note = ""
if line.query_id.open_line_count == 0:
line.query_id.state = "done"
else:
line.query_id.state = "active"
return {
"ok": True,
"query_id": line.query_id.id,
"open_line_count": line.query_id.open_line_count,
"can_repeat": (line.query_id.open_line_count == 0),
}
@api.model
def set_reschedule(self, line_id, reschedule_at=False, days=0, note=""):
line_id = int(line_id or 0)
line = self.env["dss.phonecenter.query.line"].browse(line_id)
if not line.exists() or line.query_id.user_id.id != self.env.user.id:
return {"ok": False, "message": "Eintrag nicht gefunden."}
target_dt = False
if reschedule_at:
target_dt = fields.Datetime.to_datetime(reschedule_at)
if not target_dt:
day_count = int(days or 0)
if day_count > 0:
target_dt = fields.Datetime.now() + timedelta(days=day_count)
if not target_dt:
return {"ok": False, "message": "Bitte Wiedervorlagezeit angeben."}
line.write({
"last_decision": "reschedule",
"processed": False,
"rejected": False,
"reschedule_at": target_dt,
"reschedule_note": (note or "").strip(),
})
if line.query_id.open_line_count == 0:
line.query_id.state = "done"
else:
line.query_id.state = "active"
return {
"ok": True,
"line_id": line.id,
"query_id": line.query_id.id,
"reschedule_at": fields.Datetime.to_string(line.reschedule_at) if line.reschedule_at else "",
"open_line_count": line.query_id.open_line_count,
}
@api.model
def apply_contact_decision(self, line_id, decision, reason="", summary=""):
line_id = int(line_id or 0)
line = self.env["dss.phonecenter.query.line"].browse(line_id)
if not line.exists() or line.query_id.user_id.id != self.env.user.id:
return {"ok": False, "message": "Eintrag nicht gefunden."}
decision_value = (decision or "").strip()
# Backward-compatible alias: treat no_interest as a rejection/discard decision.
if decision_value == "no_interest":
decision_value = "discard"
vals = {
"last_decision": decision_value,
"reject_reason": (reason or "").strip(),
"decision_summary": (summary or "").strip(),
}
if decision_value == "discard":
vals.update({
"rejected": True,
"processed": True,
"reschedule_at": False,
"reschedule_note": "",
})
elif decision_value in ("research", "reschedule", "keep"):
vals.update({
"rejected": False,
"processed": False,
})
if decision_value in ("research", "keep"):
vals.update({
"reschedule_at": False,
"reschedule_note": "",
})
line.write(vals)
if line.query_id.open_line_count == 0:
line.query_id.state = "done"
else:
line.query_id.state = "active"
return {
"ok": True,
"line_id": line.id,
"query_id": line.query_id.id,
"decision": decision_value,
"rejected": bool(line.rejected),
"open_line_count": line.query_id.open_line_count,
}
class DssPhonecenterQueryLine(models.Model):
_name = "dss.phonecenter.query.line"
_description = "Telefoniecenter Suchergebnis"
_order = "id desc"
query_id = fields.Many2one("dss.phonecenter.query", string="Anfrage", required=True, ondelete="cascade", index=True)
external_id = fields.Char("Externe ID", index=True)
place_id = fields.Char("Place ID")
name = fields.Char("Unternehmen", required=True)
address = fields.Char("Adresse")
city = fields.Char("Ort")
website = fields.Char("Webseite")
phone = fields.Char("Telefon")
email = fields.Char("Email")
rating = fields.Float("Rating")
source = fields.Char("Quelle")
verified = fields.Boolean("Verifiziert")
verified_at = fields.Char("Verifiziert am")
google_maps_url = fields.Char("Google Maps URL")
lat = fields.Float("Breitengrad")
lng = fields.Float("Laengengrad")
types_json = fields.Json("Typen")
processed = fields.Boolean("Abarbeitet", default=False)
rejected = fields.Boolean("Abgelehnt", default=False)
reject_reason = fields.Text("Ablehnungsgrund")
decision_summary = fields.Text("Entscheidungszusammenfassung")
last_decision = fields.Selection([
("reschedule", "Wiedervorlage"),
("discard", "Komplett verwerfen"),
("no_interest", "Kein Interesse"),
("research", "Weitere Recherchen"),
("keep", "Zunaechst behalten"),
], string="Letzte Entscheidung")
reschedule_at = fields.Datetime("Wiedervorlage am")
reschedule_note = fields.Text("Wiedervorlage Notiz")