diff --git a/voxtelesys_integration/doctype/voxtelesys_call_log/voxtelesys_call_log.py b/voxtelesys_integration/doctype/voxtelesys_call_log/voxtelesys_call_log.py
new file mode 100644
index 0000000..1904c19
--- /dev/null
+++ b/voxtelesys_integration/doctype/voxtelesys_call_log/voxtelesys_call_log.py
@@ -0,0 +1,53 @@
+"""
+Voxtelesys Call Log controller.
+"""
+import frappe
+from frappe.model.document import Document
+
+
+class VoxtelesysCallLog(Document):
+ def after_insert(self):
+ self._publish_realtime("call_initiated")
+
+ def on_update(self):
+ prev = self.get_doc_before_save()
+ if prev and prev.status in ("Ringing",) and self.status in (
+ "No Answer", "Completed", "Cancelled", "Failed"
+ ):
+ self._publish_realtime("call_disconnected")
+ if self.status == "No Answer":
+ self._maybe_create_issue()
+
+ def _publish_realtime(self, event_suffix: str):
+ frappe.publish_realtime(
+ f"voxtelesys_{event_suffix}",
+ {
+ "call_log": self.name, "call_sid": self.call_sid,
+ "from": self.from_number, "to": self.to_number,
+ "status": self.status, "direction": self.direction,
+ "start_time": str(self.start_time or ""),
+ },
+ )
+
+ def _maybe_create_issue(self):
+ settings = frappe.get_single("Voxtelesys Settings")
+ if not (settings.enabled and settings.missed_call_create_issue and self.direction == "Inbound"):
+ return
+ if frappe.db.exists("HD Ticket", {"custom_voxtelesys_call_sid": self.call_sid}):
+ return
+ try:
+ ticket = frappe.new_doc("HD Ticket")
+ ticket.subject = f"Missed Call from {self.from_number}"
+ ticket.description = (
+ f"A call from {self.from_number} was missed at "
+ f"{frappe.utils.format_datetime(self.start_time)}.
"
+ f"Voxtelesys Call SID: {self.call_sid}"
+ )
+ if settings.default_issue_queue:
+ ticket.priority = settings.default_issue_queue
+ if frappe.db.has_column("HD Ticket", "custom_voxtelesys_call_sid"):
+ ticket.custom_voxtelesys_call_sid = self.call_sid
+ ticket.insert(ignore_permissions=True)
+ frappe.logger().info(f"[Voxtelesys] Auto-created HD Ticket {ticket.name} for missed call {self.call_sid}")
+ except Exception:
+ frappe.log_error(frappe.get_traceback(), "Voxtelesys: Failed to auto-create HD Ticket")
\ No newline at end of file