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