473 lines
18 KiB
Python
Executable File
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")
|